net.java.ao.db.OracleDatabaseProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activeobjects-core Show documentation
Show all versions of activeobjects-core Show documentation
This is the core library for Active Objects. It is generic and can be embedded in any environment.
As such it is generic and won't contain all connection pooling, etc.
/*
* Copyright 2007 Daniel Spiewak
*
* 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 net.java.ao.db;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.java.ao.Common;
import net.java.ao.DBParam;
import net.java.ao.DatabaseProvider;
import net.java.ao.DisposableDataSource;
import net.java.ao.EntityManager;
import net.java.ao.Query;
import net.java.ao.RawEntity;
import net.java.ao.schema.Case;
import net.java.ao.schema.IndexNameConverter;
import net.java.ao.schema.NameConverters;
import net.java.ao.schema.TableNameConverter;
import net.java.ao.schema.UniqueNameConverter;
import net.java.ao.schema.ddl.DDLField;
import net.java.ao.schema.ddl.DDLForeignKey;
import net.java.ao.schema.ddl.DDLIndex;
import net.java.ao.schema.ddl.DDLTable;
import net.java.ao.schema.ddl.SQLAction;
import net.java.ao.types.TypeInfo;
import net.java.ao.types.TypeManager;
import net.java.ao.types.TypeQualifiers;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static net.java.ao.sql.SqlUtils.closeQuietly;
/**
* @author Daniel Spiewak
*/
public final class OracleDatabaseProvider extends DatabaseProvider {
private static final int ORA_04080_TRIGGER_DOES_NOT_EXIST = 4080;
private static final int ORA_02289_SEQUENCE_DOES_NOT_EXIST = 2289;
public OracleDatabaseProvider(DisposableDataSource dataSource) {
this(dataSource, null);
}
public OracleDatabaseProvider(DisposableDataSource dataSource, String schema) {
super(dataSource, schema, TypeManager.oracle());
}
@Override
public String renderMetadataQuery(final String tableName) {
return "SELECT * FROM (SELECT * FROM " + withSchema(tableName) + ") WHERE ROWNUM <= 1";
}
@Override
public String getSchema() {
return isSchemaNotEmpty() ? Case.UPPER.apply(super.getSchema()) : null;
}
@Override
public ResultSet getTables(Connection conn) throws SQLException {
final DatabaseMetaData metaData = conn.getMetaData();
final String schemaPattern = isSchemaNotEmpty() ? getSchema() : metaData.getUserName();
return metaData.getTables(null, schemaPattern, "%", new String[]{"TABLE"});
}
/**
* Statement can't be closed locally as this would also close the result set.
* Suppressing SonarQube warning S2095 as the result set and statement are closed in a local calling method.
*
* Alternative approach would be to redesign the API to return beans instead of live ResultSet (AO-3611).
*/
@Override
@SuppressWarnings("java:S2095")
public ResultSet getSequences(Connection conn) throws SQLException {
final DatabaseMetaData metaData = conn.getMetaData();
final String schemaPattern = isSchemaNotEmpty() ? getSchema() : metaData.getUserName();
final String sql = "SELECT NULL AS table_cat, o.owner AS table_schem, o.object_name AS table_name, o.object_type AS table_type, NULL AS remarks FROM all_objects o WHERE o.owner = ? AND o.object_type = 'SEQUENCE'";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setString(1, schemaPattern);
return statement.executeQuery();
}
@Override
public ResultSet getIndexes(Connection conn, String tableName) throws SQLException {
final DatabaseMetaData metaData = conn.getMetaData();
final String schemaPattern = isSchemaNotEmpty() ? getSchema() : metaData.getUserName();
return conn.getMetaData().getIndexInfo(null, schemaPattern, tableName, false, true);
}
@Override
public ResultSet getImportedKeys(Connection connection, String tableName) throws SQLException {
final DatabaseMetaData metaData = connection.getMetaData();
final String schemaPattern = isSchemaNotEmpty() ? getSchema() : metaData.getUserName();
return metaData.getImportedKeys(null, schemaPattern, tableName);
}
@Override
protected String renderQuerySelect(final Query query, final TableNameConverter converter, final boolean count) {
StringBuilder sql = new StringBuilder();
// see http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html
if (Query.QueryType.SELECT.equals(query.getType())) {
int offset = query.getOffset();
int limit = query.getLimit();
if (offset > 0) {
sql.append("SELECT * FROM ( SELECT QUERY_INNER.*, ROWNUM ROWNUM_INNER FROM ( ");
} else if (limit >= 0) {
sql.append("SELECT * FROM ( ");
}
}
sql.append(super.renderQuerySelect(query, converter, count));
return sql.toString();
}
@Override
protected String renderQueryLimit(Query query) {
StringBuilder sql = new StringBuilder();
if (Query.QueryType.SELECT.equals(query.getType())) {
int offset = query.getOffset();
int limit = query.getLimit();
if (offset > 0 && limit >= 0) {
sql.append(" ) QUERY_INNER WHERE ROWNUM <= ");
sql.append(offset + limit);
sql.append(" ) WHERE ROWNUM_INNER > ");
sql.append(offset);
} else if (offset > 0) {
sql.append(" ) QUERY_INNER ) WHERE ROWNUM_INNER > ");
sql.append(offset);
} else if (limit >= 0) {
sql.append(" ) WHERE ROWNUM <= ");
sql.append(limit);
}
}
return sql.toString();
}
@Override
protected String renderAutoIncrement() {
return "";
}
@Override
public Object parseValue(int type, String value) {
if (value == null || value.equals("") || value.equals("NULL")) {
return null;
}
switch (type) {
case Types.VARCHAR:
case Types.TIMESTAMP:
Matcher matcher = Pattern.compile("'(.*)'.*").matcher(value);
if (matcher.find()) {
value = matcher.group(1);
}
break;
case Types.INTEGER:
case Types.DOUBLE:
case Types.BIGINT:
case Types.BOOLEAN:
//AO-3463: in case if column definition contains `DEFAULT value NOT NULL` Oracle returns value with an extra space in the end.
//In such case we need to trim the value
value = value.trim();
break;
}
return super.parseValue(type, value);
}
@Override
protected String renderUnique(UniqueNameConverter uniqueNameConverter, DDLTable table, DDLField field) {
return "CONSTRAINT " + uniqueNameConverter.getName(table.getName(), field.getName()) + " UNIQUE";
}
@Override
protected String getDateFormat() {
return "dd-MMM-yy hh:mm:ss.SSS a";
}
@Override
protected SQLAction renderAlterTableAddColumnStatement(NameConverters nameConverters, DDLTable table, DDLField field) {
String addStmt = "ALTER TABLE " + withSchema(table.getName()) + " ADD (" + renderField(nameConverters, table, field, new RenderFieldOptions(true, true, true)) + ")";
return SQLAction.of(addStmt);
}
@Override
protected Iterable renderAlterTableChangeColumn(NameConverters nameConverters, DDLTable table, DDLField oldField, DDLField field) {
final UniqueNameConverter uniqueNameConverter = nameConverters.getUniqueNameConverter();
final ImmutableList.Builder back = ImmutableList.builder();
if (!oldField.getType().equals(field.getType())) {
if (field.getType().getSchemaProperties().getSqlTypeName().equals("CLOB") || oldField.getType().getSchemaProperties().getSqlTypeName().equals("CLOB")) {
if (!TypeQualifiers.areCompatible(oldField.getType().getQualifiers(), field.getType().getQualifiers())) {
final String fieldName = processID(field.getName());
final String tempColName = processID(getTempColumnName(field.getName()));
String tempColType;
if (field.getType().getSchemaProperties().getSqlTypeName().equals("CLOB")) {
tempColType = "CLOB";
} else {
final int stringLength = field.getType().getQualifiers().getStringLength();
tempColType = "VARCHAR(" + stringLength + ")";
}
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" ADD ").append(tempColName).append(" ").append(tempColType)));
back.add(SQLAction.of(new StringBuilder().append("UPDATE ").append(withSchema(table.getName())).append(" SET ").append(tempColName).append(" = ").append(fieldName)));
back.add(SQLAction.of("SAVEPOINT values_copied"));
back.addAll(renderDropColumnActions(nameConverters, table, field));
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" RENAME COLUMN ").append(tempColName).append(" TO ").append(fieldName)));
}
} else {
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" MODIFY (").append(processID(field.getName())).append(" ").append(renderFieldType(field)).append(")")));
}
}
if (oldField.isNotNull() && !field.isNotNull()) {
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" MODIFY (").append(processID(field.getName())).append(" NULL)")));
}
if (!oldField.isNotNull() && field.isNotNull()) {
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" MODIFY (").append(processID(field.getName())).append(" NOT NULL)")));
}
if (!Objects.equal(oldField.getDefaultValue(), field.getDefaultValue())) {
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" MODIFY (").append(processID(field.getName())).append(" DEFAULT ").append(renderValue(field.getDefaultValue())).append(")")));
}
if (oldField.isUnique() && !field.isUnique()) {
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" DROP CONSTRAINT ").append(uniqueNameConverter.getName(table.getName(), field.getName()))));
}
if (!oldField.isUnique() && field.isUnique()) {
back.add(SQLAction.of(new StringBuilder().append("ALTER TABLE ").append(withSchema(table.getName())).append(" ADD CONSTRAINT ").append(uniqueNameConverter.getName(table.getName(), field.getName())).append(" UNIQUE (").append(processID(field.getName())).append(")")));
}
return back.build();
}
@Override
protected SQLAction renderAlterTableDropKey(DDLForeignKey key) {
StringBuilder back = new StringBuilder("ALTER TABLE ");
back.append(withSchema(key.getDomesticTable())).append(" DROP CONSTRAINT ").append(processID(key.getFKName()));
return SQLAction.of(back);
}
@Override
protected SQLAction renderDropIndex(IndexNameConverter indexNameConverter, DDLIndex index) {
final String name = index.getIndexName();
return SQLAction.of(new StringBuilder().append("DROP INDEX ").append(withSchema(name)));
}
@Override
protected SQLAction renderDropTableStatement(DDLTable table) {
return SQLAction.of("DROP TABLE " + withSchema(table.getName()) + " PURGE");
}
@Override
public void handleUpdateError(String sql, SQLException e) throws SQLException {
if (isDropTrigger(sql, e)
|| isDropSequence(sql, e)) {
logger.debug("Ignoring non-existant trigger for SQL <" + sql + ">", e);
return;
}
super.handleUpdateError(sql, e);
}
private boolean isDropTrigger(String sql, SQLException e) {
return e.getErrorCode() == ORA_04080_TRIGGER_DOES_NOT_EXIST && sql.startsWith("DROP");
}
private boolean isDropSequence(String sql, SQLException e) {
return e.getErrorCode() == ORA_02289_SEQUENCE_DOES_NOT_EXIST && sql.startsWith("DROP");
}
@Override
protected , K> K executeInsertReturningKey(EntityManager manager, Connection conn,
Class entityType, Class pkType,
String pkField, String sql, DBParam... params) throws SQLException {
PreparedStatement stmt = null;
ResultSet res = null;
try {
onSql(sql);
stmt = conn.prepareStatement(sql, new String[]{pkField});
K back = (K) setParameters(manager, stmt, params, pkField);
stmt.executeUpdate();
if (back == null) {
res = stmt.getGeneratedKeys();
if (res.next()) {
back = typeManager.getType(pkType).getLogicalType().pullFromDatabase(null, res, pkType, 1);
}
}
return back;
} finally {
closeQuietly(res);
closeQuietly(stmt);
}
}
private Object setParameters(EntityManager manager, PreparedStatement stmt, DBParam[] params, String pkField) throws SQLException {
Object back = null;
int i = 0;
for (; i < params.length; i++) {
Object value = params[i].getValue();
if (value instanceof RawEntity>) {
value = Common.getPrimaryKeyValue((RawEntity>) value);
}
if (params[i].getField().equalsIgnoreCase(pkField)) {
back = value;
}
if (value == null) {
putNull(stmt, i + 1);
} else {
TypeInfo