Client 4—An Interactive Query Processor

Now, let's move on to the final JDBC client. As in previous chapters, we'll wrap up by looking at an application that processes arbitrary commands entered by the user.

Listing 13.9 shows the client4.main() method.

Listing 13.9. client4.java (Part 1)
 1 //
 2 //  File: client4.java
 3 //
 4
5 import java.sql.*;
 6 import java.io.*;
 7
 8 public class client4
 9 {
10   static String blanks = "                                   ";
11   static String dashes = "-----------------------------------";
12
13   public static void main( String args[] )
14     throws SQLException
15   {
16     Class driverClass = loadDriver( "org.postgresql.Driver" );
17
18     if( driverClass == null )
19       return;
20
21     if( args.length != 1 )
22     {
23       System.err.println( "usage: java client4 <url>" );
24       return;
25     }
26
27     Connection con = connectURL( args[0] );
28
29     if( con != null )
30     {
31       DatabaseMetaData dbmd = con.getMetaData();
32
33       System.out.print( "Connected to " );
34       System.out.print( dbmd.getDatabaseProductName());
35       System.out.println( " " + dbmd.getDatabaseProductVersion());
36
37       processCommands( con );
38
39       con.close();
40     }
41   }

client4.main() is similar to client3.main(); it loads the PostgreSQL driver and then connects to the database using the URL provided by the user. At line 31, client4 obtains a DatabaseMetaData object so you can print a welcome message that includes the product name and version.

main() finishes by calling processCommands(). Now, let's look at the processCommands() method (Listing 13.10).

Listing 13.10. client4.java (Part 2)
43   static void processCommands( Connection con )
44   {    
45     try
46     {
47       Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
48                                            ResultSet.CONCUR_READ_ONLY);
49       String         cmd  = "";
50       BufferedReader in;
51       in = new BufferedReader( new InputStreamReader( System.in ));
52
53       while( true )
54       {
55         System.out.print( "--> ");
56
57         cmd = in.readLine();
58
59         if( cmd == null )
60             break;
61
62         if( cmd.equalsIgnoreCase( "quit" ))
63           break;
64
65         processCommand( stmt, cmd );
66
67       }
68
69       System.out.println( "bye" );
70
71     }
72     catch( Exception e )
73     {
74         System.err.println( e );
75     }
76   }

The processCommands() method prompts the user for a command and then executes that command. Because this is not a graphical application, you need a way to read input from the user. Java's BufferedReader class lets you read user input one line at a time, so processCommands() creates a new BufferedReader object at line 51.

Lines 53 through 67 comprise the main processing loop in this application. At the top of the loop, processCommands() prints a prompt string and then reads the user's response using BufferedReader's readline() method.

Three things can cause you to break out of this loop. First, one of the methods that you call can throw an exception. processCommands() catches any exceptions at line 72 and simply prints the error message contained in the exception. Next, the user can close the input stream (usually by pressing Ctrl+D). In that case, readline() returns a null String reference and you break out of the loop at line 60. Finally, you break out of this loop if the user enters the string quit.

When you reach line 65, you call the processCommand() method to execute a single command. Listing 13.11 shows the processCommand() method.

Listing 13.11. client4.java (Part 3)
78   static void processCommand( Statement stmt, String cmd )
79     throws SQLException
80   {
81
82     if( stmt.execute( cmd ))
83         printResultSet( stmt.getResultSet());
84     else 
85     {
86       int count = stmt.getUpdateCount();    
87
88       if( count == -1 )
89         System.out.println( "No results returned" );
90       else
91         System.out.println( "(" + count + " rows)" );
92     }
93   }

The processCommand() method is a little difficult to understand at first. Here's some background information that might help.

