/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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 org.jkiss.dbeaver.ext.generic.model;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.ext.generic.GenericConstants;
import org.jkiss.dbeaver.ext.generic.model.meta.GenericMetaObject;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
import org.jkiss.dbeaver.model.DBPRefreshableObject;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCConstants;
import org.jkiss.dbeaver.model.impl.struct.AbstractProcedure;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.DBPUniqueObject;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.rdb.DBSProcedureParameterKind;
import org.jkiss.dbeaver.model.struct.rdb.DBSProcedureType;
import org.jkiss.utils.CommonUtils;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* GenericProcedure
*/
public class GenericProcedure extends AbstractProcedure<GenericDataSource, GenericStructContainer> implements GenericScriptObject, DBPUniqueObject, DBPRefreshableObject
{
private static final Pattern PATTERN_COL_NAME_NUMERIC = Pattern.compile("\\$?([0-9]+)");
private String specificName;
private DBSProcedureType procedureType;
private List<GenericProcedureParameter> columns;
private String source;
private GenericFunctionResultType functionResultType;
public GenericProcedure(
GenericStructContainer container,
String procedureName,
String specificName,
String description,
DBSProcedureType procedureType,
GenericFunctionResultType functionResultType)
{
super(container, true, procedureName, description);
this.procedureType = procedureType;
this.functionResultType = functionResultType;
this.specificName = specificName;
}
@Property(viewable = true, order = 3)
public GenericCatalog getCatalog()
{
return getContainer().getCatalog();
}
@Property(viewable = true, order = 4)
public GenericSchema getSchema()
{
return getContainer().getSchema();
}
@Property(viewable = true, order = 5)
public GenericPackage getPackage()
{
return getContainer() instanceof GenericPackage ? (GenericPackage) getContainer() : null;
}
@Override
@Property(viewable = true, order = 6)
public DBSProcedureType getProcedureType()
{
return procedureType;
}
@Property(viewable = true, order = 7)
public GenericFunctionResultType getFunctionResultType() {
return functionResultType;
}
@Override
public Collection<GenericProcedureParameter> getParameters(DBRProgressMonitor monitor)
throws DBException
{
if (columns == null) {
loadProcedureColumns(monitor);
}
return columns;
}
private void loadProcedureColumns(DBRProgressMonitor monitor) throws DBException
{
Collection<? extends GenericProcedure> procedures = getContainer().getProcedures(monitor, getName());
if (procedures == null || !procedures.contains(this)) {
throw new DBException("Internal error - cannot read columns for procedure '" + getName() + "' because its not found in container");
}
Iterator<? extends GenericProcedure> procIter = procedures.iterator();
GenericProcedure procedure = null;
final GenericMetaObject pcObject = getDataSource().getMetaObject(GenericConstants.OBJECT_PROCEDURE_COLUMN);
try (JDBCSession session = DBUtils.openMetaSession(monitor, getDataSource(), "Load procedure columns")) {
final JDBCResultSet dbResult;
if (functionResultType == null) {
dbResult = session.getMetaData().getProcedureColumns(
getCatalog() == null ?
this.getPackage() == null || !this.getPackage().isNameFromCatalog() ?
null :
this.getPackage().getName() :
getCatalog().getName(),
getSchema() == null ? null : getSchema().getName(),
getName(),
getDataSource().getAllObjectsPattern()
);
} else {
dbResult = session.getMetaData().getFunctionColumns(
getCatalog() == null ? null : getCatalog().getName(),
getSchema() == null ? null : getSchema().getName(),
getName(),
getDataSource().getAllObjectsPattern()
);
}
try {
int previousPosition = -1;
while (dbResult.next()) {
String columnName = GenericUtils.safeGetString(pcObject, dbResult, JDBCConstants.COLUMN_NAME);
int columnTypeNum = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.COLUMN_TYPE);
int valueType = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.DATA_TYPE);
String typeName = GenericUtils.safeGetString(pcObject, dbResult, JDBCConstants.TYPE_NAME);
int columnSize = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.LENGTH);
boolean notNull = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.NULLABLE) == DatabaseMetaData.procedureNoNulls;
int scale = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.SCALE);
int precision = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.PRECISION);
//int radix = GenericUtils.safeGetInt(dbResult, JDBCConstants.RADIX);
String remarks = GenericUtils.safeGetString(pcObject, dbResult, JDBCConstants.REMARKS);
int position = GenericUtils.safeGetInt(pcObject, dbResult, JDBCConstants.ORDINAL_POSITION);
DBSProcedureParameterKind parameterType;
if (functionResultType == null) {
switch (columnTypeNum) {
case DatabaseMetaData.procedureColumnIn:
parameterType = DBSProcedureParameterKind.IN;
break;
case DatabaseMetaData.procedureColumnInOut:
parameterType = DBSProcedureParameterKind.INOUT;
break;
case DatabaseMetaData.procedureColumnOut:
parameterType = DBSProcedureParameterKind.OUT;
break;
case DatabaseMetaData.procedureColumnReturn:
parameterType = DBSProcedureParameterKind.RETURN;
break;
case DatabaseMetaData.procedureColumnResult:
parameterType = DBSProcedureParameterKind.RESULTSET;
break;
default:
parameterType = DBSProcedureParameterKind.UNKNOWN;
break;
}
} else {
switch (columnTypeNum) {
case DatabaseMetaData.functionColumnIn:
parameterType = DBSProcedureParameterKind.IN;
break;
case DatabaseMetaData.functionColumnInOut:
parameterType = DBSProcedureParameterKind.INOUT;
break;
case DatabaseMetaData.functionColumnOut:
parameterType = DBSProcedureParameterKind.OUT;
break;
case DatabaseMetaData.functionReturn:
parameterType = DBSProcedureParameterKind.RETURN;
break;
case DatabaseMetaData.functionColumnResult:
parameterType = DBSProcedureParameterKind.RESULTSET;
break;
default:
parameterType = DBSProcedureParameterKind.UNKNOWN;
break;
}
}
if (CommonUtils.isEmpty(columnName) && parameterType == DBSProcedureParameterKind.RETURN) {
columnName = "RETURN";
}
if (position == 0) {
// Some drivers do not return ordinal position (PostgreSQL) but
// position is contained in column name
Matcher numberMatcher = PATTERN_COL_NAME_NUMERIC.matcher(columnName);
if (numberMatcher.matches()) {
position = Integer.parseInt(numberMatcher.group(1));
}
}
if (procedure == null || (previousPosition >= 0 && position <= previousPosition && procIter.hasNext())) {
procedure = procIter.next();
}
GenericProcedureParameter column = new GenericProcedureParameter(
procedure,
columnName,
typeName,
valueType,
position,
columnSize,
scale, precision, notNull,
remarks,
parameterType);
procedure.addColumn(column);
previousPosition = position;
}
} finally {
dbResult.close();
}
} catch (SQLException e) {
throw new DBException(e, getDataSource());
}
}
private void addColumn(GenericProcedureParameter column)
{
if (this.columns == null) {
this.columns = new ArrayList<>();
}
this.columns.add(column);
}
@NotNull
@Override
public String getFullyQualifiedName(DBPEvaluationContext context)
{
return DBUtils.getFullQualifiedName(getDataSource(),
getCatalog(),
getSchema(),
this);
}
@NotNull
@Override
public String getUniqueName()
{
return CommonUtils.isEmpty(specificName) ? getName() : specificName;
}
@Override
public String getObjectDefinitionText(DBRProgressMonitor monitor) throws DBException {
if (source == null) {
source = getDataSource().getMetaModel().getProcedureDDL(monitor, this);
}
return source;
}
@Override
public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException {
source = null;
return this;
}
}