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