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