blob: 7c6537ea0021e63b3ddc095ae2adb4c707808437 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.db.metadata.model.jdbc;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.modules.db.metadata.model.MetadataUtilities;
import org.netbeans.modules.db.metadata.model.api.Index.IndexType;
import org.netbeans.modules.db.metadata.model.api.*;
import org.netbeans.modules.db.metadata.model.spi.TableImplementation;
import org.openide.util.NbBundle;
/**
*
* @author Andrei Badea
*/
public class JDBCTable extends TableImplementation {
private static final Logger LOGGER = Logger.getLogger(JDBCTable.class.getName());
private final JDBCSchema jdbcSchema;
private final String name;
private final boolean system;
private Map<String, Column> columns;
private Map<String, Index> indexes;
private Map<String, ForeignKey> foreignKeys;
private PrimaryKey primaryKey;
// Need a marker because there may be *no* primary key, and we don't want
// to hit the database over and over again when there is no primary key
private boolean primaryKeyInitialized = false;
private static final String SQL_EXCEPTION_NOT_YET_IMPLEMENTED = "not yet implemented";
public JDBCTable(JDBCSchema jdbcSchema, String name, boolean system) {
this.jdbcSchema = jdbcSchema;
this.name = name;
this.system = system;
}
@Override
public final Schema getParent() {
return jdbcSchema.getSchema();
}
@Override
public final String getName() {
return name;
}
@Override
public final Collection<Column> getColumns() {
return initColumns().values();
}
@Override
public final Column getColumn(String name) {
return MetadataUtilities.find(name, initColumns());
}
@Override
public PrimaryKey getPrimaryKey() {
return initPrimaryKey();
}
@Override
public Index getIndex(String indexName) {
return MetadataUtilities.find(indexName, initIndexes());
}
@Override
public Collection<Index> getIndexes() {
return initIndexes().values();
}
@Override
public Collection<ForeignKey> getForeignKeys() {
return initForeignKeys().values();
}
@Override
public ForeignKey getForeignKeyByInternalName(String name) {
return MetadataUtilities.find(name, initForeignKeys());
}
@Override
public final void refresh() {
columns = null;
primaryKey = null;
primaryKeyInitialized = false;
}
@Override
public boolean isSystem() {
return system;
}
@Override
public String toString() {
return "JDBCTable[name='" + name + "']"; // NOI18N
}
protected JDBCColumn createJDBCColumn(ResultSet rs) throws SQLException {
int position = 0;
JDBCValue jdbcValue;
if (isOdbc(rs)) {
jdbcValue = JDBCValue.createTableColumnValueODBC(rs, this.getTable());
} else {
position = rs.getInt("ORDINAL_POSITION");
jdbcValue = JDBCValue.createTableColumnValue(rs, this.getTable());
}
return new JDBCColumn(this.getTable(), position, jdbcValue);
}
/** Returns true if this table is under ODBC connection. In such a case
* some meta data like ORDINAL_POSITION or ASC_OR_DESC are not supported. */
private boolean isOdbc(ResultSet rs) throws SQLException {
boolean odbc = jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd().getURL().startsWith("jdbc:odbc"); //NOI18N
if (odbc) {
try {
rs.getInt("PRECISION");
return true;
} catch (SQLException e) {
// ignore and return false at the end; Probably MS Access driver which supports standards
}
}
return false;
}
protected JDBCPrimaryKey createJDBCPrimaryKey(String pkName, Collection<Column> pkcols) {
return new JDBCPrimaryKey(this.getTable(), pkName, pkcols);
}
protected void createColumns() {
Map<String, Column> newColumns = new LinkedHashMap<String, Column>();
try {
ResultSet rs = MetadataUtilities.getColumns(
jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(),
jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(),
name, "%"); // NOI18N
if (rs != null) {
try {
while (rs.next()) {
Column column = createJDBCColumn(rs).getColumn();
newColumns.put(column.getName(), column);
LOGGER.log(Level.FINE, "Created column {0}", column); //NOI18N
}
} finally {
rs.close();
}
}
} catch (SQLException e) {
filterSQLException(e);
}
columns = Collections.unmodifiableMap(newColumns);
}
protected void createIndexes() {
Map<String, Index> newIndexes = new LinkedHashMap<String, Index>();
try {
ResultSet rs = MetadataUtilities.getIndexInfo(
jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(),
jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(),
name, false, true);
if (rs != null) {
try {
JDBCIndex index = null;
String currentIndexName = null;
while (rs.next()) {
// Ignore Indices marked statistic
// explicit: TYPE == DatabaseMetaData or
// implicit: ORDINAL_POSITION == 0
// @see java.sql.DatabaseMetaData#getIndexInfo
if (rs.getShort("TYPE") //NOI18N
== DatabaseMetaData.tableIndexStatistic
|| rs.getInt("ORDINAL_POSITION") == 0) { //NOI18N
continue;
}
String indexName = MetadataUtilities.trimmed(rs.getString("INDEX_NAME")); //NOI18N
if (index == null || !(currentIndexName.equals(indexName))) {
index = createJDBCIndex(indexName, rs);
LOGGER.log(Level.FINE, "Created index {0}", index); //NOI18N
newIndexes.put(index.getName(), index.getIndex());
currentIndexName = indexName;
}
JDBCIndexColumn idx = createJDBCIndexColumn(index, rs);
if (idx == null) {
LOGGER.log(Level.INFO, "Cannot create index column for {0} from {1}", //NOI18N
new Object[]{indexName, rs});
} else {
IndexColumn col = idx.getIndexColumn();
index.addColumn(col);
LOGGER.log(Level.FINE, "Added column {0} to index {1}", //NOI18N
new Object[]{col.getName(), indexName});
}
}
} finally {
rs.close();
}
}
} catch (SQLException e) {
filterSQLException(e);
}
indexes = Collections.unmodifiableMap(newIndexes);
}
protected JDBCIndex createJDBCIndex(String name, ResultSet rs) {
IndexType type = IndexType.OTHER;
boolean isUnique = false;
try {
type = JDBCUtils.getIndexType(rs.getShort("TYPE"));
isUnique = !rs.getBoolean("NON_UNIQUE");
} catch (SQLException e) {
filterSQLException(e);
}
return new JDBCIndex(this.getTable(), name, type, isUnique);
}
protected JDBCIndexColumn createJDBCIndexColumn(JDBCIndex parent, ResultSet rs) {
Column column = null;
int position = 0;
Ordering ordering = Ordering.NOT_SUPPORTED;
try {
column = getColumn(rs.getString("COLUMN_NAME"));
if (!isOdbc(rs)) {
position = rs.getInt("ORDINAL_POSITION");
ordering = JDBCUtils.getOrdering(MetadataUtilities.trimmed(rs.getString("ASC_OR_DESC")));
}
} catch (SQLException e) {
filterSQLException(e);
}
if (column == null) {
LOGGER.log(Level.INFO, "Cannot get column for index {0} from {1}", //NOI18N
new Object[] {parent, rs});
return null;
}
return new JDBCIndexColumn(parent.getIndex(), column.getName(), column, position, ordering);
}
protected void createForeignKeys() {
Map<String,ForeignKey> newKeys = new LinkedHashMap<String,ForeignKey>();
try {
ResultSet rs = MetadataUtilities.getImportedKeys(
jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(),
jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(),
name);
if (rs != null) {
try {
JDBCForeignKey fkey = null;
String currentKeyName = null;
while (rs.next()) {
String keyName = MetadataUtilities.trimmed(rs.getString("FK_NAME"));
// We have to assume that if the foreign key name is null, then this is a *new*
// foreign key, even if the last foreign key name was also null.
if (fkey == null || keyName == null || !(currentKeyName.equals(keyName))) {
fkey = createJDBCForeignKey(keyName, rs);
LOGGER.log(Level.FINE, "Created foreign key {0}", keyName); //NOI18N
newKeys.put(fkey.getInternalName(), fkey.getForeignKey());
currentKeyName = keyName;
}
ForeignKeyColumn col = createJDBCForeignKeyColumn(fkey, rs).getForeignKeyColumn();
fkey.addColumn(col);
LOGGER.log(Level.FINE, "Added foreign key column {0} to foreign key {1}", //NOI18N
new Object[]{col.getName(), keyName});
}
} finally {
rs.close();
}
}
} catch (SQLException e) {
filterSQLException(e);
}
foreignKeys = Collections.unmodifiableMap(newKeys);
}
protected JDBCForeignKey createJDBCForeignKey(String name, ResultSet rs) {
return new JDBCForeignKey(this.getTable(), name);
}
protected JDBCForeignKeyColumn createJDBCForeignKeyColumn(JDBCForeignKey parent, ResultSet rs) {
Table table;
String colname;
Column referredColumn = null;
Column referringColumn = null;
int position = 0;
try {
table = findReferredTable(rs);
colname = MetadataUtilities.trimmed(rs.getString("PKCOLUMN_NAME")); // NOI18N
referredColumn = table.getColumn(colname);
if (referredColumn == null) {
throwColumnNotFoundException(table, colname);
}
colname = MetadataUtilities.trimmed(rs.getString("FKCOLUMN_NAME"));
referringColumn = getColumn(colname);
if (referringColumn == null) {
throwColumnNotFoundException(this.getTable(), colname);
}
position = rs.getInt("KEY_SEQ");
} catch (SQLException e) {
filterSQLException(e);
}
return new JDBCForeignKeyColumn(parent.getForeignKey(), referringColumn.getName(), referringColumn, referredColumn, position);
}
private void throwColumnNotFoundException(Table table, String colname)
throws MetadataException {
String message = getMessage("ERR_COL_NOT_FOUND", //NOI18N
table.getParent().getParent().getName(),
table.getParent().getName(), table.getName(), colname);
MetadataException e = new MetadataException(message);
LOGGER.log(Level.INFO, message, e);
throw e;
}
private String getMessage(String key, String ... args) {
return NbBundle.getMessage(JDBCTable.class, key, args);
}
private Table findReferredTable(ResultSet rs) {
JDBCMetadata metadata = jdbcSchema.getJDBCCatalog().getJDBCMetadata();
Catalog catalog;
Schema schema;
Table table = null;
try {
String catalogName = MetadataUtilities.trimmed(rs.getString("PKTABLE_CAT")); // NOI18N
if (catalogName == null || catalogName.length() == 0) {
catalog = jdbcSchema.getParent();
} else {
catalog = metadata.getCatalog(catalogName);
if (catalog == null) {
throw new MetadataException(getMessage("ERR_CATALOG_NOT_FOUND", catalogName)); // NOI18N
}
}
String schemaName = MetadataUtilities.trimmed(rs.getString("PKTABLE_SCHEM")); // NOI18N
if (schemaName == null || schemaName.length() == 0) {
schema = catalog.getSyntheticSchema();
} else {
schema = catalog.getSchema(schemaName);
if (schema == null) {
throw new MetadataException(getMessage("ERR_SCHEMA_NOT_FOUND", schemaName, catalog.getName()));
}
}
String tableName = MetadataUtilities.trimmed(rs.getString("PKTABLE_NAME"));
table = schema.getTable(tableName);
if (table == null) {
throw new MetadataException(getMessage("ERR_TABLE_NOT_FOUND", catalogName, schemaName, tableName));
}
} catch (SQLException e) {
filterSQLException(e);
}
return table;
}
protected void createPrimaryKey() {
String pkname = null;
Collection<Column> pkcols = new ArrayList<Column>();
try {
ResultSet rs = MetadataUtilities.getPrimaryKeys(
jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(),
jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(),
name);
if (rs != null) {
try {
while (rs.next()) {
if (pkname == null) {
pkname = MetadataUtilities.trimmed(rs.getString("PK_NAME"));
}
String colName = MetadataUtilities.trimmed(rs.getString("COLUMN_NAME"));
pkcols.add(getColumn(colName));
}
} finally {
rs.close();
}
}
} catch (SQLException e) {
filterSQLException(e);
}
primaryKey = createJDBCPrimaryKey(pkname, Collections.unmodifiableCollection(pkcols)).getPrimaryKey();
}
private Map<String, Column> initColumns() {
if (columns != null) {
return columns;
}
LOGGER.log(Level.FINE, "Initializing columns in {0}", this);
createColumns();
return columns;
}
private Map<String, Index> initIndexes() {
if (indexes != null) {
return indexes;
}
LOGGER.log(Level.FINE, "Initializing indexes in {0}", this);
createIndexes();
return indexes;
}
private Map<String,ForeignKey> initForeignKeys() {
if (foreignKeys != null) {
return foreignKeys;
}
LOGGER.log(Level.FINE, "Initializing foreign keys in {0}", this);
createForeignKeys();
return foreignKeys;
}
private PrimaryKey initPrimaryKey() {
if (primaryKeyInitialized) {
return primaryKey;
}
LOGGER.log(Level.FINE, "Initializing columns in {0}", this);
// These need to be initialized first.
getColumns();
createPrimaryKey();
primaryKeyInitialized = true;
return primaryKey;
}
private void filterSQLException(SQLException x) throws MetadataException {
if (SQL_EXCEPTION_NOT_YET_IMPLEMENTED.equalsIgnoreCase(x.getMessage())) {
Logger.getLogger(JDBCTable.class.getName()).log(Level.FINE, x.getLocalizedMessage(), x);
} else {
throw new MetadataException(x);
}
}
}