com.oceanbase.tools.dbbrowser.schema.oracle.OracleSchemaAccessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of db-browser Show documentation
Show all versions of db-browser Show documentation
db-browser is used to quickly access databases and manage various database objects.
/*
* Copyright (c) 2023 OceanBase.
*
* 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.
*/
package com.oceanbase.tools.dbbrowser.schema.oracle;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.NonNull;
import com.oceanbase.tools.dbbrowser.model.DBBasicPLObject;
import com.oceanbase.tools.dbbrowser.model.DBColumnGroupElement;
import com.oceanbase.tools.dbbrowser.model.DBColumnTypeDisplay;
import com.oceanbase.tools.dbbrowser.model.DBConstraintDeferability;
import com.oceanbase.tools.dbbrowser.model.DBConstraintType;
import com.oceanbase.tools.dbbrowser.model.DBDatabase;
import com.oceanbase.tools.dbbrowser.model.DBForeignKeyModifyRule;
import com.oceanbase.tools.dbbrowser.model.DBFunction;
import com.oceanbase.tools.dbbrowser.model.DBIndexAlgorithm;
import com.oceanbase.tools.dbbrowser.model.DBIndexType;
import com.oceanbase.tools.dbbrowser.model.DBObjectIdentity;
import com.oceanbase.tools.dbbrowser.model.DBObjectType;
import com.oceanbase.tools.dbbrowser.model.DBPLObjectIdentity;
import com.oceanbase.tools.dbbrowser.model.DBPLParam;
import com.oceanbase.tools.dbbrowser.model.DBPLParamMode;
import com.oceanbase.tools.dbbrowser.model.DBPackage;
import com.oceanbase.tools.dbbrowser.model.DBPackageBasicInfo;
import com.oceanbase.tools.dbbrowser.model.DBPackageDetail;
import com.oceanbase.tools.dbbrowser.model.DBProcedure;
import com.oceanbase.tools.dbbrowser.model.DBSequence;
import com.oceanbase.tools.dbbrowser.model.DBSynonym;
import com.oceanbase.tools.dbbrowser.model.DBSynonymType;
import com.oceanbase.tools.dbbrowser.model.DBTable;
import com.oceanbase.tools.dbbrowser.model.DBTable.DBTableOptions;
import com.oceanbase.tools.dbbrowser.model.DBTableColumn;
import com.oceanbase.tools.dbbrowser.model.DBTableColumn.CharUnit;
import com.oceanbase.tools.dbbrowser.model.DBTableConstraint;
import com.oceanbase.tools.dbbrowser.model.DBTableIndex;
import com.oceanbase.tools.dbbrowser.model.DBTablePartition;
import com.oceanbase.tools.dbbrowser.model.DBTablePartitionDefinition;
import com.oceanbase.tools.dbbrowser.model.DBTablePartitionOption;
import com.oceanbase.tools.dbbrowser.model.DBTablePartitionType;
import com.oceanbase.tools.dbbrowser.model.DBTableSubpartitionDefinition;
import com.oceanbase.tools.dbbrowser.model.DBTrigger;
import com.oceanbase.tools.dbbrowser.model.DBType;
import com.oceanbase.tools.dbbrowser.model.DBVariable;
import com.oceanbase.tools.dbbrowser.model.DBView;
import com.oceanbase.tools.dbbrowser.model.OracleConstants;
import com.oceanbase.tools.dbbrowser.model.PLConstants;
import com.oceanbase.tools.dbbrowser.parser.PLParser;
import com.oceanbase.tools.dbbrowser.parser.result.ParseOraclePLResult;
import com.oceanbase.tools.dbbrowser.schema.DBSchemaAccessor;
import com.oceanbase.tools.dbbrowser.schema.DBSchemaAccessorSqlMapper;
import com.oceanbase.tools.dbbrowser.schema.DBSchemaAccessorSqlMappers;
import com.oceanbase.tools.dbbrowser.schema.constant.Statements;
import com.oceanbase.tools.dbbrowser.schema.constant.StatementsFiles;
import com.oceanbase.tools.dbbrowser.util.DBSchemaAccessorUtil;
import com.oceanbase.tools.dbbrowser.util.OracleDataDictTableNames;
import com.oceanbase.tools.dbbrowser.util.OracleSqlBuilder;
import com.oceanbase.tools.dbbrowser.util.PLObjectErrMsgUtils;
import com.oceanbase.tools.dbbrowser.util.SqlBuilder;
import com.oceanbase.tools.dbbrowser.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
/**
* @author jingtian
*/
@Slf4j
public class OracleSchemaAccessor implements DBSchemaAccessor {
private static final String ORACLE_TABLE_COMMENT_DDL_TEMPLATE =
"COMMENT ON TABLE ${schemaName}.${tableName} IS ${comment}";
private static final String ORACLE_COLUMN_COMMENT_DDL_TEMPLATE =
"COMMENT ON COLUMN ${schemaName}.${tableName}.${columnName} IS ${comment}";
protected OracleDataDictTableNames dataDictTableNames;
protected JdbcOperations jdbcOperations;
protected DBSchemaAccessorSqlMapper sqlMapper;
public OracleSchemaAccessor(@NonNull JdbcOperations jdbcOperations,
OracleDataDictTableNames dataDictTableNames) {
this.dataDictTableNames = dataDictTableNames;
this.jdbcOperations = jdbcOperations;
this.sqlMapper = DBSchemaAccessorSqlMappers.get(StatementsFiles.ORACLE_11_g);
}
@Override
public List showDatabases() {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select USERNAME from ");
sb.append(dataDictTableNames.USERS());
return jdbcOperations.queryForList(sb.toString(), String.class);
}
@Override
public DBDatabase getDatabase(String schemaName) {
DBDatabase database = new DBDatabase();
String sql = this.sqlMapper.getSql(Statements.GET_DATABASE);
jdbcOperations.query(sql, new Object[] {schemaName}, rs -> {
database.setId(rs.getString(2));
database.setName(rs.getString(1));
});
try {
sql = "select value from v$nls_parameters where PARAMETER = 'NLS_CHARACTERSET'";
jdbcOperations.query(sql, rs -> {
database.setCharset(rs.getString(1));
});
sql = "SELECT value from v$nls_parameters where parameter = 'NLS_SORT'";
jdbcOperations.query(sql, rs -> {
database.setCollation(rs.getString(1));
});
} catch (Exception e) {
log.warn("Failed to get oracle charset and collation, error message:{}", e.getMessage());
sql = "select value from v_$nls_parameters where PARAMETER = 'NLS_CHARACTERSET'";
jdbcOperations.query(sql, rs -> {
database.setCharset(rs.getString(1));
});
sql = "SELECT value from v_$nls_parameters where parameter = 'NLS_SORT'";
jdbcOperations.query(sql, rs -> {
database.setCollation(rs.getString(1));
});
}
return database;
}
@Override
public List listDatabases() {
List databases = new ArrayList<>();
String sql = this.sqlMapper.getSql(Statements.LIST_DATABASE);
this.jdbcOperations.query(sql, (rs) -> {
DBDatabase database = new DBDatabase();
database.setId(rs.getString(2));
database.setName(rs.getString(1));
databases.add(database);
});
AtomicReference charset = new AtomicReference<>();
AtomicReference collation = new AtomicReference<>();
try {
sql = "select value from v$nls_parameters where PARAMETER = 'NLS_CHARACTERSET'";
this.jdbcOperations.query(sql, (rs) -> {
charset.set(rs.getString(1));
});
sql = "SELECT value from v$nls_parameters where parameter = 'NLS_SORT'";
this.jdbcOperations.query(sql, (rs) -> {
collation.set(rs.getString(1));
});
} catch (Exception e) {
sql = "select value from v_$nls_parameters where PARAMETER = 'NLS_CHARACTERSET'";
this.jdbcOperations.query(sql, (rs) -> {
charset.set(rs.getString(1));
});
sql = "SELECT value from v_$nls_parameters where parameter = 'NLS_SORT'";
this.jdbcOperations.query(sql, (rs) -> {
collation.set(rs.getString(1));
});
}
databases.forEach((item) -> {
item.setCharset(charset.get());
item.setCollation(collation.get());
});
return databases;
}
@Override
public void switchDatabase(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("alter session set current_schema=");
sb.identifier(schemaName);
jdbcOperations.execute(sb.toString());
}
@Override
public List listUsers() {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("SELECT USERNAME FROM ");
sb.append(dataDictTableNames.USERS());
return jdbcOperations.query(sb.toString(), (rs, rowNum) -> {
DBObjectIdentity dbUser = new DBObjectIdentity();
dbUser.setName(rs.getString(1));
dbUser.setType(DBObjectType.USER);
return dbUser;
});
}
@Override
public List showTablesLike(String schemaName, String tableNameLike) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("SELECT TABLE_NAME FROM ");
sb.append(dataDictTableNames.TABLES());
sb.append(" WHERE OWNER=");
sb.value(schemaName);
if (StringUtils.isNotBlank(tableNameLike)) {
sb.append(" AND TABLE_NAME LIKE ");
sb.value(tableNameLike);
}
sb.append(" ORDER BY TABLE_NAME ASC");
return jdbcOperations.queryForList(sb.toString(), String.class);
}
@Override
public List listTables(String schemaName, String tableNameLike) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select OWNER as schema_name, 'TABLE' as type,TABLE_NAME as name");
sb.append(" from ");
sb.append(dataDictTableNames.TABLES());
sb.append(" where 1=1 ");
if (StringUtils.isNotBlank(schemaName)) {
sb.append(" AND OWNER=");
sb.value(schemaName);
}
if (StringUtils.isNotBlank(tableNameLike)) {
sb.append(" AND TABLE_NAME LIKE ");
sb.value(tableNameLike);
}
sb.append(" ORDER BY schema_name, type, name");
return jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBObjectIdentity.class));
}
@Override
public List listViews(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select OWNER as schema_name, 'VIEW' as type, view_name as name from ");
sb.append(dataDictTableNames.VIEWS());
sb.append(" where owner=");
sb.value(schemaName);
sb.append(" order by view_name asc");
return jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBObjectIdentity.class));
}
@Override
public List listAllViews(String viewNameLike) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select OWNER as schema_name, VIEW_NAME as name, 'VIEW' as type from ")
.append(dataDictTableNames.VIEWS())
.append(" where VIEW_NAME LIKE ")
.value('%' + viewNameLike + '%')
.append(" order by name asc");
return jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBObjectIdentity.class));
}
@Override
public List listAllUserViews() {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select OWNER as schema_name, 'VIEW' as type, VIEW_NAME as name");
sb.append(" from ");
sb.append(dataDictTableNames.VIEWS());
sb.append(" ORDER BY schema_name, type, name");
return jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBObjectIdentity.class));
}
@Override
public List listAllSystemViews() {
return Collections.emptyList();
}
@Override
public List showSystemViews(String schemaName) {
if (!StringUtils.equalsIgnoreCase("SYS", schemaName)) {
return Collections.emptyList();
}
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select VIEW_NAME from ")
.append(dataDictTableNames.VIEWS())
.append(" where OWNER='SYS' ORDER BY VIEW_NAME");
return jdbcOperations.queryForList(sb.toString(), String.class);
}
@Override
public List showVariables() {
String sql = "SELECT name, value FROM V$PARAMETER";
return jdbcOperations.query(sql, (rs, rowNum) -> {
DBVariable variable = new DBVariable();
variable.setName(rs.getString(1));
variable.setValue(rs.getString(2));
return variable;
});
}
@Override
public List showSessionVariables() {
String sql = "SELECT name, value FROM V$PARAMETER";
return jdbcOperations.query(sql, (rs, rowNum) -> {
DBVariable variable = new DBVariable();
variable.setName(rs.getString(1));
variable.setValue(rs.getString(2));
return variable;
});
}
@Override
public List showGlobalVariables() {
String sql = "SELECT name, value FROM V$SYSTEM_PARAMETER";
return jdbcOperations.query(sql, (rs, rowNum) -> {
DBVariable variable = new DBVariable();
variable.setName(rs.getString(1));
variable.setValue(rs.getString(2));
return variable;
});
}
@Override
public List showCharset() {
try {
String sql =
"SELECT DISTINCT VALUE FROM V$NLS_VALID_VALUES WHERE PARAMETER = 'CHARACTERSET' ORDER BY VALUE";
return jdbcOperations.queryForList(sql, String.class);
} catch (Exception e) {
log.warn("Failed to get oracle charset, error message:{}", e.getMessage());
String sql =
"SELECT DISTINCT VALUE FROM V_$NLS_VALID_VALUES WHERE PARAMETER = 'CHARACTERSET' ORDER BY VALUE";
return jdbcOperations.queryForList(sql, String.class);
}
}
@Override
public List showCollation() {
try {
String sql = "SELECT DISTINCT VALUE FROM V$NLS_VALID_VALUES WHERE PARAMETER = 'SORT' ORDER BY VALUE";
return jdbcOperations.queryForList(sql, String.class);
} catch (Exception e) {
log.warn("Failed to get oracle collation, error message:{}", e.getMessage());
String sql = "SELECT DISTINCT VALUE FROM V_$NLS_VALID_VALUES WHERE PARAMETER = 'SORT' ORDER BY VALUE";
return jdbcOperations.queryForList(sql, String.class);
}
}
@Override
public List listFunctions(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select OWNER as schema_name, object_type as type, OBJECT_NAME as name, STATUS from ")
.append(dataDictTableNames.OBJECTS())
.append(" where object_type = 'FUNCTION' and owner=")
.value(schemaName)
.append(" order by object_name asc");
List functions =
jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBPLObjectIdentity.class));
Map errorText = PLObjectErrMsgUtils.acquireErrorMessage(jdbcOperations,
schemaName, DBObjectType.FUNCTION.name(), null);
for (DBPLObjectIdentity function : functions) {
if (StringUtils.containsIgnoreCase(function.getStatus(), PLConstants.PL_OBJECT_STATUS_INVALID)) {
function.setErrorMessage(errorText.get(function.getName()));
}
}
return functions;
}
@Override
public List listProcedures(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select object_name as name, object_type as type, owner as schema_name, status from ");
sb.append(dataDictTableNames.OBJECTS());
sb.append(" where object_type = 'PROCEDURE' and owner=");
sb.value(schemaName);
sb.append(" order by object_name asc");
List procedures =
jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBPLObjectIdentity.class));
Map errorText = PLObjectErrMsgUtils.acquireErrorMessage(jdbcOperations,
schemaName, DBObjectType.PROCEDURE.name(), null);
for (DBPLObjectIdentity procedure : procedures) {
if (StringUtils.containsIgnoreCase(procedure.getStatus(), PLConstants.PL_OBJECT_STATUS_INVALID)) {
procedure.setErrorMessage(errorText.get(procedure.getName()));
}
}
return procedures;
}
@Override
public List listPackages(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select object_name as name, object_type as type, owner, status from ");
sb.append(dataDictTableNames.OBJECTS());
sb.append(" where (object_type = 'PACKAGE' or object_type = 'PACKAGE BODY') and owner=");
sb.value(schemaName);
sb.append(" order by name asc");
List packages = jdbcOperations.query(sb.toString(), (rs, rowNum) -> {
DBPLObjectIdentity dbPackage = new DBPLObjectIdentity();
dbPackage.setName(rs.getString("name"));
dbPackage.setStatus(rs.getString("status"));
dbPackage.setSchemaName(rs.getString("owner"));
dbPackage.setType(DBObjectType.getEnumByName(rs.getString("type")));
return dbPackage;
});
List filtered = new ArrayList<>();
Map name2Status = new HashMap<>();
for (DBPLObjectIdentity dbPackage : packages) {
String pkgName = dbPackage.getName();
String status = dbPackage.getStatus();
// merge status of 'package' and 'package body'
if (name2Status.containsKey(pkgName)) {
if (PLConstants.PL_OBJECT_STATUS_INVALID.equalsIgnoreCase(status)) {
name2Status.put(pkgName, status);
}
} else {
name2Status.put(pkgName, status);
}
}
Map errorText = PLObjectErrMsgUtils.acquireErrorMessage(jdbcOperations,
schemaName, DBObjectType.PACKAGE.name(), null);
String pkgName = null;
for (DBPLObjectIdentity pkg : packages) {
if (Objects.isNull(pkgName) || !StringUtils.equals(pkgName, pkg.getName())) {
pkgName = pkg.getName();
DBPLObjectIdentity dbPackage = new DBPLObjectIdentity();
dbPackage.setName(pkg.getName());
dbPackage.setStatus(name2Status.get(pkg.getName()));
dbPackage.setSchemaName(pkg.getSchemaName());
dbPackage.setType(pkg.getType());
if (StringUtils.containsIgnoreCase(dbPackage.getStatus(),
PLConstants.PL_OBJECT_STATUS_INVALID)) {
dbPackage.setErrorMessage(errorText.get(dbPackage.getName()));
}
filtered.add(dbPackage);
}
}
return filtered;
}
@Override
public List listPackageBodies(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select object_name as name, object_type as type, owner, status from ");
sb.append(dataDictTableNames.OBJECTS());
sb.append(" where object_type = 'PACKAGE BODY' and owner=");
sb.value(schemaName);
sb.append(" order by name asc");
return jdbcOperations.query(sb.toString(), (rs, rowNum) -> {
DBPLObjectIdentity dbPackage = new DBPLObjectIdentity();
dbPackage.setName(rs.getString("name"));
dbPackage.setStatus(rs.getString("status"));
dbPackage.setSchemaName(rs.getString("owner"));
dbPackage.setType(DBObjectType.getEnumByName(rs.getString("type")));
return dbPackage;
});
}
@Override
public List listTriggers(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select o.OWNER,s.STATUS,o.STATUS as ENABLE_STATUS,TRIGGER_NAME from "
+ "(select * from ");
sb.append(dataDictTableNames.OBJECTS());
sb.append(" where OBJECT_TYPE='TRIGGER') s right join ");
sb.append(dataDictTableNames.TRIGGERS());
sb.append(" o on s.OBJECT_NAME=o.TRIGGER_NAME and s.OWNER=o.OWNER where o.OWNER=");
sb.value(schemaName);
sb.append(" order by TRIGGER_NAME asc");
List triggers = jdbcOperations.query(sb.toString(), (rs, rowNum) -> {
DBPLObjectIdentity trigger = new DBPLObjectIdentity();
trigger.setName(rs.getString("TRIGGER_NAME"));
trigger.setSchemaName(rs.getString("OWNER"));
trigger.setStatus(rs.getString("STATUS"));
trigger.setEnable("ENABLED".equals(rs.getString("ENABLE_STATUS")));
trigger.setType(DBObjectType.TRIGGER);
return trigger;
});
Map errorText = PLObjectErrMsgUtils.acquireErrorMessage(jdbcOperations,
schemaName, DBObjectType.TRIGGER.name(), null);
for (DBPLObjectIdentity trigger : triggers) {
if (StringUtils.containsIgnoreCase(trigger.getStatus(), PLConstants.PL_OBJECT_STATUS_INVALID)) {
trigger.setErrorMessage(errorText.get(trigger.getName()));
}
}
return triggers;
}
@Override
public List listTypes(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select OBJECT_NAME as name, STATUS, OBJECT_TYPE as type, OWNER as schema_name from ");
sb.append(dataDictTableNames.OBJECTS());
sb.append(" where OBJECT_TYPE='TYPE' and OWNER=");
sb.value(schemaName);
sb.append(" order by OBJECT_NAME asc");
List types =
jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBPLObjectIdentity.class));
return fillTypeErrorMessage(types, schemaName);
}
protected List fillTypeErrorMessage(List types, String schemaName) {
Map errorText = PLObjectErrMsgUtils.acquireErrorMessage(jdbcOperations,
schemaName, DBObjectType.TYPE.name(), null);
for (DBPLObjectIdentity type : types) {
if (StringUtils.containsIgnoreCase(type.getStatus(), PLConstants.PL_OBJECT_STATUS_INVALID)) {
type.setErrorMessage(errorText.get(type.getName()));
}
}
return types;
}
@Override
public List listSequences(String schemaName) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select SEQUENCE_NAME as name, SEQUENCE_OWNER as schema_name from ");
sb.append(dataDictTableNames.SEQUENCES());
sb.append(" where SEQUENCE_OWNER=");
sb.value(schemaName);
sb.append(" order by name ASC");
return jdbcOperations.query(sb.toString(), (rs, rowNum) -> {
DBObjectIdentity sequence = new DBObjectIdentity();
sequence.setName(rs.getString("name"));
sequence.setSchemaName(rs.getString("schema_name"));
sequence.setType(DBObjectType.SEQUENCE);
return sequence;
});
}
@Override
public List listSynonyms(String schemaName, DBSynonymType synonymType) {
OracleSqlBuilder sb = new OracleSqlBuilder();
if (DBSynonymType.PUBLIC.equals(synonymType)) {
sb.append(
"SELECT OWNER as schema_name, SYNONYM_NAME as name, 'PUBLIC_SYNONYM' as type FROM ALL_SYNONYMS where owner='PUBLIC'");
} else if (DBSynonymType.COMMON.equals(synonymType)) {
sb.append(
"SELECT OWNER as schema_name, SYNONYM_NAME as name, 'SYNONYM' as type FROM ALL_SYNONYMS where owner=")
.value(schemaName);
} else {
throw new UnsupportedOperationException("Not supported Synonym type");
}
return jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBObjectIdentity.class));
}
@Override
public Map> listTableColumns(String schemaName, List tableNames) {
List tableColumns = DBSchemaAccessorUtil.partitionFind(tableNames,
DBSchemaAccessorUtil.OB_MAX_IN_SIZE, names -> {
String sql = filterByValues(sqlMapper.getSql(Statements.LIST_SCHEMA_COLUMNS), "TABLE_NAME", names);
return jdbcOperations.query(sql, new Object[] {schemaName}, listColumnsRowMapper());
});
Map> tableName2Columns = tableColumns.stream()
.collect(Collectors.groupingBy(DBTableColumn::getTableName));
tableName2Columns.forEach((table, cols) -> {
Map name2Comments = mapColumnName2ColumnComments(schemaName, table);
cols.forEach(col -> {
if (name2Comments.containsKey(col.getName())) {
col.setComment(name2Comments.get(col.getName()));
}
});
});
return tableName2Columns;
}
@Override
public Map> listBasicTableColumns(String schemaName) {
String sql = sqlMapper.getSql(Statements.LIST_BASIC_SCHEMA_TABLE_COLUMNS);
List tableColumns =
jdbcOperations.query(sql, new Object[] {schemaName, schemaName}, listBasicColumnsRowMapper());
return tableColumns.stream().collect(Collectors.groupingBy(DBTableColumn::getTableName));
}
@Override
public List listBasicTableColumns(String schemaName, String tableName) {
String sql = sqlMapper.getSql(Statements.LIST_BASIC_TABLE_COLUMNS);
return jdbcOperations.query(sql, new Object[] {schemaName, tableName}, listBasicColumnsRowMapper());
}
@Override
public Map> listBasicViewColumns(String schemaName) {
String sql = sqlMapper.getSql(Statements.LIST_BASIC_SCHEMA_VIEW_COLUMNS);
List tableColumns =
jdbcOperations.query(sql, new Object[] {schemaName, schemaName}, listBasicColumnsRowMapper());
return tableColumns.stream().collect(Collectors.groupingBy(DBTableColumn::getTableName));
}
@Override
public List listBasicViewColumns(String schemaName, String viewName) {
String sql = sqlMapper.getSql(Statements.LIST_BASIC_VIEW_COLUMNS);
return jdbcOperations.query(sql, new Object[] {schemaName, viewName}, listBasicColumnsRowMapper());
}
@Override
public Map> listBasicColumnsInfo(String schemaName) {
String sql = sqlMapper.getSql(Statements.LIST_BASIC_SCHEMA_COLUMNS_INFO);
List tableColumns =
jdbcOperations.query(sql, new Object[] {schemaName}, listBasicColumnsIdentityRowMapper());
return tableColumns.stream().collect(Collectors.groupingBy(DBTableColumn::getTableName));
}
@Override
public Map> listTableIndexes(String schemaName) {
Map> tableName2Indexes = new LinkedHashMap<>();
String sql = sqlMapper.getSql(Statements.LIST_SCHEMA_INDEX);
jdbcOperations.query(sql, new Object[] {schemaName}, (rs, num) -> {
String indexName = rs.getString(OracleConstants.INDEX_NAME);
String tableName = rs.getString(OracleConstants.INDEX_TABLE_NAME);
if (tableName2Indexes.containsKey(tableName)) {
List tableIndexes = tableName2Indexes.get(tableName);
Optional existingIndex = tableIndexes.stream()
.filter(idx -> idx.getName().equals(indexName))
.findFirst();
if (existingIndex.isPresent()) {
List columnNames = existingIndex.get().getColumnNames();
columnNames.add(rs.getString("COLUMN_NAME"));
} else {
tableIndexes.add(createIndexByResultSet(rs, num));
}
} else {
tableName2Indexes.put(tableName,
new ArrayList<>(Collections.singletonList(createIndexByResultSet(rs, num))));
}
return null;
});
fillFunctionBasedIndexInfo(
tableName2Indexes.values().stream().flatMap(List::stream).collect(Collectors.toList()));
return tableName2Indexes;
}
private DBTableIndex createIndexByResultSet(ResultSet rs, int num) throws SQLException {
DBTableIndex index = new DBTableIndex();
index.setName(rs.getString(OracleConstants.INDEX_NAME));
index.setOrdinalPosition(num);
index.setSchemaName(rs.getString("TABLE_OWNER"));
index.setOwner(rs.getString("OWNER"));
index.setTableName(rs.getString(OracleConstants.INDEX_TABLE_NAME));
index.setNonUnique(!"UNIQUE".equalsIgnoreCase(rs.getString(OracleConstants.INDEX_UNIQUENESS)));
index.setType(DBIndexType.fromString(rs.getString(OracleConstants.INDEX_TYPE)));
index.setVisible("VISIBLE".equalsIgnoreCase(rs.getString("VISIBILITY")));
if (index.isNonUnique()) {
index.setType(DBIndexType.fromString(rs.getString(OracleConstants.INDEX_TYPE)));
} else {
index.setType(DBIndexType.UNIQUE);
}
index.setAlgorithm(DBIndexAlgorithm.fromString(rs.getString(OracleConstants.INDEX_TYPE)));
index.setCompressInfo(rs.getString(OracleConstants.INDEX_COMPRESSION));
index.setColumnNames(new ArrayList<>(Collections.singletonList(rs.getString("COLUMN_NAME"))));
index.setAvailable(isTableIndexAvailable(rs.getString(OracleConstants.INDEX_STATUS)));
if (judgeIndexGlobalOrLocalFromDataDict()) {
index.setGlobal("NO".equalsIgnoreCase(rs.getString("PARTITIONED")));
}
return index;
}
@Override
public Map> listTableConstraints(String schemaName) {
String sql = this.sqlMapper.getSql(Statements.LIST_SCHEMA_CONSTRAINTS);
Map> tableName2Constraints = new LinkedHashMap<>();
jdbcOperations.query(sql, new Object[] {schemaName}, (rs, num) -> {
String tableName = rs.getString("TABLE_NAME");
String constraintName = rs.getString(OracleConstants.CONS_NAME);
if (tableName2Constraints.containsKey(tableName)) {
Map constraintName2Constraint =
tableName2Constraints.get(tableName).stream().collect(
Collectors.toMap(DBTableConstraint::getName, cons -> cons));
int currentPosition = constraintName2Constraint.size();
if (!constraintName2Constraint.containsKey(constraintName)) {
tableName2Constraints.get(tableName).add(createConstraintByResultSet(rs, currentPosition + 1));
} else {
constraintName2Constraint.get(constraintName).getColumnNames()
.add(rs.getString("COLUMN_NAME"));
constraintName2Constraint.get(constraintName).getReferenceColumnNames()
.add(rs.getString(OracleConstants.CONS_R_COLUMN_NAME));
}
} else {
tableName2Constraints.put(tableName,
new ArrayList<>(Collections.singletonList(createConstraintByResultSet(rs, 1))));
}
return constraintName;
});
filterConstraintColumns(
tableName2Constraints.values().stream().flatMap(List::stream).collect(Collectors.toList()));
return tableName2Constraints;
}
@Override
public Map listTableOptions(String schemaName) {
Map tableName2Options = new LinkedHashMap<>();
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("SELECT OWNER, TABLE_NAME, COMMENTS FROM ")
.append(dataDictTableNames.TAB_COMMENTS())
.append(" WHERE TABLE_TYPE='TABLE' AND OWNER=")
.value(schemaName)
.append(" ORDER BY OWNER, TABLE_NAME ASC");
jdbcOperations.query(sb.toString(), (rs, num) -> {
DBTableOptions options = new DBTableOptions();
options.setComment(rs.getString("COMMENTS"));
tableName2Options.put(rs.getString("TABLE_NAME"), options);
return null;
});
List tableOptions = new ArrayList<>(tableName2Options.values());
obtainTableCharset(tableOptions);
obtainTableCollation(tableOptions);
return tableName2Options;
}
@Override
public List listSubpartitions(String schemaName, String tableName) {
throw new UnsupportedOperationException("Not supported yet");
}
@Override
public Boolean isLowerCaseTableName() {
return false;
}
@Override
public List listTableColumns(String schemaName, String tableName) {
String sql = this.sqlMapper.getSql(Statements.LIST_TABLE_COLUMNS);
List tableColumns =
this.jdbcOperations.query(sql.toString(), new Object[] {schemaName, tableName},
listColumnsRowMapper());
Map name2Comments = mapColumnName2ColumnComments(schemaName, tableName);
tableColumns.stream().forEach(dbTableColumn -> {
if (name2Comments.containsKey(dbTableColumn.getName())) {
dbTableColumn.setComment(name2Comments.get(dbTableColumn.getName()));
}
});
return tableColumns;
}
@Override
public List listPartitionTables(String partitionMethod) {
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select DISTINCT OWNER as schema_name,TABLE_NAME as name,'TABLE' as type from ");
sb.append(dataDictTableNames.PART_TABLES());
sb.append(" where PARTITIONING_TYPE = ");
sb.value(partitionMethod);
return jdbcOperations.query(sb.toString(), new BeanPropertyRowMapper<>(DBObjectIdentity.class));
}
protected Map mapColumnName2ColumnComments(String schemaName, String tableName) {
Map commentsMap = new HashMap<>();
OracleSqlBuilder sb = new OracleSqlBuilder();
sb.append("select COLUMN_NAME, COMMENTS from ");
sb.append(dataDictTableNames.COL_COMMENTS());
sb.append(" where OWNER = ");
sb.value(schemaName);
sb.append(" and TABLE_NAME = ");
sb.value(tableName);
jdbcOperations.query(sb.toString(), resultSet -> {
commentsMap.put(resultSet.getString(OracleConstants.COL_COLUMN_NAME),
resultSet.getString(OracleConstants.COL_COMMENTS));
});
return commentsMap;
}
protected RowMapper listColumnsRowMapper() {
final int[] hiddenColumnOrdinaryPosition = {-1};
return (rs, rowNum) -> {
DBTableColumn tableColumn = new DBTableColumn();
/**
* All LONG or LONG RAW columns have to be retrieved from the ResultSet prior to all the other
* columns or oracle jdbc will throw “Stream has already been closed” Exception
*/
String defaultValue = rs.getString(OracleConstants.COL_DATA_DEFAULT);
tableColumn.setSchemaName(rs.getString(OracleConstants.CONS_OWNER));
tableColumn.setTableName(rs.getString(OracleConstants.COL_TABLE_NAME));
tableColumn.setName(rs.getString(OracleConstants.COL_COLUMN_NAME));
tableColumn.setTypeName(
DBSchemaAccessorUtil.normalizeTypeName(rs.getString(OracleConstants.COL_DATA_TYPE)));
tableColumn.setFullTypeName(rs.getString(OracleConstants.COL_DATA_TYPE));
tableColumn.setCharUsed(CharUnit.fromString(rs.getString(OracleConstants.COL_CHAR_USED)));
tableColumn.setOrdinalPosition(rs.getInt(OracleConstants.COL_COLUMN_ID));
tableColumn.setTypeModifiers(Collections.singletonList(rs.getString(OracleConstants.COL_DATA_TYPE_MOD)));
tableColumn.setMaxLength(
rs.getLong(tableColumn.getCharUsed() == CharUnit.CHAR ? OracleConstants.COL_CHAR_LENGTH
: OracleConstants.COL_DATA_LENGTH));
tableColumn.setNullable("Y".equalsIgnoreCase(rs.getString(OracleConstants.COL_NULLABLE)));
DBColumnTypeDisplay columnTypeDisplay = DBColumnTypeDisplay.fromName(tableColumn.getTypeName());
if (columnTypeDisplay.displayScale()) {
tableColumn.setScale(rs.getInt(OracleConstants.COL_DATA_SCALE));
}
if (columnTypeDisplay.displayPrecision()) {
if (Objects.nonNull(rs.getObject(OracleConstants.COL_DATA_PRECISION))) {
tableColumn.setPrecision(rs.getLong(OracleConstants.COL_DATA_PRECISION));
} else {
tableColumn.setPrecision(tableColumn.getMaxLength());
}
}
tableColumn.setHidden("YES".equalsIgnoreCase(rs.getString(OracleConstants.COL_HIDDEN_COLUMN)));
/**
* hidden column does not have ordinary position, we assign an negative position
* for front-end as a key to identify a column
*
*/
if (tableColumn.getHidden()) {
tableColumn.setOrdinalPosition(hiddenColumnOrdinaryPosition[0]);
hiddenColumnOrdinaryPosition[0]--;
}
tableColumn.setVirtual("YES".equalsIgnoreCase(rs.getString(OracleConstants.COL_VIRTUAL_COLUMN)));
tableColumn.setDefaultValue("NULL".equals(defaultValue) ? null : defaultValue);
if (tableColumn.getVirtual()) {
tableColumn.setGenExpression(rs.getString(OracleConstants.COL_DATA_DEFAULT));
}
return tableColumn;
};
}
protected RowMapper listBasicColumnsRowMapper() {
return (rs, rowNum) -> {
DBTableColumn tableColumn = new DBTableColumn();
tableColumn.setSchemaName(rs.getString(OracleConstants.CONS_OWNER));
tableColumn.setTableName(rs.getString(OracleConstants.COL_TABLE_NAME));
tableColumn.setName(rs.getString(OracleConstants.COL_COLUMN_NAME));
tableColumn.setComment(rs.getString(OracleConstants.COL_COMMENTS));
tableColumn
.setTypeName(DBSchemaAccessorUtil.normalizeTypeName(rs.getString(OracleConstants.COL_DATA_TYPE)));
return tableColumn;
};
}
protected RowMapper listBasicColumnsIdentityRowMapper() {
return (rs, rowNum) -> {
DBTableColumn tableColumn = new DBTableColumn();
tableColumn.setSchemaName(rs.getString(OracleConstants.CONS_OWNER));
tableColumn.setTableName(rs.getString(OracleConstants.COL_TABLE_NAME));
tableColumn.setName(rs.getString(OracleConstants.COL_COLUMN_NAME));
return tableColumn;
};
}
@Override
public List listTableConstraints(String schemaName, String tableName) {
String sql = this.sqlMapper.getSql(Statements.LIST_TABLE_CONSTRAINTS);
Map name2Constraint = new LinkedHashMap<>();
jdbcOperations.query(sql, new Object[] {schemaName, tableName}, (rs, num) -> {
String constraintName = rs.getString(OracleConstants.CONS_NAME);
if (!name2Constraint.containsKey(constraintName)) {
int currentPosition = name2Constraint.size();
name2Constraint.put(constraintName, createConstraintByResultSet(rs, currentPosition + 1));
} else {
name2Constraint.get(constraintName).getColumnNames()
.add(rs.getString("COLUMN_NAME"));
name2Constraint.get(constraintName).getReferenceColumnNames()
.add(rs.getString(OracleConstants.CONS_R_COLUMN_NAME));
}
return constraintName;
});
filterConstraintColumns(new ArrayList<>(name2Constraint.values()));
return new ArrayList<>(name2Constraint.values());
}
/**
* 三表联查的结果 ColumnNames 和 RefColumnNames 可能存在 NULL 或者重复值,这里做一个过滤
*/
protected void filterConstraintColumns(List constraints) {
for (DBTableConstraint constraint : constraints) {
if (Objects.nonNull(constraint.getReferenceColumnNames())) {
constraint.setReferenceColumnNames(constraint.getReferenceColumnNames().stream()
.filter(Objects::nonNull).distinct().collect(Collectors.toList()));
}
if (Objects.nonNull(constraint.getColumnNames())) {
constraint.setColumnNames(constraint.getColumnNames().stream().filter(Objects::nonNull).distinct()
.collect(Collectors.toList()));
}
}
}
protected DBTableConstraint createConstraintByResultSet(ResultSet rs, int num) throws SQLException {
DBTableConstraint constraint = new DBTableConstraint();
constraint.setName(rs.getString(OracleConstants.CONS_NAME));
constraint.setOrdinalPosition(num);
constraint.setSchemaName(rs.getString(OracleConstants.CONS_OWNER));
constraint.setTableName(rs.getString(OracleConstants.COL_TABLE_NAME));
constraint.setOwner(rs.getString(OracleConstants.CONS_OWNER));
constraint.setValidate("VALIDATED".equalsIgnoreCase(rs.getString(OracleConstants.CONS_VALIDATED)));
constraint.setType(DBConstraintType.fromValue(rs.getString(OracleConstants.CONS_TYPE)));
constraint.setEnabled("ENABLED".equalsIgnoreCase(rs.getString("STATUS")));
String deferrable = rs.getString(OracleConstants.CONS_DEFERRABLE);
if (StringUtils.equalsIgnoreCase(deferrable, "DEFERRABLE")) {
constraint.setDeferability(DBConstraintDeferability.fromString(rs.getString("DEFERRED")));
} else {
constraint.setDeferability(DBConstraintDeferability.NOT_DEFERRABLE);
}
List columnNames = new ArrayList<>();
columnNames.add(rs.getString("COLUMN_NAME"));
constraint.setColumnNames(columnNames);
List refColumnNames = new ArrayList<>();
constraint.setReferenceColumnNames(refColumnNames);
if (DBConstraintType.FOREIGN_KEY == constraint.getType()) {
constraint.setReferenceTableName(rs.getString(OracleConstants.CONS_R_TABLE_NAME));
constraint.setReferenceSchemaName(rs.getString(OracleConstants.CONS_R_OWNER));
refColumnNames.add(rs.getString(OracleConstants.CONS_R_COLUMN_NAME));
constraint.setOnDeleteRule(
DBForeignKeyModifyRule.fromValue(rs.getString(OracleConstants.CONS_DELETE_RULE)));
}
constraint.setCheckClause(rs.getString("SEARCH_CONDITION"));
return constraint;
}
@Override
public DBTablePartition getPartition(String schemaName, String tableName) {
DBTablePartition partition = new DBTablePartition();
partition.setSchemaName(schemaName);
partition.setTableName(tableName);
partition.setPartitionOption(obtainPartitionOption(schemaName, tableName));
partition.setPartitionDefinitions(
obtainPartitionDefinition(schemaName, tableName, partition.getPartitionOption()));
if (CollectionUtils.isNotEmpty(partition.getPartitionDefinitions())) {
partition.getPartitionOption()
.setPartitionsNum(partition.getPartitionDefinitions().size());
}
return partition;
}
@Override
public Map listTablePartitions(@NonNull String schemaName, List tableNames) {
List