blob: 609510a1db89a596bc2c0279acdc45c30bf3af2a [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.apache.ignite.console.agent.db.dialect;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ignite.cache.QueryIndex;
import org.apache.ignite.console.agent.db.DbColumn;
import org.apache.ignite.console.agent.db.DbTable;
import static java.sql.Types.BIGINT;
import static java.sql.Types.BLOB;
import static java.sql.Types.CHAR;
import static java.sql.Types.CLOB;
import static java.sql.Types.DATE;
import static java.sql.Types.DOUBLE;
import static java.sql.Types.FLOAT;
import static java.sql.Types.INTEGER;
import static java.sql.Types.LONGVARBINARY;
import static java.sql.Types.LONGVARCHAR;
import static java.sql.Types.NUMERIC;
import static java.sql.Types.OTHER;
import static java.sql.Types.SMALLINT;
import static java.sql.Types.SQLXML;
import static java.sql.Types.TIMESTAMP;
import static java.sql.Types.TINYINT;
import static java.sql.Types.VARCHAR;
/**
* Oracle specific metadata dialect.
*/
public class OracleMetadataDialect extends DatabaseMetadataDialect {
/** SQL to get columns metadata. */
private static final String SQL_COLUMNS = "SELECT a.owner, a.table_name, a.column_name, a.nullable," +
" a.data_type, a.data_precision, a.data_scale" +
" FROM all_tab_columns a %s" +
" %s " +
" ORDER BY a.owner, a.table_name, a.column_id";
/** SQL to get list of PRIMARY KEYS columns. */
private static final String SQL_PRIMARY_KEYS = "SELECT b.column_name" +
" FROM all_constraints a" +
" INNER JOIN all_cons_columns b" +
" ON a.owner = b.owner" +
" AND a.constraint_name = b.constraint_name" +
" WHERE a.owner = ? and a.table_name = ? AND a.constraint_type = 'P'";
/** SQL to get list of UNIQUE INDEX columns. */
private static final String SQL_UNIQUE_INDEXES_KEYS = "SELECT a.index_name, b.column_name" +
" FROM all_indexes a" +
" INNER JOIN all_ind_columns b" +
" ON a.index_name = b.index_name" +
" AND a.table_owner = b.table_owner" +
" AND a.table_name = b.table_name" +
" AND a.owner = b.index_owner" +
" WHERE a.owner = ? AND a.table_name = ? AND a.uniqueness = 'UNIQUE'" +
" ORDER BY b.column_position";
/** SQL to get indexes metadata. */
private static final String SQL_INDEXES = "SELECT i.index_name, u.column_expression, i.column_name, i.descend" +
" FROM all_ind_columns i" +
" LEFT JOIN user_ind_expressions u" +
" ON u.index_name = i.index_name" +
" AND i.table_name = u.table_name" +
" WHERE i.index_owner = ? and i.table_name = ?" +
" ORDER BY i.index_name, i.column_position";
/** Owner index. */
private static final int OWNER_IDX = 1;
/** Table name index. */
private static final int TBL_NAME_IDX = 2;
/** Column name index. */
private static final int COL_NAME_IDX = 3;
/** Nullable index. */
private static final int NULLABLE_IDX = 4;
/** Data type index. */
private static final int DATA_TYPE_IDX = 5;
/** Numeric precision index. */
private static final int DATA_PRECISION_IDX = 6;
/** Numeric scale index. */
private static final int DATA_SCALE_IDX = 7;
/** Unique index name index. */
private static final int UNQ_IDX_NAME_IDX = 1;
/** Unique index column name index. */
private static final int UNQ_IDX_COL_NAME_IDX = 2;
/** Index name index. */
private static final int IDX_NAME_IDX = 1;
/** Index name index. */
private static final int IDX_EXPR_IDX = 2;
/** Index column name index. */
private static final int IDX_COL_NAME_IDX = 3;
/** Index column sort order index. */
private static final int IDX_COL_DESCEND_IDX = 4;
/** {@inheritDoc} */
@Override public Set<String> systemSchemas() {
return new HashSet<>(Arrays.asList("ANONYMOUS", "APPQOSSYS", "CTXSYS", "DBSNMP", "EXFSYS", "LBACSYS", "MDSYS",
"MGMT_VIEW", "OLAPSYS", "OWBSYS", "ORDPLUGINS", "ORDSYS", "OUTLN", "SI_INFORMTN_SCHEMA", "SYS", "SYSMAN",
"SYSTEM", "TSMSYS", "WK_TEST", "WKSYS", "WKPROXY", "WMSYS", "XDB",
"APEX_040000", "APEX_PUBLIC_USER", "DIP", "FLOWS_30000", "FLOWS_FILES", "MDDATA", "ORACLE_OCM",
"SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "XS$NULL",
"BI", "HR", "OE", "PM", "IX", "SH"));
}
/** {@inheritDoc} */
@Override public Collection<String> schemas(Connection conn) throws SQLException {
Collection<String> schemas = new ArrayList<>();
ResultSet rs = conn.getMetaData().getSchemas();
Set<String> sysSchemas = systemSchemas();
while (rs.next()) {
String schema = rs.getString(1);
if (!sysSchemas.contains(schema) && !schema.startsWith("FLOWS_"))
schemas.add(schema);
}
return schemas;
}
/**
* @param rs Result set with column type metadata from Oracle database.
* @return JDBC type.
* @throws SQLException If failed to decode type.
*/
private int decodeType(ResultSet rs) throws SQLException {
String type = rs.getString(DATA_TYPE_IDX);
if (type.startsWith("TIMESTAMP"))
return TIMESTAMP;
else {
switch (type) {
case "CHAR":
case "NCHAR":
return CHAR;
case "VARCHAR2":
case "NVARCHAR2":
return VARCHAR;
case "LONG":
return LONGVARCHAR;
case "LONG RAW":
return LONGVARBINARY;
case "FLOAT":
return FLOAT;
case "NUMBER":
int precision = rs.getInt(DATA_PRECISION_IDX);
int scale = rs.getInt(DATA_SCALE_IDX);
if (scale > 0) {
if (scale < 4 && precision < 19)
return FLOAT;
if (scale > 4 || precision > 19)
return DOUBLE;
return NUMERIC;
}
else {
if (precision < 1)
return NUMERIC;
if (precision < 3)
return TINYINT;
if (precision < 5)
return SMALLINT;
if (precision < 10)
return INTEGER;
if (precision < 19)
return BIGINT;
return NUMERIC;
}
case "DATE":
return DATE;
case "BFILE":
case "BLOB":
return BLOB;
case "CLOB":
case "NCLOB":
return CLOB;
case "XMLTYPE":
return SQLXML;
}
}
return OTHER;
}
/**
* Retrieve primary key columns.
*
* @param stmt Prepared SQL statement to execute.
* @param owner DB owner.
* @param tbl Table name.
* @return Primary key columns.
* @throws SQLException If failed to retrieve primary key columns.
*/
private Set<String> primaryKeys(PreparedStatement stmt, String owner, String tbl) throws SQLException {
stmt.setString(1, owner);
stmt.setString(2, tbl);
Set<String> pkCols = new LinkedHashSet<>();
try (ResultSet pkRs = stmt.executeQuery()) {
while (pkRs.next())
pkCols.add(pkRs.getString(1));
}
return pkCols;
}
/**
* Retrieve unique indexes with columns.
*
* @param stmt Prepared SQL statement to execute.
* @param owner DB owner.
* @param tbl Table name.
* @return Unique indexes.
* @throws SQLException If failed to retrieve unique indexes columns.
*/
private Map<String, Set<String>> uniqueIndexes(PreparedStatement stmt, String owner, String tbl) throws SQLException {
stmt.setString(1, owner);
stmt.setString(2, tbl);
Map<String, Set<String>> uniqueIdxs = new LinkedHashMap<>();
try (ResultSet idxsRs = stmt.executeQuery()) {
while (idxsRs.next()) {
String idxName = idxsRs.getString(UNQ_IDX_NAME_IDX);
String colName = idxsRs.getString(UNQ_IDX_COL_NAME_IDX);
Set<String> idxCols = uniqueIdxs.get(idxName);
if (idxCols == null) {
idxCols = new LinkedHashSet<>();
uniqueIdxs.put(idxName, idxCols);
}
idxCols.add(colName);
}
}
return uniqueIdxs;
}
/**
* Retrieve index columns.
*
* @param stmt Prepared SQL statement to execute.
* @param owner DB owner.
* @param tbl Table name.
* @param uniqueIdxAsPk Optional unique index that used as PK.
* @return Indexes.
* @throws SQLException If failed to retrieve indexes columns.
*/
private Collection<QueryIndex> indexes(PreparedStatement stmt, String owner, String tbl, String uniqueIdxAsPk) throws SQLException {
stmt.setString(1, owner);
stmt.setString(2, tbl);
Map<String, QueryIndex> idxs = new LinkedHashMap<>();
try (ResultSet idxsRs = stmt.executeQuery()) {
while (idxsRs.next()) {
String idxName = idxsRs.getString(IDX_NAME_IDX);
// Skip unique index used as PK.
if (idxName.equals(uniqueIdxAsPk))
continue;
QueryIndex idx = idxs.get(idxName);
if (idx == null) {
idx = index(idxName);
idxs.put(idxName, idx);
}
String expr = idxsRs.getString(IDX_EXPR_IDX);
String col = expr == null ? idxsRs.getString(IDX_COL_NAME_IDX) : expr.replaceAll("\"", "");
idx.getFields().put(col, !"DESC".equals(idxsRs.getString(IDX_COL_DESCEND_IDX)));
}
}
return idxs.values();
}
/** {@inheritDoc} */
@Override public Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly) throws SQLException {
PreparedStatement pkStmt = conn.prepareStatement(SQL_PRIMARY_KEYS);
PreparedStatement uniqueIdxsStmt = conn.prepareStatement(SQL_UNIQUE_INDEXES_KEYS);
PreparedStatement idxStmt = conn.prepareStatement(SQL_INDEXES);
if (schemas.isEmpty())
schemas.add(null);
Set<String> sysSchemas = systemSchemas();
Collection<DbTable> tbls = new ArrayList<>();
try (Statement colsStmt = conn.createStatement()) {
for (String schema: schemas) {
if (systemSchemas().contains(schema) || (schema != null && schema.startsWith("FLOWS_")))
continue;
String sql = String.format(SQL_COLUMNS,
tblsOnly ? "INNER JOIN all_tables b on a.table_name = b.table_name and a.owner = b.owner" : "",
schema != null ? String.format(" WHERE a.owner = '%s' ", schema) : "");
try (ResultSet colsRs = colsStmt.executeQuery(sql)) {
String prevSchema = "";
String prevTbl = "";
boolean first = true;
Set<String> pkCols = Collections.emptySet();
Collection<DbColumn> cols = new ArrayList<>();
Collection<QueryIndex> idxs = Collections.emptyList();
while (colsRs.next()) {
String owner = colsRs.getString(OWNER_IDX);
String tbl = colsRs.getString(TBL_NAME_IDX);
if (sysSchemas.contains(owner) || (schema != null && schema.startsWith("FLOWS_")))
continue;
boolean changed = !owner.equals(prevSchema) || !tbl.equals(prevTbl);
if (changed) {
if (first)
first = false;
else
tbls.add(table(prevSchema, prevTbl, cols, idxs));
prevSchema = owner;
prevTbl = tbl;
cols = new ArrayList<>();
pkCols = primaryKeys(pkStmt, owner, tbl);
Map.Entry<String, Set<String>> uniqueIdxAsPk = null;
if (pkCols.isEmpty()) {
uniqueIdxAsPk = uniqueIndexAsPk(uniqueIndexes(uniqueIdxsStmt, owner, tbl));
if (uniqueIdxAsPk != null)
pkCols.addAll(uniqueIdxAsPk.getValue());
}
idxs = indexes(idxStmt, owner, tbl, uniqueIdxAsPk != null ? uniqueIdxAsPk.getKey() : null);
}
String colName = colsRs.getString(COL_NAME_IDX);
cols.add(new DbColumn(colName, decodeType(colsRs), pkCols.contains(colName),
!"N".equals(colsRs.getString(NULLABLE_IDX)), false));
}
if (!cols.isEmpty())
tbls.add(table(prevSchema, prevTbl, cols, idxs));
}
}
}
return tbls;
}
}