com.antsdb.saltedfish.sql.Session.java Source code

Java tutorial

Introduction

Here is the source code for com.antsdb.saltedfish.sql.Session.java

Source

/*-------------------------------------------------------------------------------------------------
 _______ __   _ _______ _______ ______  ______
 |_____| | \  |    |    |______ |     \ |_____]
 |     | |  \_|    |    ______| |_____/ |_____]
    
 Copyright (c) 2016, antsdb.com and/or its affiliates. All rights reserved. *-xguo0<@
    
 This program is free software: you can redistribute it and/or modify it under the terms of the
 GNU Affero General Public License, version 3, as published by the Free Software Foundation.
    
 You should have received a copy of the GNU Affero General Public License along with this program.
 If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>
-------------------------------------------------------------------------------------------------*/
package com.antsdb.saltedfish.sql;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.InputMismatchException;
import org.antlr.v4.runtime.NoViableAltException;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.slf4j.Logger;

import com.antsdb.saltedfish.sql.vdm.Parameters;
import com.antsdb.saltedfish.sql.vdm.Script;
import com.antsdb.saltedfish.sql.vdm.Transaction;
import com.antsdb.saltedfish.sql.vdm.VdmContext;
import com.antsdb.saltedfish.util.UberUtil;

public class Session {
    static Logger _log = UberUtil.getThisLogger();
    static AtomicInteger _id = new AtomicInteger();

    Orca orca;
    Map<String, Object> variables = new ConcurrentHashMap<String, Object>();
    boolean autoCommit = true;
    String currentNameSpace = "TEST";
    SqlParserFactory fac;
    Map<Integer, PreparedStatement> prepared = new HashMap<Integer, PreparedStatement>();
    String user;
    int resetAutoCommit = 0;
    ConcurrentMap<Integer, TableLock> tableLocks = new ConcurrentHashMap<>();

    private Transaction trx;
    private boolean isClosed = false;
    private long lastInsertId;
    private int id = _id.incrementAndGet();
    private String protocolCharset = "latin1";
    private SessionParameters parameters = new SessionParameters();

    public Session(Orca orca, SqlParserFactory fac, String user) {
        this.orca = orca;
        this.fac = fac;
        this.user = user;
    }

    public Object run(String sql) throws SQLException {
        return run(sql, new Parameters());
    }

    public Object run(String sql, Parameters params) throws SQLException {
        return run(new ANTLRInputStream(sql), params);
    }

    public Object run(CharStream cs) throws SQLException {
        return run(cs, new Parameters());
    }

    public Object run(CharStream cs, Parameters params) throws SQLException {
        if (this.isClosed) {
            throw new OrcaException("session is closed");
        }

        // main stuff

        startTrx();
        Script script = parse(cs);
        VdmContext ctx = new VdmContext(this, script.getVariableCount());
        return script.run(ctx, params, 0);
    }

    public Script parse(CharStream cs) {
        if (this.isClosed) {
            throw new OrcaException("session is closed");
        }

        startTrx();

        // check the global statement cache

        Script script = this.getOrca().getCachedStatement(cs.toString());

        // parse it for real if it is not cached

        if (script == null) {
            try {
                script = this.fac.parse(this, cs);
            } catch (ParseCancellationException x) {
                if (x.getCause() instanceof InputMismatchException) {
                    InputMismatchException xx = (InputMismatchException) x.getCause();
                    String offend = xx.getOffendingToken().getText();
                    throw new OrcaException(x, "Invalid SQL. Offending token: {}", offend);
                }
                if (x.getCause() instanceof NoViableAltException) {
                    NoViableAltException xx = (NoViableAltException) x.getCause();
                    String offend = xx.getOffendingToken().getText();
                    throw new OrcaException(x, "Invalid SQL. Offending token: {}", offend);
                }
                throw new OrcaException("Invalid SQL", x);
            } catch (CompileDdlException x) {
                script = new Script(null, x.nParameters, 100, cs.toString());
            }
            if (_log.isTraceEnabled()) {
                _log.trace("{}-{}: {}", getId(), script.hashCode(), cs.toString());
            }
            if (script.worthCache()) {
                this.getOrca().cacheStatement(script);
            }
        }

        return script;
    }

    public Script parse(String text) {
        return parse(new ANTLRInputStream(text));
    }

    public String getCurrentCatalog() {
        return null;
    }

    public Session setCurrentNamespace(String namespace) {
        this.currentNameSpace = namespace;
        return this;
    }

    public String getCurrentNamespace() {
        return this.currentNameSpace;
    }

    public String getCurrentSchema() {
        return this.currentNameSpace;
    }

    public Orca getOrca() {
        return this.orca;
    }

    public Map<String, Object> getVariables() {
        return this.variables;
    }

    public Object getVariable(String key) {
        return this.variables.get(key);
    }

