/*
 * $Source: /usr/local/cvsroot/erserver/erserver/java/src/com/postgres/replic/util/MasterProcessor.java,v $
 * $Author: asullivan $ $Revision: 1.1.1.1 $ $Date: 2003/08/26 19:02:29 $
 *
 */

package com.postgres.replic.util;

import com.postgres.replic.server.props.*;
import com.postgres.replic.util.struct.*;
import java.sql.*;
import java.util.*;

public class MasterProcessor extends DBProcessor {

    private TableMap tableMap; // of TableMap objects
    private int server = 0;
    private LastSyncData lastSync;
    private USTable usTable;
    private SnapshotData snapData = new SnapshotData();
    //private TableColumnCache  tableColumnCache;
    private boolean snapshotRemembered = false;
    private String sinfo = null;
    private long syncid = T_NO;
    private static final String ACTIVE_SEP = ",";

    private int activeLogTable = 0;
    // Modes of activeLogTable:
    private static final int ALT_1 = 1; // $CTable = "_RSERV_LOG_1_"; $OTable
    // = "_RSERV_LOG_2_";
    private static final int ALT_2 = 2; // $CTable = "_RSERV_LOG_2_"; $OTable
    // = "_RSERV_LOG_1_";

    private boolean useOldLogTable = true ;
    // Modes defining useOldLogTable:
    private static final int USE = 2;

    // TAble id-s:
    private static final String LOG_1 = "_RSERV_LOG_1_";
    private static final String LOG_2 = "_RSERV_LOG_2_";

    // Lock modes:
    private static final int SHARED = 0;
    private static final int EXCLUSIVE = 1;

    // Statuses for clean log:
    private static final int USE_OLD_NEW_LOG = 2;
    private static final int MARK_CLEAN = 1;
    private static final int TRUNCATE = 0;
    private  int useolt = 0;


    // Number of rows to fetch in cursors:
    private static final int FETCH_MAX = 1000;

    // Sql satatements:
    private static final String SQL_TABLE_MAP = "" +
    "select pgc.oid, pgc.relname, pga.attname, pgt.typname " +
    "from _RSERV_TABLES_ rt, pg_class pgc, " +
    "pg_attribute pga, pg_type pgt " +
    "where pgc.oid = rt.reloid and pga.attrelid = rt.reloid " +
    "and pga.attnum = rt.key and pgt.oid = pga.atttypid";


    public MasterProcessor reset(Connection conn, int server, ServerProps serverProps)
    throws RservException, Exception {
        super.init(conn, serverProps);
        lastSync = null;
        snapData.clear();
        this.server = server;
        snapshotRemembered = false;
        usTable = null;
        sinfo = null;
        syncid = T_NO;
        getLogger().debug("***** **** *** MasterProcessor::reset: server="+server+"; getPstmtUseCount()="+ getPstmtUseCount()+"; conn="+conn+"; tableColumnCache="+tableColumnCache);
        setTableColumnCache();
        getLogger().debug("MasterProcessor::reset: After setTableColumnCache");
        setInitialized(true);
        return this;
    }

    public SnapshotData getSnapshotData() throws RservException {
        if (!snapshotRemembered) {
            throw new RservException("::getSnapshotData: snapshot is not remembered");
        }
        return snapData;
    }

    protected int getActiveLog () throws RservException {
        return getActiveLog (false);
    }

    //private TableColumnCache getTableColumnCache() {
    //    return tableColumnCache;
    //}

    /*
    */

    private int getActiveLog(boolean reset) throws RservException {
        if (!reset) {
            return activeLogTable;
        }

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;

        sql = "select last_value::int4 FROM _rserv_active_log_id_";

        getLogger().debug("MasterProcessor::getActiveLog: sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();
            while (rs.next()) {
                activeLogTable = rs.getInt(1);

                if (getDebug()) getLogger().debug("MasterProcessor::getActiveLog: activeLogTable=" +
                activeLogTable);
            }
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::getActiveLog: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return activeLogTable;
    }

    /* modes are  SHARED & EXCLUSIVE
    */

    private void lock(int mode) throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;
        sql = "select _rserv_lock_altid_("+mode+")";

        getLogger().debug("MasterProcessor::lock: sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::lock: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }
    }

    /* modes are  SHARED=0 & EXCLUSIVE=1
    */

    private void unlock(int mode) throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = "select _rserv_unlock_altid_("+mode+")";

        getLogger().debug("MasterProcessor::unlock sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::unlock: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }
    }


    /*
    */

    private void lockTable()
    throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;

        try {
            if (useOldLogTable()) {
                String sql = "LOCK TABLE "
                + ((getActiveLog() == ALT_1) ? LOG_2 : LOG_1)
                + " IN ROW SHARE MODE";
                getLogger().debug("MasterProcessor::lockTable: sql=" + sql);
                stmt = getConnection().prepareStatement(sql);
                stmt.executeUpdate();
            }
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::lockTable: " + e.toString());
        } finally {
            generalClose(stmt, null);
        }
    }

    /*
    */

    private void lockTable(int logId)
    throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        String table = null;

        if (logId == ALT_1) {
             table = LOG_1;
        } else if (logId == ALT_2){
             table = LOG_2;
        } else {
             throw new RservException(
            "MasterProcessor::lockTable: wrong logId = " + logId);
        }

        try {
            String sql = "LOCK TABLE " + table + " IN SHARE MODE";
            getLogger().debug("MasterProcessor::lockTable: sql=" + sql);
            stmt = getConnection().prepareStatement(sql);
            stmt.executeUpdate();
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::lockTable: " + e.toString());
        } finally {
            generalClose(stmt, null);
        }
    }

    private void truncateTable(int logId)
    throws RservException, SQLException {

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String table = null;

        if (logId == ALT_1) {
             table = LOG_1;
        } else if (logId == ALT_2){
             table = LOG_2;
        } else {
             throw new RservException(
            "MasterProcessor::truncateTable: wrong logId = " + logId);
        }

        try {
            // We have to explicitely set autocommit to true
            // To pass truncate since it cannot be run inside of transaction:
            getConnection().setAutoCommit(true);

            String sql = "TRUNCATE TABLE  " + table ;
            getLogger().debug("MasterProcessor::truncateTable: sql=" + sql);
            stmt = getConnection().prepareStatement(sql);
            stmt.executeUpdate();
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::truncateTable: " + e.toString());
        } finally {
            generalClose(stmt, null);
            getConnection().setAutoCommit(false);
        }
    }

    private void postTruncateTable(int logId)
    throws RservException, SQLException {

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String table = null;

        if (logId == ALT_1) {
             table = LOG_1;
        } else if (logId == ALT_2){
             table = LOG_2;
        } else {
             throw new RservException(
            "MasterProcessor::postTruncateTable: wrong logId = " + logId);
        }

        try {
            // We have to explicitely set autocommit to true
            // To pass truncate since it cannot be run inside of transaction:
            getConnection().setAutoCommit(true);

            String sql = "update pg_class set relpages=1000,reltuples=100000 where relname = '" + table + "'" ;
            getLogger().debug("MasterProcessor::postTruncateTable: sql=" + sql);
            stmt = getConnection().prepareStatement(sql);
            stmt.executeUpdate();
        } catch (Exception e) {
            getLogger().error("MasterProcessor::postTruncateTable: " + e.toString());
        } finally {
            generalClose(stmt, null);
            getConnection().setAutoCommit(false);
        }
    }


    // update pg_class set relpages=1000,reltuples=100000 where relname = [your_table_name];

    protected boolean useOldLogTable() throws RservException {
        return useOldLogTable(false);
    }

    /*
    */

    private boolean useOldLogTable(boolean reset) throws RservException {
        if (!reset) {
            if (getDebug()) getLogger().debug("MasterProcessor::useOldLogTable (1) USE OLD LOG ? : "+useOldLogTable);
            return useOldLogTable;
        }
        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;

        sql = "select last_value::int4 FROM _rserv_old_log_status_";

        getLogger().debug("MasterProcessor::useOldLogTable: sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();
            while (rs.next()) {
                int use = rs.getInt(1);
                if (use < 0 || use > 2) {
                     throw new RservException(" use < 0 || use >2");
                }

                useOldLogTable = (use == USE) ? true : false;
                if (getDebug()) getLogger().debug("MasterProcessor::useOldLogTable (2) USE OLD LOG ? : "+useOldLogTable);
            }
        } catch (Exception e) {
            throw new RservException("MasterProcessor::useOldLogTable: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return useOldLogTable;
    }


    /*
    */

    private int useOldLogState() throws RservException {

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;
        int use = 0;

        sql = "select last_value::int4 FROM _rserv_old_log_status_";

        getLogger().debug("MasterProcessor::useOldLogState: sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();
            int row = 0 ;
            while (rs.next()) {
                row++;
                use = rs.getInt(1);
                if (getDebug()) getLogger().debug("MasterProcessor::useOldLogState (2) USE OLD LOG ? : "+useOldLogTable);
            }
            if (row == 0 || use < 0 || use > 2) {
                 throw new RservException(" row == 0 || use < 0 || use >2");
            }
        } catch (Exception e) {
            throw new RservException("MasterProcessor::useOldLogState: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return use;
    }

    /*
    */
    /* change active log

    */
    private void changeActiveLog(int logId) throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;

        String sql = "select setval('_rserv_active_log_id_', " + logId + ")";
        getLogger().debug("MasterProcessor::changeActiveLog: sql=" + sql);

        try {
            stmt = getConnection().prepareStatement(sql);
            stmt.executeQuery();
            getLogger().info("MasterProcessor::changeActiveLog: LOG " + logId + " IS ACTIVE NOW");

        } catch (Exception e) {
            getLogger().error("MasterProcessor::changeActiveLog: ", e);
            throw new RservException("MasterProcessor::changeActiveLog: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }
    }

    private void setLogStatus(int status) throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;

        String sql = "select setval('_rserv_old_log_status_', " + status + ")";
        getLogger().debug("MasterProcessor::setLogStatus: sql=" + sql);

        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();

        } catch (Exception e) {
            getLogger().error("MasterProcessor::setLogStatus: ", e);
            throw new RservException("MasterProcessor::setLogStatus: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }


    }


    /*
    */

    private void markLog() throws RservException {
        lock(SHARED);
        activeLogTable = getActiveLog(true);
        useOldLogTable = useOldLogTable(true);
        lockTable();
        unlock(SHARED);
    }

    /*
     * Read last succeeded sync ids NEW - 2LOG
    */

    private USTable getUSTable() throws RservException {
        if (usTable != null) {
            return usTable;
        }
        validateInit();
        usTable = new USTable();
        String lastSyncid = getLastSync().getLastSyncid();

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;

        sql = "select syncid, synctime, minid, maxid, active " +
        "from _RSERV_SYNC_ where server =? ";
        if (lastSyncid != null) {
            sql += " and syncid > " + lastSyncid;
        }
        sql += " order by minid";

        getLogger().debug("MasterProcessor::getUSTable sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            stmt.setInt(1, server);
            //stmt.setInt(2, server);
            rs = stmt.executeQuery();
            while (rs.next()) {
                USTableRow usRow = new USTableRow();
                int syncid = rs.getInt(1); // just syncid - may not be confirmed by slave
                int minid = rs.getInt(3);
                int maxid = rs.getInt(4);
                String active = rs.getString(5);

                usRow.setSyncid(syncid);
                usRow.setMinid(minid);
                usRow.setMaxid(maxid);

                if (active != null) {
                    StringTokenizer st = new StringTokenizer(active, ACTIVE_SEP);
                    while (st.hasMoreTokens()) {
                        String token = st.nextToken().trim();
                        if (!usRow.containsKey(token)) {
                            usRow.putActive(token);
                        }
                    }
                } else {
                    getLogger().warn("MasterProcessor::getUSTable active==null");
                }

                // Add row to Us table:
                usTable.add(usRow);

                if (getDebug()) getLogger().debug("MasterProcessor::getUSTable lastSyncid=" + lastSyncid
                + "; minid=" + minid + "; maxid=" + maxid + "; getLastMaxid="
                + lastSync.getLastMaxid() + "; active=" + active);
            }
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::getLastMaxid: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return usTable;
    }

    /*
    */

    protected void initPrepare() throws RservException {
        markLog();
        getTableMap();
        getUSTable();
    }


    /*
    */

    private boolean PrepareTable(String taboid, String tabname, String tabkey, String keytype)
    throws RservException, Exception {

        String cTable = null;
        String oTable = null;

        if (getActiveLog() == ALT_1) {
            cTable = LOG_1;
            oTable = LOG_2;
        } else if (getActiveLog() == ALT_2) {
            cTable = LOG_2;
            oTable = LOG_1;
        } else {
            throw new RservException("MasterProcessor::PrepareTable: unsexpected value for getActiveLog=" +
            getActiveLog());
        }

        // Declare cursor to read from current log:
        //declareLogCursor(taboid, tabname, tabkey, keytype, cTable, oTable, false);
        // boolean gotdata = ReadData(taboid, tabname, tabkey, keytype, cTable, oTable, false);

        boolean gotdata = false;
        if (useOldLogTable()) {
             getLogger().debug("MasterProcessor::PrepareTable -- I AM GOING TO USE OLD LOG --");
             //declareLogCursor(taboid, tabname, tabkey, keytype, cTable, oTable, true);
             gotdata = ReadData(taboid, tabname, tabkey, keytype, oTable, cTable, true);
        }
        gotdata = gotdata | ReadData(taboid, tabname, tabkey, keytype, cTable, oTable, false);

        if (getDebug()) getLogger().debug("MasterProcessor::PrepareTable gotdata="+gotdata);

        return gotdata;
    }

    /*
    */

    protected boolean prepareTables() throws RservException {

        boolean gotdata = false;

        try {
            TableMap tableMap = getTableMap();
            int count = 0;
            tableMap.setIterator();

            while (tableMap.hasNext()) {
                String taboid = tableMap.nextRowKey();
                tableMap.setRow(taboid);
                String tabname =  tableMap.getRelName();
                String tabkey =  tableMap.getAttName();
                String keytype = tableMap.getKeyType();
                String oidkey = (tabkey.equals("oid")) ? "_" + tabname + ".oid," : "";
                if (getDebug()) getLogger().debug("MasterProcessor::prepareTables inside iterator while: taboid="
                + taboid + "; tabname=" + tabname + "; tabkey=" + tabkey +
                "; oidkey=" + oidkey);
                boolean gotdataPart = PrepareTable(taboid, tabname, tabkey, keytype);
                gotdata = !gotdata ? gotdataPart : gotdata;

            }
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::prepareTables: " + e.toString());
        }

        return gotdata;
    }


    /* Declare 2 log cursor cursor (current & old):
    */

    private ResultSet executeLogCursor(
    String taboid,
    String tabname,
    String tabkey,
    String keytype,
    String cTable,
    String oTable,
    boolean useOldLogTable) throws RservException {
        ResultSet rs = null;
        Statement stmt = null;


        StringBuffer sqlBuff = new StringBuffer();
        //sqlBuff.append("DECLARE c cursor FOR ")
        sqlBuff.append(" SELECT l.logid, l.deleted, l.key, _")
        .append(tabname)
        .append(".* ")
        .append(" FROM ")
        .append(cTable)
        .append(" l LEFT JOIN ")
        .append(tabname)
        .append(" _")
        .append(tabname)
        .append(" ON l.key::")
        .append(keytype)
        .append("= _")
        .append(tabname)
        .append(".")
        .append(tabkey)
        .append(" WHERE l.reloid = ")
        .append(taboid)
        .append(" ")
        .append(getSinfo());

        if (useOldLogTable) {
            sqlBuff.append(" ")
            .append("and not exists (")
            .append("select lc.logid from ")
            .append(oTable)
            .append(" lc where lc.reloid = ")
            .append(taboid)
            .append(" and lc.key = l.key)");
        }

        String sql = sqlBuff.toString();
        getLogger().debug("MasterProcessor::executeLogCursor: sql=" + sql);

        try {
            stmt = getConnection().createStatement();
            rs = stmt.executeQuery(sql);

        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::executeLogCursor: " + e.toString());
        } finally {
            generalClose(stmt, null);
        }

        return rs;
    }


    /*
     * Read last succeeded sync id that was synchronized with slave
    */

    private LastSyncData getLastSync() throws RservException {
        if (lastSync != null) {
            return lastSync;
        }
        validateInit();
        lastSync = new LastSyncData();

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;

        sql = "select syncid, synctime, minid, maxid, active from _RSERV_SYNC_" +
        " where server = ? and syncid = (select max(syncid) from" +
        " _RSERV_SYNC_ where server = ? and status > 0)";
        getLogger().debug("MasterProcessor::getLastSync sql=" + sql);

        try {
            stmt = getConnection().prepareStatement(sql);
            stmt.setInt(1, server);
            stmt.setInt(2, server);
            rs = stmt.executeQuery();
            while (rs.next()) {
                String lastSyncid = rs.getString(1);
                String lastSynctime = rs.getString(2);
                String lastMinid = rs.getString(3);
                String lastMaxid = rs.getString(4);
                String lastActive = rs.getString(5);
                if (lastActive == null)
                    getLogger().warn("MasterProcessor::getLastSync lastActive==null");
                lastSync.setLastSyncid(lastSyncid);
                lastSync.setLastSynctime(lastSynctime);
                lastSync.setLastMinid(lastMinid);
                lastSync.setLastMaxid(lastMaxid);
                lastSync.setLastActive(lastActive);
                if (getDebug()) getLogger().debug("MasterProcessor::getLastSync lastSyncid=" + lastSyncid
                + "; lastSynctime=" + lastSynctime
                + "; lastMinid=" + lastMinid
                + "; lastMaxid=" + lastMaxid + "; getLastMaxid=" +
                lastSync.getLastMaxid()
                + "; lastActive=" + lastActive);
            }
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::setLastSync: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return lastSync;
    }


    private String getSinfo() throws RservException {
        if (sinfo != null) {
            if (getDebug()) getLogger().debug("MasterProcessor::getSinfo sinfo(1)=" + sinfo);
            return sinfo;
        }
        validateInit();
        LastSyncData lastSync = getLastSync();
        StringBuffer sinfoBuff = null;
        // sync info
        if (getDebug()) getLogger().debug("MasterProcessor::getSinfo lastSync.getLastMaxid()=" +
        lastSync.getLastMaxid());
        if (lastSync.getLastMaxid() != null &&
        lastSync.getLastMinid() != null) {
            sinfoBuff = new StringBuffer();
            sinfoBuff.append(" and l.logid >=")
            .append(lastSync.getLastMinid())
            .append(" and (l.logid >= ")
            .append(lastSync.getLastMaxid());

            if (lastSync.getLastActive() != null &&
            !lastSync.getLastActive().trim().equals("")) {
                sinfoBuff.append(" or l.logid in (")
                .append(lastSync.getLastActive())
                .append(")");
            }
            sinfoBuff.append(")");
            sinfo = sinfoBuff.toString();
        }
        if (getDebug()) getLogger().debug("MasterProcessor::getSinfo sinfo(2)=" + sinfo);

        return (sinfo == null) ? "" : sinfo;
    }

    /*  Main method to build the Snapshot - reads data in snapData
    */

    private boolean ReadData(
    String taboid,
    String tabname,
    String tabkey,
    String keytype,
    String cTable,
    String oTable,
    boolean useOldLogTable) throws RservException {

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;
        boolean gotdata = false;
        SnapshotDataBlock deletedBlock = null;
        SnapshotDataBlock updatedBlock = null;
        SnapshotDataBlock insertedBlock = null;
        int columnCount = 0;

        sql = "select ";
        long syncid = 0;
        USTable usTable = getUSTable();
        boolean lastRow = false;
        int row = 0;

        getLogger().debug("MasterProcessor::ReadData BEGIN");
        try {
            //for ( ; ; ) {
                //rs = fetchCursor(FETCH_MAX); // this is table specific
                rs = executeLogCursor(taboid, tabname, tabkey, keytype, cTable, oTable, useOldLogTable);
                columnCount = rs.getMetaData().getColumnCount();
                // columnCount = getTableColumnCache().size(tabname) + 3;

                while (rs.next()) {
                    //
                    // Find to what SyncID this change belongs:
                    //
                    long logid = rs.getInt(1);      // $row[0]
                    int deleted = rs.getInt(2);     // $row[1]
                    boolean useoid = (tabkey == "oid");
                    String key = rs.getString(3);   // $row[2]
                    getLogger().debug("MasterProcessor::ReadData logid="+logid+"; deleted="+deleted+"; key="+key+"; tabkey="+tabkey+"; keytype="+keytype);

                    syncid = getSnapshotSyncid(logid, usTable);
                    //syncid = syncid == null ? SnapshotDataRow.THIS : syncid; // this snapshot

                    if (deleted > 0) { // deleted
                        getLogger().debug("MasterProcessor::ReadData ADDING DELETED TO SNAP DATA");
                        deletedBlock = getDataBlock(deletedBlock,
                            columnCount, CommandType.DELETE, tabname, key, useoid, syncid, rs);
                        row++;
                    } else if (deleted == 0) { // update
                        getLogger().debug("MasterProcessor::ReadData ADDING UPDATED TO SNAP DATA");
                        updatedBlock = getDataBlock(updatedBlock,
                            columnCount, CommandType.UPDATE, tabname, key, useoid, syncid, rs);
                        row++;
                    } else { // insert
                        getLogger().debug("MasterProcessor::ReadData ADDING INSERTED TO SNAP DATA");
                        insertedBlock = getDataBlock(insertedBlock,
                            columnCount, CommandType.INSERT, tabname, key, useoid, syncid, rs);
                        row++;
                    }
                } // end of while
                //

            //}
            //if (getDebug())
            getLogger().debug("MasterProcessor::ReadData row="+row);
            if (row > 0) {
                 GetSYNCID();
                 if (deletedBlock != null) snapData.addBlock(deletedBlock);
                 if (insertedBlock != null) snapData.addBlock(insertedBlock);
                 if (updatedBlock != null) snapData.addBlock(updatedBlock);
                 gotdata = true;
            }

        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::ReadData: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return gotdata;
    }


    /*
    */

    private SnapshotDataBlock getDataBlock(
    SnapshotDataBlock dataBlock,
    int columnCount,
    int command,
    String tabname,
    String key,
    boolean useoid,
    long syncid,
    ResultSet rs) throws RservException, Exception {

        //if (getDebug())
        getLogger().debug("\n MasterProcessor:: getDataBlock BEGIN; tabname="+tabname+
        "; dataBlock="+dataBlock+"; command="+ command +
        "\n");
        if (dataBlock == null) {
             dataBlock =
             new SnapshotDataBlock(
                 new SnapshotCommand(command), tabname);
             getLogger().debug("\n MasterProcessor:: getDataBlock BEGIN; dataBlock == null - create new one for table "+tabname+
             "; command="+ command);
        }

        getLogger().debug(" MasterProcessor:: getDataBlock param="+dataBlock.getParam()+"; key="+key+"; syncid="+syncid);

        SnapshotDataRow dataRow = new SnapshotDataRow(key, syncid, useoid);
        if (command == CommandType.UPDATE || command == CommandType.INSERT) {
             int i = (useoid) ? 3 : 4;
             try {
                for ( ; i<=columnCount; i++) {
                     String data = rs.getString(i);
                     dataRow.add(data);
                }
             } catch (Exception e) {
                 getLogger().error("MasterProcessor:: getDataBlock: ", e);
                 throw new RservException("MasterProcessor:: getDataBlock: illegal command="+e.toString());
             } finally {
                  // generalClose(null, rs);
             }

        } else if (command != CommandType.DELETE) {
             throw new RservException("MasterProcessor:: getDataBlock: illegal command="+command);
        }

        dataBlock.addRow(dataRow);
        getLogger().debug("MasterProcessor:: getDataBlock: dataBlock.size()=" + dataBlock.size()+"; key="+dataBlock.getRow(dataBlock.size()-1).getKeyInt());


        return dataBlock;
    }


    /*
       Gets last syncid that belongs to this snapshot
    */

    private long getSnapshotSyncid(long logid, USTable usTable) {
        long syncid = SnapshotDataRow.THIS;
        for (int i = 0; i < usTable.size(); i++) {
            if (logid < usTable.get(i).getMinid()) { // LT than minid?
                syncid = usTable.get(i).getSyncid(); // belongs to this
                break;
            }
            if (logid > usTable.get(i).getMaxid()) { // GE than maxid?
                continue;                           // belongs to the next
            }

            if (usTable.get(i).containsKey(
                new Long(logid).toString())) { // was active?
                    continue; // belongs to the next
            }

            // (xid > minid) AND (xid < maxid) AND was committed
            syncid = usTable.get(i).getSyncid();
            break;
        }
        return syncid;
    }

    /*
    */

    private ResultSet fetchCursor(int numRows) throws RservException {
        if (numRows < 0) {
            throw new RservException("MasterProcessor::fetchCursor: numRows < 0");
        }

        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = "fetch " + numRows + " in c";
        getLogger().debug("MasterProcessor::fetchCursor: sql=" + sql);

        try {
            stmt = getConnection().prepareStatement(sql);
            rs = stmt.executeQuery();
        } catch (Exception e) {
            getLogger().error("MasterProcessor::fetchCursor: ", e);
            rs = null;
        } finally {
            generalClose(stmt, null);
        }

        return rs;
    }

    /* Get syncid for new snapshot
    */

    private long GetSYNCID() throws RservException {
        if (syncid != T_NO) {
            return syncid;
        }
        ResultSet rs = null;
        Statement stmt = null;
        String sql = "select nextval('_rserv_sync_seq_')";
        getLogger().debug("MasterProcessor::GetSYNCID: sql=" + sql);
        long rc = T_NO;

        try {
            stmt = getConnection().createStatement();
            rs = stmt.executeQuery(sql);
            while (rs.next()) {
                rc = rs.getLong(1);
            }
            if (rc <= 0) {
                throw new RservException(
                "Cannot get nextval('_rserv_sync_seq_')");
            }

            CommandType comType = new SnapshotCommand(CommandType.SYNCID);
            SnapshotDataBlock snapDataBlock = new SnapshotDataBlock(comType,
                (new Long(rc)).toString());
            snapData.addBlock(snapDataBlock);

        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::GetSYNCID: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return rc;
    }



    /* MAP oid --> tabname, keyname
     * retrieve it once since table struct does not change
    */

    protected TableMap getTableMap() throws RservException {
        if (getDebug()) getLogger().debug("MasterProcessor::getTableMap: BEGIN");
        if (tableMap != null) {
            if (getDebug()) getLogger().debug("MasterProcessor::getTableMap:  tableMap=" +
            tableMap);
            return tableMap;
        }

        if (getDebug()) getLogger().debug("MasterProcessor::getTableMap: !tableMapSet - let's set it");
        ResultSet rs = null;
        Statement stmt = null;
        String rc = null;
        String sql = null;

        /* SQL_TABLE_MAP =
        sql = "select pgc.oid, pgc.relname, pga.attname" +
        " from _RSERV_TABLES_ rt, pg_class pgc, pg_attribute pga" +
        " where pgc.oid = rt.reloid and pga.attrelid = rt.reloid" +
        " and pga.attnum = rt.key";
        */
        try {
            tableMap = new TableMap();
            stmt = getConnection().createStatement();
            rs = stmt.executeQuery(SQL_TABLE_MAP);
            getLogger().debug("MasterProcessor::getTableMap: executed sql=" + SQL_TABLE_MAP);
            int row = 0;
            while (rs.next()) {
                row++;
                String oid = rs.getString(1);
                String relName = rs.getString(2);
                String attName = rs.getString(3);
                String keyType = rs.getString(4);

                // Adding element with row key == oid
                tableMap.addElement(oid).
                setOid(oid).
                setRelName(relName).
                setAttName(attName).
                setKeyType(keyType);
            }

            if (getDebug()) getLogger().debug("MasterProcessor::getTableMap: retrieved rows=" + row);
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::getTableMap: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }

        return tableMap;
    }

    protected String getErrorPrefix() {
        return  "MasterProcessor:: ";
    }

   /* Remember this snapshot info
    */

    protected void rememberSnapshot() throws RservException {
        validateInit();
        String sql_2 = "select _rserv_sync_(" + server + ")";
        ResultSet rs = null;
        Statement stmt = null;

        try {
            stmt = getConnection().createStatement();
            rs = stmt.executeQuery (sql_2);
            snapshotRemembered = true;
        } catch (Exception e) {
            throw new RservException("MasterProcessor::rememberSnapshot: "
            + e.toString());
        } finally {
            generalClose(stmt, rs);
        }
    }

    /* Updates _RSERV_SYNC_ on Master with Slave SyncID
    */

    protected void SyncSyncID(long syncid) throws RservException {
        validateInit();
        ResultSet rs = null;
        Statement stmt = null;
        String sql = "select synctime, status from _RSERV_SYNC_" +
        " where server = " + server + " and syncid = " +
        syncid +
        " for update";
        getLogger().debug("MasterProcessor::SyncSyncID: sql(1)=" + sql);
        long rc = T_NO;

        try {
            stmt = getConnection().createStatement();
            rs = stmt.executeQuery(sql);
            int row = 0;
            while (rs.next()) {
                row++;
                rc = rs.getLong(2);
            }
            if (row == 0) {
                throw new RservException(
                "MasterProcessor::SyncSyncID: No SyncID " + syncid
                + " found for server " + server);
            } else if (rc > 0) {
                if (getDebug()) getLogger().debug("MasterProcessor::SyncSyncID: "
                + syncid + " for server " + server + " already updated");
                return;
            }

            sql = "update _RSERV_SYNC_" +
            " set synctime = now(), status = 1" +
            " where server = " + server + " and syncid = " + syncid;
            getLogger().debug("MasterProcessor::SyncSyncID: sql(2)=" + sql);

            rc = stmt.executeUpdate(sql);

            sql =  "delete from _RSERV_SYNC_" +
            " where server = " + server + " and syncid < " + syncid;
            getLogger().debug("MasterProcessor::SyncSyncID: sql(3)=" + sql);

            rc = stmt.executeUpdate(sql);

        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::SyncSyncID: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }
    }

    /* this thing is multi-transationary
    * @param - howold time in sec how long keep the log record
    */
    protected int cleanLog(int tout) throws RservException, Exception {
        getLogger().info("MasterProcessor::cleanLog BEGIN tout="+tout);
        int olt = 0;
        try {
            // Optimizer hints:
            setOptimizerHints();
            begin();
                olt = prepareCleanLog();
            commit();

            //
            // If old log has status 1 (MARK_CLEAN) then just truncate it and return.
	        //
            if (useolt == MARK_CLEAN) {
                truncateTable(olt);
                postTruncateTable(olt);
                begin();
                    lock(EXCLUSIVE);
                    setLogStatus(TRUNCATE);
                    unlock(EXCLUSIVE);
                commit();
                return olt;
            }

            getLogger().info("MasterProcessor::cleanLog: WAITING (" + tout + " millisec cycle); Log"+olt + " is not clean yet");
            waitUnreplicated(olt, tout);
    
            //
            // Ok - all records from _RSERV_LOG_${olt}_ are replicated.
    	    // Now advise "Prepare" to ignore old log table in queries
    	    // but mark old log as clean only after truncate.
    	    //
            begin();
                lock(EXCLUSIVE);
                setLogStatus(MARK_CLEAN);
                unlock(EXCLUSIVE);
            commit();
    
            //
            // Now we can just truncate old log table. Truncate will
            // grab AccessExclusive lock and so will wait for "Prepare"
            // if "Prepare" decided to read from old log table before
            // we changed old log status. This is because of "Prepare"
            // grab SHARE ROW lock on old log table while holding
            // "altid" lock and so *we wait for "Prepare"*, which is not
            // bad and *do not block "Prepare"*, which is very good.
            //
            truncateTable(olt);
            begin();
                lock(EXCLUSIVE);
                setLogStatus(TRUNCATE);
                unlock(EXCLUSIVE);
            commit();

            getLogger().info("MasterProcessor::cleanLog: END ");
        } catch (Throwable e) {
            try {
                 rollback();
            } catch (Exception ex) {}
            getLogger().error("MasterProcessor::cleanLog: ", e);
            throw new RservException("MasterProcessor::cleanLog: " + e.toString());
        } finally {
        
        }
        return olt;
    }

    /* Now wait as long as there are unreplicated records in old log
    */

    private void waitUnreplicated(int logId, int tout) throws RservException {
        ResultSet rs = null;
        ResultSet rsLogid = null;
        Statement stmt = null;
        Statement stmtLogid = null;
        String sql = null;
        Hashtable sinfoHash = new Hashtable();
        sql = "";

        try {
             for (; ; ) {
                 String server = "";
                 begin();
                     setTransactionSerializable();
                     sql = "select s.server, s.minid, s.maxid, s.active " +
        	        "from _RSERV_SYNC_ s " +
        	        "where s.syncid = (select max(s2.syncid) from " +
        	        "_RSERV_SYNC_ s2 where s2.server = s.server and s2.status > 0)";
                    getLogger().debug("MasterProcessor::waitUnreplicated: sql(1)=" + sql);
                    stmt = getConnection().createStatement();
                    rs = stmt.executeQuery(sql);

                    sinfoHash.clear();
                    String sqlAnd = "";
                    while (rs.next()) {
                        server = rs.getString(1);
                        long minid =  rs.getLong(2);
                        long maxid =  rs.getLong(3);
                        String active =  rs.getString(4);

            	        sqlAnd = "and l.logid >= " + minid + " and (l.logid >= " + maxid;
            	        sqlAnd += (active != null && !active.equals("")) ?
                            " or l.logid in (" + active + ")" : "";
            	        sqlAnd += ")";
                        if (sinfoHash.contains(server)) {
                             sinfoHash.remove(server);
                        }

            	        sinfoHash.put(server, sqlAnd);
            	    }

                    boolean found = false;
                    //
                    // For each server and for each table
            	    //
                    Iterator sinfoIterator = sinfoHash.keySet().iterator();
                    while (sinfoIterator.hasNext()) {
                        server = (String) sinfoIterator.next();
                        getTableMap().setIterator();
                        while (getTableMap().hasNext()) {
                            String taboid = tableMap.nextRowKey();
                            sql = "select l.logid from _RSERV_LOG_"+ logId + "_ l "
             	                   + "WHERE l.reloid = " + taboid + " ";
             	            sql += (String) sinfoHash.get(server);
             	            sql += " LIMIT 1";
                            getLogger().debug("MasterProcessor::waitUnreplicated: sql(2)=" + sql);
                            stmtLogid = getConnection().createStatement();
                            rsLogid = stmt.executeQuery(sql);
                            while (rsLogid.next()) {
                            	found = true;
                                break;
                            }
                            if (found) break;
                        }
                        if (found) break;
                    }

                    rollback();

                    if (!found) break;
                    Thread.sleep(tout);
             } // end of for (;;;) loop

        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::waitUnreplicated: " + e.toString());
        } finally {
            generalClose(stmt, rs);
            generalClose(stmtLogid, rsLogid);
        }
    }

    /*
    * @param - howold time in sec how long keep the log record
    */
    protected int prepareCleanLog() throws RservException {
        int olt = 0;
        getLogger().debug("MasterProcessor::prepareCleanLog BEGIN");
        try {
            lock(EXCLUSIVE);
            int alt = getActiveLog(true);
            olt = notAlt(alt);
            useolt = useOldLogState();

            if (useolt == TRUNCATE) { // Old log is clean - switch logs
                 setLogStatus(USE_OLD_NEW_LOG);
                 changeActiveLog(olt);
                 alt = olt;
                 olt = notAlt(alt);
            }

            //
            // Else - do not switch logs and cleanup old log
            //
            unlock(EXCLUSIVE);
            //
            // Make sure that no triggers use old log table.
        	// Note that triggers always place ROW EXCLUSIVE lock
        	// on active table while holding share "altid" lock,
        	// so *we wait for triggers* here and do not block
        	// any triggers while waiting. "Prepare" uses ROW SHARE
        	// lock on old log - so no conflict at this point.
        	//
            lockTable(olt);

            //
            // Got SHARED lock ==> all ROW EXCLUSIVE LOCKs from triggers
        	// released, ie triggers now use new active table only
        	//

        } catch (Exception e) {
            getLogger().error("MasterProcessor::prepareCleanLog: ", e);
            throw new RservException(
            "MasterProcessor::prepareCleanLog: " + e.toString());
        } finally {
            //generalClose(stmt, rs);
        }

        return olt;
    }


    /*
    * @param - howold time in sec how long keep the log record
    */

    protected void cleanLogOld(int howold) throws RservException {
        validateInit();
        ResultSet rs = null;
        Statement stmt = null;
        String sql = "select rs.maxid, rs.active from _RSERV_SYNC_ rs" +
        " where rs.syncid = (select max(rs2.syncid) from _RSERV_SYNC_ rs2" +
        " where rs2.server = rs.server and rs2.status > 0) order by rs.maxid";

        getLogger().debug("MasterProcessor::cleanLog: sql(1)=" + sql);
        long rc = T_NO;
        Hashtable active = new Hashtable();
        try {
            stmt = getConnection().createStatement();
            rs = stmt.executeQuery(sql);
            long maxid = 0;
            int row = 0;
            String pattern = ",";
            while (rs.next()) {
                row++;
                rc = rs.getLong(1);
                maxid = maxid == 0 ? rc : maxid;
                if (maxid > rc) { // what the hell does this mean?
                    continue;
                }
                String activeValue = rs.getString(2);
                StringTokenizer st = new StringTokenizer(activeValue, pattern);
                while (st.hasMoreTokens()) {
                    String token = st.nextToken().trim();
                    if (!active.containsKey(token)) {
                        active.put(token, token);
                    }
                }
                if (maxid == 0) {
                    if (getDebug()) getLogger().debug("MasterProcessor::cleanLog: No maxid");
                    return;
                }

                String sinfo = "logid < " + maxid;
                Iterator iterator = active.keySet().iterator();
                String alist = "";
                int count = 0;
                while (iterator.hasNext()) {
                    count++;
                    String key = (String) iterator.next();
                    alist = (count == 1) ? key : alist + ", " +  key;
                }
                if (alist != null) {
                    sinfo += " and logid not in (" + alist + ")";
                }

                sql = "delete from _RSERV_LOG_ where "
                + "logtime < now() - '" + howold
                + " second'::interval and " + sinfo;
                getLogger().debug("MasterProcessor::cleanLog: sql(2)=" + sql);
                stmt.executeUpdate(sql);
            }
        } catch (Exception e) {
            getLogger().error("MasterProcessor::cleanLog: ", e);
        } finally {
            generalClose(stmt, rs);
        }
    }

    /* Vadim calls it $olt
    */
    private int notAlt(int alt) {
         return (alt == ALT_1) ? ALT_2 : ALT_1;
    }

    // Template method for query processing:

    /*
    */

    private void execTemplate() throws RservException {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        String sql = null;

        sql = "select ";

        getLogger().debug("MasterProcessor:: sql=" + sql);
        try {
            stmt = getConnection().prepareStatement(sql);
            stmt.setInt(1, server);
            stmt.setInt(2, server);
            rs = stmt.executeQuery();
            while (rs.next()) {
                //String syncid = rs.getString(1);
                // String lastSynctime = rs.getString(2);

                getLogger().debug("" );
            }
        } catch (Exception e) {
            throw new RservException(
            "MasterProcessor::: " + e.toString());
        } finally {
            generalClose(stmt, rs);
        }
    }
}