There are three[9] ways to execute a command using a Statement object. I've used the executeQuery() method in most of the examples in this chapter. Calling executeQuery() is only appropriate if you know that you are executing a SELECT command. executeQuery() returns a ResultSet. If you know that you are executing some other type of command (such as CREATE TABLE, INSERT, or UPDATE), you should use the executeUpdate() method instead of executeQuery(). executeUpdate() returns the number of rows affected by the command (or 0 for DDL commands).

[9] Actually, there is a fourth way to execute a SQL command. You can call the addBatch() method repeatedly to build up a batch of commands, and then execute the whole batch using executeBatch().

If you don't know whether you are executing a query or a command, which is the case in this client, you can call the execute() method. execute() returns a Boolean value: true means that the command returned a result set; false means that the command returned the number of rows affected by the command (or 0 for DDL commands)[10].

[10] This is not entirely accurate. Some JDBC drivers (but not the PostgreSQL driver) can execute multiple commands in a single call to execute(). In that case, the return code from execute() indicates the type of the first result. To get subsequent results, you call the getMoreResults() method. See the JDBC documentation for more information.

Because you don't know what kind of command the user entered, you use execute(). If the command returns a result set (that is, if execute() returns true), you can call printResultSet() to display the results. If the command does not return a result set, you have to call getUpdateCount() to determine whether the command modified any rows. Note that the 7.2 version of the PostgreSQL JDBC driver seems to contain a small bug: the getUpdateCount() method returns 1, even for commands such as CREATE TABLE, GRANT, and CREATE INDEX.

Now let's look at the methods that display result sets to the user. The first one is pad(),shown in Listing 13.12.

Listing 13.12. client4.java (Part 4)
 95   static String pad( String in, int len, String fill )
 96   {
 97       String result = in;
 98
 99       len -= in.length();
100
101       while( len > 0  )
102       {
103           int l;
104
105           if( len > fill.length())
106               l = fill.length();
107           else
108               l = len;
109
110         result = result + fill.substring( 0, l );
111
112         len -= l;
113       }
114
115       return( result );
116   }

The pad() method is a helper method used by printResultSet(). It returns a string padded with fill characters to the given length.

Next, let's look at the printResultSet() method, shown in Listing 13.13.

Listing 13.13. client4.java (Part 5)
118   static void printResultSet( ResultSet rs )
119     throws SQLException
120   {
121     int[]             sizes;
122     ResultSetMetaData rsmd     = rs.getMetaData();
123     int               colCount = rsmd.getColumnCount();
124     int               rowCount = 0;
125
126     sizes = new int[colCount+1];
127
128     //
129     // Compute column widths
130     //
131     while( rs.next())
132     {
133       rowCount++;
134
135       for( int i = 1; i <= colCount; i++ )
136       {
137         String val = rs.getString(i);
138
139         if(( rs.wasNull() == false ) && ( val.length() > sizes[i] ))
140           sizes[i] = val.length();
141       }
142     }
143
144     //
145     // Print column headers
146     //
147     for( int i = 1; i <= colCount; i++ )
148     {
149       if( rsmd.getColumnLabel(i).length() > sizes[i] )
150         sizes[i] = rsmd.getColumnLabel(i).length();
151
152       System.out.print( pad( rsmd.getColumnLabel( i ), 
153                              sizes[i], 
154                              blanks ));
155
156       if( i < colCount )
157         System.out.print( " | " );
158       else
159         System.out.println();
160     }   
161
162     for( int i = 1; i <= colCount; i++ ) 
163     {
164       if( i < colCount )
165         System.out.print( pad( "", sizes[i], dashes ) + "-+-" );
166       else
167         System.out.println( pad( "", sizes[i], dashes ));                 
168     }   
169
170     //
171     //  Rewind the result set and print the contents
172     //
173     rs.beforeFirst();
174
175     while( rs.next())
176     {
177       for( int i = 1; i <= colCount; i++ )
178       {
179         String val = rs.getString(i);
180
181         if( rs.wasNull())
182           val = "";
183
184         if( i < colCount )
185           System.out.print( pad( val, sizes[i], blanks ) + " | " );
186         else
187           System.out.println( pad( val, sizes[i], blanks ));
188       }
189     }
190   }