    public void setAutoCommit(boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    public boolean isAutoCommit() {
        return this.autoCommit;
    }

    public void commit() {
        endTransaction(true);
    }

    public void rollback() {
        endTransaction(false);
    }

    public void close() {
        rollback();
        this.orca.sessions.remove(this);
        this.isClosed = true;
    }

    public boolean isClosed() {
        return this.isClosed;
    }

    private void endTransaction(boolean success) {
        // only if there is a transaction

        if (this.trx == null) {
            return;
        }

        try {
            // commit/rollback

            long trxid = trx.getTrxId();
            long trxts = -1;
            if (trxid < 0) {
                if (success) {
                    trxts = this.orca.getTrxMan().getNewVersion();
                    _log.trace("commit trxid={} trxts={}", trxid, trxts);
                    this.orca.getHumpback().commit(trxid, trxts);
                } else {
                    _log.trace("rollback trxid={} trxts={}", trxid);
                    this.orca.getHumpback().rollback(trxid);
                    ;
                }
            } else {
                _log.trace("autonomous trxid={}", trxid);
            }

            // notify metadata service 

            if (trx.isDddl()) {
                this.orca.getMetaService().commit(trx, trxts);
            }

            // release all locks

            trx.releaseAllLocks();

            // reset auto commit 

            if (this.resetAutoCommit > 0) {
                this.resetAutoCommit--;
                if (this.resetAutoCommit == 0) {
                    this.autoCommit = true;
                }
            }

            // done

            this.trx.reset();
        } catch (Throwable x) {
            _log.error("fatal!!!", x);
            throw x;
        }
    }

    /**
     * start transaction only if it is not started
     */
    public void startTrx() {
    }

    /**
     * reset transaction time stamp
     */
    public void resetTrxTs() {
        if (this.trx != null) {
            this.trx.newTrxTs();
        }
    }

    public Transaction getTransaction_() {
        return this.trx;
    }

    public Transaction getTransaction() {
        if (this.trx == null) {
            this.trx = new Transaction(this.getOrca().getTrxMan());
        }
        return this.trx;
    }

    public void setParameter(String name, Object value) {
        this.parameters.setParameter(name, value);
    }

    public int getId() {
        return this.id;
    }

    public PreparedStatement prepare(String sql) throws SQLException {
        PreparedStatement stmt = new PreparedStatement(this, sql);
        this.prepared.put(stmt.hashCode(), stmt);
        return stmt;
    }

    public PreparedStatement getPrepared(int id) {
        return this.prepared.get(id);
    }

    public void closePrepared(int id) {
        this.prepared.remove(id);
    }

    public String getUser() {
        return user;
    }

    public SqlParserFactory getParserFactory() {
        return this.fac;
    }

    public int getLockTimeoutMilliSeconds() {
        return this.parameters.getLockTimeout();
    }

    public String getProtocolCharset() {
        return this.protocolCharset;
    }

    public void setProtocolCharset(String cs) {
        this.protocolCharset = cs;
    }

    /**
     * last insert id is the last value generated for a auto_increment column.
     * 
     * @see https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-usagenotes-last-insert-id.html
     * @param value
     */
    public void setLastInsertId(long value) {
        this.lastInsertId = value;
    }

    public long getLastInsertId() {
        return this.lastInsertId;
    }

    /**
     * reset auto commit to true after the current transaction. it is required by START TRANSACTION statement
     */
    public void resetAutoCommitAfterTrx() {
        this.resetAutoCommit = 2;
    }

    public void unlockTable(int tableId) {
        unlockTable(getId(), tableId);
    }

    public void unlockTable(int owner, int tableId) {
        TableLock lock = this.tableLocks.get(tableId);
        if (lock == null) {
            throw new OrcaException("lock is not found");
        }

        // unlock

        int level = lock.getLevel();
        lock.release(owner);

        // unlock all other session

        if (level == LockLevel.EXCLUSIVE) {
            for (Session i : this.orca.sessions) {
                if (i == this) {
                    continue;
                }
                i.unlockTable(owner, tableId);
            }
        }
    }

    public void lockTable(int owner, int tableId, int level, boolean transactional) {
        // guaranteed getting a table lock

        TableLock lock = this.tableLocks.get(tableId);
        if (lock == null) {
            TableLock newone = new TableLock(getId(), tableId);
            lock = tableLocks.putIfAbsent(tableId, newone);
            if (lock == null) {
                lock = newone;
            }
        }

        // lock the sucker

        if (!lock.acquire(owner, level, getLockTimeoutMilliSeconds())) {
            return;
        }

        // is it transactional ?

        if (transactional && (!lock.isTransactional())) {
            Transaction trx = getTransaction();
            trx.addLock(lock);
        }
        if (!transactional && lock.isTransactional()) {
            lock.setTransactional(false);
        }

        // for exclusive lock, we need to lock out all other session

        if (level == LockLevel.EXCLUSIVE) {
            List<Session> undo = new ArrayList<>();
            boolean success = false;
            try {
                for (Session i : this.orca.sessions) {
                    if (i == this) {
                        continue;
                    }
                    i.lockTable(owner, tableId, LockLevel.EXCLUSIVE_BY_OTHER, false);
                    undo.add(i);
                }
                success = true;
            } finally {
                if (!success) {
                    for (Session i : undo) {
                        i.unlockTable(owner, tableId);
                    }
                }
            }
        }
    }

    public void lockTable(int tableId, int lockLevel, boolean transactional) {
        lockTable(getId(), tableId, lockLevel, transactional);
    }

    public void unlockAll() {
        for (Map.Entry<Integer, TableLock> i : this.tableLocks.entrySet()) {
            int tableId = i.getKey();
            TableLock lock = i.getValue();
            if (lock.getLevel() == LockLevel.EXCLUSIVE) {
                unlockTable(getId(), tableId);
            }
        }
    }

    public SessionParameters getParameters() {
        return this.parameters;
    }
}