/* * 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.postgresql.model; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ext.postgresql.PostgreConstants; import org.jkiss.dbeaver.ext.postgresql.PostgreUtils; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; 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.struct.DBSObject; import org.jkiss.dbeaver.model.struct.rdb.DBSProcedureParameterKind; import org.jkiss.dbeaver.model.struct.rdb.DBSProcedureType; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * PostgreProcedure */ public class PostgreProcedure extends AbstractProcedure<PostgreDataSource, PostgreSchema> implements PostgreObject, PostgreScriptObject, DBPUniqueObject, DBPOverloadedObject, DBPRefreshableObject { private static final Log log = Log.getLog(PostgreProcedure.class); private static final String CAT_FLAGS = "Flags"; private static final String CAT_PROPS = "Properties"; private static final String CAT_STATS = "Statistics"; public enum ProcedureVolatile { i, s, v, } public enum ArgumentMode { i(DBSProcedureParameterKind.IN), o(DBSProcedureParameterKind.OUT), b(DBSProcedureParameterKind.INOUT), v(DBSProcedureParameterKind.RESULTSET), t(DBSProcedureParameterKind.TABLE), u(DBSProcedureParameterKind.UNKNOWN); private final DBSProcedureParameterKind parameterKind; ArgumentMode(DBSProcedureParameterKind parameterKind) { this.parameterKind = parameterKind; } public DBSProcedureParameterKind getParameterKind() { return parameterKind; } } private long oid; private String procSrc; private String body; private long ownerId; private long languageId; private float execCost; private float estRows; private PostgreDataType varArrayType; private String procTransform; private boolean isAggregate; private boolean isWindow; private boolean isSecurityDefiner; private boolean leakproof; private boolean isStrict; private boolean returnsSet; private ProcedureVolatile procVolatile; private PostgreDataType returnType; private Object[] argDefaults; private int[] transformTypes; private String[] config; private String overloadedName; private List<PostgreProcedureParameter> params = new ArrayList<>(); public PostgreProcedure(PostgreSchema schema) { super(schema, false); } public PostgreProcedure( PostgreSchema schema, ResultSet dbResult) { super(schema, true); loadInfo(dbResult); } private void loadInfo(ResultSet dbResult) { this.oid = JDBCUtils.safeGetLong(dbResult, "oid"); setName(JDBCUtils.safeGetString(dbResult, "proname")); this.ownerId = JDBCUtils.safeGetLong(dbResult, "proowner"); this.languageId = JDBCUtils.safeGetLong(dbResult, "prolang"); this.execCost = JDBCUtils.safeGetFloat(dbResult, "procost"); this.estRows = JDBCUtils.safeGetFloat(dbResult, "prorows"); Long[] allArgTypes = JDBCUtils.safeGetArray(dbResult, "proallargtypes"); String[] argNames = JDBCUtils.safeGetArray(dbResult, "proargnames"); if (!ArrayUtils.isEmpty(allArgTypes)) { String[] argModes = JDBCUtils.safeGetArray(dbResult, "proargmodes"); for (int i = 0; i < allArgTypes.length; i++) { Long paramType = allArgTypes[i]; final PostgreDataType dataType = container.getDatabase().getDataType(paramType.intValue()); if (dataType == null) { log.warn("Parameter data type [" + paramType + "] not found"); continue; } //String paramName = argNames == null || argNames.length < inArg String paramName = argNames == null || argNames.length < allArgTypes.length ? "$" + (i + 1) : argNames[i]; ArgumentMode mode = ArgumentMode.i; if (argModes != null && argModes.length == allArgTypes.length) { try { mode = ArgumentMode.valueOf(argModes[i]); } catch (IllegalArgumentException e) { log.debug(e); } } PostgreProcedureParameter param = new PostgreProcedureParameter( this, paramName, dataType, mode == null ? DBSProcedureParameterKind.IN : mode.getParameterKind(), i + 1); params.add(param); } } else { long[] inArgTypes = PostgreUtils.getIdVector(JDBCUtils.safeGetObject(dbResult, "proargtypes")); if (!ArrayUtils.isEmpty(inArgTypes)) { for (int i = 0; i < inArgTypes.length; i++) { Long paramType = inArgTypes[i]; final PostgreDataType dataType = container.getDatabase().getDataType(paramType.intValue()); if (dataType == null) { log.warn("Parameter data type [" + paramType + "] not found"); continue; } //String paramName = argNames == null || argNames.length < inArg //String paramName = "$" + (i + 1); String paramName = argNames == null || argNames.length < inArgTypes.length ? "$" + (i + 1) : argNames[i]; PostgreProcedureParameter param = new PostgreProcedureParameter(this, paramName, dataType, DBSProcedureParameterKind.IN, i + 1); params.add(param); } } } this.overloadedName = makeOverloadedName(false); { final long varTypeId = JDBCUtils.safeGetLong(dbResult, "provariadic"); if (varTypeId != 0) { varArrayType = container.getDatabase().getDataType(varTypeId); } } this.procTransform = JDBCUtils.safeGetString(dbResult, "protransform"); this.isAggregate = JDBCUtils.safeGetBoolean(dbResult, "proisagg"); this.isWindow = JDBCUtils.safeGetBoolean(dbResult, "proiswindow"); this.isSecurityDefiner = JDBCUtils.safeGetBoolean(dbResult, "prosecdef"); this.leakproof = JDBCUtils.safeGetBoolean(dbResult, "proleakproof"); this.isStrict = JDBCUtils.safeGetBoolean(dbResult, "proisstrict"); this.returnsSet = JDBCUtils.safeGetBoolean(dbResult, "proretset"); try { this.procVolatile = ProcedureVolatile.valueOf(JDBCUtils.safeGetString(dbResult, "provolatile")); } catch (IllegalArgumentException e) { log.debug(e); } { final long retTypeId = JDBCUtils.safeGetLong(dbResult, "prorettype"); if (retTypeId != 0) { returnType = container.getDatabase().getDataType(retTypeId); } } this.procSrc = JDBCUtils.safeGetString(dbResult, "prosrc"); this.description = JDBCUtils.safeGetString(dbResult, "description"); } @NotNull @Override public PostgreDatabase getDatabase() { return container.getDatabase(); } @Override public long getObjectId() { return oid; } @Override public DBSProcedureType getProcedureType() { return DBSProcedureType.PROCEDURE; } @Property(hidden = true, editable = true, updatable = true, order = -1) public String getBody() { return body; } @Override public Collection<PostgreProcedureParameter> getParameters(DBRProgressMonitor monitor) throws DBException { return params; } @NotNull @Override public String getFullyQualifiedName(DBPEvaluationContext context) { return DBUtils.getFullQualifiedName(getDataSource(), getContainer(), this); } @NotNull @Override public String getOverloadedName() { return overloadedName; } @NotNull @Override public String getUniqueName() { return overloadedName; } @Override @Property(hidden = true, editable = true, updatable = true, order = -1) public String getObjectDefinitionText(DBRProgressMonitor monitor) throws DBException { if (body == null) { if (oid == 0 || isAggregate) { // No OID so let's use old (bad) way body = this.procSrc; } else { try (JDBCSession session = DBUtils.openMetaSession(monitor, getDataSource(), "Read procedure body")) { body = JDBCUtils.queryString(session, "SELECT pg_get_functiondef(" + getObjectId() + ")"); } catch (SQLException e) { if (!CommonUtils.isEmpty(this.procSrc)) { log.debug("Error reading procedure body", e); // At least we have it body = this.procSrc; } else { throw new DBException("Error reading procedure body", e); } } } } return body; } @Override public void setObjectDefinitionText(String sourceText) throws DBException { body = sourceText; } @Property(category = CAT_PROPS, order = 10) public PostgreRole getOwner(DBRProgressMonitor monitor) throws DBException { return PostgreUtils.getObjectById(monitor, container.getDatabase().roleCache, container.getDatabase(), ownerId); } @Property(category = CAT_PROPS, viewable = true, order = 11) public PostgreLanguage getLanguage(DBRProgressMonitor monitor) throws DBException { return PostgreUtils.getObjectById(monitor, container.getDatabase().languageCache, container.getDatabase(), languageId); } @Property(category = CAT_PROPS, viewable = true, order = 12) public PostgreDataType getReturnType() { return returnType; } @Property(category = CAT_PROPS, viewable = false, order = 13) public PostgreDataType getVarArrayType() { return varArrayType; } @Property(category = CAT_PROPS, viewable = false, order = 14) public String getProcTransform() { return procTransform; } @Property(category = CAT_STATS, viewable = false, order = 30) public float getExecCost() { return execCost; } @Property(category = CAT_STATS, viewable = false, order = 31) public float getEstRows() { return estRows; } @Property(category = CAT_FLAGS, viewable = true, order = 100) public boolean isAggregate() { return isAggregate; } @Property(category = CAT_FLAGS, viewable = true, order = 101) public boolean isWindow() { return isWindow; } @Property(category = CAT_FLAGS, viewable = true, order = 102) public boolean isSecurityDefiner() { return isSecurityDefiner; } @Property(category = CAT_FLAGS, viewable = true, order = 103) public boolean isLeakproof() { return leakproof; } @Property(category = CAT_FLAGS, viewable = true, order = 104) public boolean isStrict() { return isStrict; } @Property(category = CAT_FLAGS, viewable = true, order = 105) public boolean isReturnsSet() { return returnsSet; } @Property(category = CAT_FLAGS, viewable = true, order = 106) public ProcedureVolatile getProcVolatile() { return procVolatile; } private String makeOverloadedName(boolean quote) { String selfName = quote ? DBUtils.getQuotedIdentifier(this) : name; if (!CommonUtils.isEmpty(params)) { StringBuilder paramsSignature = new StringBuilder(64); paramsSignature.append("("); boolean hasParam = false; for (PostgreProcedureParameter param : params) { if (param.getParameterKind() == DBSProcedureParameterKind.OUT) { continue; } if (hasParam) paramsSignature.append(','); hasParam = true; final PostgreDataType dataType = param.getParameterType(); final PostgreSchema typeContainer = dataType.getParentObject(); if (typeContainer == null || typeContainer == getContainer() || typeContainer.getName().equals(PostgreConstants.CATALOG_SCHEMA_NAME)) { paramsSignature.append(dataType.getName()); } else { paramsSignature.append(dataType.getFullyQualifiedName(DBPEvaluationContext.DDL)); } } paramsSignature.append(")"); return selfName + paramsSignature.toString(); } else { return selfName + "()"; } } @Nullable @Override @Property(viewable = true, editable = true, updatable = true, order = 200) public String getDescription() { return super.getDescription(); } public String getFullQualifiedSignature() { return DBUtils.getQuotedIdentifier(getContainer()) + "." + makeOverloadedName(true); } public String getProcedureTypeName() { return isAggregate ? "AGGREGATE" : "FUNCTION"; } @Override public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException { body = null; return this; } @Override public String toString() { return overloadedName == null ? name : overloadedName; } }