<NME> PostgreDebugSession.java
<BEF> /*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2022 DBeaver Corp and others
 * Copyright (C) 2017-2018 Andrew Khitrin (ahitrin@gmail.com)
 * Copyright (C) 2017-2018 Alexander Fedorov (alexander.fedorov@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.debug.internal.impl;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.debug.*;
import org.jkiss.dbeaver.debug.core.DebugUtils;
import org.jkiss.dbeaver.debug.jdbc.DBGJDBCSession;
import org.jkiss.dbeaver.ext.postgresql.PostgreConstants;
import org.jkiss.dbeaver.ext.postgresql.debug.PostgreDebugConstants;
import org.jkiss.dbeaver.ext.postgresql.debug.core.PostgreSqlDebugCore;
import org.jkiss.dbeaver.ext.postgresql.model.PostgreDataSource;
import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedureParameter;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Typical scenario for debug session <br/>
 * <br/>
 * 0. create session (now it can only attached to target Procedure)<br/>
 * <br/>
 * 1. attach to target this method attaches to a debugging target and listening
 * on the given port - waiting for run procedure in other session(s) debugger
 * client should invoke this function after creation also created implicit
 * breakpoint for target procedure, after this call debug session in
 * <b>WAITING</b> state - isDone returns false and is isWaiting returns
 * true<br/>
 * <br/>
 * 2. when target procedure will called debug session implicit breakpoint will
 * be reached and session goes in state <b>READY</b> (isDone - true, isWaiting -
 * true) in this state possible to call getStack, getVariables, setVariables,
 * setBreakpoint or execStepXXX\continue<br/>
 * <br/>
 * 3. when execStepXXX or continue will called session goes in <b>WAITING</b>
 * state until next breakpoint or end of procedure will be reached <br/>
 */
public class PostgreDebugSession extends DBGJDBCSession {
    private final JDBCExecutionContext controllerConnection;

    private int functionOid = -1;
    private int sessionId = -1;
    private int localPortNumber = -1;

    private PostgreDebugAttachKind attachKind = PostgreDebugAttachKind.UNKNOWN;
    private DBGSessionInfo sessionInfo;

    private PostgreDebugBreakpointDescriptor bpGlobal;
    private volatile JDBCCallableStatement localStatement;

    private static final int LOCAL_WAIT = 50; // 0.5 sec

    private static final int LOCAL_TIMEOT = 1000 * LOCAL_WAIT; // 50 sec

    private static final String MAGIC_PORT = "PLDBGBREAK";

    private static final String SQL_CHECK_PLUGIN = "select 'Server version: ' || serverversionstr || '.\nProxy API version: ' ||  proxyapiver from pldbg_get_proxy_info()";

    private static final String SQL_ATTACH = "select pldbg_wait_for_target(?sessionid)";
    private static final String SQL_ATTACH_TO_PORT = "select pldbg_attach_to_port(?portnumber)";
    private static final String SQL_PREPARE_SLOT = " select pldbg_oid_debug(?objectid)";
    private static final String SQL_LISTEN = "select pldbg_create_listener() as sessionid";
    private static final String SQL_GET_SRC = "select pldbg_get_source(?sessionid,?oid)";
    private static final String SQL_GET_VARS = "select * from pldbg_get_variables(?sessionid)";
    private static final String SQL_SET_VAR = "select pldbg_deposit_value(?,?,?,?)";
    private static final String SQL_GET_STACK = "select * from pldbg_get_stack(?sessionid)";
    private static final String SQL_SELECT_FRAME = "select * from pldbg_select_frame(?sessionid,?frameno)";
    private static final String SQL_STEP_OVER = "select pldbg_step_over(?sessionid)";
    private static final String SQL_STEP_INTO = "select pldbg_step_into(?sessionid)";
    private static final String SQL_CONTINUE = "select pldbg_continue(?sessionid)";
    private static final String SQL_ABORT = "select pldbg_abort_target(?sessionid)";
    private static final String SQL_SET_GLOBAL_BREAKPOINT = "select pldbg_set_global_breakpoint(?sessionid, ?obj, ?line, ?target)";
    private static final String SQL_SET_BREAKPOINT = "select pldbg_set_breakpoint(?sessionid, ?obj, ?line)";
    private static final String SQL_DROP_BREAKPOINT = "select pldbg_drop_breakpoint(?sessionid, ?obj, ?line)";

    private static final String SQL_CURRENT_SESSION =
        "SELECT pid,usename,application_name,state,query\n" +
            "FROM pg_stat_activity WHERE pid = pg_backend_pid()"; //$NON-NLS-1$


    private static final Log log = Log.getLog(PostgreDebugSession.class);

    /**
     * Create session with two description after creation session need to be
     * attached to postgres procedure by attach method
     */
    PostgreDebugSession(DBRProgressMonitor monitor, DBGBaseController controller)
        throws DBGException {
        super(controller);
        log.debug("Creating controller session.");

        PostgreDataSource dataSource = (PostgreDataSource) controller.getDataSourceContainer().getDataSource();
        try {
            log.debug("Controller session creating.");
            PostgreDatabase instance;
            if (isGlobalSession(controller.getDebugConfiguration())) {
                instance = dataSource.getDefaultInstance();
            } else {
                PostgreProcedure function = PostgreSqlDebugCore.resolveFunction(monitor, controller.getDataSourceContainer(), controller.getDebugConfiguration());
                instance = function.getDatabase();
            }
            this.controllerConnection = (JDBCExecutionContext) instance.openIsolatedContext(monitor, "Debug controller session", null);

            log.debug("Debug controller session created.");
            JDBCDataSource src = this.controllerConnection.getDataSource();
            if (src instanceof PostgreDataSource) {
                PostgreDataSource pgSrc = (PostgreDataSource) src;
                log.debug(String.format("Active user %s", instance.getMetaContext().getActiveUser()));
                log.debug(String.format("Active schema %s", instance.getMetaContext().getDefaultSchema()));
                if (pgSrc.getInfo() instanceof JDBCDataSourceInfo) {
                    JDBCDataSourceInfo JDBCinfo = (JDBCDataSourceInfo) pgSrc.getInfo();
                    log.debug("------------DATABASE DRIVER INFO---------------");
                    log.debug(String.format("Database Product Name %s", JDBCinfo.getDatabaseProductName()));
                    log.debug(String.format("Database Product Version %s", JDBCinfo.getDatabaseProductVersion()));
                    log.debug(String.format("Database Version %s", JDBCinfo.getDatabaseVersion()));
                    log.debug(String.format("Driver Name %s", JDBCinfo.getDriverName()));
                    log.debug(String.format("Driver Version %s", JDBCinfo.getDriverVersion()));
                    log.debug("-----------------------------------------------");
                } else {
                    log.debug("No additional Driver info");
                }
            } else {
                log.debug("Unknown Driver version");
            }

        } catch (DBException e) {
            log.debug(String.format("Error creating debug session %s", e.getMessage()));
            throw new DBGException(e, dataSource);
        }
    }

    @Override
    public JDBCExecutionContext getControllerConnection() {
        return controllerConnection;
    }