The printResultSet() method is easily the most complex method in this application.

It starts by computing the width of each column header. Each column should be as wide as the widest value in that column. You have to read through the entire result set to find the widest value. The code at lines 147 through 168 prints the column headers. If getColumnLabel() returns a string longer than the widest value in the column, line 150 adjusts the width to accommodate the label.

After you have printed the column headers, you have to rewind the result set so that you are positioned just before the first row. Remember, you processed the entire result set earlier when you were computing column widths.

The loop covering lines 175 through 189 processes every row in the result set. For each column in the result set, printResultSet() retrieves the value in String form. Line 181 shows an oddity in the JDBC package: There is no way to determine whether a value is NULL without first retrieving that value. So, you must first call rs.getString() to retrieve a column from the current row and then call rs.wasNull() to detect NULL values. You may be wondering what the getXXXX() methods will return if the value is NULL. The answer depends on which getXXXX() method you call. In this chapter, you have retrieved most result values in the form of a Java String, but you can also ask for values to be returned in other data types. getString() returns a null reference if the column value is NULL. getBoolean() will return false if the column value is NULL. Of course, getBoolean() will also return false if the column value is false. Likewise, getInt() returns 0 if the value is NULL or if the value is 0. You must call wasNull() to detect NULL values.

After fixing up any NULL values, lines 184 through 187 print the result, padded to the width of the column.

The last two methods in client4.java are identical to those included in client3.java. loadDriver() is shown in Listing 13.14.

Listing 13.14. client4.java (Part 6)
192   static Class loadDriver( String driverName )
193   {
194     try 
195     { 
196       return( Class.forName( driverName ));
197     }
198     catch( ClassNotFoundException e )
199     {
200       System.err.println( "Can't load driver - " + e.getMessage());
201       return( null );
202     }
203   }
204
205   static Connection connectURL( String URL )
206   {
207     try
208     {
209       return( DriverManager.getConnection( URL ));
210     }
211     catch( SQLException e )
212     {
213       System.err.println( "Can't connect - " + e.getMessage());
214       return( null );
215     }
216   }
217 }

The loadDriver() method tries to load the named JDBC driver, and connectURL() attempts to connect to the given JDBC URL.

Now, let's compile and run this application:

$ make client4.class
javac -g client4.java

$ java client4 "jdbc:postgresql:movies?user=korry&password=cows"
Connected to PostgreSQL 8.0.0

--> SELECT * FROM tapes
tape_id  | title        
---------+--------------
AB-12345 | The Godfather
AB-67472 | The Godfather
MC-68873 | Casablanca   
OW-41221 | Citizen Kane 
AH-54706 | Rear Window  

--> SELECT * FROM customers
id | customer_name        | phone    | birth_date
---+----------------------+----------+-----------
1  | Jones, Henry         | 555-1212 | 1970-10-10
2  | Rubin, William       | 555-2211 | 1972-07-10
3  | Panky, Henry         | 555-1221 | 1968-01-21
4  | Wonderland, Alice N. | 555-1122 | 1969-03-05
5  | Funkmaster, Freddy   | 555-FUNK |           
7  | Gull, Jonathan LC    | 555-1111 | 1984-02-05
8  | Grumby, Jonas        | 555-2222 | 1984-02-21

Now, I'd like to show you a problem with this application:

--> SELECT * FROM tapes; SELECT * FROM customers
Cannot handle multiple result groups.

In this example, I tried to execute two SQL commands on one line. As the message suggests, the PostgreSQL JDBC driver cannot handle multiple result groups (this message comes from an exception thrown by the PostgreSQL driver). Note that this is not a limitation of the JDBC package, but of this particular driver. The PostgreSQL source distribution includes an example application (src/interfaces/jdbc/example/psql.java) that gets around this problem by parsing user input into individual commands.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset