001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2005-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.aggmatcher; 012 013import mondrian.olap.MondrianDef; 014import mondrian.olap.MondrianProperties; 015import mondrian.resource.MondrianResource; 016import mondrian.rolap.RolapAggregator; 017import mondrian.rolap.RolapLevel; 018import mondrian.rolap.RolapStar; 019import mondrian.spi.Dialect; 020import mondrian.util.ClassResolver; 021 022import org.apache.log4j.Logger; 023 024import org.olap4j.impl.Olap4jUtil; 025 026import java.io.PrintWriter; 027import java.io.StringWriter; 028import java.lang.ref.SoftReference; 029import java.sql.*; 030import java.util.*; 031import javax.sql.DataSource; 032 033/** 034 * Metadata gleaned from JDBC about the tables and columns in the star schema. 035 * This class is used to scrape a database and store information about its 036 * tables and columnIter. 037 * 038 * <p>The structure of this information is as follows: A database has tables. A 039 * table has columnIter. A column has one or more usages. A usage might be a 040 * column being used as a foreign key or as part of a measure. 041 * 042 * <p> Tables are created when calling code requests the set of available 043 * tables. This call <code>getTables()</code> causes all tables to be loaded. 044 * But a table's columnIter are not loaded until, on a table-by-table basis, 045 * a request is made to get the set of columnIter associated with the table. 046 * Since, the AggTableManager first attempts table name matches (recognition) 047 * most tables do not match, so why load their columnIter. 048 * Of course, as a result, there are a host of methods that can throw an 049 * {@link SQLException}, rats. 050 * 051 * @author Richard M. Emberson 052 */ 053public class JdbcSchema { 054 private static final Logger LOGGER = 055 Logger.getLogger(JdbcSchema.class); 056 057 private static final MondrianResource mres = MondrianResource.instance(); 058 059 /** 060 * Returns the Logger. 061 */ 062 public Logger getLogger() { 063 return LOGGER; 064 } 065 066 public interface Factory { 067 JdbcSchema makeDB(DataSource dataSource); 068 void clearDB(JdbcSchema db); 069 void removeDB(JdbcSchema db); 070 } 071 072 private static final Map<DataSource, SoftReference<JdbcSchema>> dbMap = 073 new HashMap<DataSource, SoftReference<JdbcSchema>>(); 074 075 /** 076 * How often between sweeping through the dbMap looking for nulls. 077 */ 078 private static final int SWEEP_COUNT = 10; 079 private static int sweepDBCount = 0; 080 081 public static class StdFactory implements Factory { 082 StdFactory() { 083 } 084 public JdbcSchema makeDB(DataSource dataSource) { 085 return new JdbcSchema(dataSource); 086 } 087 public void clearDB(JdbcSchema db) { 088 // NoOp 089 } 090 public void removeDB(JdbcSchema db) { 091 // NoOp 092 } 093 } 094 095 private static Factory factory; 096 097 private synchronized static void makeFactory() { 098 if (factory != null) { 099 return; 100 } 101 String className = 102 MondrianProperties.instance().JdbcFactoryClass.get(); 103 if (className == null) { 104 factory = new StdFactory(); 105 } else { 106 try { 107 Class<?> clz = 108 ClassResolver.INSTANCE.forName(className, true); 109 factory = (Factory) clz.newInstance(); 110 } catch (ClassNotFoundException ex) { 111 throw mres.BadJdbcFactoryClassName.ex(className); 112 } catch (InstantiationException ex) { 113 throw mres.BadJdbcFactoryInstantiation.ex(className); 114 } catch (IllegalAccessException ex) { 115 throw mres.BadJdbcFactoryAccess.ex(className); 116 } 117 } 118 } 119 120 /** 121 * Creates or retrieves an instance of the JdbcSchema for the given 122 * DataSource. 123 * 124 * @param dataSource DataSource 125 * @return instance of the JdbcSchema for the given DataSource 126 */ 127 public static synchronized JdbcSchema makeDB(DataSource dataSource) { 128 makeFactory(); 129 130 JdbcSchema db = null; 131 SoftReference<JdbcSchema> ref = dbMap.get(dataSource); 132 if (ref != null) { 133 db = ref.get(); 134 } 135 if (db == null) { 136 db = factory.makeDB(dataSource); 137 dbMap.put(dataSource, new SoftReference<JdbcSchema>(db)); 138 } 139 140 sweepDB(); 141 142 return db; 143 } 144 145 /** 146 * Clears information in a JdbcSchema associated with a DataSource. 147 * 148 * @param dataSource DataSource 149 */ 150 public static synchronized void clearDB(DataSource dataSource) { 151 makeFactory(); 152 153 SoftReference<JdbcSchema> ref = dbMap.get(dataSource); 154 if (ref != null) { 155 JdbcSchema db = ref.get(); 156 if (db != null) { 157 factory.clearDB(db); 158 db.clear(); 159 } else { 160 dbMap.remove(dataSource); 161 } 162 } 163 sweepDB(); 164 } 165 166 /** 167 * Removes a JdbcSchema associated with a DataSource. 168 * 169 * @param dataSource DataSource 170 */ 171 public static synchronized void removeDB(DataSource dataSource) { 172 makeFactory(); 173 174 SoftReference<JdbcSchema> ref = dbMap.remove(dataSource); 175 if (ref != null) { 176 JdbcSchema db = ref.get(); 177 if (db != null) { 178 factory.removeDB(db); 179 db.remove(); 180 } 181 } 182 sweepDB(); 183 } 184 185 /** 186 * Every SWEEP_COUNT calls to this method, go through all elements of 187 * the dbMap removing all that either have null values (null SoftReference) 188 * or those with SoftReference with null content. 189 */ 190 private static void sweepDB() { 191 if (sweepDBCount++ > SWEEP_COUNT) { 192 Iterator<SoftReference<JdbcSchema>> it = dbMap.values().iterator(); 193 while (it.hasNext()) { 194 SoftReference<JdbcSchema> ref = it.next(); 195 if ((ref == null) || (ref.get() == null)) { 196 try { 197 it.remove(); 198 } catch (Exception ex) { 199 // Should not happen, but might still like to 200 // know that something's funky. 201 LOGGER.warn(ex); 202 } 203 } 204 } 205 // reset 206 sweepDBCount = 0; 207 } 208 } 209 210 211 // 212 // Types of column usages. 213 // 214 public static final int UNKNOWN_COLUMN_USAGE = 0x0001; 215 public static final int FOREIGN_KEY_COLUMN_USAGE = 0x0002; 216 public static final int MEASURE_COLUMN_USAGE = 0x0004; 217 public static final int LEVEL_COLUMN_USAGE = 0x0008; 218 public static final int FACT_COUNT_COLUMN_USAGE = 0x0010; 219 public static final int IGNORE_COLUMN_USAGE = 0x0020; 220 221 public static final String UNKNOWN_COLUMN_NAME = "UNKNOWN"; 222 public static final String FOREIGN_KEY_COLUMN_NAME = "FOREIGN_KEY"; 223 public static final String MEASURE_COLUMN_NAME = "MEASURE"; 224 public static final String LEVEL_COLUMN_NAME = "LEVEL"; 225 public static final String FACT_COUNT_COLUMN_NAME = "FACT_COUNT"; 226 public static final String IGNORE_COLUMN_NAME = "IGNORE"; 227 228 /** 229 * Enumeration of ways that an aggregate table can use a column. 230 */ 231 enum UsageType { 232 UNKNOWN, 233 FOREIGN_KEY, 234 MEASURE, 235 LEVEL, 236 FACT_COUNT, 237 IGNORE 238 } 239 240 /** 241 * Determine if the parameter represents a single column type, i.e., the 242 * column only has one usage. 243 * 244 * @param columnType Column types 245 * @return true if column has only one usage. 246 */ 247 public static boolean isUniqueColumnType(Set<UsageType> columnType) { 248 return columnType.size() == 1; 249 } 250 251 /** 252 * Maps from column type enum to column type name or list of names if the 253 * parameter represents more than on usage. 254 */ 255 public static String convertColumnTypeToName(Set<UsageType> columnType) { 256 if (columnType.size() == 1) { 257 return columnType.iterator().next().name(); 258 } 259 // it's a multi-purpose column 260 StringBuilder buf = new StringBuilder(); 261 int k = 0; 262 for (UsageType usage : columnType) { 263 if (k++ > 0) { 264 buf.append('|'); 265 } 266 buf.append(usage.name()); 267 } 268 return buf.toString(); 269 } 270 271 /** 272 * Converts a {@link java.sql.Types} value to a 273 * {@link mondrian.spi.Dialect.Datatype}. 274 * 275 * @param javaType JDBC type code, as per {@link java.sql.Types} 276 * @return Datatype 277 */ 278 public static Dialect.Datatype getDatatype(int javaType) { 279 switch (javaType) { 280 case Types.TINYINT: 281 case Types.SMALLINT: 282 case Types.INTEGER: 283 return Dialect.Datatype.Integer; 284 case Types.FLOAT: 285 case Types.REAL: 286 case Types.DOUBLE: 287 case Types.NUMERIC: 288 case Types.DECIMAL: 289 case Types.BIGINT: 290 return Dialect.Datatype.Numeric; 291 case Types.BOOLEAN: 292 return Dialect.Datatype.Boolean; 293 case Types.DATE: 294 return Dialect.Datatype.Date; 295 case Types.TIME: 296 return Dialect.Datatype.Time; 297 case Types.TIMESTAMP: 298 return Dialect.Datatype.Timestamp; 299 case Types.CHAR: 300 case Types.VARCHAR: 301 default: 302 return Dialect.Datatype.String; 303 } 304 } 305 306 /** 307 * Returns true if the parameter is a java.sql.Type text type. 308 */ 309 public static boolean isText(int javaType) { 310 switch (javaType) { 311 case Types.CHAR: 312 case Types.VARCHAR: 313 case Types.LONGVARCHAR: 314 return true; 315 default: 316 return false; 317 } 318 } 319 320 enum TableUsageType { 321 UNKNOWN, 322 FACT, 323 AGG 324 } 325 326 /** 327 * A table in a database. 328 */ 329 public class Table { 330 331 /** 332 * A column in a table. 333 */ 334 public class Column { 335 336 /** 337 * A usage of a column. 338 */ 339 public class Usage { 340 private final UsageType usageType; 341 private String symbolicName; 342 private RolapAggregator aggregator; 343 344 //////////////////////////////////////////////////// 345 // 346 // These instance variables are used to hold 347 // stuff which is determines at one place and 348 // then used somewhere else. Generally, a usage 349 // is created before all of its "stuff" can be 350 // determined, hence, usage is not a set of classes, 351 // rather its one class with a bunch of instance 352 // variables which may or may not be used. 353 // 354 355 // measure stuff 356 public RolapStar.Measure rMeasure; 357 358 // hierarchy stuff 359 public MondrianDef.Relation relation; 360 public MondrianDef.Expression joinExp; 361 public String levelColumnName; 362 363 // level 364 public RolapStar.Column rColumn; 365 366 // agg stuff 367 public boolean collapsed = false; 368 public RolapLevel level = null; 369 370 // for subtables 371 public RolapStar.Table rTable; 372 public String rightJoinConditionColumnName; 373 374 /** 375 * The prefix (possibly null) to use during aggregate table 376 * generation (See AggGen). 377 */ 378 public String usagePrefix; 379 380 /** 381 * Creates a Usage. 382 * 383 * @param usageType Usage type 384 */ 385 Usage(UsageType usageType) { 386 this.usageType = usageType; 387 } 388 389 /** 390 * Returns the column with which this usage is associated. 391 * 392 * @return the usage's column. 393 */ 394 public Column getColumn() { 395 return JdbcSchema.Table.Column.this; 396 } 397 398 /** 399 * Returns the column usage type. 400 */ 401 public UsageType getUsageType() { 402 return usageType; 403 } 404 405 /** 406 * Sets the symbolic (logical) name associated with this usage. 407 * For example, this might be the measure's name. 408 * 409 * @param symbolicName Symbolic name 410 */ 411 public void setSymbolicName(final String symbolicName) { 412 this.symbolicName = symbolicName; 413 } 414 415 /** 416 * Returns the usage's symbolic name. 417 */ 418 public String getSymbolicName() { 419 return symbolicName; 420 } 421 422 /** 423 * Sets the aggregator associated with this usage (if it is a 424 * measure usage). 425 * 426 * @param aggregator Aggregator 427 */ 428 public void setAggregator(final RolapAggregator aggregator) { 429 this.aggregator = aggregator; 430 } 431 432 /** 433 * Returns the aggregator associated with this usage (if its a 434 * measure usage, otherwise null). 435 */ 436 public RolapAggregator getAggregator() { 437 return aggregator; 438 } 439 440 public String toString() { 441 StringWriter sw = new StringWriter(64); 442 PrintWriter pw = new PrintWriter(sw); 443 print(pw, ""); 444 pw.flush(); 445 return sw.toString(); 446 } 447 448 public void print(final PrintWriter pw, final String prefix) { 449 if (getSymbolicName() != null) { 450 pw.print("symbolicName="); 451 pw.print(getSymbolicName()); 452 } 453 if (getAggregator() != null) { 454 pw.print(", aggregator="); 455 pw.print(getAggregator().getName()); 456 } 457 pw.print(", columnType="); 458 pw.print(getUsageType().name()); 459 } 460 } 461 462 /** This is the name of the column. */ 463 private final String name; 464 465 /** This is the java.sql.Type enum of the column in the database. */ 466 private int type; 467 /** 468 * This is the java.sql.Type name of the column in the database. 469 */ 470 private String typeName; 471 472 /** This is the size of the column in the database. */ 473 private int columnSize; 474 475 /** The number of fractional digits. */ 476 private int decimalDigits; 477 478 /** Radix (typically either 10 or 2). */ 479 private int numPrecRadix; 480 481 /** For char types the maximum number of bytes in the column. */ 482 private int charOctetLength; 483 484 /** 485 * False means the column definitely does not allow NULL values. 486 */ 487 private boolean isNullable; 488 489 public final MondrianDef.Column column; 490 491 private final List<JdbcSchema.Table.Column.Usage> usages; 492 493 /** 494 * This contains the enums of all of the column's usages. 495 */ 496 private final Set<UsageType> usageTypes = 497 Olap4jUtil.enumSetNoneOf(UsageType.class); 498 499 private Column(final String name) { 500 this.name = name; 501 this.column = 502 new MondrianDef.Column( 503 JdbcSchema.Table.this.getName(), 504 name); 505 this.usages = new ArrayList<JdbcSchema.Table.Column.Usage>(); 506 } 507 508 /** 509 * Returns the column's name in the database, not a symbolic name. 510 */ 511 public String getName() { 512 return name; 513 } 514 515 /** 516 * Sets the columnIter java.sql.Type enun of the column. 517 * 518 * @param type Type 519 */ 520 private void setType(final int type) { 521 this.type = type; 522 } 523 524 /** 525 * Returns the columnIter java.sql.Type enun of the column. 526 */ 527 public int getType() { 528 return type; 529 } 530 531 /** 532 * Sets the columnIter java.sql.Type name. 533 * 534 * @param typeName Type name 535 */ 536 private void setTypeName(final String typeName) { 537 this.typeName = typeName; 538 } 539 540 /** 541 * Returns the columnIter java.sql.Type name. 542 */ 543 public String getTypeName() { 544 return typeName; 545 } 546 547 /** 548 * Returns this column's table. 549 */ 550 public Table getTable() { 551 return JdbcSchema.Table.this; 552 } 553 554 /** 555 * Return true if this column is numeric. 556 */ 557 public Dialect.Datatype getDatatype() { 558 return JdbcSchema.getDatatype(getType()); 559 } 560 561 /** 562 * Sets the size in bytes of the column in the database. 563 * 564 * @param columnSize Column size 565 */ 566 private void setColumnSize(final int columnSize) { 567 this.columnSize = columnSize; 568 } 569 570 /** 571 * Returns the size in bytes of the column in the database. 572 * 573 */ 574 public int getColumnSize() { 575 return columnSize; 576 } 577 578 /** 579 * Sets number of fractional digits. 580 * 581 * @param decimalDigits Number of fractional digits 582 */ 583 private void setDecimalDigits(final int decimalDigits) { 584 this.decimalDigits = decimalDigits; 585 } 586 587 /** 588 * Returns number of fractional digits. 589 */ 590 public int getDecimalDigits() { 591 return decimalDigits; 592 } 593 594 /** 595 * Sets Radix (typically either 10 or 2). 596 * 597 * @param numPrecRadix Radix 598 */ 599 private void setNumPrecRadix(final int numPrecRadix) { 600 this.numPrecRadix = numPrecRadix; 601 } 602 603 /** 604 * Returns Radix (typically either 10 or 2). 605 */ 606 public int getNumPrecRadix() { 607 return numPrecRadix; 608 } 609 610 /** 611 * For char types the maximum number of bytes in the column. 612 * 613 * @param charOctetLength Octet length 614 */ 615 private void setCharOctetLength(final int charOctetLength) { 616 this.charOctetLength = charOctetLength; 617 } 618 619 /** 620 * For char types the maximum number of bytes in the column. 621 */ 622 public int getCharOctetLength() { 623 return charOctetLength; 624 } 625 626 /** 627 * False means the column definitely does not allow NULL values. 628 * 629 * @param isNullable Whether column is nullable 630 */ 631 private void setIsNullable(final boolean isNullable) { 632 this.isNullable = isNullable; 633 } 634 635 /** 636 * False means the column definitely does not allow NULL values. 637 */ 638 public boolean isNullable() { 639 return isNullable; 640 } 641 642 /** 643 * How many usages does this column have. A column has 644 * between 0 and N usages. It has no usages if usages is some 645 * administrative column. It has one usage if, for example, its 646 * the fact_count column or a level column (for a collapsed 647 * dimension aggregate). It might have 2 usages if its a foreign key 648 * that is also used as a measure. If its a column used in N 649 * measures, then usages will have N usages. 650 */ 651 public int numberOfUsages() { 652 return usages.size(); 653 } 654 655 /** 656 * flushes all star usage references 657 */ 658 public void flushUsages() { 659 usages.clear(); 660 usageTypes.clear(); 661 } 662 663 /** 664 * Return true if the column has at least one usage. 665 */ 666 public boolean hasUsage() { 667 return (usages.size() != 0); 668 } 669 670 /** 671 * Return true if the column has at least one usage of the given 672 * column type. 673 */ 674 public boolean hasUsage(UsageType columnType) { 675 return usageTypes.contains(columnType); 676 } 677 678 /** 679 * Returns an iterator over all usages. 680 */ 681 public List<Usage> getUsages() { 682 return usages; 683 } 684 685 /** 686 * Returns an iterator over all usages of the given column type. 687 */ 688 public Iterator<Usage> getUsages(UsageType usageType) { 689 // Yes, this is legal. 690 class ColumnTypeIterator implements Iterator<Usage> { 691 private final Iterator<Usage> usageIter; 692 private final UsageType usageType; 693 private Usage nextUsage; 694 695 ColumnTypeIterator( 696 final List<Usage> usages, 697 final UsageType columnType) 698 { 699 this.usageIter = usages.iterator(); 700 this.usageType = columnType; 701 } 702 703 public boolean hasNext() { 704 while (usageIter.hasNext()) { 705 Usage usage = usageIter.next(); 706 if (usage.getUsageType() == this.usageType) { 707 nextUsage = usage; 708 return true; 709 } 710 } 711 nextUsage = null; 712 return false; 713 } 714 715 public Usage next() { 716 return nextUsage; 717 } 718 719 public void remove() { 720 usageIter.remove(); 721 } 722 } 723 724 return new ColumnTypeIterator(getUsages(), usageType); 725 } 726 727 /** 728 * Create a new usage of a given column type. 729 */ 730 public Usage newUsage(UsageType usageType) { 731 this.usageTypes.add(usageType); 732 733 Usage usage = new Usage(usageType); 734 usages.add(usage); 735 return usage; 736 } 737 738 public String toString() { 739 StringWriter sw = new StringWriter(256); 740 PrintWriter pw = new PrintWriter(sw); 741 print(pw, ""); 742 pw.flush(); 743 return sw.toString(); 744 } 745 746 public void print(final PrintWriter pw, final String prefix) { 747 pw.print(prefix); 748 pw.print("name="); 749 pw.print(getName()); 750 pw.print(", typename="); 751 pw.print(getTypeName()); 752 pw.print(", size="); 753 pw.print(getColumnSize()); 754 755 switch (getType()) { 756 case Types.TINYINT: 757 case Types.SMALLINT: 758 case Types.INTEGER: 759 case Types.BIGINT: 760 case Types.FLOAT: 761 case Types.REAL: 762 case Types.DOUBLE: 763 break; 764 case Types.NUMERIC: 765 case Types.DECIMAL: 766 pw.print(", decimalDigits="); 767 pw.print(getDecimalDigits()); 768 pw.print(", numPrecRadix="); 769 pw.print(getNumPrecRadix()); 770 break; 771 case Types.CHAR: 772 case Types.VARCHAR: 773 pw.print(", charOctetLength="); 774 pw.print(getCharOctetLength()); 775 break; 776 case Types.LONGVARCHAR: 777 case Types.DATE: 778 case Types.TIME: 779 case Types.TIMESTAMP: 780 case Types.BINARY: 781 case Types.VARBINARY: 782 case Types.LONGVARBINARY: 783 default: 784 break; 785 } 786 pw.print(", isNullable="); 787 pw.print(isNullable()); 788 789 if (hasUsage()) { 790 pw.print(" Usages ["); 791 for (Usage usage : getUsages()) { 792 pw.print('('); 793 usage.print(pw, prefix); 794 pw.print(')'); 795 } 796 pw.println("]"); 797 } 798 } 799 } 800 801 /** Name of table. */ 802 private final String name; 803 804 /** Map from column name to column. */ 805 private Map<String, Column> columnMap; 806 807 /** Sum of all of the table's column's column sizes. */ 808 private int totalColumnSize; 809 810 /** 811 * Whether the table is a fact, aggregate or other table type. 812 * Note: this assumes that a table has only ONE usage. 813 */ 814 private TableUsageType tableUsageType; 815 816 /** 817 * Typical table types are: "TABLE", "VIEW", "SYSTEM TABLE", 818 * "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM". 819 * (Depends what comes out of JDBC.) 820 */ 821 private final String tableType; 822 823 // mondriandef stuff 824 public MondrianDef.Table table; 825 826 private boolean allColumnsLoaded; 827 828 private Table(final String name, String tableType) { 829 this.name = name; 830 this.tableUsageType = TableUsageType.UNKNOWN; 831 this.tableType = tableType; 832 } 833 834 public void load() throws SQLException { 835 loadColumns(); 836 } 837 838 /** 839 * flushes all star usage references 840 */ 841 public void flushUsages() { 842 tableUsageType = TableUsageType.UNKNOWN; 843 for (Table.Column col : getColumns()) { 844 col.flushUsages(); 845 } 846 } 847 848 /** 849 * Returns the name of the table. 850 */ 851 public String getName() { 852 return name; 853 } 854 855 /** 856 * Returns the total size of a row (sum of the column sizes). 857 */ 858 public int getTotalColumnSize() { 859 return totalColumnSize; 860 } 861 862 /** 863 * Returns the number of rows in the table. 864 */ 865 public int getNumberOfRows() { 866 return -1; 867 } 868 869 /** 870 * Returns the collection of columns in this Table. 871 */ 872 public Collection<Column> getColumns() { 873 return getColumnMap().values(); 874 } 875 876 /** 877 * Returns an iterator over all column usages of a given type. 878 */ 879 public Iterator<JdbcSchema.Table.Column.Usage> getColumnUsages( 880 final UsageType usageType) 881 { 882 class CTIterator 883 implements Iterator<JdbcSchema.Table.Column.Usage> 884 { 885 private final Iterator<Column> columnIter; 886 private final UsageType columnType; 887 private Iterator<JdbcSchema.Table.Column.Usage> usageIter; 888 private JdbcSchema.Table.Column.Usage nextObject; 889 890 CTIterator(Collection<Column> columns, UsageType columnType) { 891 this.columnIter = columns.iterator(); 892 this.columnType = columnType; 893 } 894 895 public boolean hasNext() { 896 while (true) { 897 while ((usageIter == null) || ! usageIter.hasNext()) { 898 if (! columnIter.hasNext()) { 899 nextObject = null; 900 return false; 901 } 902 Column c = columnIter.next(); 903 usageIter = c.getUsages().iterator(); 904 } 905 JdbcSchema.Table.Column.Usage usage = usageIter.next(); 906 if (usage.getUsageType() == columnType) { 907 nextObject = usage; 908 return true; 909 } 910 } 911 } 912 public JdbcSchema.Table.Column.Usage next() { 913 return nextObject; 914 } 915 public void remove() { 916 usageIter.remove(); 917 } 918 } 919 return new CTIterator(getColumns(), usageType); 920 } 921 922 /** 923 * Returns a column by its name. 924 */ 925 public Column getColumn(final String columnName) { 926 return getColumnMap().get(columnName); 927 } 928 929 /** 930 * Return true if this table contains a column with the given name. 931 */ 932 public boolean constainsColumn(final String columnName) { 933 return getColumnMap().containsKey(columnName); 934 } 935 936 /** 937 * Sets the table usage (fact, aggregate or other). 938 * 939 * @param tableUsageType Usage type 940 */ 941 public void setTableUsageType(final TableUsageType tableUsageType) { 942 // if usageIter has already been set, then usageIter can NOT be 943 // reset 944 if ((this.tableUsageType != TableUsageType.UNKNOWN) 945 && (this.tableUsageType != tableUsageType)) 946 { 947 throw mres.AttemptToChangeTableUsage.ex( 948 getName(), 949 this.tableUsageType.name(), 950 tableUsageType.name()); 951 } 952 this.tableUsageType = tableUsageType; 953 } 954 955 /** 956 * Returns the table's usage type. 957 */ 958 public TableUsageType getTableUsageType() { 959 return tableUsageType; 960 } 961 962 /** 963 * Returns the table's type. 964 */ 965 public String getTableType() { 966 return tableType; 967 } 968 969 public String toString() { 970 StringWriter sw = new StringWriter(256); 971 PrintWriter pw = new PrintWriter(sw); 972 print(pw, ""); 973 pw.flush(); 974 return sw.toString(); 975 } 976 public void print(final PrintWriter pw, final String prefix) { 977 pw.print(prefix); 978 pw.println("Table:"); 979 String subprefix = prefix + " "; 980 String subsubprefix = subprefix + " "; 981 982 pw.print(subprefix); 983 pw.print("name="); 984 pw.print(getName()); 985 pw.print(", type="); 986 pw.print(getTableType()); 987 pw.print(", usage="); 988 pw.println(getTableUsageType().name()); 989 990 pw.print(subprefix); 991 pw.print("totalColumnSize="); 992 pw.println(getTotalColumnSize()); 993 994 pw.print(subprefix); 995 pw.println("Columns: ["); 996 for (Column column : getColumnMap().values()) { 997 column.print(pw, subsubprefix); 998 pw.println(); 999 } 1000 pw.print(subprefix); 1001 pw.println("]"); 1002 } 1003 1004 /** 1005 * Returns all of the columnIter associated with a table and creates 1006 * Column objects with the column's name, type, type name and column 1007 * size. 1008 * 1009 * @throws SQLException 1010 */ 1011 private void loadColumns() throws SQLException { 1012 if (! allColumnsLoaded) { 1013 Connection conn = getDataSource().getConnection(); 1014 try { 1015 DatabaseMetaData dmd = conn.getMetaData(); 1016 1017 String schema = JdbcSchema.this.getSchemaName(); 1018 String catalog = JdbcSchema.this.getCatalogName(); 1019 String tableName = getName(); 1020 String columnNamePattern = "%"; 1021 1022 ResultSet rs = null; 1023 try { 1024 Map<String, Column> map = getColumnMap(); 1025 rs = dmd.getColumns( 1026 catalog, 1027 schema, 1028 tableName, 1029 columnNamePattern); 1030 while (rs.next()) { 1031 String name = rs.getString(4); 1032 int type = rs.getInt(5); 1033 String typeName = rs.getString(6); 1034 int columnSize = rs.getInt(7); 1035 int decimalDigits = rs.getInt(9); 1036 int numPrecRadix = rs.getInt(10); 1037 int charOctetLength = rs.getInt(16); 1038 String isNullable = rs.getString(18); 1039 1040 Column column = new Column(name); 1041 column.setType(type); 1042 column.setTypeName(typeName); 1043 column.setColumnSize(columnSize); 1044 column.setDecimalDigits(decimalDigits); 1045 column.setNumPrecRadix(numPrecRadix); 1046 column.setCharOctetLength(charOctetLength); 1047 column.setIsNullable(!"NO".equals(isNullable)); 1048 1049 map.put(name, column); 1050 totalColumnSize += column.getColumnSize(); 1051 } 1052 } finally { 1053 if (rs != null) { 1054 rs.close(); 1055 } 1056 } 1057 } finally { 1058 try { 1059 conn.close(); 1060 } catch (SQLException e) { 1061 // ignore 1062 } 1063 } 1064 1065 allColumnsLoaded = true; 1066 } 1067 } 1068 1069 private Map<String, Column> getColumnMap() { 1070 if (columnMap == null) { 1071 columnMap = new HashMap<String, Column>(); 1072 } 1073 return columnMap; 1074 } 1075 } 1076 1077 private DataSource dataSource; 1078 private String schema; 1079 private String catalog; 1080 private boolean allTablesLoaded; 1081 1082 /** 1083 * Tables by name. We use a sorted map so {@link #getTables()}'s output 1084 * is in deterministic order. 1085 */ 1086 private final SortedMap<String, Table> tables = 1087 new TreeMap<String, Table>(); 1088 1089 JdbcSchema(final DataSource dataSource) { 1090 this.dataSource = dataSource; 1091 } 1092 1093 /** 1094 * This forces the tables to be loaded. 1095 * 1096 * @throws SQLException 1097 */ 1098 public void load() throws SQLException { 1099 loadTables(); 1100 } 1101 1102 protected synchronized void clear() { 1103 // keep the DataSource, clear/reset everything else 1104 allTablesLoaded = false; 1105 schema = null; 1106 catalog = null; 1107 tables.clear(); 1108 } 1109 1110 protected void remove() { 1111 // set ALL instance variables to null 1112 clear(); 1113 dataSource = null; 1114 } 1115 1116 /** 1117 * Used for testing allowing one to load tables and their columnIter 1118 * from more than one datasource 1119 */ 1120 void resetAllTablesLoaded() { 1121 allTablesLoaded = false; 1122 } 1123 1124 public DataSource getDataSource() { 1125 return dataSource; 1126 } 1127 1128 protected void setDataSource(DataSource dataSource) { 1129 this.dataSource = dataSource; 1130 } 1131 1132 /** 1133 * Sets the database's schema name. 1134 * 1135 * @param schema Schema name 1136 */ 1137 public void setSchemaName(final String schema) { 1138 this.schema = schema; 1139 } 1140 1141 /** 1142 * Returns the database's schema name. 1143 */ 1144 public String getSchemaName() { 1145 return schema; 1146 } 1147 1148 /** 1149 * Sets the database's catalog name. 1150 */ 1151 public void setCatalogName(final String catalog) { 1152 this.catalog = catalog; 1153 } 1154 1155 /** 1156 * Returns the database's catalog name. 1157 */ 1158 public String getCatalogName() { 1159 return catalog; 1160 } 1161 1162 /** 1163 * Returns the database's tables. The collection is sorted by table name. 1164 */ 1165 public synchronized Collection<Table> getTables() { 1166 return getTablesMap().values(); 1167 } 1168 1169 /** 1170 * flushes all star usage references 1171 */ 1172 public synchronized void flushUsages() { 1173 for (Table table : getTables()) { 1174 table.flushUsages(); 1175 } 1176 } 1177 1178 /** 1179 * Gets a table by name. 1180 */ 1181 public synchronized Table getTable(final String tableName) { 1182 return getTablesMap().get(tableName); 1183 } 1184 1185 public String toString() { 1186 StringWriter sw = new StringWriter(256); 1187 PrintWriter pw = new PrintWriter(sw); 1188 print(pw, ""); 1189 pw.flush(); 1190 return sw.toString(); 1191 } 1192 1193 public void print(final PrintWriter pw, final String prefix) { 1194 pw.print(prefix); 1195 pw.println("JdbcSchema:"); 1196 String subprefix = prefix + " "; 1197 String subsubprefix = subprefix + " "; 1198 1199 pw.print(subprefix); 1200 pw.println("Tables: ["); 1201 for (Table table : getTablesMap().values()) { 1202 table.print(pw, subsubprefix); 1203 } 1204 pw.print(subprefix); 1205 pw.println("]"); 1206 } 1207 1208 /** 1209 * Gets all of the tables (and views) in the database. 1210 * If called a second time, this method is a no-op. 1211 * 1212 * @throws SQLException 1213 */ 1214 private void loadTables() throws SQLException { 1215 if (allTablesLoaded) { 1216 return; 1217 } 1218 Connection conn = null; 1219 try { 1220 conn = getDataSource().getConnection(); 1221 final DatabaseMetaData databaseMetaData = conn.getMetaData(); 1222 String[] tableTypes = { "TABLE", "VIEW" }; 1223 if (databaseMetaData.getDatabaseProductName().toUpperCase().indexOf( 1224 "VERTICA") >= 0) 1225 { 1226 for (String tableType : tableTypes) { 1227 loadTablesOfType(databaseMetaData, new String[]{tableType}); 1228 } 1229 } else { 1230 loadTablesOfType(databaseMetaData, tableTypes); 1231 } 1232 allTablesLoaded = true; 1233 } finally { 1234 if (conn != null) { 1235 conn.close(); 1236 } 1237 } 1238 } 1239 1240 /** 1241 * Loads definition of tables of a given set of table types ("TABLE", "VIEW" 1242 * etc.) 1243 */ 1244 private void loadTablesOfType( 1245 DatabaseMetaData databaseMetaData, 1246 String[] tableTypes) 1247 throws SQLException 1248 { 1249 final String schema = getSchemaName(); 1250 final String catalog = getCatalogName(); 1251 final String tableName = "%"; 1252 ResultSet rs = null; 1253 try { 1254 rs = databaseMetaData.getTables( 1255 catalog, 1256 schema, 1257 tableName, 1258 tableTypes); 1259 if (rs == null) { 1260 getLogger().debug("ERROR: rs == null"); 1261 return; 1262 } 1263 while (rs.next()) { 1264 addTable(rs); 1265 } 1266 } finally { 1267 if (rs != null) { 1268 rs.close(); 1269 } 1270 } 1271 } 1272 1273 /** 1274 * Makes a Table from an ResultSet: the table's name is the ResultSet third 1275 * entry. 1276 * 1277 * @param rs Result set 1278 * @throws SQLException 1279 */ 1280 protected void addTable(final ResultSet rs) throws SQLException { 1281 String name = rs.getString(3); 1282 String tableType = rs.getString(4); 1283 Table table = new Table(name, tableType); 1284 1285 tables.put(table.getName(), table); 1286 } 1287 1288 private SortedMap<String, Table> getTablesMap() { 1289 return tables; 1290 } 1291 1292 public static synchronized void clearAllDBs() { 1293 factory = null; 1294 makeFactory(); 1295 } 1296} 1297 1298// End JdbcSchema.java