    private PostgreDebugSessionInfo getSessionDescriptor(DBRProgressMonitor monitor, JDBCExecutionContext connection) throws DBGException {
        try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.UTIL, "Read session info")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(SQL_CURRENT_SESSION)) {
                    if (rs.next()) {
                        int pid = rs.getInt("pid");
                        String usename = rs.getString("usename");
                        String applicationName = rs.getString("application_name");
                        String state = rs.getString("state");
                        String query = rs.getString("query");
                        return new PostgreDebugSessionInfo(pid, usename, applicationName, state, query);
                    }
                    throw new DBGException("Error getting session");
                }
            }
        } catch (SQLException e) {
            throw new DBGException("SQL error", e);
        }
    }

    private boolean localPortRcv(SQLWarning warn) {

        if (warn != null) {

            String notice = warn.getMessage();

            log.debug("Start local port waiting....");

            while (notice != null) {

                if (notice.startsWith(MAGIC_PORT)) {

                    try {
                        localPortNumber = Integer.valueOf(notice.substring(MAGIC_PORT.length() + 1).trim());
                        log.debug(String.format("Catch local port number %d", localPortNumber));
                    } catch (Exception e) {
                        log.debug(String.format("Error catching local port number %s", e.getMessage()));
                        return false;
                    }

                    return true;
                }

                warn = warn.getNextWarning();

                notice = warn == null ? null : warn.getMessage();
                log.debug(String.format("Next warning %s", (notice == null ? "[NULL]" : notice)));

            }
        }

        return false;
    }

    private int attachToPort(DBRProgressMonitor monitor) throws DBGException {
        // Use controller connection
        String sql = SQL_ATTACH_TO_PORT.replaceAll("\\?portnumber", String.valueOf(localPortNumber));
        log.debug(String.format("Attach to local port number %d", localPortNumber));
        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Attach to port")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    if (rs.next()) {
                        int attResult = rs.getInt(1);
                        log.debug(String.format("Attached to local port %d", attResult));
                        return attResult;
                    }
                    log.debug("Error while attaching to port");
                    throw new DBGException("Error while attaching to port");
                }
            }
        } catch (SQLException e) {
            log.debug("Error while attaching to port");
            throw new DBGException("Error attaching to port", e);
        }
    }

    private String createSlot(DBRProgressMonitor monitor, JDBCExecutionContext connection, PostgreProcedure function) throws DBGException {

        String objId = String.valueOf(function.getObjectId());
        String sql = SQL_PREPARE_SLOT.replaceAll("\\?objectid", objId);
        log.debug(String.format("Create slot for object ID %s", objId));
        try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.UTIL, "Attach to port")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    if (!rs.next()) {
                        log.debug("Error creating target slot");
                        throw new DBGException("Error creating target slot");
                    }
                    String dbgOID = rs.getString(1);
                    log.debug(String.format("Create slot OID %s", dbgOID));
                    return dbgOID;
                }
            }
        } catch (SQLException e) {
            log.debug(String.format("Error creating target %s", e.getMessage()));
            throw new DBGException("Error creating target", e);
        }
    }

    /**
     * Wait for port number passed from main executed statement
     */
    private void waitPortNumber() throws DBGException {

        int totalWait = 0;
        boolean hasStatement = false;
        log.debug(String.format("Waiting for port number with timeout %d", LOCAL_TIMEOT));
        while (totalWait < LOCAL_TIMEOT) {
                    }
                    query.append(") }");
                    localStatement = session.prepareCall(query.toString());
                    for (int i = 0; i < parameters.size(); i++) {
                        String paramValue = paramValues.get(i);
                        if (CommonUtils.isEmpty(paramValue)) {
                            localStatement.setNull(i + 1, Types.VARCHAR);
                        } else {
                            localStatement.setString(i + 1, paramValue);
                        }
                    }
                    localStatement.execute();
                    // And Now His Watch Is Ended
                    fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));
                } catch (SQLException e) {
                    try {
                        if (localStatement != null) {
                            localStatement.close();
            } catch (SQLException | InterruptedException e) {
                log.debug(String.format("Error rcv port number %s", e.getMessage()));
                throw new DBGException("Error rcv port number", e);
                        log.error(e1);
                    }
                    fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));
                    String sqlState = e.getSQLState();
                    if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {
                        log.error(name, e);
                        return DebugUtils.newErrorStatus(name, e);
            log.debug(String.format("Unable to rcv port number %d", localPortNumber));
            throw new DBGException("Unable to rcv port number");
        }
    }

    protected void runLocalProc(JDBCExecutionContext connection, PostgreProcedure function, List<String> paramValues, String name) throws DBGException {
        List<PostgreProcedureParameter> parameters = function.getInputParameters();
        log.debug("Run local proc");
        if (parameters.size() != paramValues.size()) {
            String unmatched = "Parameter value count (" + paramValues.size() + ") doesn't match actual function parameters (" + parameters.size() + ")";
            log.debug(unmatched);
            throw new DBGException(unmatched);
        }
        Job job = new AbstractJob(name) {
            @Override
            protected IStatus run(DBRProgressMonitor monitor) {
                try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.USER, "Run SQL command")) {
                    try {
                        StringBuilder query = new StringBuilder();
                        query.append("{ CALL ").append(function.getFullyQualifiedName(DBPEvaluationContext.DML)).append("(");
                        for (int i = 0; i < parameters.size(); i++) {
                            if (i > 0) query.append(",");
                            String paramValue = paramValues.get(i);
                            query.append(paramValue);
                        }
                        query.append(") }");
                        log.debug(String.format("Prepared local call %s", query));
                        localStatement = session.prepareCall(query.toString());

/*
                        for (int i = 0; i < parameters.size(); i++) {
                            PostgreProcedureParameter parameter = parameters.get(i);
                            String paramValue = paramValues.get(i);
                            DBDValueHandler valueHandler = DBUtils.findValueHandler(session, parameter);
                            valueHandler.bindValueObject(session, localStatement, parameter, i, paramValue);
                        }
*/
                        localStatement.execute();
                        // And Now His Watch Is Ended
                        log.debug("Local statement executed (ANHWIE)");
                        fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));
                    } catch (Exception e) {
                        log.debug("Error execute local statement: " + e.getMessage());
                        String sqlState = e instanceof SQLException ? ((SQLException) e).getSQLState() : null;
                        if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {
                            log.error(name, e);
                            return DebugUtils.newErrorStatus(name, e);
                        }
                    } finally {
                        try {
                            if (localStatement != null) {
                                localStatement.close();
                                localStatement = null;
                            }
                        } catch (Exception e1) {
                            log.debug("Error clearing local statment");
                            log.error(e1);
                        }
                        connection.close();

                        fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));
                    }
                }
                log.debug("Local statement executed");
                return Status.OK_STATUS;
            }
        };
        job.schedule();
    }

    private void attachLocal(DBRProgressMonitor monitor, PostgreProcedure function, List<String> parameters) throws DBGException {

        try {
            JDBCExecutionContext connection = (JDBCExecutionContext) controllerConnection.getOwnerInstance().openIsolatedContext(monitor, "Debug process session", null);
            log.debug("Attaching locally....");
            this.sessionInfo = getSessionDescriptor(monitor, connection);

            createSlot(monitor, connection, function);

            String taskName = "PostgreSQL Debug - Local session " + sessionInfo.getID();

            runLocalProc(connection, function, parameters, taskName);

            waitPortNumber();

            sessionId = attachToPort(monitor);
            log.debug(String.format("Attached local session UD = %d", sessionId));
            getController().fireEvent(new DBGEvent(this, DBGEvent.SUSPEND, DBGEvent.MODEL_SPECIFIC));
        } catch (DBException e) {
            throw new DBGException("Error opening debug session", e);
        }

    }

    private void attachGlobal(DBRProgressMonitor monitor, int oid, int targetPID) throws DBGException {

        log.debug("Attaching globally....");

        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Attach global")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(SQL_LISTEN)) {

                    if (rs.next()) {
                        sessionId = rs.getInt("sessionid");
                        log.debug(String.format("Global session ID %d", sessionId));
                    } else {
                        log.debug("Unable to create debug instance");
                        throw new DBGException("Unable to create debug instance");
                    }

                }
            }
        } catch (SQLException e) {
            throw new DBGException("SQL error", e);
        }

        bpGlobal = new PostgreDebugBreakpointDescriptor(oid, -1);
        addBreakpoint(monitor, bpGlobal);
        log.debug("Global breakpoint added");

        String sessionParam = String.valueOf(getSessionId());
        String taskName = "PostgreSQL Debug - Global session " + sessionParam;
        String sql = SQL_ATTACH.replaceAll("\\?sessionid", sessionParam);
        DBGEvent begin = new DBGEvent(this, DBGEvent.RESUME, DBGEvent.MODEL_SPECIFIC);
        DBGEvent end = new DBGEvent(this, DBGEvent.SUSPEND, DBGEvent.BREAKPOINT);
        runAsync(sql, taskName, begin, end);
        log.debug("Global session started");
    }

    /**
     * This method attach debug session to debug object (procedure) and wait
     * forever while target or any (depend on targetPID) session will run target
     * procedure
     */
    public void attach(DBRProgressMonitor monitor, Map<String, Object> configuration) throws DBException {
        if (!checkDebugPlagin(monitor)) {
            throw new DBGException("PostgreSQL debug plugin is not installed on the server.\n" +
                "Refer to this WIKI article for installation instructions:\n" +
                "https://github.com/dbeaver/dbeaver/wiki/PGDebugger#installation");
        }

        log.debug("Attaching...");

        functionOid = CommonUtils.toInt(configuration.get(PostgreDebugConstants.ATTR_FUNCTION_OID));
        log.debug(String.format("Function OID %d", functionOid));

        boolean global = isGlobalSession(configuration);

        if (global) {
            int processId = CommonUtils.toInt(configuration.get(PostgreDebugConstants.ATTR_ATTACH_PROCESS));
            attachKind = PostgreDebugAttachKind.GLOBAL;
            attachGlobal(monitor, functionOid, processId);
            log.debug("Global attached");
        } else {
            attachKind = PostgreDebugAttachKind.LOCAL;
            PostgreProcedure function = PostgreSqlDebugCore.resolveFunction(monitor, controllerConnection.getDataSource().getContainer(), configuration);
            List<String> parameterValues = (List<String>) configuration.get(PostgreDebugConstants.ATTR_FUNCTION_PARAMETERS);

            attachLocal(monitor, function, parameterValues);
            log.debug("Local attached");
        }
    }

    private boolean isGlobalSession(Map<String, Object> configuration) {
        return PostgreDebugConstants.ATTACH_KIND_GLOBAL.equals(String.valueOf(configuration.get(PostgreDebugConstants.ATTR_ATTACH_KIND)));
    }

    private boolean checkDebugPlagin(DBRProgressMonitor monitor) {
        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Check debug plugin installation")) {
            String version = JDBCUtils.executeQuery(session, SQL_CHECK_PLUGIN);
            log.debug("Debug plugin is installed:\n" + version);
            return true;
        } catch (Exception e) {
            log.debug("Debug plugin not installed: " + e.getMessage());
            return false;
        }
    }

    private void detachLocal(DBRProgressMonitor monitor, JDBCExecutionContext connection) throws DBGException {
        if (localStatement == null) {
            // Execution already terminated
            return;
        }
        try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.UTIL, "Abort local session")) {
            JDBCUtils.executeQuery(session, composeAbortCommand());
            log.debug("Local detached");
        } catch (SQLException e) {
            log.debug("Unable to abort local session");
            log.error("Unable to abort local target", e);
        }
    }

    private void detachGlobal(DBRProgressMonitor monitor) throws DBGException {
        removeBreakpoint(monitor, bpGlobal);

        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Abort global session")) {
            JDBCUtils.executeQuery(session, composeAbortCommand());
            log.debug("Global deattached");
        } catch (SQLException e) {
            log.error("Unable to abort global target", e);
        }
    }

    protected void doDetach(DBRProgressMonitor monitor) throws DBGException {
        switch (attachKind) {
            case GLOBAL:
                detachGlobal(monitor);
                break;
            case LOCAL:
                detachLocal(monitor, getControllerConnection());
                break;
            default:
                break;
        }
    }

    @Override
    public DBGSessionInfo getSessionInfo() {
        return sessionInfo;
    }

    protected String composeAddBreakpointCommand(DBGBreakpointDescriptor descriptor) {
        PostgreDebugBreakpointDescriptor bp = (PostgreDebugBreakpointDescriptor) descriptor;
        String sqlPattern = attachKind == PostgreDebugAttachKind.GLOBAL ? SQL_SET_GLOBAL_BREAKPOINT : SQL_SET_BREAKPOINT;
        long lineNumber = bp.isOnStart() ? -1 : bp.getLineNo();
        log.debug(String.format("Adding breakpoint to line #%d", lineNumber));
        return sqlPattern.replaceAll("\\?sessionid", String.valueOf(getSessionId()))
            .replaceAll("\\?obj", String.valueOf(functionOid))
            .replaceAll("\\?line", String.valueOf(lineNumber))
            .replaceAll("\\?target", bp.isAll() ? "null" : String.valueOf(bp.getTargetId()));
    }

    protected String composeRemoveBreakpointCommand(DBGBreakpointDescriptor breakpointDescriptor) {
        PostgreDebugBreakpointDescriptor bp = (PostgreDebugBreakpointDescriptor) breakpointDescriptor;
        return SQL_DROP_BREAKPOINT.replaceAll("\\?sessionid", String.valueOf(getSessionId()))
            .replaceAll("\\?obj", String.valueOf(functionOid))
            .replaceAll("\\?line", bp.isOnStart() ? "-1" : String.valueOf(bp.getLineNo()));
    }

    @Override
    public void execContinue() throws DBGException {
        log.debug("try continue for");
        execStep(SQL_CONTINUE, " continue for ", DBGEvent.RESUME);
        log.debug("continue for realized");
    }

    @Override
    public void execStepInto() throws DBGException {
        log.debug("try step into");
        execStep(SQL_STEP_INTO, " step into for ", DBGEvent.STEP_INTO);
        log.debug("step into realized");
    }

    @Override
    public void execStepOver() throws DBGException {
        log.debug("try step over");
        execStep(SQL_STEP_OVER, " step over for ", DBGEvent.STEP_OVER);
        log.debug("step over realized");
    }

    @Override
    public void execStepReturn() throws DBGException {
        log.debug("Exec return not implemented");
        throw new DBGException("Exec return not implemented");
    }

    @Override
    public void resume() throws DBGException {
        log.debug("try continue execution");
        execContinue();
        log.debug("continue execution realized");
    }

    @Override
    public void suspend() throws DBGException {
        throw new DBGException("Suspend not implemented");
    }

    /**
     * Execute step SQL command asynchronously, set debug session name to
     * [sessionID] name [managerPID]
     *
     * @param commandPattern - SQL command for execute step
     * @param nameParameter  - session 'name' part
     */
    public void execStep(String commandPattern, String nameParameter, int eventDetail) throws DBGException {
        String sql = commandPattern.replaceAll("\\?sessionid", String.valueOf(sessionId));
        String taskName = String.valueOf(sessionId) + nameParameter + sessionInfo.getID();
        DBGEvent begin = new DBGEvent(this, DBGEvent.RESUME, eventDetail);
        DBGEvent end = new DBGEvent(this, DBGEvent.SUSPEND, eventDetail);
        runAsync(sql, taskName, begin, end);
    }

    protected String composeAbortCommand() {
        return SQL_ABORT.replaceAll("\\?sessionid", String.valueOf(sessionId));
    }

    @Override
    public List<DBGVariable<?>> getVariables(DBGStackFrame stack) throws DBGException {
        if (stack != null) {
            selectFrame(stack.getLevel());
        }

        log.debug("Get vars values");
        List<DBGVariable<?>> vars = new ArrayList<>();

        String sql = SQL_GET_VARS.replaceAll("\\?sessionid", String.valueOf(sessionId));
        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Read debug variables")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(sql)) {

                    while (rs.next()) {
                        String name = rs.getString("name");
                        String varclass = rs.getString("varclass");
                        int linenumber = rs.getInt("linenumber");
                        boolean isunique = rs.getBoolean("isunique");
                        boolean isconst = rs.getBoolean("isconst");
                        boolean isnotnull = rs.getBoolean("isnotnull");
                        int dtype = rs.getInt("dtype");
                        String value = rs.getString("value");
                        PostgreDebugVariable var = new PostgreDebugVariable(name, varclass, linenumber, isunique, isconst,
                            isnotnull, dtype, value);
                        vars.add(var);
                    }

                }
            }
        } catch (SQLException e) {
            log.debug("Error getting vars: " + e.getMessage());
            throw new DBGException("SQL error", e);
        }

        log.debug(String.format("Return %d var(s)", vars.size()));
        return vars;

    }

    @Override
    public void setVariableVal(DBGVariable<?> variable, Object value) throws DBGException {
        log.debug("Set var value");
        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Set debug variable")) {
            try (PreparedStatement stmt = session.prepareStatement(SQL_SET_VAR)) {

                if (variable instanceof PostgreDebugVariable) {

                    if (value instanceof String) {

                        PostgreDebugVariable var = (PostgreDebugVariable) variable;

                        stmt.setInt(1, sessionId);
                        stmt.setString(2, var.getName());
                        stmt.setInt(3, var.getLineNumber());
                        stmt.setString(4, (String) value);

                        stmt.execute();
                        log.debug("Var value set");
                    } else {
                        log.debug("Incorrect variable value class");
                        throw new DBGException("Incorrect variable value class");
                    }

                } else {
                    log.debug("Incorrect variable class");
                    throw new DBGException("Incorrect variable class");
                }
            }

        } catch (SQLException e) {
            log.debug("Error setting var: " + e.getMessage());
            throw new DBGException("SQL error", e);
        }
    }

    @Override
    public List<DBGStackFrame> getStack() throws DBGException {
        List<DBGStackFrame> stack = new ArrayList<>(1);
        log.debug("Get stack");

        String sql = SQL_GET_STACK.replaceAll("\\?sessionid", String.valueOf(getSessionId()));
        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Get debug stack frame")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    while (rs.next()) {
                        int level = rs.getInt("level");
                        String targetname = rs.getString("targetname");
                        int func = rs.getInt("func");
                        int linenumber = rs.getInt("linenumber");
                        String args = rs.getString("args");
                        PostgreDebugStackFrame frame = new PostgreDebugStackFrame(level, targetname, func, linenumber, args);
                        stack.add(frame);
                    }

                }
            }
        } catch (SQLException e) {
            log.debug("Error loading stack frame: " + e.getMessage());
            throw new DBGException("SQL error", e);
        }
        log.debug(String.format("Return %d stack frame(s)", stack.size()));
        return stack;
    }

    @Override
    public String getSource(DBGStackFrame stack) throws DBGException {
        log.debug("Get source");
        if (stack instanceof PostgreDebugStackFrame) {
            PostgreDebugStackFrame postgreStack = (PostgreDebugStackFrame) stack;
            String src = getSource(postgreStack.getOid());
            log.debug(String.format("Return %d src char(s)", src.length()));
            return src;
        }
        String message = String.format("Unable to get source for stack %s", stack);
        throw new DBGException(message);
    }

    /**
     * Return source for func OID in debug session
     *
     * @return String
     */

    public String getSource(int OID) throws DBGException {
        log.debug("Get source for func OID in debug session");
        String sql = SQL_GET_SRC.replaceAll("\\?sessionid", String.valueOf(sessionId)).replaceAll("\\?oid",
            String.valueOf(OID));
        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Get session source")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    if (rs.next()) {
                        String src = rs.getString(1);
                        log.debug(String.format("Return %d src char(s)", src.length()));
                        return src;
                    }
                    return null;
                }
            }
        } catch (SQLException e) {
            log.debug(String.format("Unable to get source for OID %s", e.getMessage()));
            throw new DBGException("SQL error", e);
        }
    }

    /**
     * This function changes the debugger focus to the indicated frame (in the
     * call stack). Whenever the target stops (at a breakpoint or as the result
     * of a step/into or step/over), the debugger changes focus to most deeply
     * nested function in the call stack (because that's the function that's
     * executing).
     * <p>
     * You can change the debugger focus to other stack frames - once you do
     * that, you can examine the source code for that frame, the variable values
     * in that frame, and the breakpoints in that target.
     * <p>
     * The debugger focus remains on the selected frame until you change it or
     * the target stops at another breakpoint.
     */

    public void selectFrame(int frameNumber) throws DBGException {
        log.debug("Select frame");
        String sql = SQL_SELECT_FRAME.replaceAll("\\?sessionid", String.valueOf(sessionId)).replaceAll("\\?frameno",
            String.valueOf(frameNumber));

        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Select debug frame")) {
            try (Statement stmt = session.createStatement()) {
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    if (!rs.next()) {
                        log.debug("Unable to select frame");
                        throw new DBGException("Unable to select frame");
                    }

                    log.debug("Frame selected");

                }
            }
        } catch (SQLException e) {
            log.debug(String.format("Unable to select frame %s", e.getMessage()));
            throw new DBGException("SQL error", e);
        }
    }

    @Override
    public String toString() {
        return "PostgreDebugSession " + (isWaiting() ? "WAITING" : "READY") + " [sessionId=" + sessionId
            + ", breakpoints=" + getBreakpoints() + "targetId=(" + sessionInfo.getID() + ") Session=(" + sessionInfo.toString()
            + ") " + "]";
    }

    @Override
    public Integer getSessionId() {
        return sessionId;
    }

    @Override
    public boolean canStepInto() {
        return true;
    }

    @Override
    public boolean canStepOver() {
        return true;
    }

    @Override
    public boolean canStepReturn() {
        return false;
    }

    /**
     * Return true if debug session up and running on server
     *
     * @return boolean
     */
    public boolean isAttached() {
        return sessionId > 0;
    }

    /**
     * Return true if session waiting target connection (on breakpoint, after
     * step or continue) in debug thread
     *
     * @return boolean
     */
    public boolean isDone() {
        switch (attachKind) {
            case GLOBAL:
                return workerJob == null || workerJob.isFinished();
            case LOCAL:
                return sessionId > 0;
            default:
                return false;
        }
    }

    @Override
    public void closeSession(DBRProgressMonitor monitor) throws DBGException {
        if (!isAttached()) {
            return;
        }
        log.debug("Closing session.");
        try {
            super.closeSession(monitor);
            log.debug("Session closed.");
        } finally {
            if (controllerConnection != null) {
                IOUtils.close(controllerConnection);
            }
        }
    }
}

