Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/mysql/jdbc/DatabaseMetaData.java


1   /*
2    Copyright (C) 2002-2004 MySQL AB
3   
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of version 2 of the GNU General Public License as
6    published by the Free Software Foundation.
7    
8   
9    There are special exceptions to the terms and conditions of the GPL 
10   as it is applied to this software. View the full text of the 
11   exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this 
12   software distribution.
13  
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18  
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  
23   */
24  package com.mysql.jdbc;
25  
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.sql.Statement;
29  import java.sql.Types;
30  import java.util.ArrayList;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.StringTokenizer;
34  import java.util.TreeMap;
35  
36  
37  /**
38   * JDBC Interface to Mysql functions
39   * 
40   * <p>
41   * This class provides information about the database as a whole.
42   * </p>
43   * 
44   * <p>
45   * Many of the methods here return lists of information in ResultSets. You can
46   * use the normal ResultSet methods such as getString and getInt to retrieve
47   * the data from these ResultSets.  If a given form of metadata is not
48   * available, these methods show throw a java.sql.SQLException.
49   * </p>
50   * 
51   * <p>
52   * Some of these methods take arguments that are String patterns.  These
53   * methods all have names such as fooPattern.  Within a pattern String "%"
54   * means match any substring of 0 or more characters and "_" means match any
55   * one character.
56   * </p>
57   *
58   * @author Mark Matthews
59   * @version $Id: DatabaseMetaData.java,v 1.27.2.44 2004/09/30 07:35:03 mmatthew Exp $
60   */
61  public class DatabaseMetaData implements java.sql.DatabaseMetaData {
62      private static final byte[] TABLE_AS_BYTES = "TABLE".getBytes();
63  
64      /** The table type for generic tables that support foreign keys. */
65      private static final String SUPPORTS_FK = "SUPPORTS_FK";
66  
67      //
68      // Column indexes used by all DBMD foreign key
69      // ResultSets
70      //
71      private static final int PKTABLE_CAT = 0;
72      private static final int PKTABLE_SCHEM = 1;
73      private static final int PKTABLE_NAME = 2;
74      private static final int PKCOLUMN_NAME = 3;
75      private static final int FKTABLE_CAT = 4;
76      private static final int FKTABLE_SCHEM = 5;
77      private static final int FKTABLE_NAME = 6;
78      private static final int FKCOLUMN_NAME = 7;
79      private static final int KEY_SEQ = 8;
80      private static final int UPDATE_RULE = 9;
81      private static final int DELETE_RULE = 10;
82      private static final int FK_NAME = 11;
83      private static final int PK_NAME = 12;
84      private static final int DEFERRABILITY = 13;
85  
86      /** The connection to the database */
87      private Connection conn;
88  
89      /** The 'current' database name being used */
90      private String database = null;
91  
92      /** What character to use when quoting identifiers */
93      private String quotedId = null;
94  
95      /**
96       * Creates a new DatabaseMetaData object.
97       *
98       * @param conn DOCUMENT ME!
99       * @param database DOCUMENT ME!
100      */
101     public DatabaseMetaData(Connection conn, String database) {
102         this.conn = conn;
103         this.database = database;
104 
105         try {
106             this.quotedId = this.conn.supportsQuotedIdentifiers()
107                 ? getIdentifierQuoteString() : "";
108         } catch (SQLException sqlEx) {
109             // Forced by API, never thrown from getIdentifierQuoteString() in this
110             // implementation.
111             AssertionFailedException.shouldNotHappen(sqlEx);
112         }
113     }
114 
115     /**
116      * @see DatabaseMetaData#getAttributes(String, String, String, String)
117      */
118     public java.sql.ResultSet getAttributes(String arg0, String arg1,
119         String arg2, String arg3) throws SQLException {
120         Field[] fields = new Field[21];
121         fields[0] = new Field("", "TYPE_CAT", Types.CHAR, 32);
122         fields[1] = new Field("", "TYPE_SCHEM", Types.CHAR, 32);
123         fields[2] = new Field("", "TYPE_NAME", Types.CHAR, 32);
124         fields[3] = new Field("", "ATTR_NAME", Types.CHAR, 32);
125         fields[4] = new Field("", "DATA_TYPE", Types.SMALLINT, 32);
126         fields[5] = new Field("", "ATTR_TYPE_NAME", Types.CHAR, 32);
127         fields[6] = new Field("", "ATTR_SIZE", Types.INTEGER, 32);
128         fields[7] = new Field("", "DECIMAL_DIGITS", Types.INTEGER, 32);
129         fields[8] = new Field("", "NUM_PREC_RADIX", Types.INTEGER, 32);
130         fields[9] = new Field("", "NULLABLE ", Types.INTEGER, 32);
131         fields[10] = new Field("", "REMARKS", Types.CHAR, 32);
132         fields[11] = new Field("", "ATTR_DEF", Types.CHAR, 32);
133         fields[12] = new Field("", "SQL_DATA_TYPE", Types.INTEGER, 32);
134         fields[13] = new Field("", "SQL_DATETIME_SUB", Types.INTEGER, 32);
135         fields[14] = new Field("", "CHAR_OCTET_LENGTH", Types.INTEGER, 32);
136         fields[15] = new Field("", "ORDINAL_POSITION", Types.INTEGER, 32);
137         fields[16] = new Field("", "IS_NULLABLE", Types.CHAR, 32);
138         fields[17] = new Field("", "SCOPE_CATALOG", Types.CHAR, 32);
139         fields[18] = new Field("", "SCOPE_SCHEMA", Types.CHAR, 32);
140         fields[19] = new Field("", "SCOPE_TABLE", Types.CHAR, 32);
141         fields[20] = new Field("", "SOURCE_DATA_TYPE", Types.SMALLINT, 32);
142 
143         return buildResultSet(fields, new ArrayList());
144     }
145 
146     /**
147      * Get a description of a table's optimal set of columns that uniquely
148      * identifies a row. They are ordered by SCOPE.
149      * 
150      * <P>
151      * Each column description has the following columns:
152      * 
153      * <OL>
154      * <li>
155      * <B>SCOPE</B> short => actual scope of result
156      * 
157      * <UL>
158      * <li>
159      * bestRowTemporary - very temporary, while using row
160      * </li>
161      * <li>
162      * bestRowTransaction - valid for remainder of current transaction
163      * </li>
164      * <li>
165      * bestRowSession - valid for remainder of current session
166      * </li>
167      * </ul>
168      * 
169      * </li>
170      * <li>
171      * <B>COLUMN_NAME</B> String => column name
172      * </li>
173      * <li>
174      * <B>DATA_TYPE</B> short => SQL data type from java.sql.Types
175      * </li>
176      * <li>
177      * <B>TYPE_NAME</B> String => Data source dependent type name
178      * </li>
179      * <li>
180      * <B>COLUMN_SIZE</B> int => precision
181      * </li>
182      * <li>
183      * <B>BUFFER_LENGTH</B> int => not used
184      * </li>
185      * <li>
186      * <B>DECIMAL_DIGITS</B> short  => scale
187      * </li>
188      * <li>
189      * <B>PSEUDO_COLUMN</B> short => is this a pseudo column like an Oracle
190      * ROWID
191      * 
192      * <UL>
193      * <li>
194      * bestRowUnknown - may or may not be pseudo column
195      * </li>
196      * <li>
197      * bestRowNotPseudo - is NOT a pseudo column
198      * </li>
199      * <li>
200      * bestRowPseudo - is a pseudo column
201      * </li>
202      * </ul>
203      * 
204      * </li>
205      * </ol>
206      * </p>
207      *
208      * @param catalog a catalog name; "" retrieves those without a catalog
209      * @param schema a schema name; "" retrieves those without a schema
210      * @param table a table name
211      * @param scope the scope of interest; use same values as SCOPE
212      * @param nullable include columns that are nullable?
213      *
214      * @return ResultSet each row is a column description
215      *
216      * @throws java.sql.SQLException DOCUMENT ME!
217      */
218     public java.sql.ResultSet getBestRowIdentifier(String catalog,
219         String schema, String table, int scope, boolean nullable)
220         throws java.sql.SQLException {
221         Field[] fields = new Field[8];
222         fields[0] = new Field("", "SCOPE", Types.SMALLINT, 5);
223         fields[1] = new Field("", "COLUMN_NAME", Types.CHAR, 32);
224         fields[2] = new Field("", "DATA_TYPE", Types.SMALLINT, 32);
225         fields[3] = new Field("", "TYPE_NAME", Types.CHAR, 32);
226         fields[4] = new Field("", "COLUMN_SIZE", Types.INTEGER, 10);
227         fields[5] = new Field("", "BUFFER_LENGTH", Types.INTEGER, 10);
228         fields[6] = new Field("", "DECIMAL_DIGITS", Types.INTEGER, 10);
229         fields[7] = new Field("", "PSEUDO_COLUMN", Types.SMALLINT, 5);
230 
231         String databasePart = "";
232 
233         if (catalog != null) {
234             if (!catalog.equals("")) {
235                 databasePart = " FROM " + this.quotedId + catalog
236                     + this.quotedId;
237             }
238         } else {
239             databasePart = " FROM " + this.quotedId + this.database
240                 + this.quotedId;
241         }
242 
243         if (table == null) {
244             throw new java.sql.SQLException("Table not specified.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
245         }
246 
247         ResultSet results = null;
248         Statement stmt = null;
249 
250         try {
251             stmt = this.conn.createStatement();
252 
253       if (stmt.getMaxRows() != 0) {
254         stmt.setMaxRows(0);
255       }
256       
257             StringBuffer queryBuf = new StringBuffer("SHOW COLUMNS FROM ");
258             queryBuf.append(this.quotedId);
259             queryBuf.append(table);
260             queryBuf.append(this.quotedId);
261             queryBuf.append(databasePart);
262 
263             results = stmt.executeQuery(queryBuf.toString());
264 
265             ArrayList tuples = new ArrayList();
266 
267             while (results.next()) {
268                 String keyType = results.getString("Key");
269 
270                 if (keyType != null) {
271                     if (StringUtils.startsWithIgnoreCase(keyType, "PRI")) {
272                         byte[][] rowVal = new byte[8][];
273                         rowVal[0] = Integer.toString(java.sql.DatabaseMetaData.bestRowSession)
274                                            .getBytes();
275                         rowVal[1] = results.getBytes("Field");
276 
277                         String type = results.getString("Type");
278                         int size = MysqlIO.getMaxBuf();
279                         int decimals = 0;
280 
281                         /*
282                          * Parse the Type column from MySQL
283                          */
284                         if (type.indexOf("enum") != -1) {
285                             String temp = type.substring(type.indexOf("("),
286                                     type.indexOf(")"));
287                             java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(temp,
288                                     ",");
289                             int maxLength = 0;
290 
291                             while (tokenizer.hasMoreTokens()) {
292                                 maxLength = Math.max(maxLength,
293                                         (tokenizer.nextToken().length() - 2));
294                             }
295 
296                             size = maxLength;
297                             decimals = 0;
298                             type = "enum";
299                         } else if (type.indexOf("(") != -1) {
300                             if (type.indexOf(",") != -1) {
301                                 size = Integer.parseInt(type.substring(type
302                                             .indexOf("(") + 1, type.indexOf(",")));
303                                 decimals = Integer.parseInt(type.substring(type
304                                             .indexOf(",") + 1, type.indexOf(")")));
305                             } else {
306                                 size = Integer.parseInt(type.substring(type
307                                             .indexOf("(") + 1, type.indexOf(")")));
308                             }
309 
310                             type = type.substring(type.indexOf("("));
311                         }
312 
313                         rowVal[2] = new byte[0]; // FIXME!
314                         rowVal[3] = s2b(type);
315                         rowVal[4] = Integer.toString(size + decimals).getBytes();
316                         rowVal[5] = Integer.toString(size + decimals).getBytes();
317                         rowVal[6] = Integer.toString(decimals).getBytes();
318                         rowVal[7] = Integer.toString(java.sql.DatabaseMetaData.bestRowNotPseudo)
319                                            .getBytes();
320                         tuples.add(rowVal);
321                     }
322                 }
323             }
324 
325             return buildResultSet(fields, tuples);
326         } finally {
327             if (results != null) {
328                 try {
329                     results.close();
330                 } catch (Exception ex) {
331                     ;
332                 }
333 
334                 results = null;
335             }
336 
337             if (stmt != null) {
338                 try {
339                     stmt.close();
340                 } catch (Exception ex) {
341                     ;
342                 }
343 
344                 stmt = null;
345             }
346         }
347     }
348 
349     /**
350      * Does a catalog appear at the start of a qualified table name? (Otherwise
351      * it appears at the end)
352      *
353      * @return true if it appears at the start
354      *
355      * @throws java.sql.SQLException DOCUMENT ME!
356      */
357     public boolean isCatalogAtStart() throws java.sql.SQLException {
358         return true;
359     }
360 
361     /**
362      * What's the separator between catalog and table name?
363      *
364      * @return the separator string
365      *
366      * @throws java.sql.SQLException DOCUMENT ME!
367      */
368     public String getCatalogSeparator() throws java.sql.SQLException {
369         return ".";
370     }
371 
372     /**
373      * What's the database vendor's preferred term for "catalog"?
374      *
375      * @return the vendor term
376      *
377      * @throws java.sql.SQLException DOCUMENT ME!
378      */
379     public String getCatalogTerm() throws java.sql.SQLException {
380         return "database";
381     }
382 
383     /**
384      * Get the catalog names available in this database.  The results are
385      * ordered by catalog name.
386      * 
387      * <P>
388      * The catalog column is:
389      * 
390      * <OL>
391      * <li>
392      * <B>TABLE_CAT</B> String => catalog name
393      * </li>
394      * </ol>
395      * </p>
396      *
397      * @return ResultSet each row has a single String column that is a catalog
398      *         name
399      *
400      * @throws java.sql.SQLException DOCUMENT ME!
401      */
402     public java.sql.ResultSet getCatalogs() throws java.sql.SQLException {
403         java.sql.ResultSet results = null;
404         java.sql.Statement stmt = null;
405 
406         try {
407             stmt = this.conn.createStatement();
408             
409       if (stmt.getMaxRows() != 0) {
410         stmt.setMaxRows(0);
411       }
412       
413             results = stmt.executeQuery("SHOW DATABASES");
414 
415             java.sql.ResultSetMetaData resultsMD = results.getMetaData();
416             Field[] fields = new Field[1];
417             fields[0] = new Field("", "TABLE_CAT", Types.VARCHAR,
418                     resultsMD.getColumnDisplaySize(1));
419 
420             ArrayList tuples = new ArrayList();
421 
422             while (results.next()) {
423                 byte[][] rowVal = new byte[1][];
424                 rowVal[0] = results.getBytes(1);
425                 tuples.add(rowVal);
426             }
427 
428             return buildResultSet(fields, tuples);
429         } finally {
430             if (results != null) {
431                 try {
432                     results.close();
433                 } catch (SQLException sqlEx) {
434                     AssertionFailedException.shouldNotHappen(sqlEx);
435                 }
436 
437                 results = null;
438             }
439 
440             if (stmt != null) {
441                 try {
442                     stmt.close();
443                 } catch (SQLException sqlEx) {
444                     AssertionFailedException.shouldNotHappen(sqlEx);
445                 }
446 
447                 stmt = null;
448             }
449         }
450     }
451 
452     /**
453      * Get a description of the access rights for a table's columns.
454      * 
455      * <P>
456      * Only privileges matching the column name criteria are returned.  They
457      * are ordered by COLUMN_NAME and PRIVILEGE.
458      * </p>
459      * 
460      * <P>
461      * Each privilige description has the following columns:
462      * 
463      * <OL>
464      * <li>
465      * <B>TABLE_CAT</B> String => table catalog (may be null)
466      * </li>
467      * <li>
468      * <B>TABLE_SCHEM</B> String => table schema (may be null)
469      * </li>
470      * <li>
471      * <B>TABLE_NAME</B> String => table name
472      * </li>
473      * <li>
474      * <B>COLUMN_NAME</B> String => column name
475      * </li>
476      * <li>
477      * <B>GRANTOR</B> => grantor of access (may be null)
478      * </li>
479      * <li>
480      * <B>GRANTEE</B> String => grantee of access
481      * </li>
482      * <li>
483      * <B>PRIVILEGE</B> String => name of access (SELECT, INSERT, UPDATE,
484      * REFRENCES, ...)
485      * </li>
486      * <li>
487      * <B>IS_GRANTABLE</B> String => "YES" if grantee is permitted to grant to
488      * others; "NO" if not; null if unknown
489      * </li>
490      * </ol>
491      * </p>
492      *
493      * @param catalog a catalog name; "" retrieves those without a catalog
494      * @param schema a schema name; "" retrieves those without a schema
495      * @param table a table name
496      * @param columnNamePattern a column name pattern
497      *
498      * @return ResultSet each row is a column privilege description
499      *
500      * @throws java.sql.SQLException if a database access error occurs
501      *
502      * @see #getSearchStringEscape
503      */
504     public java.sql.ResultSet getColumnPrivileges(String catalog,
505         String schema, String table, String columnNamePattern)
506         throws java.sql.SQLException {
507         Field[] fields = new Field[8];
508         fields[0] = new Field("", "TABLE_CAT", Types.CHAR, 64);
509         fields[1] = new Field("", "TABLE_SCHEM", Types.CHAR, 1);
510         fields[2] = new Field("", "TABLE_NAME", Types.CHAR, 64);
511         fields[3] = new Field("", "COLUMN_NAME", Types.CHAR, 64);
512         fields[4] = new Field("", "GRANTOR", Types.CHAR, 77);
513         fields[5] = new Field("", "GRANTEE", Types.CHAR, 77);
514         fields[6] = new Field("", "PRIVILEGE", Types.CHAR, 64);
515         fields[7] = new Field("", "IS_GRANTABLE", Types.CHAR, 3);
516 
517         StringBuffer grantQuery = new StringBuffer(
518                 "SELECT c.host, c.db, t.grantor, c.user, "
519                 + "c.table_name, c.column_name, c.column_priv "
520                 + "from mysql.columns_priv c, mysql.tables_priv t "
521                 + "where c.host = t.host and c.db = t.db and "
522                 + "c.table_name = t.table_name ");
523 
524         if ((catalog != null) && (catalog.length() != 0)) {
525             grantQuery.append(" AND c.db='");
526             grantQuery.append(catalog);
527             grantQuery.append("' ");
528             ;
529         }
530 
531         grantQuery.append(" AND c.table_name ='");
532         grantQuery.append(table);
533         grantQuery.append("' AND c.column_name like '");
534         grantQuery.append(columnNamePattern);
535         grantQuery.append("'");
536 
537         Statement stmt = null;
538         ResultSet results = null;
539         ArrayList grantRows = new ArrayList();
540 
541         try {
542             stmt = this.conn.createStatement();
543             
544       if (stmt.getMaxRows() != 0) {
545         stmt.setMaxRows(0);
546       }
547       
548             results = stmt.executeQuery(grantQuery.toString());
549 
550             while (results.next()) {
551                 String host = results.getString(1);
552                 String database = results.getString(2);
553                 String grantor = results.getString(3);
554                 String user = results.getString(4);
555 
556                 if ((user == null) || (user.length() == 0)) {
557                     user = "%";
558                 }
559 
560                 StringBuffer fullUser = new StringBuffer(user);
561 
562                 if ((host != null) && this.conn.useHostsInPrivileges()) {
563                     fullUser.append("@");
564                     fullUser.append(host);
565                 }
566 
567                 String columnName = results.getString(6);
568                 String allPrivileges = results.getString(7);
569 
570                 if (allPrivileges != null) {
571                     allPrivileges = allPrivileges.toUpperCase();
572 
573                     StringTokenizer st = new StringTokenizer(allPrivileges, ",");
574 
575                     while (st.hasMoreTokens()) {
576                         String privilege = st.nextToken().trim();
577                         byte[][] tuple = new byte[8][];
578                         tuple[0] = s2b(database);
579                         tuple[1] = null;
580                         tuple[2] = s2b(table);
581                         tuple[3] = s2b(columnName);
582 
583                         if (grantor != null) {
584                             tuple[4] = s2b(grantor);
585                         } else {
586                             tuple[4] = null;
587                         }
588 
589                         tuple[5] = s2b(fullUser.toString());
590                         tuple[6] = s2b(privilege);
591                         tuple[7] = null;
592                         grantRows.add(tuple);
593                     }
594                 }
595             }
596         } finally {
597             if (results != null) {
598                 try {
599                     results.close();
600                 } catch (Exception ex) {
601                     ;
602                 }
603 
604                 results = null;
605             }
606 
607             if (stmt != null) {
608                 try {
609                     stmt.close();
610                 } catch (Exception ex) {
611                     ;
612                 }
613 
614                 stmt = null;
615             }
616         }
617 
618         return buildResultSet(fields, grantRows);
619     }
620 
621     /**
622      * Get a description of table columns available in a catalog.
623      * 
624      * <P>
625      * Only column descriptions matching the catalog, schema, table and column
626      * name criteria are returned.  They are ordered by TABLE_SCHEM,
627      * TABLE_NAME and ORDINAL_POSITION.
628      * </p>
629      * 
630      * <P>
631      * Each column description has the following columns:
632      * 
633      * <OL>
634      * <li>
635      * <B>TABLE_CAT</B> String => table catalog (may be null)
636      * </li>
637      * <li>
638      * <B>TABLE_SCHEM</B> String => table schema (may be null)
639      * </li>
640      * <li>
641      * <B>TABLE_NAME</B> String => table name
642      * </li>
643      * <li>
644      * <B>COLUMN_NAME</B> String => column name
645      * </li>
646      * <li>
647      * <B>DATA_TYPE</B> short => SQL type from java.sql.Types
648      * </li>
649      * <li>
650      * <B>TYPE_NAME</B> String => Data source dependent type name
651      * </li>
652      * <li>
653      * <B>COLUMN_SIZE</B> int => column size.  For char or date types this is
654      * the maximum number of characters, for numeric or decimal types this is
655      * precision.
656      * </li>
657      * <li>
658      * <B>BUFFER_LENGTH</B> is not used.
659      * </li>
660      * <li>
661      * <B>DECIMAL_DIGITS</B> int => the number of fractional digits
662      * </li>
663      * <li>
664      * <B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2)
665      * </li>
666      * <li>
667      * <B>NULLABLE</B> int => is NULL allowed?
668      * 
669      * <UL>
670      * <li>
671      * columnNoNulls - might not allow NULL values
672      * </li>
673      * <li>
674      * columnNullable - definitely allows NULL values
675      * </li>
676      * <li>
677      * columnNullableUnknown - nullability unknown
678      * </li>
679      * </ul>
680      * 
681      * </li>
682      * <li>
683      * <B>REMARKS</B> String => comment describing column (may be null)
684      * </li>
685      * <li>
686      * <B>COLUMN_DEF</B> String => default value (may be null)
687      * </li>
688      * <li>
689      * <B>SQL_DATA_TYPE</B> int => unused
690      * </li>
691      * <li>
692      * <B>SQL_DATETIME_SUB</B> int => unused
693      * </li>
694      * <li>
695      * <B>CHAR_OCTET_LENGTH</B> int => for char types the maximum number of
696      * bytes in the column
697      * </li>
698      * <li>
699      * <B>ORDINAL_POSITION</B> int => index of column in table (starting at 1)
700      * </li>
701      * <li>
702      * <B>IS_NULLABLE</B> String => "NO" means column definitely does not allow
703      * NULL values; "YES" means the column might allow NULL values.  An empty
704      * string means nobody knows.
705      * </li>
706      * </ol>
707      * </p>
708      *
709      * @param catalog a catalog name; "" retrieves those without a catalog
710      * @param schemaPattern a schema name pattern; "" retrieves those without a
711      *        schema
712      * @param tableName a table name pattern
713      * @param columnNamePattern a column name pattern
714      *
715      * @return ResultSet each row is a column description
716      *
717      * @throws java.sql.SQLException if a database access error occurs
718      *
719      * @see #getSearchStringEscape
720      */
721     public java.sql.ResultSet getColumns(String catalog, String schemaPattern,
722         String tableName, String columnNamePattern)
723         throws java.sql.SQLException {
724         String databasePart = "";
725 
726         if (columnNamePattern == null) {
727             columnNamePattern = "%";
728         }
729 
730         if (catalog != null) {
731             if (!catalog.equals("")) {
732                 databasePart = " FROM " + this.quotedId + catalog
733                     + this.quotedId;
734             }
735         } else {
736             databasePart = " FROM " + this.quotedId + this.database
737                 + this.quotedId;
738         }
739 
740         ArrayList tableNameList = new ArrayList();
741         int tablenameLength = 0;
742 
743         if (tableName == null) {
744             // Select from all tables
745             java.sql.ResultSet tables = null;
746 
747             try {
748                 tables = getTables(catalog, schemaPattern, "%", new String[0]);
749 
750                 while (tables.next()) {
751                     String tableNameFromList = tables.getString("TABLE_NAME");
752                     tableNameList.add(tableNameFromList);
753 
754                     if (tableNameFromList.length() > tablenameLength) {
755                         tablenameLength = tableNameFromList.length();
756                     }
757                 }
758             } finally {
759                 if (tables != null) {
760                     try {
761                         tables.close();
762                     } catch (Exception sqlEx) {
763                         AssertionFailedException.shouldNotHappen(sqlEx);
764                     }
765 
766                     tables = null;
767                 }
768             }
769         } else {
770             java.sql.ResultSet tables = null;
771 
772             try {
773                 tables = getTables(catalog, schemaPattern, tableName,
774                         new String[0]);
775 
776                 while (tables.next()) {
777                     String tableNameFromList = tables.getString("TABLE_NAME");
778                     tableNameList.add(tableNameFromList);
779 
780                     if (tableNameFromList.length() > tablenameLength) {
781                         tablenameLength = tableNameFromList.length();
782                     }
783                 }
784             } finally {
785                 if (tables != null) {
786                     try {
787                         tables.close();
788                     } catch (SQLException sqlEx) {
789                         AssertionFailedException.shouldNotHappen(sqlEx);
790                     }
791 
792                     tables = null;
793                 }
794             }
795         }
796 
797         int catalogLength = 0;
798 
799         byte[] connectionCatalogAsBytes = null;
800         if (catalog != null) {
801             catalogLength = catalog.length();
802             connectionCatalogAsBytes = s2b(catalog);
803         } else {
804             catalog = "";
805             connectionCatalogAsBytes = s2b(this.conn.getCatalog());
806             catalogLength = 0;
807         }
808         
809         java.util.Iterator tableNames = tableNameList.iterator();
810         Field[] fields = new Field[18];
811         fields[0] = new Field("", "TABLE_CAT", Types.CHAR, catalogLength);
812         fields[1] = new Field("", "TABLE_SCHEM", Types.CHAR, 0);
813         fields[2] = new Field("", "TABLE_NAME", Types.CHAR, tablenameLength);
814         fields[3] = new Field("", "COLUMN_NAME", Types.CHAR, 32);
815         fields[4] = new Field("", "DATA_TYPE", Types.SMALLINT, 5);
816         fields[5] = new Field("", "TYPE_NAME", Types.CHAR, 16);
817         fields[6] = new Field("", "COLUMN_SIZE", Types.INTEGER,
818                 Integer.toString(Integer.MAX_VALUE).length());
819         fields[7] = new Field("", "BUFFER_LENGTH", Types.INTEGER, 10);
820         fields[8] = new Field("", "DECIMAL_DIGITS", Types.INTEGER, 10);
821         fields[9] = new Field("", "NUM_PREC_RADIX", Types.INTEGER, 10);
822         fields[10] = new Field("", "NULLABLE", Types.INTEGER, 10);
823         fields[11] = new Field("", "REMARKS", Types.CHAR, 0);
824         fields[12] = new Field("", "COLUMN_DEF", Types.CHAR, 0);
825         fields[13] = new Field("", "SQL_DATA_TYPE", Types.INTEGER, 10);
826         fields[14] = new Field("", "SQL_DATETIME_SUB", Types.INTEGER, 10);
827         fields[15] = new Field("", "CHAR_OCTET_LENGTH", Types.INTEGER,
828                 Integer.toString(Integer.MAX_VALUE).length());
829         fields[16] = new Field("", "ORDINAL_POSITION", Types.INTEGER, 10);
830         fields[17] = new Field("", "IS_NULLABLE", Types.CHAR, 3);
831 
832         ArrayList tuples = new ArrayList();
833 
834         
835 
836         while (tableNames.hasNext()) {
837             String tableNamePattern = (String) tableNames.next();
838             Statement stmt = null;
839             ResultSet results = null;
840 
841             try {
842                 stmt = this.conn.createStatement();
843                 
844         if (stmt.getMaxRows() != 0) {
845           stmt.setMaxRows(0);
846         }
847 
848                 StringBuffer queryBuf = new StringBuffer("SHOW COLUMNS FROM ");
849                 queryBuf.append(this.quotedId);
850                 queryBuf.append(tableNamePattern);
851                 queryBuf.append(this.quotedId);
852                 queryBuf.append(databasePart);
853                 queryBuf.append(" LIKE '");
854                 queryBuf.append(columnNamePattern);
855                 queryBuf.append("'");
856 
857                 results = stmt.executeQuery(queryBuf.toString());
858 
859                 int ordPos = 1;
860 
861                 while (results.next()) {
862                     byte[][] rowVal = new byte[18][];
863                     rowVal[0] = connectionCatalogAsBytes; // TABLE_CAT
864                     rowVal[1] = null;
865 
866                     // TABLE_SCHEM (No schemas in MySQL)
867                     rowVal[2] = s2b(tableNamePattern); // TABLE_NAME
868                     rowVal[3] = results.getBytes("Field");
869 
870                     String typeInfo = results.getString("Type");
871 
872                     if (Driver.DEBUG) {
873                         System.out.println("Type: " + typeInfo);
874                     }
875 
876                     String mysqlType = "";
877                     String fullMysqlType = null;
878 
879                     if (typeInfo.indexOf("(") != -1) {
880                         mysqlType = typeInfo.substring(0, typeInfo.indexOf("("));
881                     } else {
882                         mysqlType = typeInfo;
883                     }
884                     
885                     int indexOfUnsignedInMysqlType = mysqlType.toLowerCase().indexOf("unsigned");
886                    
887                     if (indexOfUnsignedInMysqlType != -1) {
888                       mysqlType = mysqlType.substring(0, (indexOfUnsignedInMysqlType - 1));
889                     }
890                     
891                     // Add unsigned to typename reported to enduser as 'native type', if present
892                     
893                     if (typeInfo.toLowerCase().indexOf("unsigned") != -1) {
894                       fullMysqlType = mysqlType + " unsigned";
895                     } else {
896                       fullMysqlType = mysqlType;
897                     }
898 
899                     if (this.conn.capitalizeDBMDTypes()) {
900                         fullMysqlType = fullMysqlType.toUpperCase();
901                     }
902 
903                     /*
904                      * Convert to XOPEN (thanks JK)
905                      */
906                     rowVal[4] = Integer.toString(MysqlDefs.mysqlToJavaType(
907                                 mysqlType)).getBytes();
908 
909                     // DATA_TYPE (jdbc)
910                     rowVal[5] = s2b(fullMysqlType); // TYPE_NAME (native)
911 
912                     // Figure Out the Size
913                     if (typeInfo != null) {
914                         if (StringUtils.startsWithIgnoreCase(typeInfo, "enum")
915                                 || StringUtils.startsWithIgnoreCase(typeInfo,
916                                     "set")) {
917                             String temp = typeInfo.substring(typeInfo.indexOf(
918                                         "("), typeInfo.lastIndexOf(")"));
919                             java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(temp,
920                                     ",");
921                             int maxLength = 0;
922 
923                             while (tokenizer.hasMoreTokens()) {
924                                 maxLength = Math.max(maxLength,
925                                         (tokenizer.nextToken().length() - 2));
926                             }
927 
928                             rowVal[6] = Integer.toString(maxLength).getBytes();
929                             rowVal[8] = new byte[] { (byte) '0' };
930                         } else if (typeInfo.indexOf(",") != -1) {
931                             // Numeric with decimals
932                             String size = typeInfo.substring((typeInfo.indexOf(
933                                         "(") + 1), (typeInfo.indexOf(",")));
934                             String decimals = typeInfo.substring((typeInfo
935                                     .indexOf(",") + 1), (typeInfo.indexOf(")")));
936                             rowVal[6] = s2b(size);
937                             rowVal[8] = s2b(decimals);
938                         } else {
939                             String size = "0";
940 
941                             /* If the size is specified with the DDL, use that */
942                             if (typeInfo.indexOf("(") != -1) {
943                                 size = typeInfo.substring((typeInfo.indexOf("(")
944                                         + 1), (typeInfo.indexOf(")")));
945                             } else if (typeInfo.equalsIgnoreCase("tinyint")) {
946                                 size = "1";
947                             } else if (typeInfo.equalsIgnoreCase("smallint")) {
948                                 size = "6";
949                             } else if (typeInfo.equalsIgnoreCase("mediumint")) {
950                                 size = "6";
951                             } else if (typeInfo.equalsIgnoreCase("int")) {
952                                 size = "11";
953                             } else if (typeInfo.equalsIgnoreCase("integer")) {
954                                 size = "11";
955                             } else if (typeInfo.equalsIgnoreCase("bigint")) {
956                                 size = "25";
957                             } else if (typeInfo.equalsIgnoreCase("int24")) {
958                                 size = "25";
959                             } else if (typeInfo.equalsIgnoreCase("real")) {
960                                 size = "12";
961                             } else if (typeInfo.equalsIgnoreCase("float")) {
962                                 size = "12";
963                             } else if (typeInfo.equalsIgnoreCase("decimal")) {
964                                 size = "12";
965                             } else if (typeInfo.equalsIgnoreCase("numeric")) {
966                                 size = "12";
967                             } else if (typeInfo.equalsIgnoreCase("double")) {
968                                 size = "22";
969                             } else if (typeInfo.equalsIgnoreCase("char")) {
970                                 size = "1";
971                             } else if (typeInfo.equalsIgnoreCase("varchar")) {
972                                 size = "255";
973                             } else if (typeInfo.equalsIgnoreCase("date")) {
974                                 size = "10";
975                             } else if (typeInfo.equalsIgnoreCase("time")) {
976                                 size = "8";
977                             } else if (typeInfo.equalsIgnoreCase("timestamp")) {
978                                 size = "19";
979                             } else if (typeInfo.equalsIgnoreCase("datetime")) {
980                                 size = "19";
981                             } else if (typeInfo.equalsIgnoreCase("tinyblob")) {
982                                 size = "255";
983                             } else if (typeInfo.equalsIgnoreCase("blob")) {
984                                 size = "65535";
985                             } else if (typeInfo.equalsIgnoreCase("mediumblob")) {
986                                 size = "16277215";
987                             } else if (typeInfo.equalsIgnoreCase("longblob")) {
988                                 size = Integer.toString(Integer.MAX_VALUE);
989                             } else if (typeInfo.equalsIgnoreCase("tinytext")) {
990                                 size = "255";
991                             } else if (typeInfo.equalsIgnoreCase("text")) {
992                                 size = "65535";
993                             } else if (typeInfo.equalsIgnoreCase("mediumtext")) {
994                                 size = "16277215";
995                             } else if (typeInfo.equalsIgnoreCase("longtext")) {
996                                 size = Integer.toString(Integer.MAX_VALUE);
997                             } else if (typeInfo.equalsIgnoreCase("enum")) {
998                                 size = "255";
999                             } else if (typeInfo.equalsIgnoreCase("set")) {
1000                                size = "255";
1001                            }
1002
1003                            rowVal[6] = size.getBytes();
1004                            rowVal[8] = new byte[] { (byte) '0' };
1005                        }
1006                    } else {
1007                        rowVal[8] = new byte[] { (byte) '0' };
1008                        rowVal[6] = new byte[] { (byte) '0' };
1009                    }
1010
1011                    rowVal[7] = Integer.toString(MysqlIO.getMaxBuf()).getBytes();
1012
1013                    // BUFFER_LENGTH
1014                    rowVal[9] = new byte[] { (byte) '1', (byte) '0' };
1015
1016                    // NUM_PREC_RADIX (is this right for char?)
1017                    String nullable = results.getString("Null");
1018
1019                    // Nullable?
1020                    if (nullable != null) {
1021                        if (nullable.equals("YES")) {
1022                            rowVal[10] = Integer.toString(java.sql.DatabaseMetaData.columnNullable)
1023                                                .getBytes();
1024                            rowVal[17] = "YES".getBytes();
1025
1026                            // IS_NULLABLE
1027                        } else {
1028                            rowVal[10] = Integer.toString(java.sql.DatabaseMetaData.columnNoNulls)
1029                                                .getBytes();
1030                            rowVal[17] = "NO".getBytes();
1031                        }
1032                    } else {
1033                        rowVal[10] = Integer.toString(java.sql.DatabaseMetaData.columnNoNulls)
1034                                            .getBytes();
1035                        rowVal[17] = "NO".getBytes();
1036                    }
1037
1038                    //
1039                    // Doesn't always have this field, depending on version
1040                    //
1041                    //
1042                    // REMARK column
1043                    //
1044                    try {
1045                        rowVal[11] = results.getBytes("Extra");
1046                    } catch (Exception E) {
1047                        rowVal[11] = new byte[0];
1048                    }
1049
1050                    // COLUMN_DEF
1051                    rowVal[12] = results.getBytes("Default");
1052
1053                    rowVal[13] = new byte[] { (byte) '0' }; // SQL_DATA_TYPE
1054                    rowVal[14] = new byte[] { (byte) '0' }; // SQL_DATE_TIME_SUB
1055                    rowVal[15] = rowVal[6]; // CHAR_OCTET_LENGTH
1056                    rowVal[16] = Integer.toString(ordPos++).getBytes();
1057
1058                    // ORDINAL_POSITION
1059                    tuples.add(rowVal);
1060                }
1061            } finally {
1062                if (results != null) {
1063                    try {
1064                        results.close();
1065                    } catch (Exception ex) {
1066                        ;
1067                    }
1068
1069                    results = null;
1070                }
1071
1072                if (stmt != null) {
1073                    try {
1074                        stmt.close();
1075                    } catch (Exception ex) {
1076                        ;
1077                    }
1078
1079                    stmt = null;
1080                }
1081            }
1082        }
1083
1084        java.sql.ResultSet results = buildResultSet(fields, tuples);
1085
1086        return results;
1087    }
1088
1089    /**
1090     * JDBC 2.0 Return the connection that produced this metadata object.
1091     *
1092     * @return the connection that produced this metadata object.
1093     *
1094     * @throws SQLException if a database error occurs
1095     */
1096    public java.sql.Connection getConnection() throws SQLException {
1097        return (java.sql.Connection) this.conn;
1098    }
1099
1100    /**
1101     * Get a description of the foreign key columns in the foreign key table
1102     * that reference the primary key columns of the primary key table
1103     * (describe how one table imports another's key.) This should normally
1104     * return a single foreign key/primary key pair (most tables only import a
1105     * foreign key from a table once.)  They are ordered by FKTABLE_CAT,
1106     * FKTABLE_SCHEM, FKTABLE_NAME, and KEY_SEQ.
1107     * 
1108     * <P>
1109     * Each foreign key column description has the following columns:
1110     * 
1111     * <OL>
1112     * <li>
1113     * <B>PKTABLE_CAT</B> String => primary key table catalog (may be null)
1114     * </li>
1115     * <li>
1116     * <B>PKTABLE_SCHEM</B> String => primary key table schema (may be null)
1117     * </li>
1118     * <li>
1119     * <B>PKTABLE_NAME</B> String => primary key table name
1120     * </li>
1121     * <li>
1122     * <B>PKCOLUMN_NAME</B> String => primary key column name
1123     * </li>
1124     * <li>
1125     * <B>FKTABLE_CAT</B> String => foreign key table catalog (may be null)
1126     * being exported (may be null)
1127     * </li>
1128     * <li>
1129     * <B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null)
1130     * being exported (may be null)
1131     * </li>
1132     * <li>
1133     * <B>FKTABLE_NAME</B> String => foreign key table name being exported
1134     * </li>
1135     * <li>
1136     * <B>FKCOLUMN_NAME</B> String => foreign key column name being exported
1137     * </li>
1138     * <li>
1139     * <B>KEY_SEQ</B> short => sequence number within foreign key
1140     * </li>
1141     * <li>
1142     * <B>UPDATE_RULE</B> short => What happens to foreign key when primary is
1143     * updated:
1144     * 
1145     * <UL>
1146     * <li>
1147     * importedKeyCascade - change imported key to agree with primary key
1148     * update
1149     * </li>
1150     * <li>
1151     * importedKeyRestrict - do not allow update of primary key if it has been
1152     * imported
1153     * </li>
1154     * <li>
1155     * importedKeySetNull - change imported key to NULL if its primary key has
1156     * been updated
1157     * </li>
1158     * </ul>
1159     * 
1160     * </li>
1161     * <li>
1162     * <B>DELETE_RULE</B> short => What happens to the foreign key when primary
1163     * is deleted.
1164     * 
1165     * <UL>
1166     * <li>
1167     * importedKeyCascade - delete rows that import a deleted key
1168     * </li>
1169     * <li>
1170     * importedKeyRestrict - do not allow delete of primary key if it has been
1171     * imported
1172     * </li>
1173     * <li>
1174     * importedKeySetNull - change imported key to NULL if its primary key has
1175     * been deleted
1176     * </li>
1177     * </ul>
1178     * 
1179     * </li>
1180     * <li>
1181     * <B>FK_NAME</B> String => foreign key identifier (may be null)
1182     * </li>
1183     * <li>
1184     * <B>PK_NAME</B> String => primary key identifier (may be null)
1185     * </li>
1186     * </ol>
1187     * </p>
1188     *
1189     * @param primaryCatalog a catalog name; "" retrieves those without a
1190     *        catalog
1191     * @param primarySchema a schema name pattern; "" retrieves those without a
1192     *        schema
1193     * @param primaryTable a table name
1194     * @param foreignCatalog a catalog name; "" retrieves those without a
1195     *        catalog
1196     * @param foreignSchema a schema name pattern; "" retrieves those without a
1197     *        schema
1198     * @param foreignTable a table name
1199     *
1200     * @return ResultSet each row is a foreign key column description
1201     *
1202     * @throws java.sql.SQLException if a database access error occurs
1203     */
1204    public java.sql.ResultSet getCrossReference(String primaryCatalog,
1205        String primarySchema, String primaryTable, String foreignCatalog,
1206        String foreignSchema, String foreignTable) throws java.sql.SQLException {
1207        if (Driver.TRACE) {
1208            Object[] args = {
1209                primaryCatalog, primarySchema, primaryTable, foreignCatalog,
1210                foreignSchema, foreignTable
1211            };
1212            Debug.methodCall(this, "getCrossReference", args);
1213        }
1214
1215        if (primaryTable == null) {
1216            throw new java.sql.SQLException("Table not specified.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1217        }
1218
1219        Field[] fields = new Field[14];
1220        fields[0] = new Field("", "PKTABLE_CAT", Types.CHAR, 255);
1221        fields[1] = new Field("", "PKTABLE_SCHEM", Types.CHAR, 0);
1222        fields[2] = new Field("", "PKTABLE_NAME", Types.CHAR, 255);
1223        fields[3] = new Field("", "PKCOLUMN_NAME", Types.CHAR, 32);
1224        fields[4] = new Field("", "FKTABLE_CAT", Types.CHAR, 255);
1225        fields[5] = new Field("", "FKTABLE_SCHEM", Types.CHAR, 0);
1226        fields[6] = new Field("", "FKTABLE_NAME", Types.CHAR, 255);
1227        fields[7] = new Field("", "FKCOLUMN_NAME", Types.CHAR, 32);
1228        fields[8] = new Field("", "KEY_SEQ", Types.SMALLINT, 2);
1229        fields[9] = new Field("", "UPDATE_RULE", Types.SMALLINT, 2);
1230        fields[10] = new Field("", "DELETE_RULE", Types.SMALLINT, 2);
1231        fields[11] = new Field("", "FK_NAME", Types.CHAR, 255);
1232        fields[12] = new Field("", "PK_NAME", Types.CHAR, 0);
1233        fields[13] = new Field("", "DEFERRABILITY", Types.INTEGER, 2);
1234
1235        if (this.conn.getIO().versionMeetsMinimum(3, 23, 0)) {
1236            Statement stmt = null;
1237            ResultSet fkresults = null;
1238
1239            try {
1240                /*
1241                 * Get foreign key information for table
1242                 */
1243                if (this.conn.getIO().versionMeetsMinimum(3, 23, 50)) {
1244                    // we can use 'SHOW CREATE TABLE'
1245                    String database = this.database;
1246
1247                    if (foreignCatalog != null) {
1248                        if (!foreignCatalog.equals("")) {
1249                            database = foreignCatalog;
1250                        }
1251                    }
1252
1253                    fkresults = extractForeignKeyFromCreateTable(this.conn,
1254                            this, database, null);
1255                } else {
1256                    String databasePart = "";
1257
1258                    if (foreignCatalog != null) {
1259                        if (!foreignCatalog.equals("")) {
1260                            databasePart = " FROM " + foreignCatalog;
1261                        }
1262                    } else {
1263                        databasePart = " FROM " + this.database;
1264                    }
1265
1266                    stmt = this.conn.createStatement();
1267                    
1268          if (stmt.getMaxRows() != 0) {
1269            stmt.setMaxRows(0);
1270          }
1271          
1272                    fkresults = stmt.executeQuery("show table status "
1273                            + databasePart);
1274                }
1275
1276                String foreignTableWithCase = getTableNameWithCase(foreignTable);
1277                String primaryTableWithCase = getTableNameWithCase(primaryTable);
1278
1279