package io.mycat.backend.postgresql;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import io.mycat.backend.BackendConnection;
import io.mycat.backend.PhysicalDatasource;
import io.mycat.backend.postgresql.packet.Query;
import io.mycat.backend.postgresql.packet.Terminate;
import io.mycat.backend.postgresql.utils.PgSqlApaterUtils;
import io.mycat.net.Connection;
import io.mycat.net.NetSystem;
import io.mycat.route.RouteResultsetNode;
import io.mycat.server.Isolations;
import io.mycat.server.MySQLFrontConnection;
import io.mycat.server.exception.UnknownTxIsolationException;
import io.mycat.server.executors.ResponseHandler;
import io.mycat.server.packet.util.CharsetUtil;
import io.mycat.server.parser.ServerParse;
import io.mycat.util.TimeUtil;
/*************************************************************
* PostgreSQL Native Connection impl
*
* @author Coollf
*
*/
public class PostgreSQLBackendConnection extends Connection implements
BackendConnection {
private static final Query _COMMIT = new Query("commit");
private static final Query _ROLLBACK = new Query("rollback");
/**
* 来自子接口
*/
private boolean fromSlaveDB;
/***
* 用户名
*/
private String user;
/**
* 密码
*/
private String password;
/***
* 对应数据库空间
*/
private String schema;
/**
* 数据源配置
*/
private PostgreSQLDataSource pool;
private Object attachment;
protected volatile String charset = "utf8";
private volatile boolean autocommit;
/****
* PG是否在事物中
*/
private volatile boolean inTransaction = false;
/***
* 响应handler
*/
private volatile ResponseHandler responseHandler;
private boolean borrowed;
private volatile int txIsolation;
private volatile boolean modifiedSQLExecuted = false;
private long lastTime;
private AtomicBoolean isQuit;
// PostgreSQL服务端密码
private int serverSecretKey;
protected volatile int charsetIndex;
private int xaStatus;
private String oldSchema;
// 已经认证通过
private boolean isAuthenticated;
/**
* 元数据同步
*/
private volatile boolean metaDataSyned = true;
private volatile StatusSync statusSync;
/***
* 当前事物ID
*/
private volatile String currentXaTxId;
private long currentTimeMillis;
public PostgreSQLBackendConnection(SocketChannel channel,
boolean fromSlaveDB) {
super(channel);
this.fromSlaveDB = fromSlaveDB;
this.lastTime = TimeUtil.currentTimeMillis();
this.isQuit = new AtomicBoolean(false);
this.autocommit = true;
}
@Override
public boolean isFromSlaveDB() {
return fromSlaveDB;
}
@Override
public String getSchema() {
return schema;
}
@Override
public void setSchema(String newSchema) {
String curSchema = schema;
if (curSchema == null) {
this.schema = newSchema;
this.oldSchema = newSchema;
} else {
this.oldSchema = curSchema;
this.schema = newSchema;
}
}
@Override
public void setAttachment(Object attachment) {
this.attachment = attachment;
}
@Override
public void setLastTime(long currentTimeMillis) {
this.currentTimeMillis = currentTimeMillis;
}
@Override
public void setResponseHandler(ResponseHandler queryHandler) {
this.responseHandler = queryHandler;
}
@Override
public Object getAttachment() {
return attachment;
}
@Override
public boolean isBorrowed() {
return borrowed;
}
@Override
public void setBorrowed(boolean borrowed) {
this.lastTime = TimeUtil.currentTimeMillis();
this.borrowed = borrowed;
}
@Override
public int getTxIsolation() {
return txIsolation;
}
@Override
public boolean isAutocommit() {
return autocommit;
}
@Override
public String getCharset() {
return charset;
}
@Override
public PhysicalDatasource getPool() {
return pool;
}
/**
* @return the user
*/
public String getUser() {
return user;
}
/**
* @param user
* the user to set
*/
public void setUser(String user) {
this.user = user;
}
/**
* @return the password
*/
public String getPassword() {
return password;
}
/**
* @param password
* the password to set
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @param fromSlaveDB
* the fromSlaveDB to set
*/
public void setFromSlaveDB(boolean fromSlaveDB) {
this.fromSlaveDB = fromSlaveDB;
}
/**
* @param pool
* the pool to set
*/
public void setPool(PostgreSQLDataSource pool) {
this.pool = pool;
}
@Override
public boolean isModifiedSQLExecuted() {
return modifiedSQLExecuted;
}
@Override
public long getLastTime() {
return lastTime;
}
@Override
public boolean isClosedOrQuit() {
return isClosed() || isQuit.get();
}
@Override
public void quit() {
if (isQuit.compareAndSet(false, true) && !isClosed()) {
if (isAuthenticated) {// 断开 与PostgreSQL连接
Terminate terminate = new Terminate();
ByteBuffer buf = NetSystem.getInstance().getBufferPool()
.allocate();
terminate.write(buf);
write(buf);
} else {
close("normal");
}
}
}
@Override
public void release() {
if (!metaDataSyned) {// indicate connection not normalfinished
// ,and
// we can't know it's syn status ,so
// close
// it
LOGGER.warn("can't sure connection syn result,so close it " + this);
this.responseHandler = null;
this.close("syn status unkown ");
return;
}
metaDataSyned = true;
attachment = null;
statusSync = null;
modifiedSQLExecuted = false;
setResponseHandler(null);
pool.releaseChannel(this);
}
@Override
public void query(String query) throws UnsupportedEncodingException {
RouteResultsetNode rrn = new RouteResultsetNode("default",
ServerParse.SELECT, query);
synAndDoExecute(null, rrn, this.charsetIndex, this.txIsolation, true);
}
@Override
public void execute(RouteResultsetNode rrn, MySQLFrontConnection sc,
boolean autocommit) throws IOException {
if (!modifiedSQLExecuted && rrn.isModifySQL()) {
modifiedSQLExecuted = true;
}
String xaTXID = sc.getSession2().getXaTXID();
synAndDoExecute(xaTXID, rrn,sc.getCharsetIndex(),
sc.getTxIsolation(), autocommit);
}
private void synAndDoExecute(String xaTxID, RouteResultsetNode rrn,
int clientCharSetIndex, int clientTxIsoLation,
boolean clientAutoCommit) {
String xaCmd = null;
boolean conAutoComit = this.autocommit;
String conSchema = this.schema;
// never executed modify sql,so auto commit
boolean expectAutocommit = !modifiedSQLExecuted || isFromSlaveDB()
|| clientAutoCommit;
if (!expectAutocommit && xaTxID != null && xaStatus == 0) {
clientTxIsoLation = Isolations.SERIALIZABLE;
xaCmd = "XA START " + xaTxID + ';';
currentXaTxId = xaTxID;
}
int schemaSyn = conSchema.equals(oldSchema) ? 0 : 1;
int charsetSyn = (this.charsetIndex == clientCharSetIndex) ? 0 : 1;
int txIsoLationSyn = (txIsolation == clientTxIsoLation) ? 0 : 1;
int autoCommitSyn = (conAutoComit == expectAutocommit) ? 0 : 1;
int synCount = schemaSyn + charsetSyn + txIsoLationSyn + autoCommitSyn;
if(synCount == 0){
String sql = rrn.getStatement();
Query query = new Query(PgSqlApaterUtils.apater(sql));
ByteBuffer buf = NetSystem.getInstance().getBufferPool().allocate();
query.write(buf);
this.write(buf);
return;
}
// TODO COOLLF 此处大锅待实现. 相关 事物, 切换 库,自动提交等功能实现
StringBuilder sb = new StringBuilder();
if (charsetSyn == 1) {
getCharsetCommand(sb, clientCharSetIndex);
}
if (txIsoLationSyn == 1) {
getTxIsolationCommand(sb, clientTxIsoLation);
}
if (autoCommitSyn == 1) {
getAutocommitCommand(sb, expectAutocommit);
}
if (xaCmd != null) {
sb.append(xaCmd);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("con need syn ,total syn cmd " + synCount
+ " commands " + sb.toString() + "schema change:"
+ ("" != null) + " con:" + this);
}
metaDataSyned = false;
statusSync = new StatusSync(xaCmd != null, conSchema,
clientCharSetIndex, clientTxIsoLation, expectAutocommit,
synCount);
String sql = sb.append(PgSqlApaterUtils.apater(rrn.getStatement())).toString();
System.err.println("con="+ this.hashCode() + ":SQL:"+sql);
Query query = new Query(sql);
ByteBuffer buf = NetSystem.getInstance().getBufferPool().allocate();
query.write(buf);
this.write(buf);
metaDataSyned = true;
}
/**
* 获取 更改事物级别sql
* @param
* @param txIsolation
*/
private static void getTxIsolationCommand(StringBuilder sb, int txIsolation) {
switch (txIsolation) {
case Isolations.READ_UNCOMMITTED:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
return;
case Isolations.READ_COMMITTED:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;");
return;
case Isolations.REPEATED_READ:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;");
return;
case Isolations.SERIALIZABLE:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;");
return;
default:
throw new UnknownTxIsolationException("txIsolation:" + txIsolation);
}
}
private void getAutocommitCommand(StringBuilder sb, boolean autoCommit) {
if (autoCommit) {
sb.append("SET autocommit=1;");
} else {
sb.append("begin transaction;");
}
}
private static void getCharsetCommand(StringBuilder sb, int clientCharIndex) {
sb.append("SET names '").append(CharsetUtil.getCharset(clientCharIndex).toUpperCase()).append("';");
}
@Override
public void commit() {
ByteBuffer buf = NetSystem.getInstance().getBufferPool().allocate();
_COMMIT.write(buf);
this.write(buf);
}
@Override
public boolean syncAndExcute() {
StatusSync sync = this.statusSync;
if (sync == null) {
return true;
} else {
boolean executed = sync.synAndExecuted(this);
if (executed) {
statusSync = null;
}
return executed;
}
}
@Override
public void rollback() {
ByteBuffer buf = NetSystem.getInstance().getBufferPool().allocate();
_ROLLBACK.write(buf);
this.write(buf);
}
@SuppressWarnings("unchecked")
@Override
public void onReadData(int got) throws IOException {
ByteBuffer buf = getReadBuffer();
if (buf != null) {
this.handler.handle(this, buf, 0, got);
buf.clear();// 使用完成后清理
} else {
System.err.println("getReadBuffer()为空");
}
}
public void setServerSecretKey(int serverSecretKey) {
this.serverSecretKey = serverSecretKey;
}
/**
* @return the serverSecretKey
*/
public int getServerSecretKey() {
return serverSecretKey;
}
/**
* @return the responseHandler
*/
public ResponseHandler getResponseHandler() {
return responseHandler;
}
private static class StatusSync {
private final String schema;
private final Integer charsetIndex;
private final Integer txtIsolation;
private final Boolean autocommit;
private final AtomicInteger synCmdCount;
private final boolean xaStarted;
public StatusSync(boolean xaStarted, String schema,
Integer charsetIndex, Integer txtIsolation, Boolean autocommit,
int synCount) {
super();
this.xaStarted = xaStarted;
this.schema = schema;
this.charsetIndex = charsetIndex;
this.txtIsolation = txtIsolation;
this.autocommit = autocommit;
this.synCmdCount = new AtomicInteger(synCount);
}
public boolean synAndExecuted(PostgreSQLBackendConnection conn) {
int remains = synCmdCount.decrementAndGet();
if (remains == 0) {// syn command finished
this.updateConnectionInfo(conn);
conn.metaDataSyned = true;
return false;
} else if (remains < 0) {
return true;
}
return false;
}
private void updateConnectionInfo(PostgreSQLBackendConnection conn)
{
conn.xaStatus = (xaStarted) ? 1 : 0;
if (schema != null) {
conn.schema = schema;
conn.oldSchema = conn.schema;
}
if (charsetIndex != null) {
conn.setCharset(CharsetUtil.getCharset(charsetIndex));
}
if (txtIsolation != null) {
conn.txIsolation = txtIsolation;
}
if (autocommit != null) {
conn.autocommit = autocommit;
}
}
}
public boolean setCharset(String charset) {
if ( charset != null ) {
charset = charset.replace("'", "");
}
int ci = CharsetUtil.getIndex(charset);
if (ci > 0) {
this.charset = charset.equalsIgnoreCase("utf8mb4") ? "utf8"
: charset;
this.charsetIndex = ci;
return true;
} else {
return false;
}
}
/**
* @return the inTransaction
*/
public boolean isInTransaction() {
return inTransaction;
}
/**
* @param inTransaction
* the inTransaction to set
*/
public void setInTransaction(boolean inTransaction) {
this.inTransaction = inTransaction;
}
}