<MSG> PG debug session: parametersa binding fix


Former-commit-id: 8095e6c136ed4b874ec3459f62a8339aa5b349c6
<DFF> @@ -34,10 +34,13 @@ import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedure;
 import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedureParameter;
 import org.jkiss.dbeaver.model.DBPDataSource;
 import org.jkiss.dbeaver.model.DBPEvaluationContext;
+import org.jkiss.dbeaver.model.DBUtils;
+import org.jkiss.dbeaver.model.data.DBDValueHandler;
 import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
 import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
 import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;
 import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
+import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCCallableStatementImpl;
 import org.jkiss.dbeaver.model.runtime.AbstractJob;
 import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
 import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
@@ -280,18 +283,17 @@ public class PostgreDebugSession extends DBGJDBCSession {
                     }
                     query.append(") }");
                     localStatement = session.prepareCall(query.toString());
+                    JDBCCallableStatementImpl callImpl = new JDBCCallableStatementImpl(session, localStatement, query.toString(), false);
                     for (int i = 0; i < parameters.size(); i++) {
+                        PostgreProcedureParameter parameter = parameters.get(i);
                         String paramValue = paramValues.get(i);
-                        if (CommonUtils.isEmpty(paramValue)) {
-                            localStatement.setNull(i + 1, Types.VARCHAR);
-                        } else {
-                            localStatement.setString(i + 1, paramValue);
-                        }
+                        DBDValueHandler valueHandler = DBUtils.findValueHandler(session, parameter);
+                        valueHandler.bindValueObject(session, callImpl, parameter, i, paramValue);
                     }
                     localStatement.execute();
                     // And Now His Watch Is Ended
                     fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));
-                } catch (SQLException e) {
+                } catch (Exception e) {
                     try {
                         if (localStatement != null) {
                             localStatement.close();
@@ -301,7 +303,7 @@ public class PostgreDebugSession extends DBGJDBCSession {
                         log.error(e1);
                     }
                     fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));
-                    String sqlState = e.getSQLState();
+                    String sqlState = e instanceof SQLException ? ((SQLException) e).getSQLState() : null;
                     if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {
                         log.error(name, e);
                         return DebugUtils.newErrorStatus(name, e);

{'commit': 'diff --git a/plugins/org.jkiss.dbeaver.ext.postgresql.debug.core/src/org/jkiss/dbeaver/ext/postgresql/debug/internal/impl/PostgreDebugSession.java b/plugins/org.jkiss.dbeaver.ext.postgresql.debug.core/src/org/jkiss/dbeaver/ext/postgresql/debug/internal/impl/PostgreDebugSession.java\nindex 6dbe3f925b..6fb5feb7ee 100644\n--- a/plugins/org.jkiss.dbeaver.ext.postgresql.debug.core/src/org/jkiss/dbeaver/ext/postgresql/debug/internal/impl/PostgreDebugSession.java\n+++ b/plugins/org.jkiss.dbeaver.ext.postgresql.debug.core/src/org/jkiss/dbeaver/ext/postgresql/debug/internal/impl/PostgreDebugSession.java\n@@ -34,10 +34,13 @@ import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedure;\n import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedureParameter;\n import org.jkiss.dbeaver.model.DBPDataSource;\n import org.jkiss.dbeaver.model.DBPEvaluationContext;\n+import org.jkiss.dbeaver.model.DBUtils;\n+import org.jkiss.dbeaver.model.data.DBDValueHandler;\n import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;\n import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;\n import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;\n import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;\n+import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCCallableStatementImpl;\n import org.jkiss.dbeaver.model.runtime.AbstractJob;\n import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;\n import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;\n@@ -280,18 +283,17 @@ public class PostgreDebugSession extends DBGJDBCSession {\n                     }\n                     query.append(") }");\n                     localStatement = session.prepareCall(query.toString());\n+                    JDBCCallableStatementImpl callImpl = new JDBCCallableStatementImpl(session, localStatement, query.toString(), false);\n                     for (int i = 0; i < parameters.size(); i++) {\n+                        PostgreProcedureParameter parameter = parameters.get(i);\n                         String paramValue = paramValues.get(i);\n-                        if (CommonUtils.isEmpty(paramValue)) {\n-                            localStatement.setNull(i + 1, Types.VARCHAR);\n-                        } else {\n-                            localStatement.setString(i + 1, paramValue);\n-                        }\n+                        DBDValueHandler valueHandler = DBUtils.findValueHandler(session, parameter);\n+                        valueHandler.bindValueObject(session, callImpl, parameter, i, paramValue);\n                     }\n                     localStatement.execute();\n                     // And Now His Watch Is Ended\n                     fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));\n-                } catch (SQLException e) {\n+                } catch (Exception e) {\n                     try {\n                         if (localStatement != null) {\n                             localStatement.close();\n@@ -301,7 +303,7 @@ public class PostgreDebugSession extends DBGJDBCSession {\n                         log.error(e1);\n                     }\n                     fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));\n-                    String sqlState = e.getSQLState();\n+                    String sqlState = e instanceof SQLException ? ((SQLException) e).getSQLState() : null;\n                     if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {\n                         log.error(name, e);\n                         return DebugUtils.newErrorStatus(name, e);\n', 'commit_subject': 'PG debug session: parametersa binding fix', 'repo_name': 'dbeaver/dbeaver', 'committer': {'name': 'serge@jkiss.org', 'email': 'lfyeyf[eq87', 'time_sec': '1525626087', 'tz_offset': '180', 'date': {'seconds': '1525626087'}}, 'commit_tree': 'c2744e7a93cb6a4e5103b29b560334bb1085e870', 'commit_message': 'PG debug session: parametersa binding fix\n\n\nFormer-commit-id: 8095e6c136ed4b874ec3459f62a8339aa5b349c6', 'language_name': 'Shell', 'license': 'apache-2.0', 'hunks': '@@ -34,10 +34,13 @@ import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedure;\n import org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedureParameter;\n import org.jkiss.dbeaver.model.DBPDataSource;\n import org.jkiss.dbeaver.model.DBPEvaluationContext;\n+import org.jkiss.dbeaver.model.DBUtils;\n+import org.jkiss.dbeaver.model.data.DBDValueHandler;\n import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;\n import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;\n import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;\n import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;\n+import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCCallableStatementImpl;\n import org.jkiss.dbeaver.model.runtime.AbstractJob;\n import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;\n import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;\n@@ -280,18 +283,17 @@ public class PostgreDebugSession extends DBGJDBCSession {\n                     }\n                     query.append(") }");\n                     localStatement = session.prepareCall(query.toString());\n+                    JDBCCallableStatementImpl callImpl = new JDBCCallableStatementImpl(session, localStatement, query.toString(), false);\n                     for (int i = 0; i < parameters.size(); i++) {\n+                        PostgreProcedureParameter parameter = parameters.get(i);\n                         String paramValue = paramValues.get(i);\n-                        if (CommonUtils.isEmpty(paramValue)) {\n-                            localStatement.setNull(i + 1, Types.VARCHAR);\n-                        } else {\n-                            localStatement.setString(i + 1, paramValue);\n-                        }\n+                        DBDValueHandler valueHandler = DBUtils.findValueHandler(session, parameter);\n+                        valueHandler.bindValueObject(session, callImpl, parameter, i, paramValue);\n                     }\n                     localStatement.execute();\n                     // And Now His Watch Is Ended\n                     fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));\n-                } catch (SQLException e) {\n+                } catch (Exception e) {\n                     try {\n                         if (localStatement != null) {\n                             localStatement.close();\n@@ -301,7 +303,7 @@ public class PostgreDebugSession extends DBGJDBCSession {\n                         log.error(e1);\n                     }\n                     fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));\n-                    String sqlState = e.getSQLState();\n+                    String sqlState = e instanceof SQLException ? ((SQLException) e).getSQLState() : null;\n                     if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {\n                         log.error(name, e);\n                         return DebugUtils.newErrorStatus(name, e);\n', 'addition_count': 9, 'deletion_count': 7, 'src_file': 'a/plugins/org.jkiss.dbeaver.ext.postgresql.debug.core/src/org/jkiss/dbeaver/ext/postgresql/debug/internal/impl/PostgreDebugSession.java', 'tgt_file': 'b/plugins/org.jkiss.dbeaver.ext.postgresql.debug.core/src/org/jkiss/dbeaver/ext/postgresql/debug/internal/impl/PostgreDebugSession.java', 'file_extension': '.java', 'before_file': '/*\n * DBeaver - Universal Database Manager\n * Copyright (C) 2010-2022 DBeaver Corp and others\n * Copyright (C) 2017-2018 Andrew Khitrin (ahitrin@gmail.com)\n * Copyright (C) 2017-2018 Alexander Fedorov (alexander.fedorov@jkiss.org)\n *\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.jkiss.dbeaver.ext.postgresql.debug.internal.impl;\n\nimport org.eclipse.core.runtime.IStatus;\nimport org.eclipse.core.runtime.Status;\nimport org.eclipse.core.runtime.jobs.Job;\nimport org.jkiss.dbeaver.DBException;\nimport org.jkiss.dbeaver.Log;\nimport org.jkiss.dbeaver.debug.*;\nimport org.jkiss.dbeaver.debug.core.DebugUtils;\nimport org.jkiss.dbeaver.debug.jdbc.DBGJDBCSession;\nimport org.jkiss.dbeaver.ext.postgresql.PostgreConstants;\nimport org.jkiss.dbeaver.ext.postgresql.debug.PostgreDebugConstants;\nimport org.jkiss.dbeaver.ext.postgresql.debug.core.PostgreSqlDebugCore;\nimport org.jkiss.dbeaver.ext.postgresql.model.PostgreDataSource;\nimport org.jkiss.dbeaver.ext.postgresql.model.PostgreProcedureParameter;\nimport org.jkiss.dbeaver.model.DBPDataSource;\nimport org.jkiss.dbeaver.model.DBPEvaluationContext;\nimport org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;\nimport org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;\nimport org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;\nimport org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;\nimport org.jkiss.dbeaver.model.runtime.AbstractJob;\nimport org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;\nimport org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;\nimport org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;\nimport org.jkiss.utils.CommonUtils;\nimport org.jkiss.utils.IOUtils;\n\nimport java.sql.*;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Typical scenario for debug session <br/>\n * <br/>\n * 0. create session (now it can only attached to target Procedure)<br/>\n * <br/>\n * 1. attach to target this method attaches to a debugging target and listening\n * on the given port - waiting for run procedure in other session(s) debugger\n * client should invoke this function after creation also created implicit\n * breakpoint for target procedure, after this call debug session in\n * <b>WAITING</b> state - isDone returns false and is isWaiting returns\n * true<br/>\n * <br/>\n * 2. when target procedure will called debug session implicit breakpoint will\n * be reached and session goes in state <b>READY</b> (isDone - true, isWaiting -\n * true) in this state possible to call getStack, getVariables, setVariables,\n * setBreakpoint or execStepXXX\\continue<br/>\n * <br/>\n * 3. when execStepXXX or continue will called session goes in <b>WAITING</b>\n * state until next breakpoint or end of procedure will be reached <br/>\n */\npublic class PostgreDebugSession extends DBGJDBCSession {\n    private final JDBCExecutionContext controllerConnection;\n\n    private int functionOid = -1;\n    private int sessionId = -1;\n    private int localPortNumber = -1;\n\n    private PostgreDebugAttachKind attachKind = PostgreDebugAttachKind.UNKNOWN;\n    private DBGSessionInfo sessionInfo;\n\n    private PostgreDebugBreakpointDescriptor bpGlobal;\n    private volatile JDBCCallableStatement localStatement;\n\n    private static final int LOCAL_WAIT = 50; // 0.5 sec\n\n    private static final int LOCAL_TIMEOT = 1000 * LOCAL_WAIT; // 50 sec\n\n    private static final String MAGIC_PORT = "PLDBGBREAK";\n\n    private static final String SQL_CHECK_PLUGIN = "select \'Server version: \' || serverversionstr || \'.\\nProxy API version: \' ||  proxyapiver from pldbg_get_proxy_info()";\n\n    private static final String SQL_ATTACH = "select pldbg_wait_for_target(?sessionid)";\n    private static final String SQL_ATTACH_TO_PORT = "select pldbg_attach_to_port(?portnumber)";\n    private static final String SQL_PREPARE_SLOT = " select pldbg_oid_debug(?objectid)";\n    private static final String SQL_LISTEN = "select pldbg_create_listener() as sessionid";\n    private static final String SQL_GET_SRC = "select pldbg_get_source(?sessionid,?oid)";\n    private static final String SQL_GET_VARS = "select * from pldbg_get_variables(?sessionid)";\n    private static final String SQL_SET_VAR = "select pldbg_deposit_value(?,?,?,?)";\n    private static final String SQL_GET_STACK = "select * from pldbg_get_stack(?sessionid)";\n    private static final String SQL_SELECT_FRAME = "select * from pldbg_select_frame(?sessionid,?frameno)";\n    private static final String SQL_STEP_OVER = "select pldbg_step_over(?sessionid)";\n    private static final String SQL_STEP_INTO = "select pldbg_step_into(?sessionid)";\n    private static final String SQL_CONTINUE = "select pldbg_continue(?sessionid)";\n    private static final String SQL_ABORT = "select pldbg_abort_target(?sessionid)";\n    private static final String SQL_SET_GLOBAL_BREAKPOINT = "select pldbg_set_global_breakpoint(?sessionid, ?obj, ?line, ?target)";\n    private static final String SQL_SET_BREAKPOINT = "select pldbg_set_breakpoint(?sessionid, ?obj, ?line)";\n    private static final String SQL_DROP_BREAKPOINT = "select pldbg_drop_breakpoint(?sessionid, ?obj, ?line)";\n\n    private static final String SQL_CURRENT_SESSION =\n        "SELECT pid,usename,application_name,state,query\\n" +\n            "FROM pg_stat_activity WHERE pid = pg_backend_pid()"; //$NON-NLS-1$\n\n\n    private static final Log log = Log.getLog(PostgreDebugSession.class);\n\n    /**\n     * Create session with two description after creation session need to be\n     * attached to postgres procedure by attach method\n     */\n    PostgreDebugSession(DBRProgressMonitor monitor, DBGBaseController controller)\n        throws DBGException {\n        super(controller);\n        log.debug("Creating controller session.");\n\n        PostgreDataSource dataSource = (PostgreDataSource) controller.getDataSourceContainer().getDataSource();\n        try {\n            log.debug("Controller session creating.");\n            PostgreDatabase instance;\n            if (isGlobalSession(controller.getDebugConfiguration())) {\n                instance = dataSource.getDefaultInstance();\n            } else {\n                PostgreProcedure function = PostgreSqlDebugCore.resolveFunction(monitor, controller.getDataSourceContainer(), controller.getDebugConfiguration());\n                instance = function.getDatabase();\n            }\n            this.controllerConnection = (JDBCExecutionContext) instance.openIsolatedContext(monitor, "Debug controller session", null);\n\n            log.debug("Debug controller session created.");\n            JDBCDataSource src = this.controllerConnection.getDataSource();\n            if (src instanceof PostgreDataSource) {\n                PostgreDataSource pgSrc = (PostgreDataSource) src;\n                log.debug(String.format("Active user %s", instance.getMetaContext().getActiveUser()));\n                log.debug(String.format("Active schema %s", instance.getMetaContext().getDefaultSchema()));\n                if (pgSrc.getInfo() instanceof JDBCDataSourceInfo) {\n                    JDBCDataSourceInfo JDBCinfo = (JDBCDataSourceInfo) pgSrc.getInfo();\n                    log.debug("------------DATABASE DRIVER INFO---------------");\n                    log.debug(String.format("Database Product Name %s", JDBCinfo.getDatabaseProductName()));\n                    log.debug(String.format("Database Product Version %s", JDBCinfo.getDatabaseProductVersion()));\n                    log.debug(String.format("Database Version %s", JDBCinfo.getDatabaseVersion()));\n                    log.debug(String.format("Driver Name %s", JDBCinfo.getDriverName()));\n                    log.debug(String.format("Driver Version %s", JDBCinfo.getDriverVersion()));\n                    log.debug("-----------------------------------------------");\n                } else {\n                    log.debug("No additional Driver info");\n                }\n            } else {\n                log.debug("Unknown Driver version");\n            }\n\n        } catch (DBException e) {\n            log.debug(String.format("Error creating debug session %s", e.getMessage()));\n            throw new DBGException(e, dataSource);\n        }\n    }\n\n    @Override\n    public JDBCExecutionContext getControllerConnection() {\n        return controllerConnection;\n    }\n\n    private PostgreDebugSessionInfo getSessionDescriptor(DBRProgressMonitor monitor, JDBCExecutionContext connection) throws DBGException {\n        try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.UTIL, "Read session info")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(SQL_CURRENT_SESSION)) {\n                    if (rs.next()) {\n                        int pid = rs.getInt("pid");\n                        String usename = rs.getString("usename");\n                        String applicationName = rs.getString("application_name");\n                        String state = rs.getString("state");\n                        String query = rs.getString("query");\n                        return new PostgreDebugSessionInfo(pid, usename, applicationName, state, query);\n                    }\n                    throw new DBGException("Error getting session");\n                }\n            }\n        } catch (SQLException e) {\n            throw new DBGException("SQL error", e);\n        }\n    }\n\n    private boolean localPortRcv(SQLWarning warn) {\n\n        if (warn != null) {\n\n            String notice = warn.getMessage();\n\n            log.debug("Start local port waiting....");\n\n            while (notice != null) {\n\n                if (notice.startsWith(MAGIC_PORT)) {\n\n                    try {\n                        localPortNumber = Integer.valueOf(notice.substring(MAGIC_PORT.length() + 1).trim());\n                        log.debug(String.format("Catch local port number %d", localPortNumber));\n                    } catch (Exception e) {\n                        log.debug(String.format("Error catching local port number %s", e.getMessage()));\n                        return false;\n                    }\n\n                    return true;\n                }\n\n                warn = warn.getNextWarning();\n\n                notice = warn == null ? null : warn.getMessage();\n                log.debug(String.format("Next warning %s", (notice == null ? "[NULL]" : notice)));\n\n            }\n        }\n\n        return false;\n    }\n\n    private int attachToPort(DBRProgressMonitor monitor) throws DBGException {\n        // Use controller connection\n        String sql = SQL_ATTACH_TO_PORT.replaceAll("\\\\?portnumber", String.valueOf(localPortNumber));\n        log.debug(String.format("Attach to local port number %d", localPortNumber));\n        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Attach to port")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(sql)) {\n                    if (rs.next()) {\n                        int attResult = rs.getInt(1);\n                        log.debug(String.format("Attached to local port %d", attResult));\n                        return attResult;\n                    }\n                    log.debug("Error while attaching to port");\n                    throw new DBGException("Error while attaching to port");\n                }\n            }\n        } catch (SQLException e) {\n            log.debug("Error while attaching to port");\n            throw new DBGException("Error attaching to port", e);\n        }\n    }\n\n    private String createSlot(DBRProgressMonitor monitor, JDBCExecutionContext connection, PostgreProcedure function) throws DBGException {\n\n        String objId = String.valueOf(function.getObjectId());\n        String sql = SQL_PREPARE_SLOT.replaceAll("\\\\?objectid", objId);\n        log.debug(String.format("Create slot for object ID %s", objId));\n        try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.UTIL, "Attach to port")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(sql)) {\n                    if (!rs.next()) {\n                        log.debug("Error creating target slot");\n                        throw new DBGException("Error creating target slot");\n                    }\n                    String dbgOID = rs.getString(1);\n                    log.debug(String.format("Create slot OID %s", dbgOID));\n                    return dbgOID;\n                }\n            }\n        } catch (SQLException e) {\n            log.debug(String.format("Error creating target %s", e.getMessage()));\n            throw new DBGException("Error creating target", e);\n        }\n    }\n\n    /**\n     * Wait for port number passed from main executed statement\n     */\n    private void waitPortNumber() throws DBGException {\n\n        int totalWait = 0;\n        boolean hasStatement = false;\n        log.debug(String.format("Waiting for port number with timeout %d", LOCAL_TIMEOT));\n        while (totalWait < LOCAL_TIMEOT) {\n                    }\n                    query.append(") }");\n                    localStatement = session.prepareCall(query.toString());\n                    for (int i = 0; i < parameters.size(); i++) {\n                        String paramValue = paramValues.get(i);\n                        if (CommonUtils.isEmpty(paramValue)) {\n                            localStatement.setNull(i + 1, Types.VARCHAR);\n                        } else {\n                            localStatement.setString(i + 1, paramValue);\n                        }\n                    }\n                    localStatement.execute();\n                    // And Now His Watch Is Ended\n                    fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));\n                } catch (SQLException e) {\n                    try {\n                        if (localStatement != null) {\n                            localStatement.close();\n            } catch (SQLException | InterruptedException e) {\n                log.debug(String.format("Error rcv port number %s", e.getMessage()));\n                throw new DBGException("Error rcv port number", e);\n                        log.error(e1);\n                    }\n                    fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));\n                    String sqlState = e.getSQLState();\n                    if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {\n                        log.error(name, e);\n                        return DebugUtils.newErrorStatus(name, e);\n            log.debug(String.format("Unable to rcv port number %d", localPortNumber));\n            throw new DBGException("Unable to rcv port number");\n        }\n    }\n\n    protected void runLocalProc(JDBCExecutionContext connection, PostgreProcedure function, List<String> paramValues, String name) throws DBGException {\n        List<PostgreProcedureParameter> parameters = function.getInputParameters();\n        log.debug("Run local proc");\n        if (parameters.size() != paramValues.size()) {\n            String unmatched = "Parameter value count (" + paramValues.size() + ") doesn\'t match actual function parameters (" + parameters.size() + ")";\n            log.debug(unmatched);\n            throw new DBGException(unmatched);\n        }\n        Job job = new AbstractJob(name) {\n            @Override\n            protected IStatus run(DBRProgressMonitor monitor) {\n                try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.USER, "Run SQL command")) {\n                    try {\n                        StringBuilder query = new StringBuilder();\n                        query.append("{ CALL ").append(function.getFullyQualifiedName(DBPEvaluationContext.DML)).append("(");\n                        for (int i = 0; i < parameters.size(); i++) {\n                            if (i > 0) query.append(",");\n                            String paramValue = paramValues.get(i);\n                            query.append(paramValue);\n                        }\n                        query.append(") }");\n                        log.debug(String.format("Prepared local call %s", query));\n                        localStatement = session.prepareCall(query.toString());\n\n/*\n                        for (int i = 0; i < parameters.size(); i++) {\n                            PostgreProcedureParameter parameter = parameters.get(i);\n                            String paramValue = paramValues.get(i);\n                            DBDValueHandler valueHandler = DBUtils.findValueHandler(session, parameter);\n                            valueHandler.bindValueObject(session, localStatement, parameter, i, paramValue);\n                        }\n*/\n                        localStatement.execute();\n                        // And Now His Watch Is Ended\n                        log.debug("Local statement executed (ANHWIE)");\n                        fireEvent(new DBGEvent(this, DBGEvent.RESUME, DBGEvent.STEP_RETURN));\n                    } catch (Exception e) {\n                        log.debug("Error execute local statement: " + e.getMessage());\n                        String sqlState = e instanceof SQLException ? ((SQLException) e).getSQLState() : null;\n                        if (!PostgreConstants.EC_QUERY_CANCELED.equals(sqlState)) {\n                            log.error(name, e);\n                            return DebugUtils.newErrorStatus(name, e);\n                        }\n                    } finally {\n                        try {\n                            if (localStatement != null) {\n                                localStatement.close();\n                                localStatement = null;\n                            }\n                        } catch (Exception e1) {\n                            log.debug("Error clearing local statment");\n                            log.error(e1);\n                        }\n                        connection.close();\n\n                        fireEvent(new DBGEvent(this, DBGEvent.TERMINATE, DBGEvent.CLIENT_REQUEST));\n                    }\n                }\n                log.debug("Local statement executed");\n                return Status.OK_STATUS;\n            }\n        };\n        job.schedule();\n    }\n\n    private void attachLocal(DBRProgressMonitor monitor, PostgreProcedure function, List<String> parameters) throws DBGException {\n\n        try {\n            JDBCExecutionContext connection = (JDBCExecutionContext) controllerConnection.getOwnerInstance().openIsolatedContext(monitor, "Debug process session", null);\n            log.debug("Attaching locally....");\n            this.sessionInfo = getSessionDescriptor(monitor, connection);\n\n            createSlot(monitor, connection, function);\n\n            String taskName = "PostgreSQL Debug - Local session " + sessionInfo.getID();\n\n            runLocalProc(connection, function, parameters, taskName);\n\n            waitPortNumber();\n\n            sessionId = attachToPort(monitor);\n            log.debug(String.format("Attached local session UD = %d", sessionId));\n            getController().fireEvent(new DBGEvent(this, DBGEvent.SUSPEND, DBGEvent.MODEL_SPECIFIC));\n        } catch (DBException e) {\n            throw new DBGException("Error opening debug session", e);\n        }\n\n    }\n\n    private void attachGlobal(DBRProgressMonitor monitor, int oid, int targetPID) throws DBGException {\n\n        log.debug("Attaching globally....");\n\n        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Attach global")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(SQL_LISTEN)) {\n\n                    if (rs.next()) {\n                        sessionId = rs.getInt("sessionid");\n                        log.debug(String.format("Global session ID %d", sessionId));\n                    } else {\n                        log.debug("Unable to create debug instance");\n                        throw new DBGException("Unable to create debug instance");\n                    }\n\n                }\n            }\n        } catch (SQLException e) {\n            throw new DBGException("SQL error", e);\n        }\n\n        bpGlobal = new PostgreDebugBreakpointDescriptor(oid, -1);\n        addBreakpoint(monitor, bpGlobal);\n        log.debug("Global breakpoint added");\n\n        String sessionParam = String.valueOf(getSessionId());\n        String taskName = "PostgreSQL Debug - Global session " + sessionParam;\n        String sql = SQL_ATTACH.replaceAll("\\\\?sessionid", sessionParam);\n        DBGEvent begin = new DBGEvent(this, DBGEvent.RESUME, DBGEvent.MODEL_SPECIFIC);\n        DBGEvent end = new DBGEvent(this, DBGEvent.SUSPEND, DBGEvent.BREAKPOINT);\n        runAsync(sql, taskName, begin, end);\n        log.debug("Global session started");\n    }\n\n    /**\n     * This method attach debug session to debug object (procedure) and wait\n     * forever while target or any (depend on targetPID) session will run target\n     * procedure\n     */\n    public void attach(DBRProgressMonitor monitor, Map<String, Object> configuration) throws DBException {\n        if (!checkDebugPlagin(monitor)) {\n            throw new DBGException("PostgreSQL debug plugin is not installed on the server.\\n" +\n                "Refer to this WIKI article for installation instructions:\\n" +\n                "https://github.com/dbeaver/dbeaver/wiki/PGDebugger#installation");\n        }\n\n        log.debug("Attaching...");\n\n        functionOid = CommonUtils.toInt(configuration.get(PostgreDebugConstants.ATTR_FUNCTION_OID));\n        log.debug(String.format("Function OID %d", functionOid));\n\n        boolean global = isGlobalSession(configuration);\n\n        if (global) {\n            int processId = CommonUtils.toInt(configuration.get(PostgreDebugConstants.ATTR_ATTACH_PROCESS));\n            attachKind = PostgreDebugAttachKind.GLOBAL;\n            attachGlobal(monitor, functionOid, processId);\n            log.debug("Global attached");\n        } else {\n            attachKind = PostgreDebugAttachKind.LOCAL;\n            PostgreProcedure function = PostgreSqlDebugCore.resolveFunction(monitor, controllerConnection.getDataSource().getContainer(), configuration);\n            List<String> parameterValues = (List<String>) configuration.get(PostgreDebugConstants.ATTR_FUNCTION_PARAMETERS);\n\n            attachLocal(monitor, function, parameterValues);\n            log.debug("Local attached");\n        }\n    }\n\n    private boolean isGlobalSession(Map<String, Object> configuration) {\n        return PostgreDebugConstants.ATTACH_KIND_GLOBAL.equals(String.valueOf(configuration.get(PostgreDebugConstants.ATTR_ATTACH_KIND)));\n    }\n\n    private boolean checkDebugPlagin(DBRProgressMonitor monitor) {\n        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Check debug plugin installation")) {\n            String version = JDBCUtils.executeQuery(session, SQL_CHECK_PLUGIN);\n            log.debug("Debug plugin is installed:\\n" + version);\n            return true;\n        } catch (Exception e) {\n            log.debug("Debug plugin not installed: " + e.getMessage());\n            return false;\n        }\n    }\n\n    private void detachLocal(DBRProgressMonitor monitor, JDBCExecutionContext connection) throws DBGException {\n        if (localStatement == null) {\n            // Execution already terminated\n            return;\n        }\n        try (JDBCSession session = connection.openSession(monitor, DBCExecutionPurpose.UTIL, "Abort local session")) {\n            JDBCUtils.executeQuery(session, composeAbortCommand());\n            log.debug("Local detached");\n        } catch (SQLException e) {\n            log.debug("Unable to abort local session");\n            log.error("Unable to abort local target", e);\n        }\n    }\n\n    private void detachGlobal(DBRProgressMonitor monitor) throws DBGException {\n        removeBreakpoint(monitor, bpGlobal);\n\n        try (JDBCSession session = getControllerConnection().openSession(monitor, DBCExecutionPurpose.UTIL, "Abort global session")) {\n            JDBCUtils.executeQuery(session, composeAbortCommand());\n            log.debug("Global deattached");\n        } catch (SQLException e) {\n            log.error("Unable to abort global target", e);\n        }\n    }\n\n    protected void doDetach(DBRProgressMonitor monitor) throws DBGException {\n        switch (attachKind) {\n            case GLOBAL:\n                detachGlobal(monitor);\n                break;\n            case LOCAL:\n                detachLocal(monitor, getControllerConnection());\n                break;\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public DBGSessionInfo getSessionInfo() {\n        return sessionInfo;\n    }\n\n    protected String composeAddBreakpointCommand(DBGBreakpointDescriptor descriptor) {\n        PostgreDebugBreakpointDescriptor bp = (PostgreDebugBreakpointDescriptor) descriptor;\n        String sqlPattern = attachKind == PostgreDebugAttachKind.GLOBAL ? SQL_SET_GLOBAL_BREAKPOINT : SQL_SET_BREAKPOINT;\n        long lineNumber = bp.isOnStart() ? -1 : bp.getLineNo();\n        log.debug(String.format("Adding breakpoint to line #%d", lineNumber));\n        return sqlPattern.replaceAll("\\\\?sessionid", String.valueOf(getSessionId()))\n            .replaceAll("\\\\?obj", String.valueOf(functionOid))\n            .replaceAll("\\\\?line", String.valueOf(lineNumber))\n            .replaceAll("\\\\?target", bp.isAll() ? "null" : String.valueOf(bp.getTargetId()));\n    }\n\n    protected String composeRemoveBreakpointCommand(DBGBreakpointDescriptor breakpointDescriptor) {\n        PostgreDebugBreakpointDescriptor bp = (PostgreDebugBreakpointDescriptor) breakpointDescriptor;\n        return SQL_DROP_BREAKPOINT.replaceAll("\\\\?sessionid", String.valueOf(getSessionId()))\n            .replaceAll("\\\\?obj", String.valueOf(functionOid))\n            .replaceAll("\\\\?line", bp.isOnStart() ? "-1" : String.valueOf(bp.getLineNo()));\n    }\n\n    @Override\n    public void execContinue() throws DBGException {\n        log.debug("try continue for");\n        execStep(SQL_CONTINUE, " continue for ", DBGEvent.RESUME);\n        log.debug("continue for realized");\n    }\n\n    @Override\n    public void execStepInto() throws DBGException {\n        log.debug("try step into");\n        execStep(SQL_STEP_INTO, " step into for ", DBGEvent.STEP_INTO);\n        log.debug("step into realized");\n    }\n\n    @Override\n    public void execStepOver() throws DBGException {\n        log.debug("try step over");\n        execStep(SQL_STEP_OVER, " step over for ", DBGEvent.STEP_OVER);\n        log.debug("step over realized");\n    }\n\n    @Override\n    public void execStepReturn() throws DBGException {\n        log.debug("Exec return not implemented");\n        throw new DBGException("Exec return not implemented");\n    }\n\n    @Override\n    public void resume() throws DBGException {\n        log.debug("try continue execution");\n        execContinue();\n        log.debug("continue execution realized");\n    }\n\n    @Override\n    public void suspend() throws DBGException {\n        throw new DBGException("Suspend not implemented");\n    }\n\n    /**\n     * Execute step SQL command asynchronously, set debug session name to\n     * [sessionID] name [managerPID]\n     *\n     * @param commandPattern - SQL command for execute step\n     * @param nameParameter  - session \'name\' part\n     */\n    public void execStep(String commandPattern, String nameParameter, int eventDetail) throws DBGException {\n        String sql = commandPattern.replaceAll("\\\\?sessionid", String.valueOf(sessionId));\n        String taskName = String.valueOf(sessionId) + nameParameter + sessionInfo.getID();\n        DBGEvent begin = new DBGEvent(this, DBGEvent.RESUME, eventDetail);\n        DBGEvent end = new DBGEvent(this, DBGEvent.SUSPEND, eventDetail);\n        runAsync(sql, taskName, begin, end);\n    }\n\n    protected String composeAbortCommand() {\n        return SQL_ABORT.replaceAll("\\\\?sessionid", String.valueOf(sessionId));\n    }\n\n    @Override\n    public List<DBGVariable<?>> getVariables(DBGStackFrame stack) throws DBGException {\n        if (stack != null) {\n            selectFrame(stack.getLevel());\n        }\n\n        log.debug("Get vars values");\n        List<DBGVariable<?>> vars = new ArrayList<>();\n\n        String sql = SQL_GET_VARS.replaceAll("\\\\?sessionid", String.valueOf(sessionId));\n        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Read debug variables")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(sql)) {\n\n                    while (rs.next()) {\n                        String name = rs.getString("name");\n                        String varclass = rs.getString("varclass");\n                        int linenumber = rs.getInt("linenumber");\n                        boolean isunique = rs.getBoolean("isunique");\n                        boolean isconst = rs.getBoolean("isconst");\n                        boolean isnotnull = rs.getBoolean("isnotnull");\n                        int dtype = rs.getInt("dtype");\n                        String value = rs.getString("value");\n                        PostgreDebugVariable var = new PostgreDebugVariable(name, varclass, linenumber, isunique, isconst,\n                            isnotnull, dtype, value);\n                        vars.add(var);\n                    }\n\n                }\n            }\n        } catch (SQLException e) {\n            log.debug("Error getting vars: " + e.getMessage());\n            throw new DBGException("SQL error", e);\n        }\n\n        log.debug(String.format("Return %d var(s)", vars.size()));\n        return vars;\n\n    }\n\n    @Override\n    public void setVariableVal(DBGVariable<?> variable, Object value) throws DBGException {\n        log.debug("Set var value");\n        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Set debug variable")) {\n            try (PreparedStatement stmt = session.prepareStatement(SQL_SET_VAR)) {\n\n                if (variable instanceof PostgreDebugVariable) {\n\n                    if (value instanceof String) {\n\n                        PostgreDebugVariable var = (PostgreDebugVariable) variable;\n\n                        stmt.setInt(1, sessionId);\n                        stmt.setString(2, var.getName());\n                        stmt.setInt(3, var.getLineNumber());\n                        stmt.setString(4, (String) value);\n\n                        stmt.execute();\n                        log.debug("Var value set");\n                    } else {\n                        log.debug("Incorrect variable value class");\n                        throw new DBGException("Incorrect variable value class");\n                    }\n\n                } else {\n                    log.debug("Incorrect variable class");\n                    throw new DBGException("Incorrect variable class");\n                }\n            }\n\n        } catch (SQLException e) {\n            log.debug("Error setting var: " + e.getMessage());\n            throw new DBGException("SQL error", e);\n        }\n    }\n\n    @Override\n    public List<DBGStackFrame> getStack() throws DBGException {\n        List<DBGStackFrame> stack = new ArrayList<>(1);\n        log.debug("Get stack");\n\n        String sql = SQL_GET_STACK.replaceAll("\\\\?sessionid", String.valueOf(getSessionId()));\n        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Get debug stack frame")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(sql)) {\n                    while (rs.next()) {\n                        int level = rs.getInt("level");\n                        String targetname = rs.getString("targetname");\n                        int func = rs.getInt("func");\n                        int linenumber = rs.getInt("linenumber");\n                        String args = rs.getString("args");\n                        PostgreDebugStackFrame frame = new PostgreDebugStackFrame(level, targetname, func, linenumber, args);\n                        stack.add(frame);\n                    }\n\n                }\n            }\n        } catch (SQLException e) {\n            log.debug("Error loading stack frame: " + e.getMessage());\n            throw new DBGException("SQL error", e);\n        }\n        log.debug(String.format("Return %d stack frame(s)", stack.size()));\n        return stack;\n    }\n\n    @Override\n    public String getSource(DBGStackFrame stack) throws DBGException {\n        log.debug("Get source");\n        if (stack instanceof PostgreDebugStackFrame) {\n            PostgreDebugStackFrame postgreStack = (PostgreDebugStackFrame) stack;\n            String src = getSource(postgreStack.getOid());\n            log.debug(String.format("Return %d src char(s)", src.length()));\n            return src;\n        }\n        String message = String.format("Unable to get source for stack %s", stack);\n        throw new DBGException(message);\n    }\n\n    /**\n     * Return source for func OID in debug session\n     *\n     * @return String\n     */\n\n    public String getSource(int OID) throws DBGException {\n        log.debug("Get source for func OID in debug session");\n        String sql = SQL_GET_SRC.replaceAll("\\\\?sessionid", String.valueOf(sessionId)).replaceAll("\\\\?oid",\n            String.valueOf(OID));\n        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Get session source")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(sql)) {\n                    if (rs.next()) {\n                        String src = rs.getString(1);\n                        log.debug(String.format("Return %d src char(s)", src.length()));\n                        return src;\n                    }\n                    return null;\n                }\n            }\n        } catch (SQLException e) {\n            log.debug(String.format("Unable to get source for OID %s", e.getMessage()));\n            throw new DBGException("SQL error", e);\n        }\n    }\n\n    /**\n     * This function changes the debugger focus to the indicated frame (in the\n     * call stack). Whenever the target stops (at a breakpoint or as the result\n     * of a step/into or step/over), the debugger changes focus to most deeply\n     * nested function in the call stack (because that\'s the function that\'s\n     * executing).\n     * <p>\n     * You can change the debugger focus to other stack frames - once you do\n     * that, you can examine the source code for that frame, the variable values\n     * in that frame, and the breakpoints in that target.\n     * <p>\n     * The debugger focus remains on the selected frame until you change it or\n     * the target stops at another breakpoint.\n     */\n\n    public void selectFrame(int frameNumber) throws DBGException {\n        log.debug("Select frame");\n        String sql = SQL_SELECT_FRAME.replaceAll("\\\\?sessionid", String.valueOf(sessionId)).replaceAll("\\\\?frameno",\n            String.valueOf(frameNumber));\n\n        try (JDBCSession session = getControllerConnection().openSession(new VoidProgressMonitor(), DBCExecutionPurpose.UTIL, "Select debug frame")) {\n            try (Statement stmt = session.createStatement()) {\n                try (ResultSet rs = stmt.executeQuery(sql)) {\n                    if (!rs.next()) {\n                        log.debug("Unable to select frame");\n                        throw new DBGException("Unable to select frame");\n                    }\n\n                    log.debug("Frame selected");\n\n                }\n            }\n        } catch (SQLException e) {\n            log.debug(String.format("Unable to select frame %s", e.getMessage()));\n            throw new DBGException("SQL error", e);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return "PostgreDebugSession " + (isWaiting() ? "WAITING" : "READY") + " [sessionId=" + sessionId\n            + ", breakpoints=" + getBreakpoints() + "targetId=(" + sessionInfo.getID() + ") Session=(" + sessionInfo.toString()\n            + ") " + "]";\n    }\n\n    @Override\n    public Integer getSessionId() {\n        return sessionId;\n    }\n\n    @Override\n    public boolean canStepInto() {\n        return true;\n    }\n\n    @Override\n    public boolean canStepOver() {\n        return true;\n    }\n\n    @Override\n    public boolean canStepReturn() {\n        return false;\n    }\n\n    /**\n     * Return true if debug session up and running on server\n     *\n     * @return boolean\n     */\n    public boolean isAttached() {\n        return sessionId > 0;\n    }\n\n    /**\n     * Return true if session waiting target connection (on breakpoint, after\n     * step or continue) in debug thread\n     *\n     * @return boolean\n     */\n    public boolean isDone() {\n        switch (attachKind) {\n            case GLOBAL:\n                return workerJob == null || workerJob.isFinished();\n            case LOCAL:\n                return sessionId > 0;\n            default:\n                return false;\n        }\n    }\n\n    @Override\n    public void closeSession(DBRProgressMonitor monitor) throws DBGException {\n        if (!isAttached()) {\n            return;\n        }\n        log.debug("Closing session.");\n        try {\n            super.closeSession(monitor);\n            log.debug("Session closed.");\n        } finally {\n            if (controllerConnection != null) {\n                IOUtils.close(controllerConnection);\n            }\n        }\n    }\n}\n', 'message': 'PG debug session: parametersa binding fix\n\n\nFormer-commit-id: 8095e6c136ed4b874ec3459f62a8339aa5b349c6', 'author_name': 'serge@jkiss.org', 'lang': 'jkiss'}