001package bradleyross.library.database;
002import java.sql.PreparedStatement;
003import java.sql.SQLException;
004import java.sql.Driver;
005import java.sql.DriverManager;
006import java.sql.ResultSet;
007import java.sql.Statement;
008import java.sql.ResultSetMetaData;
009import java.io.PrintWriter;
010import java.io.PrintStream;
011import java.sql.Types;
012import java.util.regex.Pattern;
013import java.util.regex.Matcher;
014import bradleyross.library.NotSupportedException;
015import bradleyross.library.database.DatabaseExtras;
016import bradleyross.library.helpers.GenericPrinter;
017/**
018 * Supports usage of databases via JDBC (Java DataBase Connection)
019 * protocol.
020 *<p>This is an abstract class and can't be used directly in the
021 *   creation of objects.  This means that subclasses must be
022 *   created.</p>
023 * <p>When creating subclasses, many fields will have to be set, 
024 *   including
025 *   <code><a href="#handlerClass">handlerClass</a></code>,
026 *   <code><a href="#accountName">accountName</a></code>,
027 *   <code><a href="#connectionString">connectionString</a></code>, and 
028 *   <code><a href="#password">password</a></code>.</p>
029 *<p>By including the objects for the JDBC connection and statement, it
030 *   is possible to encapsulate a large amount of the code for
031 *   the database connectivity.</p>
032 *<p>Please note that the methods in this class do not include one
033 *   for obtaining the password used in connecting to the database.
034 *   If required, this method can be added in the subclass.  
035 *   Eliminating this method adds to the security of the system.
036 *   By encapsulating the password in a field in this class for
037 *   use by the connect method, there is no need for other classes
038 *   to have access to the information.</p>
039 *  <p>SQL Server notes</p>
040 *  <ul>
041 *  <li><p>Wants the string <code>dbo.</code> in front of table names.</p></li>
042 *  <li><p>Specify the default database as part of the user configuration rather
043 *         than including it in the connection string.</p></li>
044 *  </ul>
045 *  <p>Oracle notes</p>
046 *  <ul>
047 *  <li><p>Will not accept semicolons at the end of SQL statements.</p></li>
048 *  </ul>
049 *  <p>MySQL notes</p>
050 * @author Bradley Ross
051 */
052public abstract class DatabaseProperties 
053{
054        protected Driver driver = null;
055        /**
056         * Constructor using a default system and account name.
057         */
058        public DatabaseProperties()
059        { 
060                invalidParameters = false;
061                clearTableNamePrefixes(); 
062                setDatabaseInstance();
063        }
064        /**
065         * Constructor specifying identifier for system and using
066         * a default account name.
067         * @param value Identifier code for system
068         */
069        public DatabaseProperties(String value)
070        { 
071                invalidParameters = false;
072                clearTableNamePrefixes(); 
073                setDatabaseInstance(value);
074        }
075        /**
076         * Constructor specifying system and account name.
077         * @param systemName  Identifier code for system
078         * @param accountName Account name to be used
079         */
080        public DatabaseProperties (String systemName, String accountName)
081        {
082                invalidParameters = false;
083                clearTableNamePrefixes();
084                setDatabaseInstance(systemName, accountName);
085        }
086        /**
087         * Internal program for setting up connection using default
088         * database and account.
089         * @see #setDatabaseInstance(String, String)
090         */
091        public abstract void setDatabaseInstance();
092        /**
093         * Internal program for setting up connection using specified
094         * database with a default account for the database.
095         * @param system Identifying code for database
096         */
097        public abstract void setDatabaseInstance(String system);
098        /**
099         * Internal program for setting up connection with a specified
100         * database and account name.
101         * @param system Identifier code for database
102         * @param user Account name to be used
103         */
104        public abstract void setDatabaseInstance(String system, String user);
105        /**
106         * Amount of diagnostic messages.
107         *<p>A value of zero indicates that no diagnostic messages
108         *   should be displayed.  The higher the value, the more
109         *   diagnostic messages will be generated.</p>
110         */
111        protected int debugLevel = 0;
112        /**
113         * Used to represent associated DatabaseExtras object.
114         */
115        protected DatabaseExtras extras = null;
116        /**
117         * Set value of extras.
118         * @param value Object to be used for DatabaseExtras object.
119         * @see #extras
120         */
121        public void setDatabaseExtras(DatabaseExtras value)
122        {
123                extras = value;
124        }
125        /**
126         * Get value of extras.
127         * @return DatabaseExtras object being used.
128         * @throws NotSupportedException if not supported for database
129         * @see #extras
130         */
131        public DatabaseExtras getDatabaseExtras() throws NotSupportedException
132        {
133                if (extras == null)
134                {
135                        throw new NotSupportedException("Database Extras is not supported");
136                }
137                else
138                {
139                        return extras;
140                }
141        }
142        /**
143         * Set of prefixes for table names
144         */
145        protected String tableNamePrefixes[] = new String[10];
146        /** Get the value of the default table name prefix (item 0)
147         * @return Default table name prefix
148         */
149        public String getTableNamePrefixes()
150        { return tableNamePrefixes[0]; }
151        /** 
152         * Get the value of a table name prefix
153         * @param i Location of table name prefix in list
154         * @return Value of table name prefix
155         */
156        public String getTableNamePrefixes(int i)
157        { return tableNamePrefixes[i]; }
158        /** 
159         * True indicates that autoCommit is set for this database connection when
160         * the connection is opened.
161         */
162        protected boolean autoCommit = true;
163        /**
164         * Optional field containing a human readable description of the
165         * database.  This string will have to be escaped before it is safe
166         * for use in HTML code and SQL queries.
167         */
168        protected String systemDescription = null;
169        /**
170         * Getter for systemDescription
171         * @return Description of system
172         * @see #systemDescription
173         */
174        public String getSystemDescription()
175        {
176                return systemDescription;
177        }
178        /**
179         * This is the name of the system identifier to identify
180         * the database in database systems that can support multiple
181         * databases.
182         */
183        protected String systemName = null;
184        /**
185         * Name of the handler class for the database.
186         * <table>
187         * <caption>Database driver classes</caption>
188         * <tr><td>Product</td><td>Handler Class</td><td>Default<br>Port</td></tr>
189         * <tr><td>MySQL</td><td><code>com.mysql.jdbc.Driver</code></td><td>3306</td></tr>
190         * <tr><td>Oracle</td><td><code>oracle.jdbc.driver.OracleDriver</code></td><td>1521</td></tr>
191         * <tr><td>SQL Server</td><td><code>com.microsoft.sqlserver.jdbc.SQLServerDriver</code></td><td>1433</td></tr>
192         * </table>
193         * <p>It may be necessary to use the name of the handlerClass in
194         *    generating the SQL statements, since the syntax for different
195         *    databases such as MySQL, Oracle, DB2, Postgres, etc. differ for
196         *    many of the built in functions.</p>
197         * <p>Although there is no <i>get</i> method for this field, one can
198         *    be added when creating subclasses.</p>
199         */
200        protected String handlerClass = null;
201        /** 
202         * Port number of the database connection.
203         * <p>The default port number is shown in the discussion of the
204         *    <a href="#handlerClass">handler class names</a>.  If a port
205         *    number other than the default is used, it is included as
206         *    part of the <a href="#domainName">domain name</a> and
207         *    <a href="#connectionString">connection string</a>.</p>
208         */
209        protected int portNumber = 0;
210        /**
211         * The connection string to be used to connect to the database using
212         * JDBC (Java DataBase Connectivity)
213         * <p>The connection string is composed as a series of tokens
214         *    separated by colons.  The first token is always
215         *    <code>jdbc</code> while the second is the name
216         *    of the database manager product.  The remaining
217         *    tokens are dependent on the DBMS package used.</p>
218         * <p>The following are examples of connection strings.</p>
219         * <table>
220         * <caption>Formats for database connection strings</caption>
221         * <tr><td>MySQL</td>
222         *     <td><code>jdbc:mysql://server.foo.com/alpha</code></td>
223         *     <td>alpha is default database</td>
224         *     </tr>
225         * <tr><td>Oracle</td>
226         *     <td><code>jdbc:oracle:thin:@//server.foo.com/weather<br>
227         *                          jdbc:oracle:oci:</code></td>
228         *     <td>Connects to weather service<br>
229         *          The options thin and oci refer to the two types of JDBC connections.  The
230         *          OCI method requires the installation of additional pieces of software
231         *          and uses SQL*Net to connect to the database.</td>
232         *     </tr>
233         * <tr><td>SQL Server</td>
234         *     <td><code>jdbc:sqlserver://server.foo.com</code></td>
235         *     <td>SQL Server allows you to specify a default database as part of the
236         *         user configuration.</td>
237         *     </tr>
238         * </table>
239         */
240        protected String connectionString = null;
241        /**
242         * Domain name for the system containing the database.
243         * <p>The domain name is included as part of the
244         *    <a href="#connectionString">connection string</a>.</p>
245         */
246        protected String domainName = null;
247        /**
248         * The account name to be used on the database server.
249         */
250        protected String accountName = null;
251        /** 
252         * The password to be used for accessing the database server.
253         * <p>It should be noted that there is no <i>get</i> method for
254         *    the field.  This improves security for the package.  If necessary,
255         *    a <i>get</i> method can be added in a subclass.</p>
256         */
257        protected String password = null;
258        /**
259         * When creating MySQL tables, this field can be
260         * used by the getEngineClause to add the ENGINE
261         * statement to CREATE TABLE statements.
262         * @see #getEngineClause()
263         */
264        protected String defaultEngine = null;
265        /**
266         * Default catalog for methods allowing the use
267         * of a default value.
268         * @see #getDefaultCatalog()
269         * @see #setDefaultCatalog(String)
270         */
271        protected String defaultCatalog = null;
272        /**
273         * Get the value of defaultCatalog.
274         * @return Value of defaultCatalog
275         * @see #defaultCatalog
276         */
277        public String getDefaultCatalog()
278        {
279                return defaultCatalog;
280        }
281        /**
282         * Sets value of defaultCatalog.
283         * @param value Value to be used for defaultCatalog
284         * @see #defaultCatalog
285         */
286        public void setDefaultCatalog(String value)
287        {
288                defaultCatalog = value;
289        }
290        /**
291         * Default schema for methods allowing the use
292         * of a default value.
293         * @see #getDefaultSchema()
294         * @see #setDefaultSchema(String)
295         */
296        protected String defaultSchema = null;
297        /**
298         * Gets value of defaultSchema
299         * @return Value of defaultSchema
300         * @see #defaultSchema
301         */
302        public String getDefaultSchema()
303        {
304                return defaultSchema;
305        }
306        /**
307         * Sets value of defaultSchema.
308         * @param value Value to be used for defaultSchema
309         * @see #defaultSchema
310         */
311        public void setDefaultSchema(String value)
312        {
313                defaultSchema = value;
314        }
315        /**
316         * The object representing the connection to the database.
317         * @see #getConnection()
318         */
319        protected java.sql.Connection connection = null;
320        /**
321         * Flag indicating whether database has been connected.
322         */
323        protected boolean isConnected = false;
324        /**
325         * Set the amount of diagnostic messages.
326         * @param level Amount of diagnostic messages.
327         */
328        public void setDebugLevel(int level)
329        { debugLevel = level; }
330        /**
331         * Gets the amount of diagnostic messages to be printed.
332         *
333         * @return Level of diagnostic messages to be printed.
334         */
335        public int getDebugLevel()
336        { return debugLevel; }
337        /**
338         * If the parameters supplied for instantiating the object are
339         * invalid, this field should be set to true.
340         * 
341         * @see #setInvalidParameters(boolean)
342         * @see #hasInvalidParameters()
343         */
344        protected boolean invalidParameters = false;
345        /**
346         * Returns the instance of the JDBC driver used to connect
347         * to the database.
348         * @return JDBC driver
349         */
350        public Driver getDriver() {
351                return driver;
352        }
353        /**
354         * Return the system identifier used for the database connection.
355         * @return System identifier
356         */
357        public String getSystem()
358        { return systemName; }
359        /**
360         * Get name of database management system
361         * <p>The database name is extracted from the 
362         *    <a href="#connectionString">connection string</a>
363         *    as the second token of the string.</p>
364         * @return DBMS descriptor
365         */
366        public String getDbms()
367        {
368                String working = connectionString.substring(connectionString.indexOf(":")+1);
369                working = working.substring(0, working.indexOf(":"));
370                return working.toLowerCase();
371        }
372        /**
373         * Return the account name used for the database connection.
374         * @return Account name for database.
375         */
376        public String getAccountName()
377        { return accountName; }
378        /**
379         * Create optional ENGINE clause
380         * @see #defaultEngine
381         * @return ENGINE clause for CREATE TABLE SQL statement
382         */
383        public String getEngineClause()
384        {
385                if (defaultEngine == null)
386                {
387                        return " ";
388                }
389                else if (getDbms().equalsIgnoreCase("mysql"))
390                {
391                        return " ENGINE " + defaultEngine + " ";
392                }
393                else return " ";
394        }
395        /**
396         * Open the database connection.
397         * @throws SQLException exception encountered while connecting
398         */
399        public void connect() throws java.sql.SQLException
400        {
401                if (invalidParameters)
402                {
403                        throw new SQLException("Parameters for system and account name are not valid : " +
404                                        showAttributes());
405                }
406                try
407                {
408                        driver = (Driver) Class.forName(handlerClass).newInstance();
409                }
410                catch (InstantiationException e)
411                {
412                        System.out.println("*** Error in establishing instance of handler");
413                        System.out.println("*** Instantiation Exception");
414                        System.out.println(e.getMessage());
415                        e.printStackTrace(System.out);
416                }
417                catch (IllegalAccessException e )
418                {
419                        System.out.println("*** Error in establishing instance of handler");
420                        System.out.println("*** Illegal Access Exception");
421                        System.out.println(e.getMessage());
422                        e.printStackTrace(System.out);
423                }
424                catch (ClassNotFoundException e)
425                {
426                        System.out.println("*** Error in establishing instance of handler");
427                        System.out.println("*** Class Not Found Exception");
428                        System.out.println(e.getMessage());
429                        e.printStackTrace(System.out);
430                }
431                try
432                {
433                        connection = DriverManager.getConnection(connectionString, accountName,
434                                        password); 
435                        connection.setAutoCommit(autoCommit);
436                }
437                catch (SQLException e)
438                {
439                        System.out.println("SQL Exception");
440                        System.out.println(e.getMessage());
441                        e.printStackTrace(System.out);
442                        throw e;
443                }
444                isConnected = true;
445                if (debugLevel > 1)
446                {
447                        System.out.println("Database connected");
448                }
449        }
450        /**
451         * Close the database connection.
452         * @throws SQLException  if database error
453         */
454        public void close() throws SQLException
455        {
456                try
457                {
458                        connection.close();
459                }
460                catch (SQLException e)
461                {
462                        e.printStackTrace(System.out);
463                        throw e;
464                }
465        }
466        /**
467         * Obtain the connection object for the database connection.
468         * @return connection
469         */
470        public java.sql.Connection getConnection()
471        { return connection;}
472        /**
473         * Execute SQL statement without returning results.
474         * <p>This method is usually used to make updates to the
475         *    database.</p>
476         * @param sqlCode SQL code to be executed.
477         * @return record count
478         * @throws SQLException exceptions encountered while running update
479         */
480        public int executeUpdate(String sqlCode) throws SQLException
481        {
482                java.sql.Statement stmt = null;
483                int recordCount = -1;
484                try
485                {
486                        if (debugLevel > 0)
487                        { System.out.println("Starting executeUpdate"); }
488                        if (connection == (java.sql.Connection) null)
489                        { connect(); }
490                        stmt = connection.createStatement();
491                        recordCount  = stmt.executeUpdate(sqlCode);
492                        if (debugLevel > 0)
493                        { System.out.println("Statement executed: " +
494                                        Integer.toString(recordCount) + 
495                        " records updated"); }
496                        stmt.close();
497                }
498                catch (SQLException e)
499                {
500                        if (debugLevel > 0 )
501                        {
502                                System.out.println("SQLException in executeUpdate");
503                                System.out.println(e.getMessage());
504                                e.printStackTrace(System.out);
505                        }
506                        throw e;
507                }
508                catch (Exception ex)
509                {
510                        System.out.println("Unknown Exception in executeUpdate");
511                        ex.printStackTrace(System.out);
512                        throw new SQLException(ex.getClass().getName() + " : " + ex.getMessage() + 
513                                        "Unknown Exception in executeUpdate");
514                }
515                return recordCount;
516        }
517        /**
518         * This class reads the first field on the first record from
519         * an SQL Query.
520         * <p>Since only the first field is read, the statement is
521         *    closed after reading the field.  This eliminates the possibility
522         *    of leaving a statement open if it returns multiple rows.</p>
523         * @param sqlCode SQL code to be executed.
524         * @return String object continuing result of SQL query
525         * @throws SQLException exceptions encountered while executing query
526         */
527        public String executeSimpleQuery(String sqlCode) 
528        throws SQLException
529        {
530                java.sql.Statement stmt = null;
531                java.sql.ResultSet rs = null;
532                java.lang.String result = null;
533                try
534                {
535                        if (debugLevel > 0)
536                        { System.out.println("Starting executeSimpleQuery"); }
537                        if (connection == (java.sql.Connection) null)
538                        { connect(); }
539                        stmt = connection.createStatement();
540                        rs = stmt.executeQuery(sqlCode);
541                        if (debugLevel > 0)
542                        { System.out.println("Statement executed"); }
543                        /*
544                         * The first method is not supported for forward only
545                         *    result sets.  It looks like forward only result
546                         *    sets are the default for SQL Server.
547                         */
548                        rs.next();
549                        result = rs.getString(1);
550                        stmt.close();
551                }
552                catch (SQLException e)
553                {
554                        if (debugLevel > 0)
555                        {
556                                System.out.println("SQLException in executeSimpleQuery");
557                                System.out.println(e.getMessage());
558                                e.printStackTrace(System.out);
559                        }
560                        throw e;
561                }
562                catch (Exception ex)
563                {
564                        System.out.println("Unknown Exception in executeSimpleQuery");
565                        ex.printStackTrace(System.out);
566                }
567                return result;
568        }
569        /**
570         * This class executes an SQL query and returns a
571         * ResultSet.
572         * @param sqlCode SQL code to be executed.
573         * @return ResultSet
574         * @throws SQLException exceptions encountered while executing query
575         */
576        public java.sql.ResultSet executeQuery(String sqlCode) 
577        throws java.sql.SQLException
578        {
579                java.sql.Statement stmt = null;
580                java.sql.ResultSet rs = null;
581                if (!isConnected)
582                {
583                        System.out.println(" Error in executeQuery - database is not connected");
584                        throw new SQLException("Database not connected");
585                }
586                try
587                {
588                        if (debugLevel > 0)
589                        { System.out.println("Starting executeQuery for " + sqlCode); }
590                        if (connection == (java.sql.Connection) null)
591                        { 
592                                if (debugLevel > 1)
593                                {
594                                        System.out.println("Creating connection");
595                                }
596                                connect(); 
597                        }
598                        stmt = connection.createStatement();
599                        if (debugLevel > 1)
600                        {
601                                System.out.println("Statement created");
602                        }
603                        rs = stmt.executeQuery(sqlCode);
604                        if (debugLevel > 0)
605                        { System.out.println("Statement executed"); }
606                }
607                catch (SQLException e)
608                {
609                        System.out.println("SQLException in executeQuery");
610                        System.out.println(e.getMessage());
611                        e.printStackTrace(System.out);
612                        throw e;
613                }
614                catch (Exception ex)
615                {
616                        System.out.println("Unknown Exception in executeQuery");
617                        System.out.println(ex.getClass().getName() + " " + ex.getMessage());
618                        ex.printStackTrace(System.out);
619                }
620                return rs;
621        }
622        /**
623         * Execute a commit statement for the SQL connection.
624         */
625        public void commit()
626        {
627                try
628                { connection.commit(); }
629                catch (SQLException e)
630                {
631                        System.out.println("SQLException during commit");
632                        e.printStackTrace(System.out);
633                }
634                catch (Exception e)
635                {
636                        System.out.println("Unknown exception during commit");
637                        e.printStackTrace(System.out);
638                }
639        }
640        /**
641         * Execute a rollback statement for the SQL connection.
642         */
643        public void rollback()
644        {
645                try
646                { connection.rollback(); }
647                catch (SQLException e)
648                {
649                        System.out.println("SQLException during rollback");
650                        e.printStackTrace(System.out);
651                }
652                catch (Exception e)
653                {
654                        System.out.println("Unknown exception during rollback");
655                        e.printStackTrace(System.out);
656                }
657        }
658        /** 
659         * Set autoCommit flag
660         * @param value Value to be used for setting autoCommit flag
661         */
662        public void setAutoCommit(boolean value)
663        {
664                try
665                { connection.setAutoCommit(value); }
666                catch (SQLException e)
667                {
668                        System.out.println("SQLException while setting autoCommit to " +
669                                        Boolean.toString(value));
670                        e.printStackTrace(System.out);
671                }
672        }
673        /**
674         * Return attributes of the connection properties
675         *
676         * @return String containing connection information
677         */
678        public java.lang.String showAttributes()
679        {
680                return ("systemName:" + systemName +
681                                "\naccountName:" + accountName +
682                                "\ndomainName:" + domainName +
683                                "\nhandlerClass:" + handlerClass);
684        }
685        /**
686         * Setter for accountName.
687         * 
688         * <p>Must be used before database connection is made.</p>
689         * @param value Value for accountName
690         *
691         */
692        
693        public void setAccountName(String value)
694        {
695                if (isConnected) { return; }
696                accountName = value;
697                setDatabaseInstance(systemName, accountName);
698        }
699        /**
700         * Specify the connection string to be used.
701         * 
702         * <p>Do not specify the account name or password as
703         *    part of the connection string.</p>
704         *    
705         * @param value Connection string to be used
706         * @see #connectionString
707         */
708        public void setConnectionString(String value)
709        {
710                if (isConnected) { return; }
711                connectionString = value;
712        }
713        /**
714         * Setter for domainName
715         * <p>Does not include port number</p>
716         * @see #domainName
717         * @param value Value to be used for domain name
718         */
719        public void setDomainName(String value)
720        {
721                if (isConnected) { return; }
722                domainName = value;
723        }
724        /**
725         * Set the class name to be used for the handler class.
726         * @param value Name of handler class
727         * @see #handlerClass
728         */
729        public void setHandlerClass(String value)
730        {
731                if (isConnected) { return; }
732                handlerClass = value;
733        }
734        /** 
735         * Sets the password to be used for the connection
736         * when it is desired to avoid placing the value in
737         * the program.
738         *
739         * @param value Value to be used for the password
740         */
741        public void setPassword(String value)
742        { 
743                if (isConnected) { return; }
744                password = value; 
745        }
746        /**
747         * Setter for port number
748         * @param value port number to be used for connection
749         * @see #portNumber
750         */
751        public void setPortNumber(int value) {
752                portNumber = value;
753        }
754        /**
755         * Setter for systemName.
756         * 
757         * @see #systemName
758         * @param value Value to be used for system name
759         * 
760         */     
761        public void setSystemName(String value)
762        {
763                if (isConnected) { return; }
764                systemName = value;
765        }       
766        /**
767         * Clears the list of table name prefix values.
768         */
769        public void clearTableNamePrefixes()
770        {
771                for (int i=0; i<tableNamePrefixes.length; i++)
772                { tableNamePrefixes[i] = null; }
773        }
774        /**
775         * Prefixes a table name with a value that was set as part of the
776         * database connection.
777         *
778         * @param type Location of the desired prefix in the tableNamePrefixes list
779         * @param value Table name to be prefixed
780         * @return Prefixed table name
781         */
782        public java.lang.String prefixTableName(int type, String value)
783        {
784                if (type < 0 || type > 9)
785                { return " " + value + " "; }
786                if (tableNamePrefixes[type] == null)
787                { return " " + value + " "; }
788                if (tableNamePrefixes[type].length() <= 0)
789                { return value; }
790                return (" " + new String(tableNamePrefixes[type] + value) + " ");
791        }
792        /**
793         * Prefixes a table name with a value that was set as part of the database
794         * connection.
795         *
796         * <p>This version of the method uses the first entry in the tableNamePrefixes 
797         *    array.  (index=0)</p>
798         *
799         * @param value Table name to be prefixed
800         * @return Prefixed table name
801         */
802        public java.lang.String prefixTableName(java.lang.String value)
803        { return prefixTableName(0, value); }
804        /**
805         * Generate a prepared statement for this database.
806         * @param query SQL Query to be used in statement
807         * @return PreparedStatement object
808         * @throws java.sql.SQLException if database errors
809         */
810        public java.sql.PreparedStatement prepareStatement(String query)
811        throws java.sql.SQLException
812        { 
813                if (connection == null)
814                {
815                        throw new SQLException("The value of the connection field is null");
816                }
817                return connection.prepareStatement(query);
818        }
819        /**
820         * Returns the parameter as a DDL comment
821         * if the database is of a type that allows comments in
822         * DDL statements.
823         * 
824         * <p>SQL Server does not allow comments in DDL statements.</p>
825         * 
826         * @param text Text to be used in comment
827         * @return Formatted material for comment in CREATE TABLE statement
828         */
829        public String comment(String text)
830        {
831                if (text == null) { return " "; }
832                if (!getDbms().equalsIgnoreCase("sqlserver"))
833                {
834                        Pattern pattern = Pattern.compile("\'");
835                        Matcher matcher = pattern.matcher(text);
836                        String temp = matcher.replaceAll("\'\'");
837                        return (" COMMENT \'" + temp + "\' "); 
838                }
839                else
840                {
841                        return (" ");
842                }
843        }
844        /**
845         * Return the data type used for large BLOBS
846         * (Binary Large Objects).
847         * @return Name of data type.
848         */
849        public String getLongblobTerm()
850        {
851                if (getDbms().equalsIgnoreCase("sqlserver"))
852                {
853                        return " VARBINARY(MAX) ";
854                }
855                else if (getDbms().equalsIgnoreCase("mysql"))
856                {
857                        return " LONGBLOB ";
858                }
859                else 
860                {
861                        return " LONGBLOB ";
862                }
863        }
864        /**
865         * Return the expression used in SQL statements to
866         * obtain the current time.
867         * @return SQL clause.
868         */
869        public String getNowTerm()
870        {
871                if (getDbms().equalsIgnoreCase("sqlserver"))
872                {
873                        return " GETDATE() ";
874                }
875                else if (getDbms().equalsIgnoreCase("mysql"))
876                {
877                        return " NOW() ";
878                }
879                else if (getDbms().equalsIgnoreCase("oracle"))
880                {
881                        return " SYSDATE ";
882                }
883                else 
884                {
885                        return " NOW() ";
886                }
887        }
888        /**
889         * Return the expression used in SQL statements to
890         * represent a data type for holding a date.
891         * 
892         * <p>SQL Server 2000 and 2005 do not have a DATE data type,
893         *    and SMALLDATETIME should be used instead.</p>
894         *    
895         * @return SQL clause.
896         */
897        public String getDateTerm()
898        {
899                if (getDbms().equalsIgnoreCase("sqlserver"))
900                {
901                        return " SMALLDATETIME ";
902                }
903                else if (getDbms().equalsIgnoreCase("mysql"))
904                {
905                        return " DATE ";
906                }
907                else if (getDbms().equalsIgnoreCase("oracle"))
908                {
909                        return " DATE ";
910                }
911                else 
912                {
913                        return " DATE ";
914                }
915        }
916        /**
917         * Return the expression used in SQL statements to
918         * represent a data type for holding a date and time.
919         * 
920         * <p>It appears that Oracle refers to this data type
921         *    as TIMESTAMP.</p>
922         *    
923         * @return SQL clause.
924         */
925        public String getDatetimeTerm()
926        {
927                if (getDbms().equalsIgnoreCase("sqlserver"))
928                {
929                        return " DATETIME ";
930                }
931                else if (getDbms().equalsIgnoreCase("mysql"))
932                {
933                        return " DATETIME ";
934                }
935                else if (getDbms().equalsIgnoreCase("oracle"))
936                {
937                        return " TIMESTAMP ";
938                }
939                else 
940                {
941                        return " DATETIME ";
942                }
943        }
944        /**
945         * Decode the values of constants in java.sql.TYPES to 
946         *    String values.
947         * @param value  Value of constant
948         * @return Corresponding string describing constant
949         * @see Types
950         */
951        public static String decodeSqlType(int value)
952        {
953                if (value == Types.ARRAY)
954                {
955                        return "ARRAY";
956                }
957                else if (value == Types.BIGINT)
958                {
959                        return "BIGINT";
960                }
961                else if (value == Types.BINARY)
962                {
963                        return "BINARY";
964                }
965                else if (value == Types.BIT)
966                {
967                        return "BIT";
968                }
969                else if (value == Types.BLOB)
970                {
971                        return "BLOB";
972                }
973                else if (value == Types.BOOLEAN)
974                {
975                        return "BOOLEAN";
976                }
977                else if (value == Types.CHAR)
978                {
979                        return "CHAR";
980                }
981                else if (value == Types.CLOB)
982                {
983                        return "CLOB";
984                }
985                else if (value == Types.DOUBLE)
986                {
987                        return "DOUBLE";
988                }
989                else if (value == Types.FLOAT)
990                {
991                        return "FLOAT";
992                }
993                else if (value == Types.INTEGER)
994                {
995                        return "INTEGER";
996                }
997                else if (value == Types.JAVA_OBJECT)
998                {
999                        return "JAVA_OBJECT";
1000                }
1001                else if (value == Types.LONGVARBINARY)
1002                {
1003                        return "LONGVARBINARY";
1004                }
1005                else if (value == Types.LONGVARCHAR)
1006                {
1007                        return "LONGVARCHAR";
1008                }
1009                else if (value == Types.REAL)
1010                {
1011                        return "REAL";
1012                }
1013                else if (value == Types.SMALLINT)
1014                {
1015                        return "SMALLINT";
1016                }
1017                else if (value == Types.TINYINT)
1018                {
1019                        return "TINYINT";
1020                }
1021                else if (value == Types.VARCHAR)
1022                {
1023                        return "VARCHAR";
1024                }
1025                else if (value == Types.VARBINARY)
1026                {
1027                        return "VARBINARY";
1028                }
1029                else if (value == Types.DATE)
1030                {
1031                        return "DATE";
1032                }
1033                else if (value == Types.TIME)
1034                {
1035                        return "TIME";
1036                }
1037                else if (value == Types.TIMESTAMP)
1038                {
1039                        return "TIMESTAMP";
1040                }
1041                else
1042                {
1043                        return "Unrecognized value";
1044                }
1045        }
1046        
1047        /**
1048         * Set value of invalidParameters to indicate that constructor is not valid.
1049         * @param value Value to be used for notSupported
1050         */
1051        protected void setInvalidParameters(boolean value)
1052        {
1053                invalidParameters = value;
1054        }
1055        /**
1056         * Determine whether parameters for constructor were valid.
1057         * @return True if combination of system and account is not supported
1058         */
1059        public boolean hasInvalidParameters()
1060        {
1061                return invalidParameters;
1062        }
1063        /**
1064         * Dump contents of database table to PrintWriter object.
1065         * 
1066         * @param table Name of table to be dumped
1067         * @param output Destination PrintWriter
1068         */
1069        public void dumpTable(String table, PrintWriter output)
1070        {
1071                dumpTable(table, table, output, (PrintStream) null);
1072        }
1073        /**
1074         * Dump contents of database to PrintStream object
1075         * @param table  Name of table to be dumped
1076         * @param output Destination PrintStream
1077         */
1078        public void dumpTable(String table, PrintStream output)
1079        {
1080                dumpTable(table, table, (PrintWriter) null, output);
1081        }
1082        /**
1083         * Dumps the contents of a table as a series of SQL statements.
1084         * <p>An OutputStreamWriter can be created from an OutputStream using the
1085         *    constructor <code>OutputStreamWriter(OutputStream out)</code>.</p>
1086         * <p>The objects <code>System.out</code> and <code>System.err</code> are
1087         *    PrintStream objects.  A PrintStream object can be created from an
1088         *    OutputStream object using the constructor
1089         *    <code>PrintStream(OutputStream out)</code>.</p>
1090         * <p>A PrintStream object can be created from a File object using the
1091         *    constructor
1092         *    <code>PrintStream (File out)</code>.</p>
1093         * 
1094         * @param sourceTableName Table to be dumped
1095         * @param targetTableName Table name to be used in SELECT statement
1096         * @param outputWriter PrintWriter object to which information is to be passed
1097         * @param outputStream PrintStream object to which information is to be passed
1098         */
1099        protected void dumpTable(String sourceTableName, String targetTableName, PrintWriter outputWriter,
1100                        PrintStream outputStream)
1101        {
1102                System.out.println("Starting table dumper for " + sourceTableName);
1103                setAutoCommit(false);
1104                String tableName = prefixTableName(sourceTableName);
1105                String sqlQuery = "SELECT * FROM " + tableName ;
1106                if (debugLevel > 1)
1107                {
1108                        System.out.println("Query is " + sqlQuery);
1109                }
1110                Statement stmt = null;
1111                ResultSet rs = null;
1112                try
1113                {               
1114                        boolean test;
1115                        stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
1116                        /*
1117                         * This is apparently a fix for large result sets specific to MySQL.
1118                         * There was also a comment to try 
1119                         * ((com.mysql.jdbc.Statement) stmt).enableStreamingResults();
1120                         */
1121                        if (getDbms().equalsIgnoreCase("mysql"))
1122                        {
1123                                if (debugLevel > 1)
1124                                {
1125                                        System.out.println("Special case for MySQL large result sets");
1126                                }
1127                                stmt.setFetchSize(Integer.MIN_VALUE);
1128                        }
1129                        else if (getDbms().equalsIgnoreCase("sqlserver"))
1130                                stmt.setFetchSize(20);
1131                        if (debugLevel > 1)
1132                        {
1133                                int sample = stmt.getResultSetType();
1134                                if (sample == ResultSet.TYPE_FORWARD_ONLY)
1135                                {
1136                                        System.out.println("Result set is TYPE_FORWARD_ONLY");
1137                                }
1138                                else if (sample == ResultSet.TYPE_SCROLL_INSENSITIVE)
1139                                {
1140                                        System.out.println("Result set is TYPE_SCROLL_INSENSITIVE");
1141                                }
1142                                else if (sample == ResultSet.TYPE_SCROLL_SENSITIVE)
1143                                {
1144                                        System.out.println("Result set is TYPE_SCROLL_SENSITIVE");
1145                                }
1146                                else
1147                                {
1148                                        System.out.println("Result set is of unknown type");
1149                                }
1150                        }
1151                        test = stmt.execute(sqlQuery);
1152                        if (!test)
1153                        {
1154                                System.out.println("Problem in executing " + sqlQuery);
1155                                return;
1156                        }
1157                        rs = stmt.getResultSet();
1158                        if (rs == null)
1159                        {
1160                                System.out.println("Result set was null");
1161                        }
1162                        else
1163                        {
1164                                System.out.println("Result set generated");
1165                        }
1166                }
1167                catch (java.sql.SQLException e)
1168                {
1169                        System.out.println("SQL Exception encountered: " + e.getMessage());
1170                        e.printStackTrace(System.out);
1171                }
1172                catch (Exception e)
1173                {
1174                        System.out.println("Unexpected exception in table dumper");
1175                        System.out.println(e.getClass().getName() + " " + e.getMessage());
1176                        e.printStackTrace(System.out);
1177                }
1178        }
1179        public void dumpTable(String sourceTableName, String targetTableName, 
1180                        GenericPrinter output)
1181        {
1182                System.out.println("dumpTable: Starting table dumper for " + sourceTableName);
1183                setAutoCommit(false);
1184                String tableName = prefixTableName(sourceTableName);
1185                String sqlQuery = "SELECT * FROM " + tableName ;
1186                if (debugLevel > 1)
1187                {
1188                        System.out.println("dumpTable: Query is " + sqlQuery);
1189                }
1190                Statement stmt = null;
1191                ResultSet rs = null;
1192                try
1193                {               
1194                        boolean test;
1195                        stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
1196                        /*
1197                         * This is apparently a fix for large result sets specific to MySQL.
1198                         * There was also a comment to try 
1199                         * ((com.mysql.jdbc.Statement) stmt).enableStreamingResults();
1200                         */
1201                        if (getDbms().equalsIgnoreCase("mysql"))
1202                        {
1203                                if (debugLevel > 1)
1204                                {
1205                                        System.out.println("Special case for MySQL large result sets");
1206                                }
1207                                stmt.setFetchSize(Integer.MIN_VALUE);
1208                        }
1209                        else if (getDbms().equalsIgnoreCase("sqlserver"))
1210                                stmt.setFetchSize(20);
1211                        if (debugLevel > 1)
1212                        {
1213                                int sample = stmt.getResultSetType();
1214                                if (sample == ResultSet.TYPE_FORWARD_ONLY)
1215                                {
1216                                        System.out.println("Result set is TYPE_FORWARD_ONLY");
1217                                }
1218                                else if (sample == ResultSet.TYPE_SCROLL_INSENSITIVE)
1219                                {
1220                                        System.out.println("Result set is TYPE_SCROLL_INSENSITIVE");
1221                                }
1222                                else if (sample == ResultSet.TYPE_SCROLL_SENSITIVE)
1223                                {
1224                                        System.out.println("Result set is TYPE_SCROLL_SENSITIVE");
1225                                }
1226                                else
1227                                {
1228                                        System.out.println("Result set is of unknown type");
1229                                }
1230                        }
1231                        test = stmt.execute(sqlQuery);
1232                        if (!test)
1233                        {
1234                                System.out.println("Problem in executing " + sqlQuery);
1235                                return;
1236                        }
1237                        rs = stmt.getResultSet();
1238                        if (rs == null)
1239                        {
1240                                System.out.println("dumpTable: Result set was null");
1241                        }
1242                        else
1243                        {
1244                                System.out.println("dumpTable: Result set generated");
1245                                dumpResultSet(rs, targetTableName, output);
1246                        }
1247                }
1248                catch (java.sql.SQLException e)
1249                {
1250                        System.out.println("SQL Exception encountered: " + e.getMessage());
1251                        e.printStackTrace(System.out);
1252                }
1253                catch (Exception e)
1254                {
1255                        System.out.println("Unexpected exception in table dumper");
1256                        System.out.println(e.getClass().getName() + " " + e.getMessage());
1257                        e.printStackTrace(System.out);
1258                }
1259        }       
1260        
1261        /**
1262         * Dumps a result set to a PrintWriter object
1263         * @param rs Result Set
1264         * @param targetTableName Table name to be used in INSERT statement
1265         * @param outputWriter Destination PrintWriter object
1266         */
1267        public void dumpResultSet(ResultSet rs, String targetTableName, PrintWriter outputWriter)
1268        {
1269                GenericPrinter out = new GenericPrinter(outputWriter);
1270                dumpResultSet(rs, targetTableName, out);
1271        }
1272        /**
1273         * Dumps a result set to a PrintStream object
1274         * @param rs Result set
1275         * @param targetTableName Table name to be used in INSERT statement
1276         * @param outputStream Destination PrintStream object
1277         */
1278        public void dumpResultSet(ResultSet rs, String targetTableName, PrintStream outputStream)
1279        {
1280                GenericPrinter out = new GenericPrinter(outputStream);
1281                dumpResultSet(rs, targetTableName,  out);
1282        }
1283        /**
1284         * Dumps a result set as a set of INSERT SQL statements.
1285         * 
1286         * @param rs Result set to be dumped
1287         * @param targetTableName Table name to be used in INSERT statements
1288         * @param output GenericPrinter object to which information is passed
1289         *
1290         */
1291        public void dumpResultSet(ResultSet rs, String targetTableName, GenericPrinter output)
1292        {
1293                ResultSetMetaData meta = null;
1294                int columnCount = 0;
1295                String columnNames[];
1296                int columnTypes[];
1297                int rowCounter = 0;
1298                try
1299                {
1300                        rowCounter = 0;
1301                        meta = rs.getMetaData();
1302                        columnCount = meta.getColumnCount();
1303                        if (debugLevel > 0)
1304                        {
1305                                System.out.println("Query executed, " + Integer.toString(columnCount) + " columns"); 
1306                        }
1307                        columnNames = new String[columnCount];
1308                        columnTypes = new int[columnCount];
1309                        for (int i = 0; i < columnCount; i++)
1310                        {
1311                                columnNames[i] = meta.getColumnName(i+1);
1312                                columnTypes[i] = meta.getColumnType(i+1);
1313                        }
1314                        /* Get result set */
1315                        while (rs.next())
1316                        {
1317                                boolean nullValue[] = new boolean[columnCount];
1318                                String columnValue[] = new String[columnCount];
1319                                for (int i = 0; i < columnCount; i++)
1320                                {
1321                                        nullValue[i] = false;
1322                                        columnValue[i] = null;
1323                                }
1324                                rowCounter++;
1325                                if (rowCounter % 500 == 49) 
1326                                { 
1327                                        output.flush();
1328                                }       
1329                                StringBuffer working;
1330                                /* Read values for SQL statement */
1331                                for (int i = 0; i < columnCount; i++)
1332                                {
1333                                        
1334                                        int temp = columnTypes[i];
1335                                        if (temp == Types.TIMESTAMP)
1336                                        {
1337                                                java.sql.Timestamp date = rs.getTimestamp(i+1);
1338                                                if (rs.wasNull())
1339                                                {
1340                                                        nullValue[i] = true;
1341                                                        columnValue[i] = "dummy";
1342                                                }
1343                                                else
1344                                                {
1345                                                        columnValue[i] ="\'" + date.toString() + "\'"; 
1346                                                }
1347                                        }
1348                                        else if (temp == Types.VARCHAR || temp == Types.LONGVARCHAR ||
1349                                                        temp == Types.CHAR)
1350                                        {
1351                                                String value = rs.getString(i+1);
1352                                                if (rs.wasNull())
1353                                                {
1354                                                        nullValue[i] = true;
1355                                                        
1356                                                }
1357                                                else
1358                                                {
1359                                                        
1360                                                        columnValue[i] = "\'" + value + "\'";
1361                                                }
1362                                        }
1363                                        else if (temp == Types.FLOAT || temp == Types.DOUBLE || temp == Types.DECIMAL ||
1364                                                        temp == Types.NUMERIC || temp == Types.REAL)
1365                                        {
1366                                                float value = 0.0f;
1367                                                value = rs.getFloat(i+1);
1368                                                if (rs.wasNull())
1369                                                {
1370                                                        nullValue[i] = true;
1371                                                        
1372                                                }
1373                                                else
1374                                                {
1375                                                        
1376                                                        columnValue[i] = Float.toString(value);
1377                                                }
1378                                        }
1379                                        else if (temp == Types.TINYINT || temp == Types.SMALLINT ||
1380                                                        temp == Types.INTEGER)
1381                                        {
1382                                                int value = rs.getInt(i+1);
1383                                                if (rs.wasNull()) 
1384                                                {
1385                                                        nullValue[i] = true;
1386                                                        
1387                                                }
1388                                                else
1389                                                {
1390                                                        
1391                                                        columnValue[i] = Integer.toString(value);
1392                                                }
1393                                        }
1394                                }
1395                                /*
1396                                 * Write SQL statement
1397                                 */
1398                                working = new StringBuffer();
1399                                boolean firstItemProcessed = false;
1400                                working.append("INSERT INTO " + targetTableName + "(");
1401                                for (int i = 0; i < columnCount; i++)
1402                                {
1403                                        if (nullValue[i])
1404                                        {
1405                                                continue;
1406                                        }
1407                                        if (firstItemProcessed)
1408                                        {
1409                                                working.append(", " + columnNames[i]);
1410                                        }
1411                                        else
1412                                        {
1413                                                working.append(columnNames[i]);
1414                                                firstItemProcessed = true;
1415                                        }
1416                                }
1417                                working.append(") VALUES(");
1418                                firstItemProcessed = false;
1419                                for (int i = 0; i < columnCount; i++)
1420                                {
1421                                        if (nullValue[i])
1422                                        {
1423                                                continue;
1424                                        }
1425                                        if (firstItemProcessed)
1426                                        {
1427                                                working.append(", " + columnValue[i]);
1428                                        }
1429                                        else
1430                                        {
1431                                                working.append(columnValue[i]);
1432                                                firstItemProcessed = true;
1433                                        }
1434                                }
1435                                working.append(");");
1436                                output.println(new String(working));
1437                        }
1438                        if (debugLevel > 0)
1439                        {
1440                                System.out.println(Integer.toString(rowCounter) + " rows were processed");
1441                        }
1442                        rs.close();             
1443                }
1444                catch (java.sql.SQLException e)
1445                {
1446                        System.out.println("SQL Exception encountered: ");
1447                        System.out.println(e.getClass().getName()+ e.getMessage());
1448                        e.printStackTrace(System.out);
1449                }
1450                catch (Exception e)
1451                {
1452                        System.out.println("Unexpected exception in table dumper");
1453                        System.out.println(e.getClass().getName() + " " + e.getMessage());
1454                        e.printStackTrace(System.out);
1455                }
1456        }
1457        /**
1458         * Insert rows in a table where there is only one column in
1459         * the primary key.
1460         * 
1461         * <p>The primary key should be the first column in the list.</p>
1462         * 
1463         * @param tableName Name of the table
1464         * @param columnNames Array containing column names in the database
1465         *        table for the columns to be populated with data.
1466         * @param value Values to be inserted in the columns
1467         * @throws SQLException if database errors
1468         * @deprecated
1469         */
1470        public void insertRow (String tableName,
1471                String columnNames[], Object... value) throws SQLException      
1472                        {
1473                                insertRowWithKeys (tableName, columnNames, 1, value);
1474                        }
1475        /**
1476         * Insert rows in a table where there is more than one column in
1477         * the primary key.
1478         * 
1479         * @param tableName Name of the table
1480         * @param columnNames Array containing column names in the database
1481         *        table.
1482         * @param columnCount Number of columns in primary key.  These are the first
1483         *        columns in the list.
1484         * @param value Values to be inserted in the columns
1485         * @throws SQLException if database errors
1486         */
1487        public void insertRowWithKeys (String tableName,
1488                        String columnNames[], int columnCount, Object... value) throws SQLException
1489        {
1490                if (debugLevel > 1 || columnNames.length != value.length)
1491                {
1492                        System.out.println("Starting insertRow");
1493
1494                        for (int i = 0; i < columnNames.length; i++)
1495                        {
1496                                System.out.print(columnNames[i] + " ");
1497                        }
1498                        System.out.println();
1499                        for (int i = 0; i < value.length; i ++)
1500                        {
1501                                System.out.print(value[i] + " ");
1502                        }
1503                        System.out.println();
1504                }
1505                StringBuffer queryBuffer = new StringBuffer();
1506                queryBuffer.append("SELECT COUNT(*) AS COUNTER FROM " + 
1507                                prefixTableName(tableName) +
1508                        " WHERE ");
1509                
1510                for (int i = 0; i < columnCount; i++)
1511                {
1512                        if (i > 0) {    queryBuffer.append(" AND "); }
1513                                queryBuffer.append(columnNames[i] + "=? ");
1514                        
1515                }
1516                PreparedStatement stmt = prepareStatement(new String(queryBuffer));
1517                if (debugLevel > 0)
1518                {
1519                        System.out.println(new String(queryBuffer));
1520                        System.out.print("     ");
1521                        for (int i = 0; i < columnCount; i++)
1522                        {
1523                                if (i > 0) System.out.print(" ");
1524                                System.out.print(value[i].toString());
1525                        }
1526                        System.out.println();
1527                }
1528                for (int i = 0; i < columnCount; i++)
1529                {
1530                        stmt.setObject(i + 1, value[i]);
1531                }
1532                ResultSet rs = stmt.executeQuery();
1533                queryBuffer = new StringBuffer();
1534                rs.next();
1535                int rowCount = rs.getInt("COUNTER");
1536                if (rowCount == 0)
1537                {
1538                        boolean firstItem = true;
1539                        if (debugLevel > 1)
1540                        {
1541                                System.out.println("Inserting record");
1542                        }
1543                        queryBuffer.append("INSERT INTO " + prefixTableName(tableName) + " ( ");
1544                        for (int i = 0; i < columnNames.length; i++)
1545                        {
1546                                if (value[i] == null) { continue; }
1547                                if (!firstItem) { queryBuffer.append(", "); }
1548                                queryBuffer.append(columnNames[i]);
1549                                firstItem = false;
1550                        }
1551                        firstItem = true;
1552                        queryBuffer.append(" ) VALUES ( ");
1553                        for (int i = 0; i < columnNames.length; i++)
1554                        {
1555                                if (value[i] == null) { continue; }
1556                                if (!firstItem) { queryBuffer.append(", "); }
1557                                if (Number.class.isAssignableFrom(value[i].getClass()))
1558                                {
1559                                        queryBuffer.append(value[i].toString());
1560                                }
1561                                else if (String.class.isAssignableFrom(value[i].getClass()))
1562                                {
1563                                        queryBuffer.append("\'" + value[i].toString() + "\'");
1564                                }
1565                                firstItem = false;
1566                        }
1567                        queryBuffer.append(" )");
1568                }
1569                else
1570                {
1571                        boolean firstItem = true;
1572                        if (debugLevel > 1)
1573                        {
1574                                System.out.println("Updating row");
1575                        }
1576                        if (columnNames.length == 1)
1577                        {
1578                                return;
1579                        }
1580                        if (value[0] == null)
1581                        {
1582                                throw new SQLException("Primary key value may not be null");
1583                        }
1584                        queryBuffer.append("UPDATE " + prefixTableName(tableName) + " SET ");
1585                        for (int i = columnCount; i < columnNames.length; i++)
1586                        {
1587                                if (value[i] == null) { continue; }
1588                                if (!firstItem) 
1589                                { 
1590                                        queryBuffer.append(", "); 
1591                                }
1592                                queryBuffer.append(" " + columnNames[i] + "=");
1593                                if (Number.class.isAssignableFrom(value[i].getClass()))
1594                                {
1595                                        queryBuffer.append(value[i].toString());
1596                                }
1597                                else if (String.class.isAssignableFrom(value[i].getClass()))
1598                                {
1599                                        queryBuffer.append("\'" + value[i].toString() + "\'");
1600                                }
1601                                firstItem = false;
1602                        }
1603                        queryBuffer.append(" WHERE ");
1604                        firstItem = true;
1605                        for (int i = 0; i < columnCount; i++)
1606                        {
1607                                if (!firstItem)
1608                                {
1609                                        queryBuffer.append(" AND ");
1610                                }
1611                                queryBuffer.append(columnNames[i] + "=");
1612                                if (Number.class.isAssignableFrom(value[i].getClass()))
1613                                {
1614                                        queryBuffer.append(value[i].toString());
1615                                }
1616                                else if (String.class.isAssignableFrom(value[i].getClass()))
1617                                {
1618                                        queryBuffer.append("\'" + value[i].toString() + "\'");
1619                                }
1620                                firstItem = false;
1621                        }
1622                }
1623                if (debugLevel > 0)
1624                {
1625                        System.out.println(new String(queryBuffer));
1626                }
1627                executeUpdate(new String(queryBuffer));
1628        }       
1629}