1 //
2 // Copyright 1998, 1999 CDS Networks, Inc., Medford Oregon
3 //
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 1. Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // 3. All advertising materials mentioning features or use of this software
14 // must display the following acknowledgement:
15 // This product includes software developed by CDS Networks, Inc.
16 // 4. The name of CDS Networks, Inc. may not be used to endorse or promote
17 // products derived from this software without specific prior
18 // written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY CDS NETWORKS, INC. ``AS IS'' AND
21 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 // ARE DISCLAIMED. IN NO EVENT SHALL CDS NETWORKS, INC. BE LIABLE
24 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 // SUCH DAMAGE.
31 //
32
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 /***
55 * Cancel the current SQL if the timeout expires.
56 *
57 * @version $Id: Tds.html,v 1.1 2003/05/12 16:19:44 sinisa Exp $
58 * @author Craig Spannring
59 */
60 class TimeoutHandler extends Thread
61 {
62 public static final String cvsVersion = "$Id: Tds.html,v 1.1 2003/05/12 16:19:44 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 // nop
87 }
88 catch(java.lang.InterruptedException e)
89 {
90 // nop
91 }
92 }
93 }
94
95
96
97 /***
98 * Implement the TDS protocol.
99 *
100 * @version $Id: Tds.html,v 1.1 2003/05/12 16:19:44 sinisa Exp $
101 * @author Craig Spannring
102 * @author Igor Petrovski
103 * @author The FreeTDS project
104 */
105 public class Tds implements TdsDefinitions
106 {
107 public static final String cvsVersion = "$Id: Tds.html,v 1.1 2003/05/12 16:19:44 sinisa Exp $";
108
109
110 //
111 // If the following variable is false we will consider calling
112 // unimplemented methods to be an error and will raise an exception.
113 // If you want to ignore any unimplemented methods, set it to
114 // true. Only do this if you know what you are doing and can tolerate
115 // bogus results from the unimplemented methods.
116 //
117 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; // either Tds.SYBASE or Tds.SQLSERVER
129 int port; // Port numbers are _unsigned_ 16 bit, short is too small
130 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 // int rowCount = -1; // number of rows selected or updated
150 private boolean moreResults = false; // Is there another result set?
151
152 // Jens Jakobsen 1999-01-10
153 // Until TDS_END_TOKEN is reached we assume that there are outstanding
154 // UpdateCounts or ResultSets
155 private boolean moreResults2 = true;
156
157 CancelController cancelController = null;
158
159 SqlMessage lastServerMessage = null;
160
161 // Added 2000-06-07. Used to control TDS version-specific behavior.
162 private int tdsVer = Tds.TDS42;
163
164 // RMK 2000-06-08.
165 private boolean showWarnings = false;
166
167 // RMK 2000-06-12. Time zone offset on client (disregarding DST).
168 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", "42");
192 String verString = props_.getProperty("TDS", "7.0");
193
194 // XXX This driver doesn't properly support TDS 5.0, AFAIK.
195 // Added 2000-06-07.
196
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 // RMK 2000-06-08
208 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 // Send the logon packet to the server
218 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 // everything is hunky-dory.
227 }
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 // nop
243 }
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 // XXX should do something here
276 }
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 /***
295 * Convert a JDBC escaped SQL string into the native SQL
296 *
297 * @param input escaped string to convert
298 *
299 * @return native SQL string
300 */
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 /***
319 * Convert a JDBC java.sql.Types identifier to a SQLServer type identifier
320 *
321 * @author Craig Spannring
322 *
323 * @param jdbcType JDBC type to convert. Should be one of the
324 * constants from java.sql.Types.
325 *
326 * @return The corresponding SQLServer type identifier.
327 */
328 public static byte cvtJdbcTypeToNativeType(int jdbcType)
329 throws TdsNotImplemented
330 {
331 // This function is thread safe.
332 byte result = 0;
333 switch(jdbcType)
334 {
335 case java.sql.Types.CHAR:
336 // case java.sql.Types.VARCHAR:
337 // case java.sql.Types.LONGVARCHAR:
338 {
339 result = SYBCHAR;
340 break;
341 }
342 //Sinisa
343 //Add java.sql.types VARCHAR & LONGVARCHAR as a native type SYBVARCHAR
344 case java.sql.Types.VARCHAR:
345 case java.sql.Types.LONGVARCHAR:
346 {
347 result = SYBVARCHAR;
348 break;
349 }
350 //Sinisa
351 //Add java.sql.types VARCHAR & LONGVARCHAR as a native type SYBFLT8
352 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 //Dusan
384 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 /***
401 * Convert a JDBC java.sql.Types identifier to a
402 * SQLServer type identifier
403 *
404 * @author Craig Spannring
405 *
406 * @param nativeType SQLServer type to convert.
407 * @param size Maximum size of data coming back from server.
408 *
409 * @return The corresponding JDBC type identifier.
410 */
411 public static int cvtNativeTypeToJdbcType(int nativeType,
412 int size)
413 throws TdsException
414 {
415
416 // This function is thread safe.
417
418 int result = java.sql.Types.OTHER;
419 switch(nativeType)
420 {
421 // XXX We need to figure out how to map _all_ of these types
422 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 // XXX Should money types by NUMERIC or OTHER?
449 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 SYBNUMERIC: result = java.sql.Types.NUMERIC; break;
454 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 // case SYBVOID: result = java.sql.Types. ; break;
462 default: throw new TdsException("Unknown native data type "
463 + Integer.toHexString(
464 nativeType&0xff));
465 }
466 return result;
467 } /* cvtNativeTypeToJdbcType() */
468
469
470 /***
471 * Return the type of server that we attempted to connect to.
472 *
473 * @return TdsDefinitions.SYBASE or TdsDefinitions.SQLSERVER
474 */
475 public int getServerType()
476 {
477 return serverType;
478 }
479
480
481 /***
482 * Try to figure out what client name we should identify
483 * ourselves as. Get the hostname of this machine,
484 *
485 * @return name we will use as the client.
486 */
487 private String getClientName()
488 {
489 // This method is thread safe.
490 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 // This means hostname wasn't found for this machine.
506 return "JOHNDOE";
507 }
508
509 // Look at the first (and possibly only) word in the name.
510 tmp = st.nextToken();
511 if (tmp.length()==0)
512 {
513 // This means the hostname had no leading component.
514 // (This case would be strange.)
515 return "JANEDOE";
516 }
517 else if (Character.isDigit(tmp.charAt(0)))
518 {
519 // This probably means that the name was a quad-decimal
520 // number. We don't want to send that as our name,
521 // so make one up.
522 return "BABYDOE";
523 }
524 else
525 {
526 // Ah, Life is good. We have a name. All other
527 // applications I've seen have upper case client names,
528 // and so shall we.
529 return tmp.toUpperCase();
530 }
531 }
532
533
534 /***
535 * Log onto the SQLServer
536 * <p>
537 *
538 * This method is not synchronized and does not need to be so long
539 * as it can only be called from the constructor.
540 *
541 * <p>
542 * <U>Login Packet</U>
543 * <P>
544 * Packet type (first byte) is 2. The following is from tds.h the numbers
545 * on the left are offsets <I>not including</I> the packet header.
546 * <br>
547 * Note: The logical logon packet is split into two physical
548 * packets. Each physical packet has its own header.
549 * <br>
550 * <PRE>
551 * -- 0 -- DBCHAR host_name[30];
552 * -- 30 -- DBTINYINT host_name_length;
553 * -- 31 -- DBCHAR user_name[30];
554 * -- 61 -- DBTINYINT user_name_length;
555 * -- 62 -- DBCHAR password[30];
556 * -- 92 -- DBTINYINT password_length;
557 * -- 93 -- DBCHAR host_process[30];
558 * -- 123 -- DBTINYINT host_process_length;
559 * -- 124 -- DBCHAR magic1[6]; -- here were most of the mystery stuff is --
560 * -- 130 -- DBTINYINT bulk_copy;
561 * -- 131 -- DBCHAR magic2[9]; -- here were most of the mystery stuff is --
562 * -- 140 -- DBCHAR app_name[30];
563 * -- 170 -- DBTINYINT app_name_length;
564 * -- 171 -- DBCHAR server_name[30];
565 * -- 201 -- DBTINYINT server_name_length;
566 * -- 202 -- DBCHAR magic3; -- 0, dont know this one either --
567 * -- 203 -- DBTINYINT password2_length;
568 * -- 204 -- DBCHAR password2[30];
569 * -- 234 -- DBCHAR magic4[223];
570 * -- 457 -- DBTINYINT password2_length_plus2;
571 * -- 458 -- DBSMALLINT major_version; -- TDS version --
572 * -- 460 -- DBSMALLINT minor_version; -- TDS version --
573 * -- 462 -- DBCHAR library_name[10]; -- Ct-Library or DB-Library --
574 * -- 472 -- DBTINYINT library_length; -- Ct-Library or DB-Library --
575 * -- 473 -- DBSMALLINT major_version2; -- program version --
576 * -- 475 -- DBSMALLINT minor_version2; -- program version --
577 * -- 477 -- DBCHAR magic6[3]; -- ? last two octets are 13 and 17 --
578 * -- bdw reports last two as 12 and 16 here --
579 * -- possibly a bitset flag --
580 * -- 480 -- DBCHAR language[30]; -- ie us-english --
581 * -- second packet --
582 * -- 524 -- DBTINYINT language_length; -- 10 in this case --
583 * -- 525 -- DBCHAR magic7; -- no clue... has 1 in the first octet --
584 * -- bdw reports 0x0 --
585 * -- 526 -- DBSMALLINT old_secure; -- explaination? --
586 * -- 528 -- DBTINYINT encrypted; -- 1 means encrypted all password fields blank --
587 * -- 529 -- DBCHAR magic8; -- no clue... zeros --
588 * -- 530 -- DBCHAR sec_spare[9]; -- explaination --
589 * -- 539 -- DBCHAR char_set[30]; -- ie iso_1 --
590 * -- 569 -- DBTINYINT char_set_length; -- 5 --
591 * -- 570 -- DBTINYINT magic9; -- 1 --
592 * -- 571 -- DBCHAR block_size[6]; -- in text --
593 * -- 577 -- DBTINYINT block_size_length;
594 * -- 578 -- DBCHAR magic10[25]; -- lots of stuff here...no clue --
595 *
596 * </PRE>
597 *
598 * This routine will basically eat all of the data returned from the
599 * SQLServer.
600 *
601 * @author Craig Spannring
602 *
603 * @exception TdsUnknownPacketSubType
604 * @exception com.internetcds.jdbc.tds.TdsException
605 * @exception java.io.IOException
606 * @exception java.sql.SQLException
607 */
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 // Added 2000-06-07.
618 if (tdsVer == Tds.TDS70)
619 send70Login();
620 else {
621
622 comm.startPacket(TdsComm.LOGON);
623
624 // hostname (offset0)
625 // comm.appendString("TOLEDO", 30, (byte)0);
626 byte[] tmp = encoder.getBytes(getClientName());
627 comm.appendBytes(tmp, 30, pad);
628 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
629
630 // username (offset 31 0x1f)
631 tmp = encoder.getBytes(user);
632 comm.appendBytes(tmp, 30, pad);
633 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
634
635 // password (offset 62 0x3e)
636 tmp = encoder.getBytes(password);
637 comm.appendBytes(tmp, 30, pad);
638 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
639
640 // hostproc (offset 93 0x5d)
641 tmp = encoder.getBytes("00000116");
642 comm.appendBytes(tmp, 8, pad);
643
644 // unused (offset 109 0x6d)
645 comm.appendBytes(empty, (30-14), pad);
646
647 // apptype (offset )
648 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 // hostproc length (offset )
656 comm.appendByte((byte)8);
657
658 // type of int2
659 comm.appendByte((byte)3);
660
661 // type of int4
662 comm.appendByte((byte)1);
663
664 // type of char
665 comm.appendByte((byte)6);
666
667 // type of flt
668 comm.appendByte((byte)10);
669
670 // type of date
671 comm.appendByte((byte)9);
672
673 // notify of use db
674 comm.appendByte((byte)1);
675
676 // disallow dump/load and bulk insert
677 comm.appendByte((byte)1);
678
679 // sql interface type
680 comm.appendByte((byte)0);
681
682 // type of network connection
683 comm.appendByte((byte)0);
684
685 // spare[7]
686 comm.appendBytes(empty, 7, pad);
687
688 // appname
689 tmp = encoder.getBytes(appName);
690 comm.appendBytes(tmp, 30, pad);
691 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
692
693 // server name
694 tmp = encoder.getBytes(serverName);
695 comm.appendBytes(tmp, 30, pad);
696 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
697
698 // remote passwords
699 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 // tds version
705 comm.appendByte((byte)4);
706 comm.appendByte((byte)2);
707 comm.appendByte((byte)0);
708 comm.appendByte((byte)0);
709
710 // prog name
711 tmp = encoder.getBytes(progName);
712 comm.appendBytes(tmp, 10, pad);
713 comm.appendByte((byte)(tmp.length < 10 ? tmp.length : 10));
714
715 // prog version
716 comm.appendByte((byte) 6); // Tell the server we can handle SQLServer version 6
717 comm.appendByte((byte) 0); // Send zero to tell the server we can't handle any other version
718 comm.appendByte((byte) 0);
719 comm.appendByte((byte) 0);
720
721 // auto convert short
722 comm.appendByte((byte)0);
723
724 // type of flt4
725 comm.appendByte((byte)0x0D);
726
727 // type of date4
728 comm.appendByte((byte)0x11);
729
730 // language
731 tmp = encoder.getBytes("us_english");
732 comm.appendBytes(tmp, 30, pad);
733 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
734
735 // notify on lang change
736 comm.appendByte((byte)1);
737
738 // security label hierachy
739 comm.appendShort((short)0);
740
741 // security components
742 comm.appendBytes(empty, 8, pad);
743
744 // security spare
745 comm.appendShort((short)0);
746
747 // security login role
748 comm.appendByte((byte)0);
749
750 // charset
751 tmp = encoder.getBytes(charset);
752 comm.appendBytes(tmp, 30, pad);
753 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30));
754
755 // notify on charset change
756 comm.appendByte((byte)1);
757
758 // length of tds packets
759 tmp = encoder.getBytes("512");
760 comm.appendBytes(tmp, 6, pad);
761 comm.appendByte((byte)3);
762
763 // pad out to a longword
764 comm.appendBytes(empty, 8, pad);
765
766 moreResults2=true; //JJ 1999-01-10
767 }
768
769 comm.sendPacket();
770
771 // Get the reply to the logon packet.
772 PacketResult result;
773
774 while (! ((result = processSubPacket()) instanceof PacketEndTokenResult))
775 {
776 if (result instanceof PacketErrorResult)
777 {
778 isOkay = false;
779 }
780 // XXX Should really process some more types of packets.
781 }
782
783
784 if (isOkay)
785 {
786 // XXX Should we move this to the Connection class?
787 isOkay = changeSettings(database, initialSettings);
788 }
789
790 // XXX Possible bug. What happend if this is cancelled before the logon
791 // takes place? Should isOkay be false?
792 return isOkay;
793 }
794
795
796 /*
797 * New code added to handle TDS 7.0 login, which uses a completely
798 * different packet layout. Logic taken directly from freetds C
799 * code in tds/login.c. Lots of magic values: I don't pretend
800 * to have any idea what most of this means.
801 *
802 * Added 2000-06-05.
803 */
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 // Pack up value lengths, positions.
833 short curPos = 86;
834
835 // Unknown
836 comm.appendTdsShort(curPos);
837 comm.appendTdsShort((short)0);
838
839 // Username
840 comm.appendTdsShort(curPos);
841 comm.appendTdsShort((short)user.length());
842 curPos += user.length() * 2;
843
844 // Password
845 comm.appendTdsShort(curPos);
846 comm.appendTdsShort((short)password.length());
847 curPos += password.length() * 2;
848
849 // App name
850 comm.appendTdsShort(curPos);
851 comm.appendTdsShort((short)appName.length());
852 curPos += appName.length() * 2;
853
854 // Server name
855 comm.appendTdsShort(curPos);
856 comm.appendTdsShort((short)serverName.length());
857 curPos += serverName.length() * 2;
858
859 // Another unknown value
860 comm.appendTdsShort((short)0);
861 comm.appendTdsShort((short)0);
862
863 // Library name
864 comm.appendTdsShort(curPos);
865 comm.appendTdsShort((short)libName.length());
866 curPos += libName.length() * 2;
867
868 // Two more unknowns
869 comm.appendTdsShort(curPos);
870 comm.appendTdsShort((short)0);
871 comm.appendTdsShort(curPos);
872 comm.appendTdsShort((short)0);
873
874 // More magic.
875 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 // Pack up the login values.
882 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 // Still more magic!
890 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 /***
904 * This is a <B>very</B> poor man's "encryption."
905 */
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 /***
920 * change the connection level settings for this connection
921 * stream to the database.
922 *
923 * @return true if the database accepted the changes, false if rejected.
924 */
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; //JJ 1999-01-10
952 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 // XXX Should really process some more types of packets.
965 }
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 } // changeSettings
982
983 /***
984 * Select a new database to use.
985 *
986 * @param database Name of the database to use.
987 *
988 * @return true if the change was accepted, false otherwise
989 */
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 // XXX Check to make sure the database name
1001 // doesn't have funny characters.
1002
1003
1004 // if (database name has funny characters)
1005 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; //JJ 1999-01-10
1035 comm.sendPacket();
1036
1037 // XXX Should we check that the change actual was okay
1038 // and throw some sort of exception if it wasn't?
1039 // Get the reply to the change database request.
1040 while (! ((result = processSubPacket())
1041 instanceof PacketEndTokenResult))
1042 {
1043 if (result instanceof PacketErrorResult)
1044 {
1045 isOkay = false;
1046 }
1047 // XXX Should really process some more types of packets.
1048 }
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 } // changeDB()
1065
1066
1067 public void cancel()
1068 throws java.io.IOException, com.internetcds.jdbc.tds.TdsException
1069 {
1070 // XXX How should this be synchronized? What sort of deadlock
1071 // conditions do we need to consider?
1072
1073 cancelController.doCancel(comm);
1074 }
1075
1076
1077 public boolean moreResults()
1078 {
1079 return moreResults2;
1080 }
1081
1082
1083 /***
1084 * Get the length of the current subpacket.
1085 * <p>
1086 * This will eat two bytes from the input socket.
1087 *
1088 * @return length of the current subpacket.
1089 */
1090 private int getSubPacketLength()
1091 throws java.io.IOException, com.internetcds.jdbc.tds.TdsException
1092 {
1093 return comm.getTdsShort();
1094 }
1095
1096
1097 /***
1098 * This will read a error (or warning) message from the SQLServer and
1099 * create a SqlMessage object from that message.
1100 * <p>
1101 * <b> Warning! </b> This is not synchronized because it assumes
1102 * it will only be called by processSubPacket() which is synchronized.
1103 *
1104 * @param packetSubType type of the current subpacket
1105 *
1106 * @return The message returned by the SQLServer.
1107 *
1108 */
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(); // ?class?
1121
1122 int msgLen = comm.getTdsShort();
1123 msg.message = comm.getString(msgLen);
1124
1125 // RMK 2000-06-08: the getWarnings() methods aren't implemented, so we
1126 // need to do something with these.
1127 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 // nop
1139 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 // unknonw byte
1152 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 /***
1168 * Process an env change message (TDS_ENV_CHG_TOKEN)
1169 * <p>
1170 * <b> Warning! </b> This is not synchronized because it assumes
1171 * it will only be called by processSubPacket() which is synchronized.
1172 *
1173 * @exception java.io.IOException
1174 * @exception com.internetcds.jdbc.tds.TdsException
1175 */
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 // XXX Should actually look at the env change
1205 // instead of ignoring it.
1206 comm.skip(len-1);
1207 break;
1208 }
1209 }
1210
1211 return new PacketResult(TDS_ENV_CHG_TOKEN);
1212 }
1213
1214 /***
1215 * Process an column name subpacket.
1216 * <p>
1217 * <p>
1218 * <b> Warning! </b> This is not synchronized because it assumes
1219 * it will only be called by processSubPacket() which is synchronized.
1220 *
1221 */
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 } // processColumnNames()
1243
1244
1245 /***
1246 * Process the columns information subpacket.
1247 * <p>
1248 * <b> Warning! </b> This is not synchronized because it assumes
1249 * it will only be called by processSubPacket() which is synchronized.
1250 *
1251 */
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 // Get the type of column
1283 byte columnType = comm.getByte();
1284 bytesRead++;
1285
1286 if (columnType == SYBTEXT
1287 || columnType == SYBIMAGE)
1288 {
1289 int i;
1290 int tmpByte;
1291
1292 // XXX Need to find out what these next 4 bytes are
1293 // Could they be the column size?
1294 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(); // Total number of digits
1311 bytesRead++;
1312 scale = comm.getByte(); // # of digits after the decimal point
1313 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 // Don't know what the rest is except that the
1344 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 } // processColumnInfo
1353
1354
1355
1356 private PacketTabNameResult processTabName()
1357 throws java.io.IOException, com.internetcds.jdbc.tds.TdsException
1358 {
1359 int totalLen = comm.getTdsShort();
1360
1361 // RMK 2000-06-11. Not sure why the original code is bothering
1362 // to extract the bytes with such meticulous care if it isn't
1363 // going to use the extracted strings for creating the returned
1364 // object. At any rate, this approach doesn't work under TDS 7.0,
1365 // because (1) the name length is a short, not a byte under 7.0,
1366 // and (2) the name string is nameLen wide characters for 7.0,
1367 // not 8-bit characters. So I'm commenting the wasted effort
1368 // and replacing it with a simple call to TdsComm.skip().
1369 //int bytesRead = 0;
1370 //int nameLen = 0;
1371 //String tabName = null;
1372
1373 //while(bytesRead < totalLen)
1374 //{
1375 // nameLen = comm.getByte();
1376 // bytesRead++;
1377 // tabName = new String(comm.getBytes(nameLen));
1378 // bytesRead += nameLen;
1379 //}
1380
1381 comm.skip(totalLen);
1382
1383 return new PacketTabNameResult();
1384 } // processTabName()
1385
1386
1387
1388 /***
1389 * Process an end subpacket.
1390 * <p>
1391 * This routine assumes that the TDS_END_TOKEN byte has already
1392 * been read.
1393 *
1394 * @return
1395 *
1396 * @exception com.internetcds.jdbc.tds.TdsException
1397 *
1398 * @exception java.io.IOException
1399 * Thrown if some sort of error occured reading bytes from the network.
1400 */
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 // XXX If we executed something that returns multiple result
1422 // sets then we don't want to clear the query in progress flag.
1423 // See the CancelController class for details.
1424 cancelController.finishQuery(result.wasCanceled(),
1425 result.moreResults());
1426
1427
1428 // XXX Problem handling cancels that were sent after the server
1429 // send the endToken packet
1430 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 // XXX If we executed something that returns multiple result
1469 // sets then we don't want to clear the query in progress flag.
1470 // See the CancelController class for details.
1471 cancelController.finishQuery(result.wasCanceled(),
1472 result.moreResults());
1473 return result;
1474 }
1475
1476
1477
1478
1479 /***
1480 * Process a subpacket reply
1481 * <p>
1482 * <b>Note-</b> All subpackets must be processed through here.
1483 * This is the only routine has the proper locking to support
1484 * the cancel method in the Statement class.
1485 * <br>
1486 *
1487 * @return packet subtype the was processed.
1488 */
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 /***
1499 * Process a subpacket reply
1500 * <p>
1501 * <b>Note-</b> All subpackets must be processed through here. Only this
1502 * routine has the proper locking to support the cancel method in the
1503 * Statement class.
1504 * <br>
1505 *
1506 *
1507 * @return packet subtype the was processed.
1508 */
1509 synchronized PacketResult processSubPacket(Context context)
1510 throws TdsUnknownPacketSubType,
1511 java.io.IOException,
1512 com.internetcds.jdbc.tds.TdsException
1513 {
1514 // NOTE!!! Before adding anything to this list you must
1515 // consider the ramifications to the the handling of cancels
1516 // as implemented by the CancelController class.
1517 //
1518 // The CancelController class might implicitly assume it can call
1519 // processSubPacket() whenever it is looking for a cancel
1520 // acknowledgment. It assumes that any results of the call
1521 // can be discarded.
1522
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 // XXX Need to figure out what this packet is
1594 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 // FIXME - I'm just ignoring this
1616 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 /***
1640 * Find out how many bytes a particular SQLServer data type takes.
1641 *
1642 * @param nativeColumnType
1643 *
1644 * @return number of bytes required by the given type
1645 *
1646 * @exception com.internetcds.jdbc.tds.TdsException
1647 * Thrown if the given type either doesn't exist or is a variable
1648 * sized data type.
1649 */
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 } // lookupColumnSize()
1703
1704
1705
1706 /***
1707 * determine if a given datatype is a fixed size
1708 *
1709 * @param nativeColumnType The SQLServer datatype to check
1710 *
1711 * @return <code>true</code> if the datatype is a fixed size,
1712 * <code>false</code> if the datatype is a variable size
1713 *
1714 * @exception com.internetcds.jdbc.tds.TdsException
1715 * If the <code>nativeColumnType</code> is not a knowm datatype.
1716 *
1717 */
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 } // getMoneyValue
1857
1858
1859 /***
1860 * Extracts decimal value from the server's results packet. Takes
1861 * advantage of Java's superb handling of large numbers, which does
1862 * all the heavy lifting for us. Format is:
1863 * <UL>
1864 * <LI>Length byte <code>len</code>; count includes sign byte.</LI>
1865 * <LI>Sign byte (0=negative; 1=positive).</LI>
1866 * <LI>Magnitude bytes (array of <code>len</code> - 1 bytes,
1867 * in little-endian order.</LI>
1868 * </UL>
1869 *
1870 * @param scale number of decimal digits after the decimal
1871 * point.
1872 * @return <code>BigDecimal</code> for extracted value
1873 * (or (<code>null</code> if appropriate).
1874 */
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 // RMK 2000-06-10. Deduced from some testing/packet sniffing.
1883 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 // Some useful constants
1897 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; // XXX shouldn't this be an error?
1914 }
1915
1916
1917 switch (len)
1918 {
1919 case 0:
1920 {
1921 result = null;
1922 break;
1923 }
1924 case 8:
1925 {
1926 // It appears that a datetime is made of of 2 32bit ints
1927 // The first one is the number of days since 1900
1928 // The second integer is the number of seconds*300
1929 // The reason the calculations below are sliced up into
1930 // such small baby steps is to avoid a bug in JDK1.2.2's
1931 // runtime, which got confused by the original complexity.
1932 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 // Round up if appropriate.
1940 if (micros % 1000L >= 500L)
1941 millis++;
1942
1943 result = new Timestamp(millis - getDstOffset(millis));
1944 break;
1945 }
1946 case 4:
1947 {
1948 // Accroding to Transact SQL Reference
1949 // a smalldatetime is two small integers.
1950 // The first is the number of days past January 1, 1900,
1951 // the second smallint is the number of minutes past
1952 // midnight.
1953
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 } // getDatetimeValue()
1974
1975
1976 /***
1977 * Determines the number of milliseconds needed to adjust for daylight
1978 * savings time for a given date/time value. Note that there is a problem
1979 * with the way SQL Server sends a DATETIME value, since it is constructed
1980 * to represent the local time for the server. This means that each fall
1981 * there is a window of approximately one hour during which a single value
1982 * can represent two different times.
1983 */
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()); // XXX Are we sure this should be unsigned?
2035 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 } // getIntValue()
2052
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 // In SQL trailing spaces are stripped from strings
2074 // MS SQLServer denotes a zero length string
2075 // as a single space.
2076 result = "";
2077 }
2078 }
2079 else
2080 {
2081 throw new TdsConfused("String with length<0");
2082 }
2083 return result;
2084 } // getCharValue()
2085
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 // XXX Have no idea what these 24 bytes are
2100 // 2000-06-06 RMK They are the TEXTPTR (16 bytes) and the TIMESTAMP.
2101 comm.skip(24);
2102
2103 int len = comm.getTdsInt();
2104
2105 // RMK 2000-06-11
2106 // The logic immediately below does not agree with test t0031,
2107 // so I'm commenting it out. On the other hand, it's a bit
2108 // puzzling that a column defined as TEXT NOT NULL needs the
2109 // hasValue byte read just above, but apparently it does.
2110 //if (len == 0)
2111 //{
2112 // result = null;
2113 //}
2114 //else
2115 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 // In SQL trailing spaces are stripped from strings
2126 // MS SQLServer denotes a zero length string
2127 // as a single space.
2128 result = "";
2129 }
2130 }
2131 else
2132 {
2133 throw new TdsConfused("String with length<0");
2134 }
2135 }
2136 return result;
2137 } // getTextValue()
2138
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 // XXX Have no idea what these 24 bytes are
2152 // 2000-06-06 RMK They are the TEXTPTR (16 bytes) and the TIMESTAMP.
2153 comm.skip(24);
2154
2155 int len = comm.getTdsInt();
2156
2157 // RMK 2000-06-11
2158 // The logic immediately below does not agree with test t0031,
2159 // so I'm commenting it out. On the other hand, it's a bit
2160 // puzzling that a column defined as TEXT NOT NULL needs the
2161 // hasValue byte read just above, but apparently it does.
2162 //if (len == 0)
2163 //{
2164 // result = null;
2165 //}
2166 //else
2167 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 } // getImageValue()
2178
2179 /***
2180 * get one result row from the TDS stream
2181 * <p>
2182 * This will read a full row from the TDS stream and store it in
2183 * a PacketRowResult object.
2184 *
2185 */
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 } // getRow()
2313
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 // ignore any of the exceptions thrown because they either
2326 // don't matter or they will make themselves known when we try
2327 // to use the name generator stored procedure.
2328 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 // don't care
2342 }
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 // don't care
2374 }
2375
2376 stmt = null;
2377 }
2378 catch(java.sql.SQLException e)
2379 {
2380 // don't care
2381 }
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 /***
2410 * Create a new and unique name for a store procedure.
2411 *
2412 * This routine will return a unique name for a stored procedure
2413 * that will be associated with a PreparedStatement().
2414 * <p>
2415 * Since SQLServer supports temporary procedure names we can just
2416 * use UniqueId.getUniqueId() to generate a unique (for the connection)
2417 * name.
2418 * <p>
2419 * Sybase does not support temporary procedure names so we will have
2420 * to have a per user table devoted to storing user specific stored
2421 * procedures. The table name will be of the form
2422 * database.user.jdbc_temp_stored_proc_names. The table will be defined
2423 * as
2424 * <code>
2425 * CREATE TABLE database.user.jdbc_temp_stored_proc_names (
2426 * id NUMERIC(10, 0) IDENTITY;
2427 * session int not null;
2428 * name char(29)
2429 * )
2430 * <code>
2431 * This routine will use that table to track names that are being
2432 * used.
2433 */
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 //
2449 // Attempt to create the table for the stored procedure names
2450 // If it already exists we'll get an error, but we don't care.
2451 // Also create a stored procedure for generating the unique
2452 // names.
2453 //
2454 haveProcNameTable = createStoredProcedureNameTable();
2455
2456 result = generateUniqueProcName();
2457 }
2458 else
2459 {
2460 result = "#jdbc#" + UniqueId.getUniqueId();
2461 }
2462 return result;
2463 } // getUniqueProcedureName()
2464
2465
2466 /***
2467 *
2468 */
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 // XXX I'm sure we need to do more here.
2494
2495 // how about throwing an Exception? --SB
2496 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 /***
2537 * Execute a stored procedure on the SQLServer
2538 * <p>
2539 *
2540 * @param procedure the stored procedure to execute.
2541 * @param parameterList the parameter to pass to the stored procedure
2542 *
2543 * @exception java.sql.SQLException
2544 * @exception com.internetcds.jdbc.tds.TdsException
2545 */
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 // A stored procedure has a packet type of 0x03 in the header packet.
2556 // for non-image date the packets consists of
2557 // offset length desc.
2558 // 0 1 The length of the name of the stored proc
2559 // 1 N1 The name of the stored proc
2560 // N1 + 1 2 unknown (filled with zeros?)
2561 // N1 + 3 2 unknown prefix for param 1 (zero filled?)
2562 // N1 + 5 1 datatype for param 1
2563 // N1 + 6 1 max length of param 1
2564 // N1 + 7 N2 parameter 1 data
2565 // ...
2566 //
2567 // For image data (datatype 0x22) the packet consists of
2568 // 0 1 The length of the name of the stored proc
2569 // 1 N1 The name of the stored proc
2570 // N1 + 1 2 unknown (filled with zeros?)
2571 // N1 + 3 2 unknown prefix for param 1 (zero filled?)
2572 // N1 + 5 1 datatype for param 1
2573 // N1 + 6 4 length of param 1
2574 // N1 + 10 4 length of param 1 (duplicated?)
2575 // N1 + 7 N2 parameter 1 data
2576 // ...
2577
2578 int i;
2579
2580 try
2581 {
2582 // mark that we are performing a query
2583 cancelController.setQueryInProgressFlag();
2584
2585 // Start sending the procedure execute packet.
2586 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 // Now handle the parameters
2602 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 //Sinisa
2618 //using actualParameters caused problems
2619 // if (actualParameterList[i].formalType.startsWith("n")) {
2620 if (formalParameterList[i].formalType.startsWith("n")) {
2621 /*
2622 * This is a Unicode column, save to assume TDS 7.0
2623 */
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 /*
2647 * Either VARCHAR or TEXT, TEXT can not happen
2648 * with TDS 7.0 as we would always use NTEXT there
2649 */
2650 if (tdsVer != TDS70 && max > 255) {
2651 // TEXT
2652 comm.appendByte((byte)SYBTEXT);
2653 sendSybImage(encoder.getBytes((String)actualParameterList[i]
2654 .value));
2655 } else {
2656 // VARCHAR
2657 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 // set the maximum length of the field,
2672 comm.appendByte((byte)4);
2673
2674 // set the actual length, and the data
2675 if (actualParameterList[i].value == null)
2676 {
2677 comm.appendByte((byte)0);
2678 // comm.appendTdsInt((byte)0);
2679 }
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 //sinisa
2702 //Add possibility with NULL value as a SYBFLT8 parameter
2703 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 //sinisa
2710 //If parameter value is NULL send INTEGER value for NULL value to database
2711 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 // epochsDifference is the number of days between unix
2746 // epoch (1970 based) and the sybase epoch (1900 based)
2747 final int epochsDifference = 25567;
2748
2749 long nanoseconds = value.getNanos();
2750
2751 // ms is the number of milliseconds into unix epoch
2752
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 //Sinisa
2784 //Add implementation of SYBBIT native type
2785
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 // comm.appendByte((byte)nativeType);
2805 //sendSybImage(byte[])actualParameterList[i].value);
2806 // sendSybChar(((String)actualParameterList[i].value),1);
2807 // break;
2808
2809
2810 }
2811 case SYBVOID:
2812 case SYBVARBINARY:
2813 //Sinisa
2814 // case SYBVARCHAR:
2815 case SYBBINARY:
2816 case SYBINT1:
2817 //case SYBBIT:
2818 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 //sinisa
2836 // moreResults2=true;
2837 comm.sendPacket();
2838 waitForDataOrTimeout(stmt, timeout);
2839 }
2840 catch(java.io.IOException e)
2841 {
2842 throw new SQLException("Network error- " + e.getMessage());
2843 }
2844 } /* executeProcedure() */
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 // send the lenght of this piece of data
2854 comm.appendTdsInt(length);
2855
2856 // send the length of this piece of data again
2857 comm.appendTdsInt(length);
2858
2859 // send the data
2860 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 // set the type of the column
2885 // set the maximum length of the field
2886 // set the actual lenght of the field.
2887 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 /***
2901 * Process a login ack supacket
2902 */
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 /***
2940 * Process an proc id subpacket.
2941 * <p>
2942 * This routine assumes that the TDS_PROCID byte has already
2943 * been read.
2944 *
2945 * @exception com.internetcds.jdbc.tds.TdsException
2946 *
2947 * @exception java.io.IOException
2948 * Thrown if some sort of error occured reading bytes from the network.
2949 */
2950 private PacketResult processProcId()
2951 throws java.io.IOException, com.internetcds.jdbc.tds.TdsException
2952 {
2953 // XXX Try to find out what meaning this subpacket has.
2954 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 /***
2964 * Process a TDS_RET_STAT_TOKEN subpacket.
2965 * <p>
2966 * This routine assumes that the TDS_RET_STAT_TOKEN
2967 * byte has already been read.
2968 *
2969 * @exception com.internetcds.jdbc.tds.TdsException
2970 *
2971 * @exception java.io.IOException
2972 * Thrown if some sort of error occured reading bytes from the network.
2973 */
2974 private PacketRetStatResult processRetStat()
2975 throws java.io.IOException, com.internetcds.jdbc.tds.TdsException
2976 {
2977 // XXX Not completely sure of this.
2978 return new PacketRetStatResult(comm.getTdsInt());
2979 }
2980
2981 /***
2982 * Processes a TDS 7.0-style result packet, extracting column information
2983 * for the result set.
2984 *
2985 * Added 2000-06-05.
2986 */
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 //try {
2994 //throw new Exception();
2995 //}
2996 //catch (Exception e) {
2997 //e.printStackTrace();
2998 //}
2999 for (int colNum = 1; colNum <= numColumns; ++colNum) {
3000
3001 /*
3002 * The freetds C code didn't know what to do with these four
3003 * bytes, but initial inspection appears to tentatively confirm
3004 * that they serve the same purpose as the flag bytes read by the
3005 * Java code for 4.2 column information.
3006 */
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 /*
3015 * Get the type of column. Large types have 2-byte size fields and
3016 * type codes OR'd with 0x80. Except SYBNCHAR, whose type code
3017 * is already above 0x80.
3018 */
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 // Determine the column size.
3029 int colSize;
3030 if (isBlobType(columnType)) {
3031
3032 // Text and image columns have 4-byte size fields.
3033 colSize = comm.getTdsInt();
3034
3035 // Swallow table name.
3036 comm.getString(comm.getTdsShort());
3037 }
3038
3039 // Fixed types have no size field in the packet.
3040 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 // Get precision, scale for decimal types.
3050 int precision = -1;
3051 int scale = -1;
3052 if (columnType == SYBDECIMAL || columnType == SYBNUMERIC) {
3053 precision = comm.getByte();
3054 scale = comm.getByte();
3055 }
3056
3057 /*
3058 * NB: under 7.0 lengths are number of characters, not number of
3059 * bytes. The getString() method handles this.
3060 */
3061 int colNameLen = comm.getByte();
3062 String columnName = comm.getString(colNameLen);
3063
3064 // Populate the Column object.
3065 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 } // processTds7Result()
3082
3083 /***
3084 * Reports whether the type is for a large object. Name is a bit of a
3085 * misnomer, since it returns true for large text types, not just binary
3086 * objects (took it over from the freetds C code).
3087 */
3088 private static boolean isBlobType(int type) {
3089 return type == SYBTEXT || type == SYBIMAGE || type == SYBNTEXT;
3090 }
3091
3092 /***
3093 * Reports whether the type uses a 2-byte size value.
3094 */
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 // XXX How should this be syncrhonized?
3106 if (timeout==0 || stmt==null)
3107 {
3108 comm.peek();
3109 }
3110 else
3111 {
3112 // start the timeout thread
3113 TimeoutHandler t = new TimeoutHandler(stmt, timeout);
3114
3115
3116 t.start();
3117
3118 // wait until there is at least one byte of data
3119 comm.peek();
3120
3121 // kill the timeout thread
3122 t.stop();
3123 t = null;
3124 }
3125 }
3126
3127
3128 /***
3129 * send a query to the SQLServer for execution.
3130 * <p>
3131 *
3132 * @param sql sql statement to execute.
3133 * @param stmt
3134 * @param timeout
3135 *
3136 * @exception com.internetcds.jdbc.tds.TdsException
3137 * @exception java.io.IOException
3138 */
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; //JJ 1999-01-10
3159 comm.sendPacket();
3160
3161 waitForDataOrTimeout(stmt, timeout);
3162 }
3163 }
3164
3165
3166 /***
3167 * skip over and discard any remaining data from a result set.
3168 *
3169 * @exception com.internetcds.jdbc.tds.TdsException
3170 * @exception java.io.IOException
3171 */
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 // XXX Is there ever going to be a situation where the
3191 // TDS_DONEINPROC is the real end of data? If so then the
3192 // next section of code will hang forever waiting for more data
3193 // from the socket.
3194 if (isEndOfResults())
3195 {
3196 processSubPacket();
3197 }
3198
3199 // RMK 2000-06-08 Don't choke on additional result sets.
3200 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 } // peek()
3214
3215
3216 /***
3217 * Determine if the next subpacket is a result set.
3218 * <p>
3219 * This does not eat any input.
3220 *
3221 * @return true if the next piece of data to read is a result set.
3222 *
3223 * @exception com.internetcds.jdbc.tds.TdsException
3224 * @exception java.io.IOException
3225 */
3226 synchronized public boolean isResultSet()
3227 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException
3228 {
3229 byte type = comm.peek();
3230
3231 /*
3232 * XXX to support 5.0 we need to expand our view of what a result
3233 * set is.
3234 */
3235 return type==TDS_COL_NAME_TOKEN || type == TDS7_RESULT_TOKEN;
3236 }
3237
3238 /***
3239 * Determine if the next subpacket is a ret stat
3240 * <p>
3241 * This does not eat any input.
3242 *
3243 * @return true if the next piece of data to read is a result row.
3244 *
3245 * @exception com.internetcds.jdbc.tds.TdsException
3246 * @exception java.io.IOException
3247 */
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 /***
3257 * Determine if the next subpacket is a result row.
3258 * <p>
3259 * This does not eat any input.
3260 *
3261 * @return true if the next piece of data to read is a result row.
3262 *
3263 * @exception com.internetcds.jdbc.tds.TdsException
3264 * @exception java.io.IOException
3265 */
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 /***
3276 * Determine if the next subpacket is an end of result set marker.
3277 * <p>
3278 * This does not eat any input.
3279 *
3280 * @return true if the next piece of data to read is end of result set
3281 * marker.
3282 *
3283 * @exception com.internetcds.jdbc.tds.TdsException
3284 * @exception java.io.IOException
3285 */
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 /***
3295 * Determine if the next subpacket is a DONEINPROC marker
3296 * <p>
3297 * This does not eat any input.
3298 *
3299 * @return
3300 *
3301 * @exception com.internetcds.jdbc.tds.TdsException
3302 * @exception java.io.IOException
3303 */
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 /***
3313 * Determine if the next subpacket is a message packet
3314 * <p>
3315 * This does not eat any input.
3316 *
3317 * @return true if the next piece of data to read is message
3318 *
3319 * @exception com.internetcds.jdbc.tds.TdsException
3320 * @exception java.io.IOException
3321 */
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 /***
3330 * Determine if the next subpacket is a text update packet
3331 * <p>
3332 * This does not eat any input.
3333 *
3334 * @return true if the next piece of data to read is text update
3335 *
3336 * @exception com.internetcds.jdbc.tds.TdsException
3337 * @exception java.io.IOException
3338 */
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 /***
3347 * Determine if the next subpacket is an error packet
3348 * <p>
3349 * This does not eat any input.
3350 *
3351 * @return true if the next piece of data to read is an error
3352 *
3353 * @exception com.internetcds.jdbc.tds.TdsException
3354 * @exception java.io.IOException
3355 */
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 //sinisa
3363 /***
3364 * Determine if the next subpacket is an return status packet
3365 * <p>
3366 * This does not eat any input.
3367 *
3368 * @return true if the next piece of data to read is an return status
3369 *
3370 * @exception com.internetcds.jdbc.tds.TdsException
3371 * @exception java.io.IOException
3372 */
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 /***
3381 * Determine if the next subpacket is an procid subpacket
3382 * <p>
3383 * This does not eat any input.
3384 *
3385 * @return true if the next piece of data to read is end of result set
3386 * marker.
3387 *
3388 * @exception com.internetcds.jdbc.tds.TdsException
3389 * @exception java.io.IOException
3390 */
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 /***
3400 * Accessor method to determine the TDS level used.
3401 *
3402 * @return TDS42, TDS50, or TDS70.
3403 */
3404 int getTdsVer() { return tdsVer; }
3405
3406 /***
3407 * Return the name that this database server program calls itself.
3408 */
3409 String getDatabaseProductName()
3410 {
3411 return databaseProductName;
3412 }
3413
3414 /***
3415 * Return the name that this database server program calls itself.
3416 */
3417 String getDatabaseProductVersion()
3418 {
3419 return databaseProductVersion;
3420 }
3421 }
This page automatically generated by Maven