All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooq.impl.MetaImpl Maven / Gradle / Ivy

There is a newer version: 3.19.11
Show newest version
/**
 * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com)
 * All rights reserved.
 *
 * Licensed 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.
 *
 * Other licenses:
 * -----------------------------------------------------------------------------
 * Commercial licenses for this work are available. These replace the above
 * ASL 2.0 and offer limited warranties, support, maintenance, and commercial
 * database integrations.
 *
 * For more information, please visit: http://www.jooq.org/licenses
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
/**
 * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com)
 * All rights reserved.
 *
 * Licensed 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.
 *
 * Other licenses:
 * -----------------------------------------------------------------------------
 * Commercial licenses for this work are available. These replace the above
 * ASL 2.0 and offer limited warranties, support, maintenance, and commercial
 * database integrations.
 *
 * For more information, please visit: http://www.jooq.org/licenses
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package org.jooq.impl;

import static java.util.Arrays.asList;
// ...
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.impl.DSL.name;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.jooq.Catalog;
import org.jooq.Configuration;
import org.jooq.ConnectionProvider;
import org.jooq.Constraint;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Meta;
import org.jooq.Name;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.SQLDialectNotSupportedException;

/**
 * An implementation of the public {@link Meta} type.
 * 

* This implementation implements {@link Serializable}, without taking care of * properly deserialising the referenced executor. * * @author Lukas Eder */ final class MetaImpl implements Meta, Serializable { /** * Generated UID */ private static final long serialVersionUID = 3582980783173033809L; private final DSLContext create; private final Configuration configuration; private final boolean inverseSchemaCatalog; MetaImpl(Configuration configuration) { this.create = DSL.using(configuration); this.configuration = configuration; this.inverseSchemaCatalog = asList(MYSQL, MARIADB).contains(configuration.family()); } private interface MetaFunction { Result run(DatabaseMetaData meta) throws SQLException; } private final Result meta(MetaFunction consumer) { ConnectionProvider provider = configuration.connectionProvider(); Connection connection = null; try { connection = provider.acquire(); return consumer.run(connection.getMetaData()); } catch (SQLException e) { throw new DataAccessException("Error while accessing DatabaseMetaData", e); } finally { if (connection != null) provider.release(connection); } } @Override public final List getCatalogs() { List result = new ArrayList(); // [#2760] MySQL JDBC confuses "catalog" and "schema" if (!inverseSchemaCatalog) { Result catalogs = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { return create.fetch( meta.getCatalogs(), SQLDataType.VARCHAR // TABLE_CATALOG ); } }); for (String name : catalogs.getValues(0, String.class)) { result.add(new MetaCatalog(name)); } } // There should always be at least one (empty) catalog in a database if (result.isEmpty()) { result.add(new MetaCatalog("")); } return result; } @Override public final List getSchemas() { List result = new ArrayList(); for (Catalog catalog : getCatalogs()) { result.addAll(catalog.getSchemas()); } return result; } @Override public final List> getTables() { List> result = new ArrayList>(); for (Schema schema : getSchemas()) { result.addAll(schema.getTables()); } return result; } @Override public final List> getPrimaryKeys() { List> result = new ArrayList>(); for (Table table : getTables()) { UniqueKey pk = table.getPrimaryKey(); if (pk != null) { result.add(pk); } } return result; } private class MetaCatalog extends CatalogImpl { /** * Generated UID */ private static final long serialVersionUID = -2821093577201327275L; MetaCatalog(String name) { super(name); } @Override public final List getSchemas() { List result = new ArrayList(); if (!inverseSchemaCatalog) { Result schemas = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { return create.fetch( meta.getSchemas(), // [#2681] Work around a flaw in the MySQL JDBC driver SQLDataType.VARCHAR // TABLE_SCHEM ); } }); for (String name : schemas.getValues(0, String.class)) { result.add(new MetaSchema(name, MetaCatalog.this)); } } // [#2760] MySQL JDBC confuses "catalog" and "schema" else { Result schemas = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { return create.fetch( meta.getCatalogs(), SQLDataType.VARCHAR // TABLE_CATALOG ); } }); for (String name : schemas.getValues(0, String.class)) { result.add(new MetaSchema(name, MetaCatalog.this)); } } // There should always be at least one (empty) schema in a database if (result.isEmpty()) { result.add(new MetaSchema("", MetaCatalog.this)); } return result; } } private class MetaSchema extends SchemaImpl { /** * Generated UID */ private static final long serialVersionUID = -2621899850912554198L; private transient volatile Map> columnCache; MetaSchema(String name, Catalog catalog) { super(name, catalog); } @Override public final synchronized List> getTables() { Result tables = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { String[] types = null; switch (configuration.family()) { // [#3977] PostgreSQL returns other object types, too case POSTGRES: types = new String[] { "TABLE", "VIEW", "SYSTEM_TABLE", "SYSTEM_VIEW", "MATERIALIZED VIEW" }; break; // [#2323] SQLite JDBC drivers have a bug. They return other // object types, too: https://bitbucket.org/xerial/sqlite-jdbc/issue/68 case SQLITE: types = new String[] { "TABLE", "VIEW" }; break; } ResultSet rs; if (!inverseSchemaCatalog) { rs = meta.getTables(null, getName(), "%", types); } // [#2760] MySQL JDBC confuses "catalog" and "schema" else { rs = meta.getTables(getName(), null, "%", types); } return create.fetch( rs, // [#2681] Work around a flaw in the MySQL JDBC driver SQLDataType.VARCHAR, // TABLE_CAT SQLDataType.VARCHAR, // TABLE_SCHEM SQLDataType.VARCHAR, // TABLE_NAME SQLDataType.VARCHAR // TABLE_TYPE ); } }); List> result = new ArrayList>(); for (Record table : tables) { String catalog = table.get(0, String.class); String schema = table.get(1, String.class); String name = table.get(2, String.class); // [#2760] MySQL JDBC confuses "catalog" and "schema" result.add(new MetaTable(name, this, getColumns( inverseSchemaCatalog ? catalog : schema, name ))); // TODO: Find a more efficient way to do this // Result pkColumns = executor.fetch(meta().getPrimaryKeys(catalog, schema, name)) // .sortAsc("KEY_SEQ"); // // result.add(new MetaTable(name, this, columnCache.get(name))); } return result; } @SuppressWarnings("unchecked") private final Result getColumns(String schema, String table) { // SQLite JDBC's DatabaseMetaData.getColumns() can only return a single // table's columns if (columnCache == null && configuration.dialect() != SQLITE) { Result columns = getColumns0(schema, "%"); Field tableCat = (Field) columns.field(0); // TABLE_CAT Field tableSchem = (Field) columns.field(1); // TABLE_SCHEM Field tableName = (Field) columns.field(2); // TABLE_NAME Map> groups = columns.intoGroups(new Field[] { inverseSchemaCatalog ? tableCat : tableSchem, tableName }); columnCache = new LinkedHashMap>(); for (Entry> entry : groups.entrySet()) { Record key = entry.getKey(); Result value = entry.getValue(); columnCache.put(name(key.get(inverseSchemaCatalog ? tableCat : tableSchem), key.get(tableName)), value); } } if (columnCache != null) { return columnCache.get(name(schema, table)); } else { return getColumns0(schema, table); } } private final Result getColumns0(final String schema, final String table) { return meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { ResultSet rs; if (!inverseSchemaCatalog) { rs = meta.getColumns(null, schema, table, "%"); } // [#2760] MySQL JDBC confuses "catalog" and "schema" else { rs = meta.getColumns(schema, null, table, "%"); } return create.fetch( rs, // Work around a bug in the SQL Server JDBC driver by // coercing data types to the expected types // The bug was reported here: // https://connect.microsoft.com/SQLServer/feedback/details/775425/jdbc-4-0-databasemetadata-getcolumns-returns-a-resultset-whose-resultsetmetadata-is-inconsistent String.class, // TABLE_CAT String.class, // TABLE_SCHEM String.class, // TABLE_NAME String.class, // COLUMN_NAME int.class, // DATA_TYPE String.class, // TYPE_NAME int.class, // COLUMN_SIZE String.class, // BUFFER_LENGTH int.class, // DECIMAL_DIGITS int.class, // NUM_PREC_RADIX int.class // NULLABLE ); } }); } } private class MetaTable extends TableImpl { /** * Generated UID */ private static final long serialVersionUID = 4843841667753000233L; MetaTable(String name, Schema schema, Result columns) { super(name, schema); // Possible scenarios for columns being null: // - The "table" is in fact a SYNONYM if (columns != null) { init(columns); } } @Override public final List> getKeys() { UniqueKey pk = getPrimaryKey(); return pk == null ? Collections.>emptyList() : Collections.>singletonList(pk); } @Override public final UniqueKey getPrimaryKey() { SQLDialect family = configuration.family(); final String schema = getSchema() == null ? null : getSchema().getName(); Result result = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { ResultSet rs; if (!inverseSchemaCatalog) { rs = meta.getPrimaryKeys(null, schema, getName()); } // [#2760] MySQL JDBC confuses "catalog" and "schema" else { rs = meta.getPrimaryKeys(schema, null, getName()); } return create.fetch( rs, String.class, // TABLE_CAT String.class, // TABLE_SCHEM String.class, // TABLE_NAME String.class, // COLUMN_NAME int.class, // KEY_SEQ String.class // PK_NAME ); } }); // Sort by KEY_SEQ result.sortAsc(4); return createPrimaryKey(result, 3); } @Override @SuppressWarnings("unchecked") public List> getReferences() { List> references = new ArrayList>(); Result result = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { ResultSet rs = meta.getImportedKeys(null, getSchema().getName(), getName()); return create.fetch( rs, String.class, // PKTABLE_CAT String.class, // PKTABLE_SCHEM String.class, // PKTABLE_NAME String.class, // PKCOLUMN_NAME String.class, // FKTABLE_CAT String.class, // FKTABLE_SCHEM String.class, // FKTABLE_NAME String.class, // FKCOLUMN_NAME Short.class, // KEY_SEQ Short.class, // UPDATE_RULE Short.class, // DELETE_RULE String.class, // FK_NAME String.class // PK_NAME ); } }); Map> groups = result.intoGroups(new Field[] { result.field(inverseSchemaCatalog ? 1 : 0), result.field(inverseSchemaCatalog ? 0 : 1), result.field(2), result.field(11), result.field(12), }); Map schemas = new HashMap(); for (Schema schema : getSchemas()) { schemas.put(schema.getName(), schema); } for (Entry> entry : groups.entrySet()) { Schema schema = schemas.get(entry.getKey().get(1)); String pkName = entry.getKey().get(4, String.class); Table pkTable = (Table) schema.getTable(entry.getKey().get(2, String.class)); TableField[] pkFields = new TableField[entry.getValue().size()]; TableField[] fkFields = new TableField[entry.getValue().size()]; for (int i = 0; i < entry.getValue().size(); i++) { Record record = entry.getValue().get(i); pkFields[i] = (TableField) pkTable.field(record.get(3, String.class)); fkFields[i] = (TableField) field(record.get(7, String.class)); } references.add(new ReferenceImpl(new MetaPrimaryKey(pkTable, pkName, pkFields), this, fkFields)); } return references; } @SuppressWarnings("unchecked") private final UniqueKey createPrimaryKey(Result result, int columnName) { if (result.size() > 0) { TableField[] f = new TableField[result.size()]; for (int i = 0; i < f.length; i++) { String name = result.get(i).get(columnName, String.class); f[i] = (TableField) field(name); // [#5097] Work around a bug in the Xerial JDBC driver for SQLite if (f[i] == null && configuration.family() == SQLITE) // [#2656] Use native support for case-insensitive column // lookup, once this is implemented for (Field field : fields()) if (field.getName().equalsIgnoreCase(name)) f[i] = (TableField) field; } String indexName = result.get(0).get(5, String.class); return new MetaPrimaryKey(this, indexName, f); } else { return null; } } private final void init(Result columns) { for (Record column : columns) { String columnName = column.getValue(3, String.class); // COLUMN_NAME String typeName = column.getValue(5, String.class); // TYPE_NAME int precision = column.getValue(6, int.class); // COLUMN_SIZE int scale = column.getValue(8, int.class); // DECIMAL_DIGITS int nullable = column.getValue(10, int.class); // NULLABLE // TODO: Exception handling should be moved inside SQLDataType DataType type = null; try { type = DefaultDataType.getDataType(configuration.family(), typeName, precision, scale); // JDBC doesn't distinguish between precision and length type = type.precision(precision, scale); type = type.length(precision); if (nullable == DatabaseMetaData.columnNoNulls) type = type.nullable(false); } catch (SQLDialectNotSupportedException e) { type = SQLDataType.OTHER; } createField(columnName, type, this); } } } private class MetaPrimaryKey implements UniqueKey { /** * Generated UID */ private static final long serialVersionUID = 6997258619475953490L; private final String pkName; private final Table pkTable; private final TableField[] pkFields; MetaPrimaryKey(Table table, String pkName, TableField[] fields) { this.pkName = pkName; this.pkTable = table; this.pkFields = fields; } @Override public final String getName() { return pkName; } @Override public final Table getTable() { return pkTable; } @Override public final List> getFields() { return Collections.unmodifiableList(Arrays.asList(pkFields)); } @Override public final TableField[] getFieldsArray() { return pkFields.clone(); } @Override public final boolean isPrimary() { return true; } @Override @SuppressWarnings("unchecked") public final List> getReferences() { List> references = new ArrayList>(); Result result = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { ResultSet rs = meta.getExportedKeys(null, pkTable.getSchema().getName(), pkTable.getName()); return create.fetch( rs, String.class, // PKTABLE_CAT String.class, // PKTABLE_SCHEM String.class, // PKTABLE_NAME String.class, // PKCOLUMN_NAME String.class, // FKTABLE_CAT String.class, // FKTABLE_SCHEM String.class, // FKTABLE_NAME String.class, // FKCOLUMN_NAME Short.class, // KEY_SEQ Short.class, // UPDATE_RULE Short.class, // DELETE_RULE String.class, // FK_NAME String.class // PK_NAME ); } }); Map> groups = result.intoGroups(new Field[] { result.field(inverseSchemaCatalog ? 5 : 4), result.field(inverseSchemaCatalog ? 4 : 5), result.field(6), result.field(11), result.field(12), }); Map schemas = new HashMap(); for (Schema schema : getSchemas()) { schemas.put(schema.getName(), schema); } for (Entry> entry : groups.entrySet()) { Schema schema = schemas.get(entry.getKey().get(1)); Table fkTable = (Table) schema.getTable(entry.getKey().get(2, String.class)); TableField[] fkFields = new TableField[entry.getValue().size()]; for (int i = 0; i < entry.getValue().size(); i++) { Record record = entry.getValue().get(i); fkFields[i] = (TableField) fkTable.field(record.get(7, String.class)); } references.add(new ReferenceImpl(this, fkTable, fkFields)); } return references; } @Override public final Constraint constraint() { if (isPrimary()) return DSL.constraint(getName()).primaryKey(getFieldsArray()); else return DSL.constraint(getName()).unique(getFieldsArray()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy