diff --git a/src/main/java/io/mycat/backend/BackendConnection.java b/src/main/java/io/mycat/backend/BackendConnection.java index e1a8cbb7e..56b1e761e 100644 --- a/src/main/java/io/mycat/backend/BackendConnection.java +++ b/src/main/java/io/mycat/backend/BackendConnection.java @@ -1,6 +1,5 @@ package io.mycat.backend; -import java.io.IOException; import java.io.UnsupportedEncodingException; import io.mycat.backend.mysql.nio.handler.ResponseHandler; @@ -44,7 +43,7 @@ public interface BackendConnection extends ClosableConnection { public void execute(RouteResultsetNode node, ServerConnection source, - boolean autocommit) throws IOException; + boolean autocommit); public void recordSql(String host, String schema, String statement); diff --git a/src/main/java/io/mycat/backend/mysql/CharsetUtil.java b/src/main/java/io/mycat/backend/mysql/CharsetUtil.java index 56df40738..755b6430e 100644 --- a/src/main/java/io/mycat/backend/mysql/CharsetUtil.java +++ b/src/main/java/io/mycat/backend/mysql/CharsetUtil.java @@ -23,31 +23,245 @@ */ package io.mycat.backend.mysql; +import java.io.FileInputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.FileInputStream; -import java.util.*; - /** * @author mycat */ public class CharsetUtil { public static final Logger logger = LoggerFactory .getLogger(CharsetUtil.class); - private static final Map INDEX_TO_CHARSET = new HashMap<>(); + private static final String[] INDEX_TO_CHARSET = new String[248]; private static final Map CHARSET_TO_INDEX = new HashMap<>(); + private static final Map CHARSET_TO_JAVA = new HashMap(); static { // index_to_charset.properties - INDEX_TO_CHARSET.put(1,"big5"); - INDEX_TO_CHARSET.put(8,"latin1"); - INDEX_TO_CHARSET.put(9,"latin2"); - INDEX_TO_CHARSET.put(14,"cp1251"); - INDEX_TO_CHARSET.put(28,"gbk"); - INDEX_TO_CHARSET.put(24,"gb2312"); - INDEX_TO_CHARSET.put(33,"utf8"); - INDEX_TO_CHARSET.put(45,"utf8mb4"); + INDEX_TO_CHARSET[1] = "big5"; + INDEX_TO_CHARSET[2] = "latin2"; + INDEX_TO_CHARSET[3] = "dec8"; + INDEX_TO_CHARSET[4] = "cp850"; + INDEX_TO_CHARSET[5] = "latin1"; + INDEX_TO_CHARSET[6] = "hp8"; + INDEX_TO_CHARSET[7] = "koi8r"; + INDEX_TO_CHARSET[8] = "latin1"; + INDEX_TO_CHARSET[9] = "latin2"; + INDEX_TO_CHARSET[10] = "swe7"; + INDEX_TO_CHARSET[11] = "ascii"; + INDEX_TO_CHARSET[12] = "ujis"; + INDEX_TO_CHARSET[13] = "sjis"; + INDEX_TO_CHARSET[14] = "cp1251"; + INDEX_TO_CHARSET[15] = "latin1"; + INDEX_TO_CHARSET[16] = "hebrew"; + INDEX_TO_CHARSET[18] = "tis620"; + INDEX_TO_CHARSET[19] = "euckr"; + INDEX_TO_CHARSET[20] = "latin7"; + INDEX_TO_CHARSET[21] = "latin2"; + INDEX_TO_CHARSET[22] = "koi8u"; + INDEX_TO_CHARSET[23] = "cp1251"; + INDEX_TO_CHARSET[24] = "gb2312"; + INDEX_TO_CHARSET[25] = "greek"; + INDEX_TO_CHARSET[26] = "cp1250"; + INDEX_TO_CHARSET[27] = "latin2"; + INDEX_TO_CHARSET[28] = "gbk"; + INDEX_TO_CHARSET[29] = "cp1257"; + INDEX_TO_CHARSET[30] = "latin5"; + INDEX_TO_CHARSET[31] = "latin1"; + INDEX_TO_CHARSET[32] = "armscii8"; + INDEX_TO_CHARSET[33] = "utf8"; + INDEX_TO_CHARSET[34] = "cp1250"; + INDEX_TO_CHARSET[35] = "ucs2"; + INDEX_TO_CHARSET[36] = "cp866"; + INDEX_TO_CHARSET[37] = "keybcs2"; + INDEX_TO_CHARSET[38] = "macce"; + INDEX_TO_CHARSET[39] = "macroman"; + INDEX_TO_CHARSET[40] = "cp852"; + INDEX_TO_CHARSET[41] = "latin7"; + INDEX_TO_CHARSET[42] = "latin7"; + INDEX_TO_CHARSET[43] = "macce"; + INDEX_TO_CHARSET[44] = "cp1250"; + INDEX_TO_CHARSET[45] = "utf8mb4"; + INDEX_TO_CHARSET[46] = "utf8mb4"; + INDEX_TO_CHARSET[47] = "latin1"; + INDEX_TO_CHARSET[48] = "latin1"; + INDEX_TO_CHARSET[49] = "latin1"; + INDEX_TO_CHARSET[50] = "cp1251"; + INDEX_TO_CHARSET[51] = "cp1251"; + INDEX_TO_CHARSET[52] = "cp1251"; + INDEX_TO_CHARSET[53] = "macroman"; + INDEX_TO_CHARSET[54] = "utf16"; + INDEX_TO_CHARSET[55] = "utf16"; + INDEX_TO_CHARSET[56] = "utf16le"; + INDEX_TO_CHARSET[57] = "cp1256"; + INDEX_TO_CHARSET[58] = "cp1257"; + INDEX_TO_CHARSET[59] = "cp1257"; + INDEX_TO_CHARSET[60] = "utf32"; + INDEX_TO_CHARSET[61] = "utf32"; + INDEX_TO_CHARSET[62] = "utf16le"; + INDEX_TO_CHARSET[63] = "binary"; + INDEX_TO_CHARSET[64] = "armscii8"; + INDEX_TO_CHARSET[65] = "ascii"; + INDEX_TO_CHARSET[66] = "cp1250"; + INDEX_TO_CHARSET[67] = "cp1256"; + INDEX_TO_CHARSET[68] = "cp866"; + INDEX_TO_CHARSET[69] = "dec8"; + INDEX_TO_CHARSET[70] = "greek"; + INDEX_TO_CHARSET[71] = "hebrew"; + INDEX_TO_CHARSET[72] = "hp8"; + INDEX_TO_CHARSET[73] = "keybcs2"; + INDEX_TO_CHARSET[74] = "koi8r"; + INDEX_TO_CHARSET[75] = "koi8u"; + INDEX_TO_CHARSET[77] = "latin2"; + INDEX_TO_CHARSET[78] = "latin5"; + INDEX_TO_CHARSET[79] = "latin7"; + INDEX_TO_CHARSET[80] = "cp850"; + INDEX_TO_CHARSET[81] = "cp852"; + INDEX_TO_CHARSET[82] = "swe7"; + INDEX_TO_CHARSET[83] = "utf8"; + INDEX_TO_CHARSET[84] = "big5"; + INDEX_TO_CHARSET[85] = "euckr"; + INDEX_TO_CHARSET[86] = "gb2312"; + INDEX_TO_CHARSET[87] = "gbk"; + INDEX_TO_CHARSET[88] = "sjis"; + INDEX_TO_CHARSET[89] = "tis620"; + INDEX_TO_CHARSET[90] = "ucs2"; + INDEX_TO_CHARSET[91] = "ujis"; + INDEX_TO_CHARSET[92] = "geostd8"; + INDEX_TO_CHARSET[93] = "geostd8"; + INDEX_TO_CHARSET[94] = "latin1"; + INDEX_TO_CHARSET[95] = "cp932"; + INDEX_TO_CHARSET[96] = "cp932"; + INDEX_TO_CHARSET[97] = "eucjpms"; + INDEX_TO_CHARSET[98] = "eucjpms"; + INDEX_TO_CHARSET[99] = "cp1250"; + INDEX_TO_CHARSET[101] = "utf16"; + INDEX_TO_CHARSET[102] = "utf16"; + INDEX_TO_CHARSET[103] = "utf16"; + INDEX_TO_CHARSET[104] = "utf16"; + INDEX_TO_CHARSET[105] = "utf16"; + INDEX_TO_CHARSET[106] = "utf16"; + INDEX_TO_CHARSET[107] = "utf16"; + INDEX_TO_CHARSET[108] = "utf16"; + INDEX_TO_CHARSET[109] = "utf16"; + INDEX_TO_CHARSET[110] = "utf16"; + INDEX_TO_CHARSET[111] = "utf16"; + INDEX_TO_CHARSET[112] = "utf16"; + INDEX_TO_CHARSET[113] = "utf16"; + INDEX_TO_CHARSET[114] = "utf16"; + INDEX_TO_CHARSET[115] = "utf16"; + INDEX_TO_CHARSET[116] = "utf16"; + INDEX_TO_CHARSET[117] = "utf16"; + INDEX_TO_CHARSET[118] = "utf16"; + INDEX_TO_CHARSET[119] = "utf16"; + INDEX_TO_CHARSET[120] = "utf16"; + INDEX_TO_CHARSET[121] = "utf16"; + INDEX_TO_CHARSET[122] = "utf16"; + INDEX_TO_CHARSET[123] = "utf16"; + INDEX_TO_CHARSET[124] = "utf16"; + INDEX_TO_CHARSET[128] = "ucs2"; + INDEX_TO_CHARSET[129] = "ucs2"; + INDEX_TO_CHARSET[130] = "ucs2"; + INDEX_TO_CHARSET[131] = "ucs2"; + INDEX_TO_CHARSET[132] = "ucs2"; + INDEX_TO_CHARSET[133] = "ucs2"; + INDEX_TO_CHARSET[134] = "ucs2"; + INDEX_TO_CHARSET[135] = "ucs2"; + INDEX_TO_CHARSET[136] = "ucs2"; + INDEX_TO_CHARSET[137] = "ucs2"; + INDEX_TO_CHARSET[138] = "ucs2"; + INDEX_TO_CHARSET[139] = "ucs2"; + INDEX_TO_CHARSET[140] = "ucs2"; + INDEX_TO_CHARSET[141] = "ucs2"; + INDEX_TO_CHARSET[142] = "ucs2"; + INDEX_TO_CHARSET[143] = "ucs2"; + INDEX_TO_CHARSET[144] = "ucs2"; + INDEX_TO_CHARSET[145] = "ucs2"; + INDEX_TO_CHARSET[146] = "ucs2"; + INDEX_TO_CHARSET[147] = "ucs2"; + INDEX_TO_CHARSET[148] = "ucs2"; + INDEX_TO_CHARSET[149] = "ucs2"; + INDEX_TO_CHARSET[150] = "ucs2"; + INDEX_TO_CHARSET[151] = "ucs2"; + INDEX_TO_CHARSET[159] = "ucs2"; + INDEX_TO_CHARSET[160] = "utf32"; + INDEX_TO_CHARSET[161] = "utf32"; + INDEX_TO_CHARSET[162] = "utf32"; + INDEX_TO_CHARSET[163] = "utf32"; + INDEX_TO_CHARSET[164] = "utf32"; + INDEX_TO_CHARSET[165] = "utf32"; + INDEX_TO_CHARSET[166] = "utf32"; + INDEX_TO_CHARSET[167] = "utf32"; + INDEX_TO_CHARSET[168] = "utf32"; + INDEX_TO_CHARSET[169] = "utf32"; + INDEX_TO_CHARSET[170] = "utf32"; + INDEX_TO_CHARSET[171] = "utf32"; + INDEX_TO_CHARSET[172] = "utf32"; + INDEX_TO_CHARSET[173] = "utf32"; + INDEX_TO_CHARSET[174] = "utf32"; + INDEX_TO_CHARSET[175] = "utf32"; + INDEX_TO_CHARSET[176] = "utf32"; + INDEX_TO_CHARSET[177] = "utf32"; + INDEX_TO_CHARSET[178] = "utf32"; + INDEX_TO_CHARSET[179] = "utf32"; + INDEX_TO_CHARSET[180] = "utf32"; + INDEX_TO_CHARSET[181] = "utf32"; + INDEX_TO_CHARSET[182] = "utf32"; + INDEX_TO_CHARSET[183] = "utf32"; + INDEX_TO_CHARSET[192] = "utf8"; + INDEX_TO_CHARSET[193] = "utf8"; + INDEX_TO_CHARSET[194] = "utf8"; + INDEX_TO_CHARSET[195] = "utf8"; + INDEX_TO_CHARSET[196] = "utf8"; + INDEX_TO_CHARSET[197] = "utf8"; + INDEX_TO_CHARSET[198] = "utf8"; + INDEX_TO_CHARSET[199] = "utf8"; + INDEX_TO_CHARSET[200] = "utf8"; + INDEX_TO_CHARSET[201] = "utf8"; + INDEX_TO_CHARSET[202] = "utf8"; + INDEX_TO_CHARSET[203] = "utf8"; + INDEX_TO_CHARSET[204] = "utf8"; + INDEX_TO_CHARSET[205] = "utf8"; + INDEX_TO_CHARSET[206] = "utf8"; + INDEX_TO_CHARSET[207] = "utf8"; + INDEX_TO_CHARSET[208] = "utf8"; + INDEX_TO_CHARSET[209] = "utf8"; + INDEX_TO_CHARSET[210] = "utf8"; + INDEX_TO_CHARSET[211] = "utf8"; + INDEX_TO_CHARSET[212] = "utf8"; + INDEX_TO_CHARSET[213] = "utf8"; + INDEX_TO_CHARSET[214] = "utf8"; + INDEX_TO_CHARSET[215] = "utf8"; + INDEX_TO_CHARSET[223] = "utf8"; + INDEX_TO_CHARSET[224] = "utf8mb4"; + INDEX_TO_CHARSET[225] = "utf8mb4"; + INDEX_TO_CHARSET[226] = "utf8mb4"; + INDEX_TO_CHARSET[227] = "utf8mb4"; + INDEX_TO_CHARSET[228] = "utf8mb4"; + INDEX_TO_CHARSET[229] = "utf8mb4"; + INDEX_TO_CHARSET[230] = "utf8mb4"; + INDEX_TO_CHARSET[231] = "utf8mb4"; + INDEX_TO_CHARSET[232] = "utf8mb4"; + INDEX_TO_CHARSET[233] = "utf8mb4"; + INDEX_TO_CHARSET[234] = "utf8mb4"; + INDEX_TO_CHARSET[235] = "utf8mb4"; + INDEX_TO_CHARSET[236] = "utf8mb4"; + INDEX_TO_CHARSET[237] = "utf8mb4"; + INDEX_TO_CHARSET[238] = "utf8mb4"; + INDEX_TO_CHARSET[239] = "utf8mb4"; + INDEX_TO_CHARSET[240] = "utf8mb4"; + INDEX_TO_CHARSET[241] = "utf8mb4"; + INDEX_TO_CHARSET[242] = "utf8mb4"; + INDEX_TO_CHARSET[243] = "utf8mb4"; + INDEX_TO_CHARSET[244] = "utf8mb4"; + INDEX_TO_CHARSET[245] = "utf8mb4"; + INDEX_TO_CHARSET[246] = "utf8mb4"; + INDEX_TO_CHARSET[247] = "utf8mb4"; String filePath = Thread.currentThread().getContextClassLoader() .getResource("").getPath().replaceAll("%20", " ") @@ -56,27 +270,44 @@ public class CharsetUtil { try { prop.load(new FileInputStream(filePath)); for (Object index : prop.keySet()){ - INDEX_TO_CHARSET.put(Integer.parseInt((String) index), prop.getProperty((String) index)); + INDEX_TO_CHARSET[Integer.parseInt((String) index)] = prop.getProperty((String) index); } } catch (Exception e) { logger.error("error:",e); } // charset --> index - for(Integer key : INDEX_TO_CHARSET.keySet()){ - String charset = INDEX_TO_CHARSET.get(key); + for(int index =0;index fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("received field eof from " + conn); } } + + + + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + + } + } class HeartBeatCon { diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/DelegateResponseHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/DelegateResponseHandler.java index cedd06407..5ef6b8fc9 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/DelegateResponseHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/DelegateResponseHandler.java @@ -26,6 +26,8 @@ package io.mycat.backend.mysql.nio.handler; import java.util.List; import io.mycat.backend.BackendConnection; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; /** * @author mycat @@ -61,18 +63,19 @@ public class DelegateResponseHandler implements ResponseHandler { } @Override - public void fieldEofResponse(byte[] header, List fields, byte[] eof, BackendConnection conn) { - target.fieldEofResponse(header, fields, eof, conn); + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { + target.fieldEofResponse(header, fields,fieldPackets, eof, isLeft, conn); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { - target.rowResponse(row, conn); + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + return target.rowResponse(row, rowPacket, isLeft, conn); } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { - target.rowEofResponse(eof, conn); + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { + target.rowEofResponse(eof, isLeft, conn); } @Override @@ -86,5 +89,11 @@ public class DelegateResponseHandler implements ResponseHandler { target.connectionClose(conn, reason); } - + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/FetchStoreNodeOfChildTableHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/FetchStoreNodeOfChildTableHandler.java index 6a898c464..b0de9616e 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/FetchStoreNodeOfChildTableHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/FetchStoreNodeOfChildTableHandler.java @@ -23,7 +23,6 @@ */ package io.mycat.backend.mysql.nio.handler; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -41,6 +40,8 @@ import io.mycat.backend.datasource.PhysicalDBNode; import io.mycat.cache.CachePool; import io.mycat.config.MycatConfig; import io.mycat.net.mysql.ErrorPacket; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultsetNode; import io.mycat.server.NonBlockingSession; import io.mycat.server.parser.ServerParse; @@ -99,11 +100,8 @@ public class FetchStoreNodeOfChildTableHandler implements ResponseHandler { return null; } conn.setResponseHandler(this); - try { - conn.execute(node, session.getSource(), isAutoCommit()); - } catch (IOException e) { - connectionError(e, conn); - } + conn.execute(node, session.getSource(), isAutoCommit()); + } else { mysqlDN.getConnection(mysqlDN.getDatabase(), true, node, this, node); } @@ -188,7 +186,7 @@ public class FetchStoreNodeOfChildTableHandler implements ResponseHandler { } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("received rowResponse response from " + conn); } @@ -203,11 +201,12 @@ public class FetchStoreNodeOfChildTableHandler implements ResponseHandler { } else { LOGGER.warn("find multi data nodes for child table store, sql is: " + sql); } + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("rowEofResponse" + conn); } @@ -236,8 +235,14 @@ public class FetchStoreNodeOfChildTableHandler implements ResponseHandler { } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { + } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { } + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/GetConnectionHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/GetConnectionHandler.java index d748f2114..3382fe52a 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/GetConnectionHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/GetConnectionHandler.java @@ -27,9 +27,12 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.mycat.backend.BackendConnection; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; /** * wuzh @@ -88,18 +91,18 @@ public class GetConnectionHandler implements ResponseHandler { } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { } @Override - public void rowResponse(byte[] row, BackendConnection conn) { - + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { } @@ -111,6 +114,12 @@ public class GetConnectionHandler implements ResponseHandler { @Override public void connectionClose(BackendConnection conn, String reason) { + } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { } + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/KillConnectionHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/KillConnectionHandler.java index 0fd1dbd21..8c3da11c3 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/KillConnectionHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/KillConnectionHandler.java @@ -26,13 +26,16 @@ package io.mycat.backend.mysql.nio.handler; import java.io.UnsupportedEncodingException; import java.util.List; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.mycat.backend.BackendConnection; import io.mycat.backend.mysql.nio.MySQLConnection; import io.mycat.net.mysql.CommandPacket; import io.mycat.net.mysql.ErrorPacket; +import io.mycat.net.mysql.FieldPacket; import io.mycat.net.mysql.MySQLPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.server.NonBlockingSession; /** @@ -80,7 +83,7 @@ public class KillConnectionHandler implements ResponseHandler { } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { LOGGER.warn(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); @@ -105,12 +108,13 @@ public class KillConnectionHandler implements ResponseHandler { } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + return false; } @Override @@ -121,5 +125,11 @@ public class KillConnectionHandler implements ResponseHandler { @Override public void connectionClose(BackendConnection conn, String reason) { } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + } + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/LockTablesHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/LockTablesHandler.java index cfff92588..d6501d330 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/LockTablesHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/LockTablesHandler.java @@ -1,6 +1,5 @@ package io.mycat.backend.mysql.nio.handler; -import java.io.IOException; import java.util.List; import java.util.concurrent.locks.ReentrantLock; @@ -11,7 +10,9 @@ import io.mycat.MycatServer; import io.mycat.backend.BackendConnection; import io.mycat.backend.datasource.PhysicalDBNode; import io.mycat.config.MycatConfig; +import io.mycat.net.mysql.FieldPacket; import io.mycat.net.mysql.OkPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultset; import io.mycat.route.RouteResultsetNode; import io.mycat.server.NonBlockingSession; @@ -56,11 +57,7 @@ public class LockTablesHandler extends MultiNodeHandler { return; } conn.setResponseHandler(this); - try { - conn.execute(node, session.getSource(), autocommit); - } catch (IOException e) { - connectionError(e, conn); - } + conn.execute(node, session.getSource(), autocommit); } @Override @@ -106,21 +103,23 @@ public class LockTablesHandler extends MultiNodeHandler { } @Override - public void fieldEofResponse(byte[] header, List fields, byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { LOGGER.warn(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": row data packet").toString()); + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": row's eof").toString()); diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeHandler.java index 5b114b8b6..48ccafcb2 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeHandler.java @@ -193,4 +193,11 @@ public abstract class MultiNodeHandler implements ResponseHandler { public void clearResources() { } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeQueryHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeQueryHandler.java index e62687aec..c306ce79b 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeQueryHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/MultiNodeQueryHandler.java @@ -23,7 +23,6 @@ */ package io.mycat.backend.mysql.nio.handler; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -208,11 +207,7 @@ public class MultiNodeQueryHandler extends MultiNodeHandler implements LoadDataR return; } conn.setResponseHandler(this); - try { - conn.execute(node, session.getSource(), sessionAutocommit&&!session.getSource().isTxstart()&&!node.isModifySQL()); - } catch (IOException e) { - connectionError(e, conn); - } + conn.execute(node, session.getSource(), sessionAutocommit&&!session.getSource().isTxstart()&&!node.isModifySQL()); } @Override public void connectionClose(BackendConnection conn, String reason) { @@ -350,7 +345,7 @@ public class MultiNodeQueryHandler extends MultiNodeHandler implements LoadDataR } @Override - public void rowEofResponse(final byte[] eof, BackendConnection conn) { + public void rowEofResponse(final byte[] eof, boolean isLeft, BackendConnection conn) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("on row end reseponse " + conn); } @@ -565,8 +560,8 @@ public class MultiNodeQueryHandler extends MultiNodeHandler implements LoadDataR } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPacketsnull, byte[] eof, + boolean isLeft, BackendConnection conn) { this.netOutBytes += header.length; @@ -711,7 +706,7 @@ public class MultiNodeQueryHandler extends MultiNodeHandler implements LoadDataR } @Override - public void rowResponse(final byte[] row, final BackendConnection conn) { + public boolean rowResponse(final byte[] row, RowDataPacket rowPacketnull, boolean isLeft, BackendConnection conn) { if (errorRepsponsed.get()) { // the connection has been closed or set to "txInterrupt" properly @@ -720,7 +715,7 @@ public class MultiNodeQueryHandler extends MultiNodeHandler implements LoadDataR // @author Uncle-pan // @since 2016-03-25 //conn.close(error); - return; + return true; } @@ -767,6 +762,7 @@ public class MultiNodeQueryHandler extends MultiNodeHandler implements LoadDataR } finally { lock.unlock(); } + return false; } @Override diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/NewConnectionRespHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/NewConnectionRespHandler.java index 5578b629f..02fb30f42 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/NewConnectionRespHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/NewConnectionRespHandler.java @@ -25,9 +25,12 @@ package io.mycat.backend.mysql.nio.handler; import java.util.List; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.mycat.backend.BackendConnection; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; public class NewConnectionRespHandler implements ResponseHandler{ private static final Logger LOGGER = LoggerFactory @@ -59,20 +62,20 @@ public class NewConnectionRespHandler implements ResponseHandler{ } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { LOGGER.info("fieldEofResponse: " + conn ); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { LOGGER.info("rowResponse: " + conn ); - + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { LOGGER.info("rowEofResponse: " + conn ); } @@ -89,4 +92,12 @@ public class NewConnectionRespHandler implements ResponseHandler{ } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } + } \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/ResponseHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/ResponseHandler.java index e4c06498f..458c0a256 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/ResponseHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/ResponseHandler.java @@ -26,6 +26,8 @@ package io.mycat.backend.mysql.nio.handler; import java.util.List; import io.mycat.backend.BackendConnection; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; /** * @author mycat @@ -59,19 +61,28 @@ public interface ResponseHandler { /** * 收到字段数据包结束的响应处理 */ - void fieldEofResponse(byte[] header, List fields, byte[] eof, - BackendConnection conn); + void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn); /** * 收到行数据包的响应处理 */ - void rowResponse(byte[] row, BackendConnection conn); + boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn); /** * 收到行数据包结束的响应处理 */ - void rowEofResponse(byte[] eof, BackendConnection conn); + void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn); + + /** + * 收到中继数据包的响应处理 + */ + void relayPacketResponse(byte[] relayPacket, BackendConnection conn); + /** + * 收到结束数据包的响应处理 + */ + void endPacketResponse(byte[] endPacket, BackendConnection conn); /** * 写队列为空,可以写数据了 * diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/SimpleLogHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/SimpleLogHandler.java deleted file mode 100644 index 5b46714af..000000000 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/SimpleLogHandler.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software;Designed and Developed mainly by many Chinese - * opensource volunteers. you can redistribute it and/or modify it under the - * terms of the GNU General Public License version 2 only, as published by the - * Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Any questions about this component can be directed to it's project Web address - * https://code.google.com/p/opencloudb/. - * - */ -package io.mycat.backend.mysql.nio.handler; - -import java.util.List; - -import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import io.mycat.backend.BackendConnection; - -public class SimpleLogHandler implements ResponseHandler{ - private static final Logger LOGGER = LoggerFactory - .getLogger(SimpleLogHandler.class); - @Override - public void connectionError(Throwable e, BackendConnection conn) { - LOGGER.warn(conn+" connectionError "+e); - - } - - @Override - public void connectionAcquired(BackendConnection conn) { - LOGGER.info("connectionAcquired "+conn); - - } - - @Override - public void errorResponse(byte[] err, BackendConnection conn) { - LOGGER.warn("caught error resp: " + conn + " " + new String(err)); - } - - @Override - public void okResponse(byte[] ok, BackendConnection conn) { - LOGGER.info("okResponse: " + conn ); - - } - - @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { - LOGGER.info("fieldEofResponse: " + conn ); - - } - - @Override - public void rowResponse(byte[] row, BackendConnection conn) { - LOGGER.info("rowResponse: " + conn ); - - } - - @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { - LOGGER.info("rowEofResponse: " + conn ); - - } - - @Override - public void writeQueueAvailable() { - - - } - - @Override - public void connectionClose(BackendConnection conn, String reason) { - - - } - -} \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/SingleNodeHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/SingleNodeHandler.java index 9724e5c3b..b00d186ef 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/SingleNodeHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/SingleNodeHandler.java @@ -153,27 +153,12 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl return; } conn.setResponseHandler(this); - try { - boolean isAutocommit = session.getSource().isAutocommit()&&!session.getSource().isTxstart(); - if(!isAutocommit&& node.isModifySQL()){ - TxnLogHelper.putTxnLog(session.getSource(), node.getStatement()); - } - conn.execute(node, session.getSource(), isAutocommit); - } catch (Exception e1) { - executeException(conn, e1); - return; + boolean isAutocommit = session.getSource().isAutocommit()&&!session.getSource().isTxstart(); + if(!isAutocommit&& node.isModifySQL()){ + TxnLogHelper.putTxnLog(session.getSource(), node.getStatement()); } + conn.execute(node, session.getSource(), isAutocommit); } - - private void executeException(BackendConnection c, Exception e) { - ErrorPacket err = new ErrorPacket(); - err.packetId = ++packetId; - err.errno = ErrorCode.ERR_FOUND_EXCEPION; - err.message = StringUtil.encode(e.toString(), session.getSource().getCharset()); - - this.backConnectionErr(err, c); - } - @Override public void connectionError(Throwable e, BackendConnection conn) { ErrorPacket err = new ErrorPacket(); @@ -270,7 +255,7 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl * 行结束标志返回时触发,将EOF标志写入缓冲区,最后调用source.write(buffer)将缓冲区放入前端连接的写缓冲队列中,等待NIOSocketWR将其发送给应用 */ @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { this.netOutBytes += eof.length; @@ -322,8 +307,8 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl * 元数据返回时触发,将header和元数据内容依次写入缓冲区中 */ @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPacketsnull, byte[] eof, + boolean isLeft, BackendConnection conn) { this.netOutBytes += header.length; for (int i = 0, len = fields.size(); i < len; ++i) { @@ -378,7 +363,7 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl * 行数据返回时触发,将行数据写入缓冲区中 */ @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { this.netOutBytes += row.length; this.selectRows++; @@ -388,14 +373,14 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl rowDataPacket.read(row); String table = StringUtil.decode(rowDataPacket.fieldValues.get(0), conn.getCharset()); if (shardingTablesSet.contains(table.toUpperCase())) { - return; + return false; } } row[3] = ++packetId; if ( prepared ) { RowDataPacket rowDataPk = new RowDataPacket(fieldCount); - rowDataPk.read(row); + rowDataPk.read(row); BinaryRowDataPacket binRowDataPk = new BinaryRowDataPacket(); binRowDataPk.read(fieldPackets, rowDataPk); binRowDataPk.packetId = rowDataPk.packetId; @@ -410,7 +395,7 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl buffer = session.getSource().writeToBuffer(row, allocBuffer()); //session.getSource().write(row); } - + return false; } @Override @@ -450,4 +435,16 @@ public class SingleNodeHandler implements ResponseHandler, LoadDataResponseHandl return "SingleNodeHandler [node=" + node + ", packetId=" + packetId + "]"; } + + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + + } + + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + + } + } diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/UnLockTablesHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/UnLockTablesHandler.java index 24cbfbc66..4457e99de 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/UnLockTablesHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/UnLockTablesHandler.java @@ -8,7 +8,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.mycat.backend.BackendConnection; +import io.mycat.net.mysql.FieldPacket; import io.mycat.net.mysql.OkPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultsetNode; import io.mycat.server.NonBlockingSession; import io.mycat.server.parser.ServerParse; @@ -103,21 +105,23 @@ public class UnLockTablesHandler extends MultiNodeHandler implements ResponseHan } @Override - public void fieldEofResponse(byte[] header, List fields, byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { LOGGER.warn(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": row data packet").toString()); + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": row's eof").toString()); @@ -135,4 +139,15 @@ public class UnLockTablesHandler extends MultiNodeHandler implements ResponseHan } + + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + + } + } diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/BaseHandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/BaseHandlerBuilder.java new file mode 100644 index 000000000..5815bb3a9 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/BaseHandlerBuilder.java @@ -0,0 +1,410 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; + +import io.mycat.MycatServer; +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.GlobalVisitor; +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.PushDownVisitor; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.DistinctHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.HavingHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.LimitHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.MultiNodeMergeHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.OrderByHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.SendMakeHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.WhereHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.groupby.DirectGroupByHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.groupby.OrderedGroupByHandler; +import io.mycat.config.ErrorCode; +import io.mycat.config.MycatConfig; +import io.mycat.config.model.SchemaConfig; +import io.mycat.config.model.TableConfig; +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.plan.common.item.function.sumfunc.ItemSum.Sumfunctype; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.TableNode; +import io.mycat.plan.util.PlanUtil; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; +import io.mycat.server.parser.ServerParse; + +abstract class BaseHandlerBuilder { + public enum MySQLNodeType { + MASTER, SLAVE + } + private static AtomicLong sequenceId = new AtomicLong(0); + protected NonBlockingSession session; + protected HandlerBuilder hBuilder; + protected MySQLNodeType nodeType; + protected DMLResponseHandler start; + /* 当前的最后一个handler */ + protected DMLResponseHandler currentLast; + private PlanNode node; + protected MycatConfig mycatConfig; + /* 是否可以全下推 */ + protected boolean canPushDown = false; + /* 是否需要common中的handler,包括group by,order by,limit等 */ + protected boolean needCommon = true; + /* 是否需要过wherehandler过滤 */ + protected boolean needWhereHandler = true; + /* 直接从用户的sql下发的是不需要sendmaker */ + protected boolean needSendMaker = true; + + protected BaseHandlerBuilder(NonBlockingSession session, PlanNode node, HandlerBuilder hBuilder) { + this.session = session; + this.nodeType = session.getSource().isTxstart() || !session.getSource().isAutocommit() ? MySQLNodeType.MASTER + : MySQLNodeType.SLAVE; + this.node = node; + this.hBuilder = hBuilder; + this.mycatConfig = MycatServer.getInstance().getConfig(); + if (mycatConfig.getSchemas().isEmpty()) + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "current router config is empty!"); + } + + public DMLResponseHandler getEndHandler() { + return currentLast; + } + + /** + * 生成正确的hanlder链 + */ + public final void build() { + List preHandlers = null; + // 是否切换了join策略切换了join策略 + boolean joinStrategyed = isNestLoopStrategy(node); + if (joinStrategyed) { + nestLoopBuild(); +// } else if (!node.isExsitView() && (node.getUnGlobalTableCount() == 0 || !PlanUtil.existShardTable(node))) {// global优化过的节点 +// noShardBuild(); + } else if (canDoAsMerge()) { + mergeBuild(); + } else { + preHandlers = buildPre(); + buildOwn(); + } + if (needCommon) + buildCommon(); + if (needSendMaker) { + // view subalias + String tbAlias = node.getAlias(); + if (node.getParent() != null && node.getParent().getSubAlias() != null) + tbAlias = node.getParent().getSubAlias(); + SendMakeHandler sh = new SendMakeHandler(getSequenceId(), session, node.getColumnsSelected(), tbAlias); + addHandler(sh); + } + if (preHandlers != null) { + for (DMLResponseHandler preHandler : preHandlers) { + preHandler.setNextHandler(start); + } + } + } + + /** + * 虽然where和otherjoinOn过滤条件为空,但是存在strategyFilters作为过滤条件 + */ + protected void nestLoopBuild() { + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "not implement yet, node type["+node.type()+"]" ); + } + + /* join的er关系,或者global优化以及tablenode,可以当成merge来做 */ + protected boolean canDoAsMerge() { + return false; + } + + protected void mergeBuild() { + // + } + + protected abstract List buildPre(); + + /** + * 生成自己的handler + * + */ + protected abstract void buildOwn(); + + /** + * 不存在拆分表,下推到第一个节点 + */ + protected final void noShardBuild() { + this.needCommon = false; + // 默认的可以global的都是unglobalcount=0,除了是joinnode有特殊情况 + // 當且僅當node.unglobalcount=0,所以所有的語句都可以下發,僅需要將語句拼出來下發到一個節點即可 + String sql = null; + if (node.getParent() == null) {// root节点 + sql = node.getSql(); + } + // 有可能node来自于view + if (sql == null) { + GlobalVisitor visitor = new GlobalVisitor(node, true); + visitor.visit(); + sql = visitor.getSql().toString(); + } else { + needSendMaker = false; + } + RouteResultsetNode[] rrss = getTableSources(sql); + hBuilder.checkRRSS(rrss); + MultiNodeMergeHandler mh = new MultiNodeMergeHandler(getSequenceId(), rrss, session.getSource().isAutocommit(), + session, null); + addHandler(mh); + } + + /** + * 构建node公共的属性,包括where,groupby,having,orderby,limit,还有最后的sendmakehandler + * + */ + protected void buildCommon() { + if (node.getWhereFilter() != null && needWhereHandler) { + WhereHandler wh = new WhereHandler(getSequenceId(), session, node.getWhereFilter()); + addHandler(wh); + } + /* need groupby handler */ + if (nodeHasGroupBy(node)) { + boolean needOrderBy = (node.getGroupBys().size() > 0) ? isOrderNeeded(node, node.getGroupBys()) : false; + boolean canDirectGroupBy = true; + List sumRefs = new ArrayList(); + for (ItemSum funRef : node.sumFuncs) { + if (funRef.has_with_distinct() || funRef.sumType().equals(Sumfunctype.GROUP_CONCAT_FUNC)) + canDirectGroupBy = false; + sumRefs.add(funRef); + } + if (needOrderBy) { + if (canDirectGroupBy) { + // we go direct groupby + DirectGroupByHandler gh = new DirectGroupByHandler(getSequenceId(), session, node.getGroupBys(), + sumRefs); + addHandler(gh); + } else { + OrderByHandler oh = new OrderByHandler(getSequenceId(), session, node.getGroupBys()); + addHandler(oh); + OrderedGroupByHandler gh = new OrderedGroupByHandler(getSequenceId(), session, node.getGroupBys(), + sumRefs); + addHandler(gh); + } + } else {// @bug 1052 canDirectGroupby condition we use + // directgroupby already + OrderedGroupByHandler gh = new OrderedGroupByHandler(getSequenceId(), session, node.getGroupBys(), + sumRefs); + addHandler(gh); + } + } + // having + if (node.getHavingFilter() != null) { + HavingHandler hh = new HavingHandler(getSequenceId(), session, node.getHavingFilter()); + addHandler(hh); + } + + if (node.isDistinct() && node.getOrderBys().size() > 0) { + // distinct and order by both exists + List mergedOrders = mergeOrderBy(node.getColumnsSelected(), node.getOrderBys()); + if (mergedOrders == null) { + // can not merge,need distinct then order by + DistinctHandler dh = new DistinctHandler(getSequenceId(), session, node.getColumnsSelected()); + addHandler(dh); + OrderByHandler oh = new OrderByHandler(getSequenceId(), session, node.getOrderBys()); + addHandler(oh); + } else { + DistinctHandler dh = new DistinctHandler(getSequenceId(), session, node.getColumnsSelected(), + mergedOrders); + addHandler(dh); + } + } else { + if (node.isDistinct()) { + DistinctHandler dh = new DistinctHandler(getSequenceId(), session, node.getColumnsSelected()); + addHandler(dh); + } + // order by + if (node.getOrderBys().size() > 0) { + if (node.getGroupBys().size() > 0) { + if (!PlanUtil.orderContains(node.getGroupBys(), node.getOrderBys())) { + OrderByHandler oh = new OrderByHandler(getSequenceId(), session, node.getOrderBys()); + addHandler(oh); + } + } else if (isOrderNeeded(node, node.getOrderBys())) { + OrderByHandler oh = new OrderByHandler(getSequenceId(), session, node.getOrderBys()); + addHandler(oh); + } + } + } + if (node.getLimitTo() > 0) { + LimitHandler lh = new LimitHandler(getSequenceId(), session, node.getLimitFrom(), node.getLimitTo()); + addHandler(lh); + } + + } + + /** + * 添加一个handler到hanlder链 + */ + protected void addHandler(DMLResponseHandler bh) { + if (currentLast == null) { + start = bh; + currentLast = bh; + } else { + currentLast.setNextHandler(bh); + currentLast = bh; + } + bh.setAllPushDown(canPushDown); + } + + /*----------------------------- helper method -------------------*/ + private boolean isNestLoopStrategy(PlanNode node) { + if (node.type() == PlanNodeType.TABLE && node.getNestLoopFilters() != null) + return true; + return false; + } + + /** + * 是否需要对该node进行orderby排序 + * 如果该node的上一层handler返回的结果已经按照orderBys排序,则无需再次进行orderby + * + * @param node + * @param orderBys + * @return + */ + private boolean isOrderNeeded(PlanNode node, List orderBys) { + if (node.isGlobaled() || node instanceof TableNode || PlanUtil.isERNode(node)) + return false; + else if (node instanceof JoinNode) { + return !isJoinNodeOrderMatch((JoinNode) node, orderBys); + } else if (node instanceof QueryNode) { + return !isQueryNodeOrderMatch((QueryNode) node, orderBys); + } + return true; + } + + /** + * joinnode的默认排序记录在leftjoinonorders和rightjoinonorders中 + * + * @param jn + * @param orderBys + * 目标排序 + * @return + */ + private boolean isJoinNodeOrderMatch(JoinNode jn, List orderBys) { + // 记录orderBys中前面出现的onCondition列,如jn.onCond = (t1.id=t2.id), + // orderBys为t1.id,t2.id,t1.name,则onOrders = {t1.id,t2.id}; + List onOrders = new ArrayList(); + List leftOnOrders = jn.getLeftJoinOnOrders(); + List rightOnOrders = jn.getRightJoinOnOrders(); + for (Order orderBy : orderBys) { + if (leftOnOrders.contains(orderBy) || rightOnOrders.contains(orderBy)) { + onOrders.add(orderBy); + } else { + break; + } + } + if (onOrders.isEmpty()) { + // joinnode的数据一定是按照joinOnCondition进行排序的 + return false; + } else { + List remainOrders = orderBys.subList(onOrders.size(), orderBys.size()); + if (remainOrders.isEmpty()) { + return true; + } else { + List pushedOrders = PlanUtil.getPushDownOrders(jn, remainOrders); + if (jn.isLeftOrderMatch()) { + List leftChildOrders = jn.getLeftNode().getOrderBys(); + List leftRemainOrders = leftChildOrders.subList(leftOnOrders.size(), leftChildOrders.size()); + if (PlanUtil.orderContains(leftRemainOrders, pushedOrders)) + return true; + } else if (jn.isRightOrderMatch()) { + List rightChildOrders = jn.getRightNode().getOrderBys(); + List rightRemainOrders = rightChildOrders.subList(rightOnOrders.size(), + rightChildOrders.size()); + if (PlanUtil.orderContains(rightRemainOrders, pushedOrders)) + return true; + } + return false; + } + } + } + + /** + * @param qn + * @param orderBys + * 目标排序 + * @return + */ + private boolean isQueryNodeOrderMatch(QueryNode qn, List orderBys) { + List childOrders = qn.getChild().getOrderBys(); + List pushedOrders = PlanUtil.getPushDownOrders(qn, orderBys); + return PlanUtil.orderContains(childOrders, pushedOrders); + } + + /** + * 尝试将order by的顺序合并到columnsSelected中 + * + * @param columnsSelected + * @param orderBys + * @return + */ + private List mergeOrderBy(List columnsSelected, List orderBys) { + List orderIndexes = new ArrayList(); + List newOrderByList = new ArrayList(); + for (Order orderBy : orderBys) { + Item column = orderBy.getItem(); + int index = columnsSelected.indexOf(column); + if (index < 0) + return null; + else + orderIndexes.add(index); + Order newOrderBy = new Order(columnsSelected.get(index), orderBy.getSortOrder()); + newOrderByList.add(newOrderBy); + } + for (int index = 0; index < columnsSelected.size(); index++) { + if (!orderIndexes.contains(index)) { + Order newOrderBy = new Order(columnsSelected.get(index), SQLOrderingSpecification.ASC); + newOrderByList.add(newOrderBy); + } + } + return newOrderByList; + } + + protected static boolean nodeHasGroupBy(PlanNode arg) { + return (arg.sumFuncs.size() > 0 || arg.getGroupBys().size() > 0); + } + + protected static long getSequenceId() { + return sequenceId.incrementAndGet(); + } + + /*-----------------计算datasource相关start------------------*/ + + protected void buildMergeHandler(PlanNode node, RouteResultsetNode[] rrssArray, PushDownVisitor sqlVisitor, + boolean simpleVisited) { + hBuilder.checkRRSS(rrssArray); + MultiNodeMergeHandler mh = null; + List orderBys = node.getGroupBys().size() > 0 ? node.getGroupBys() : node.getOrderBys(); + + mh = new MultiNodeMergeHandler(getSequenceId(), rrssArray, session.getSource().isAutocommit(), session, + orderBys); + addHandler(mh); + } + + protected RouteResultsetNode[] getTableSources(String sql) { + String schema = session.getSource().getSchema(); + SchemaConfig schemacfg = mycatConfig.getSchemas().get(schema); + RouteResultsetNode rrss = new RouteResultsetNode(schemacfg.getDataNode(), ServerParse.SELECT, sql); + return new RouteResultsetNode[]{rrss}; + } + + protected TableConfig getTableConfig(String schema, String table) { + SchemaConfig schemaConfig = this.mycatConfig.getSchemas().get(schema); + if (schemaConfig == null) + return null; + return schemaConfig.getTables().get(table); + } + /*-------------------------------计算datasource相关end------------------*/ + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/HandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/HandlerBuilder.java new file mode 100644 index 000000000..757005ae3 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/HandlerBuilder.java @@ -0,0 +1,111 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Logger; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.MultiNodeMergeHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.OutputHandler; +import io.mycat.plan.PlanNode; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.node.NoNameNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.TableNode; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; + +public class HandlerBuilder { + private static Logger logger = Logger.getLogger(HandlerBuilder.class); + + private PlanNode node; + private NonBlockingSession session; + private OutputHandler fh; + private Set rrsNodes = new HashSet(); + public HandlerBuilder(PlanNode node, NonBlockingSession session) { + this.node = node; + this.session = session; + } + + public List buildRouteSources() { + List list = new ArrayList(); + BaseHandlerBuilder builder = createBuilder(session, node, this); + builder.build(); + fh = new OutputHandler(BaseHandlerBuilder.getSequenceId(), session, false); + DMLResponseHandler endHandler = builder.getEndHandler(); + endHandler.setNextHandler(fh); + for (DMLResponseHandler handler : fh.getMerges()) { + MultiNodeMergeHandler mergeHandler = (MultiNodeMergeHandler) handler; + for (int i = 0; i < mergeHandler.getRouteSources().length; i++) { + list.add(mergeHandler.getRouteSources()[i]); + } + } + return list; + } + + public void checkRRSS(RouteResultsetNode[] rrssArray) { + for (RouteResultsetNode rrss : rrssArray) { + while (rrsNodes.contains(rrss)) { + rrss.getMultiplexNum().incrementAndGet(); + } + rrsNodes.add(rrss); + } + } + + /** + * 启动一个节点下面的所有的启动节点 + */ + public static void startHandler(DMLResponseHandler handler) throws Exception { + for (DMLResponseHandler startHandler : handler.getMerges()) { + MultiNodeMergeHandler mergeHandler = (MultiNodeMergeHandler) startHandler; + mergeHandler.execute(); + } + } + + /** + * 生成node链,返回endHandler + * + * @param node + * @return + */ + public DMLResponseHandler buildNode(NonBlockingSession session, PlanNode node) { + BaseHandlerBuilder builder = createBuilder(session, node, this); + builder.build(); + return builder.getEndHandler(); + } + + public void build(boolean hasNext) throws Exception { + long startTime = System.nanoTime(); + DMLResponseHandler endHandler = buildNode(session, node); + fh = new OutputHandler(BaseHandlerBuilder.getSequenceId(), session, hasNext); + endHandler.setNextHandler(fh); + HandlerBuilder.startHandler(fh); + long endTime = System.nanoTime(); + logger.info("HandlerBuilder.build cost:" + (endTime - startTime)); + } + + private BaseHandlerBuilder createBuilder(final NonBlockingSession session, PlanNode node, HandlerBuilder context) { + switch (node.type()) { + case TABLE: { + return new TableNodeHandlerBuilder(session, (TableNode) node, this); + } + case JOIN: { + return new JoinNodeHandlerBuilder(session, (JoinNode) node, this); + } + case MERGE: { + return new MergeNodeHandlerBuilder(session, (MergeNode) node, this); + } + case QUERY: + return new QueryNodeHandlerBuilder(session, (QueryNode) node, this); + case NONAME: + return new NoNameNodeHandlerBuilder(session, (NoNameNode) node, this); + default: + } + throw new RuntimeException("not supported tree node type:" + node.type()); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/JoinNodeHandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/JoinNodeHandlerBuilder.java new file mode 100644 index 000000000..59b822ffb --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/JoinNodeHandlerBuilder.java @@ -0,0 +1,191 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.PushDownVisitor; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.OrderByHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.TempTableHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.join.JoinHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.join.NotInHandler; +import io.mycat.backend.mysql.nio.handler.util.CallBackHandler; +import io.mycat.config.ErrorCode; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.ItemString; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIn; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.util.PlanUtil; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; + +class JoinNodeHandlerBuilder extends BaseHandlerBuilder { + private JoinNode node; + + protected JoinNodeHandlerBuilder(NonBlockingSession session, JoinNode node, HandlerBuilder hBuilder) { + super(session, node, hBuilder); + this.node = node; + } + + @Override + public boolean canDoAsMerge() { + if (PlanUtil.isGlobalOrER(node)) + return true; + else + return false; + } + + @Override + public void mergeBuild() { + try { + this.needWhereHandler = false; + this.canPushDown = !node.existUnPushDownGroup(); + PushDownVisitor pdVisitor = new PushDownVisitor(node, true); + MergeBuilder mergeBuilder = new MergeBuilder(session, node, needCommon, needSendMaker, pdVisitor); + RouteResultsetNode[] rrssArray = mergeBuilder.construct(); + boolean simpleVisited = mergeBuilder.isSimpleVisited(); + this.needCommon = mergeBuilder.getNeedCommonFlag(); + this.needSendMaker = mergeBuilder.getNeedSendMakerFlag(); + buildMergeHandler(node, rrssArray, pdVisitor, simpleVisited); + } catch (Exception e) { + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "join node mergebuild exception!", e); + } + } + + @Override + public List buildPre() { + List pres = new ArrayList(); + PlanNode left = node.getLeftNode(); + PlanNode right = node.getRightNode(); + switch (node.getStrategy()) { + case NESTLOOP: + final boolean isLeftSmall = left.getNestLoopFilters() == null; + final PlanNode tnSmall = isLeftSmall ? left : right; + final PlanNode tnBig = isLeftSmall ? right : left; + // 确定准备传递的列 + List keySources = isLeftSmall ? node.getLeftKeys() : node.getRightKeys(); + List keyToPasses = isLeftSmall ? node.getRightKeys() : node.getLeftKeys(); + // 只需要传递第一个key过滤条件给目标节点就ok, 尽量选择toPass的是Column的 + int columnIndex = 0; + for (int index = 0; index < keyToPasses.size(); index++) { + Item keyToPass = keyToPasses.get(index); + if (keyToPass.type().equals(ItemType.FIELD_ITEM)) { + columnIndex = index; + break; + } + } + final Item keySource = keySources.get(columnIndex); + final Item keyToPass = keyToPasses.get(columnIndex); + DMLResponseHandler endHandler = buildJoinChild(tnSmall, isLeftSmall); + final TempTableHandler tempHandler = new TempTableHandler(getSequenceId(), session, keySource); + endHandler.setNextHandler(tempHandler); + tempHandler.setLeft(isLeftSmall); + pres.add(tempHandler); + CallBackHandler tempDone = new CallBackHandler() { + + @Override + public void call() throws Exception { + Set valueSet = tempHandler.getValueSet(); + buildNestFilters(tnBig, keyToPass, valueSet, tempHandler.getMaxPartSize()); + DMLResponseHandler bigLh = buildJoinChild(tnBig, !isLeftSmall); + bigLh.setNextHandler(tempHandler.getNextHandler()); + tempHandler.setCreatedHandler(bigLh); + // 启动handler + HandlerBuilder.startHandler(bigLh); + } + }; + tempHandler.setTempDoneCallBack(tempDone); + break; + case SORTMERGE: + DMLResponseHandler lh = buildJoinChild(left, true); + pres.add(lh); + DMLResponseHandler rh = buildJoinChild(right, false); + pres.add(rh); + break; + default: + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "","strategy ["+node.getStrategy()+"] not implement yet!" ); + } + return pres; + } + + private DMLResponseHandler buildJoinChild(PlanNode child, boolean isLeft) { + DMLResponseHandler endHandler = hBuilder.buildNode(session, child); + if (isLeft) { + if (!node.isLeftOrderMatch()) { + OrderByHandler oh = new OrderByHandler(getSequenceId(), session, node.getLeftJoinOnOrders()); + endHandler.setNextHandler(oh); + endHandler = oh; + } + endHandler.setLeft(true); + } else { + if (!node.isRightOrderMatch()) { + OrderByHandler oh = new OrderByHandler(getSequenceId(), session, node.getRightJoinOnOrders()); + endHandler.setNextHandler(oh); + endHandler = oh; + } + } + return endHandler; + } + + @Override + public void buildOwn() { + if (node.isNotIn()) { + NotInHandler nh = new NotInHandler(getSequenceId(), session, node.getLeftJoinOnOrders(), + node.getRightJoinOnOrders()); + addHandler(nh); + } else { + JoinHandler jh = new JoinHandler(getSequenceId(), session, node.isLeftOuterJoin(), + node.getLeftJoinOnOrders(), node.getRightJoinOnOrders(), node.getOtherJoinOnFilter()); + addHandler(jh); + } + } + + /** + * 根据临时表的数据生成新的大表的过滤条件 + * + * @param tnBig + * @param keyToPass + * @param valueSet + */ + protected void buildNestFilters(PlanNode tnBig, Item keyToPass, Set valueSet, int maxPartSize) { + List strategyFilters = tnBig.getNestLoopFilters(); + List partList = null; + Item keyInBig = PlanUtil.pushDownItem(node, keyToPass); + int partSize = 0; + for (String value : valueSet) { + if (partList == null) + partList = new ArrayList(); + if (value == null) { // is null will never join + continue; + } else { + partList.add(new ItemString(value)); + if (++partSize >= maxPartSize) { + List argList = new ArrayList(); + argList.add(keyInBig); + argList.addAll(partList); + ItemFuncIn inFilter = new ItemFuncIn(argList, false); + strategyFilters.add(inFilter); + partList = null; + partSize = 0; + } + } + } + if (partSize > 0) { + List argList = new ArrayList(); + argList.add(keyInBig); + argList.addAll(partList); + ItemFuncIn inFilter = new ItemFuncIn(argList, false); + strategyFilters.add(inFilter); + } + // 没有数据 + if (strategyFilters.isEmpty()) { + strategyFilters.add(new ItemInt(0)); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/MergeBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/MergeBuilder.java new file mode 100644 index 000000000..f984863eb --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/MergeBuilder.java @@ -0,0 +1,95 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.sql.SQLNonTransientException; +import java.sql.SQLSyntaxErrorException; + +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.parser.SQLStatementParser; + +import io.mycat.MycatServer; +import io.mycat.backend.mysql.nio.handler.builder.BaseHandlerBuilder.MySQLNodeType; +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.PushDownVisitor; +import io.mycat.cache.LayerCachePool; +import io.mycat.config.MycatConfig; +import io.mycat.config.model.SchemaConfig; +import io.mycat.plan.PlanNode; +import io.mycat.route.RouteResultset; +import io.mycat.route.RouteResultsetNode; +import io.mycat.route.parser.druid.DruidParser; +import io.mycat.route.parser.druid.MycatSchemaStatVisitor; +import io.mycat.route.parser.druid.impl.DruidBaseSelectParser; +import io.mycat.route.util.RouterUtil; +import io.mycat.server.NonBlockingSession; +import io.mycat.server.parser.ServerParse; + +public class MergeBuilder { + private boolean simpleVisited; + private boolean needCommonFlag; + private boolean needSendMakerFlag; + private PlanNode node; + private NonBlockingSession session; + private MySQLNodeType nodeType; + private String schema; + private MycatConfig mycatConfig; + private PushDownVisitor pdVisitor; + + public MergeBuilder(NonBlockingSession session, PlanNode node, boolean needCommon, boolean needSendMaker, + PushDownVisitor pdVisitor) { + this.node = node; + this.simpleVisited = false; + this.needCommonFlag = needCommon; + this.needSendMakerFlag = needSendMaker; + this.session = session; + this.schema = session.getSource().getSchema(); + this.nodeType = session.getSource().isTxstart() || !session.getSource().isAutocommit() ? MySQLNodeType.MASTER + : MySQLNodeType.SLAVE; + this.mycatConfig = MycatServer.getInstance().getConfig(); + this.pdVisitor = pdVisitor; + } + + /** + * 将一个或者多个条件合并后计算出所需要的节点... + * + * @return + * @throws SQLNonTransientException + * @throws SQLSyntaxErrorException + */ + public RouteResultsetNode[] construct() throws SQLNonTransientException { + pdVisitor.visit(); + String sql = pdVisitor.getSql().toString(); + SQLStatementParser parser = new MySqlStatementParser(sql); + SQLSelectStatement select = (SQLSelectStatement) parser.parseStatement(); + MycatSchemaStatVisitor visitor = new MycatSchemaStatVisitor(); + DruidParser druidParser = new DruidBaseSelectParser(); + + RouteResultset rrs = new RouteResultset(sql, ServerParse.SELECT, null); + LayerCachePool pool = MycatServer.getInstance().getRouterservice().getTableId2DataNodeCache(); + SchemaConfig schemaConfig = mycatConfig.getSchemas().get(schema); + rrs = RouterUtil.routeFromParser(druidParser, schemaConfig, rrs, select, sql, pool, visitor); + + return rrs.getNodes(); + } + + /* -------------------- getter/setter -------------------- */ + public boolean getNeedCommonFlag() { + return needCommonFlag; + } + + public boolean isSimpleVisited() { + return simpleVisited; + } + + public boolean getNeedSendMakerFlag() { + return needSendMakerFlag; + } + + public void setNodeType(MySQLNodeType nodeType) { + this.nodeType = nodeType; + } + + public void setSchema(String schema) { + this.schema = schema; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/MergeNodeHandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/MergeNodeHandlerBuilder.java new file mode 100644 index 000000000..10dce6a42 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/MergeNodeHandlerBuilder.java @@ -0,0 +1,41 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.DistinctHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.UnionHandler; +import io.mycat.plan.PlanNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.server.NonBlockingSession; + +class MergeNodeHandlerBuilder extends BaseHandlerBuilder { + private MergeNode node; + + protected MergeNodeHandlerBuilder(NonBlockingSession session, MergeNode node, HandlerBuilder hBuilder) { + super(session, node, hBuilder); + this.node = node; + } + + @Override + protected List buildPre() { + List pres = new ArrayList(); + for (PlanNode child : node.getChildren()) { + DMLResponseHandler ch = hBuilder.buildNode(session, child); + pres.add(ch); + } + return pres; + } + + @Override + public void buildOwn() { + UnionHandler uh = new UnionHandler(getSequenceId(), session, node.getComeInFields(), node.getChildren().size()); + addHandler(uh); + if (node.isUnion()) { + DistinctHandler dh = new DistinctHandler(getSequenceId(), session, node.getColumnsSelected()); + addHandler(dh); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/NoNameNodeHandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/NoNameNodeHandlerBuilder.java new file mode 100644 index 000000000..718af50c3 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/NoNameNodeHandlerBuilder.java @@ -0,0 +1,47 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.PushDownVisitor; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.MultiNodeMergeHandler; +import io.mycat.plan.node.NoNameNode; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; + +/** + * select 1 as name这种sql + * + * @author chenzifei + * @CreateTime 2015年3月23日 + */ +class NoNameNodeHandlerBuilder extends BaseHandlerBuilder { + private NoNameNode node; + + protected NoNameNodeHandlerBuilder(NonBlockingSession session, NoNameNode node, HandlerBuilder hBuilder) { + super(session, node, hBuilder); + this.node = node; + this.needWhereHandler = false; + this.needCommon = false; + } + + @Override + public List buildPre() { + return new ArrayList(); + } + + @Override + public void buildOwn() { + PushDownVisitor vistor = new PushDownVisitor(node, true); + vistor.visit(); + this.canPushDown = true; + String sql = vistor.getSql().toString(); + RouteResultsetNode[] rrss = getTableSources(sql.toString()); + hBuilder.checkRRSS(rrss); + MultiNodeMergeHandler mh = new MultiNodeMergeHandler(getSequenceId(), rrss, session.getSource().isAutocommit(), + session, null); + addHandler(mh); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/QueryNodeHandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/QueryNodeHandlerBuilder.java new file mode 100644 index 000000000..79e52545a --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/QueryNodeHandlerBuilder.java @@ -0,0 +1,33 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.plan.PlanNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.server.NonBlockingSession; + +class QueryNodeHandlerBuilder extends BaseHandlerBuilder { + + private QueryNode node; + + protected QueryNodeHandlerBuilder(NonBlockingSession session, + QueryNode node, HandlerBuilder hBuilder) { + super(session, node, hBuilder); + this.node = node; + } + + @Override + public List buildPre() { + List pres = new ArrayList(); + PlanNode subNode = node.getChild(); + DMLResponseHandler subHandler = hBuilder.buildNode(session, subNode); + pres.add(subHandler); + return pres; + } + + @Override + public void buildOwn() { + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/TableNodeHandlerBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/TableNodeHandlerBuilder.java new file mode 100644 index 000000000..d0fdbc2e6 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/TableNodeHandlerBuilder.java @@ -0,0 +1,99 @@ +package io.mycat.backend.mysql.nio.handler.builder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.PushDownVisitor; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.config.ErrorCode; +import io.mycat.config.model.TableConfig; +import io.mycat.config.model.TableConfig.TableTypeEnum; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.node.TableNode; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; + +class TableNodeHandlerBuilder extends BaseHandlerBuilder { + private TableNode node; + private TableConfig tableConfig = null; + + protected TableNodeHandlerBuilder(NonBlockingSession session, TableNode node, HandlerBuilder hBuilder) { + super(session, node, hBuilder); + this.node = node; + this.canPushDown = !node.existUnPushDownGroup(); + this.needWhereHandler = false; + this.tableConfig = getTableConfig(node.getSchema(), node.getTableName()); + } + + @Override + public List buildPre() { + return new ArrayList(); + } + + @Override + public void buildOwn() { + try { + PushDownVisitor pdVisitor = new PushDownVisitor(node, true); + MergeBuilder mergeBuilder = new MergeBuilder(session, node, needCommon, needSendMaker, pdVisitor); + RouteResultsetNode[] rrssArray = mergeBuilder.construct(); + boolean simpleVisited = mergeBuilder.isSimpleVisited(); + this.needCommon = mergeBuilder.getNeedCommonFlag(); + this.needSendMaker = mergeBuilder.getNeedSendMakerFlag(); + buildMergeHandler(node, rrssArray, pdVisitor, simpleVisited); + } catch (Exception e) { + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "tablenode buildOwn exception!", e); + } + } + + @Override + protected void nestLoopBuild() { + try { + List filters = node.getNestLoopFilters(); + PushDownVisitor pdVisitor = new PushDownVisitor(node, true); + if (filters == null || filters.isEmpty()) + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "unexpected exception!"); + List rrssList = new ArrayList(); + MergeBuilder mergeBuilder = new MergeBuilder(session, node, needCommon, needSendMaker, pdVisitor); + if (tableConfig == null || tableConfig.getTableType()==TableTypeEnum.TYPE_GLOBAL_TABLE) { + for (Item filter : filters) { + node.setWhereFilter(filter); + RouteResultsetNode[] rrssArray = mergeBuilder.construct(); + rrssList.addAll(Arrays.asList(rrssArray)); + } + if (filters.size() == 1) { + this.needCommon = false; + this.needSendMaker = mergeBuilder.getNeedSendMakerFlag(); + } +// } else if (!node.isPartitioned()) { +// // 防止in的列数太多,不再进行parti计算 +// for (Item filter : filters) { +// node.setWhereFilter(filter); +// pdVisitor.visit(); +// String sql = pdVisitor.getSql().toString(); +// RouteResultsetNode[] rrssArray = getTableSources(node.getSchema(), node.getTableName(), sql); +// rrssList.addAll(Arrays.asList(rrssArray)); +// } + } else { + boolean tryGlobal = filters.size() == 1; + for (Item filter : filters) { + node.setWhereFilter(filter); + pdVisitor.visit(); + RouteResultsetNode[] rrssArray = mergeBuilder.construct(); + rrssList.addAll(Arrays.asList(rrssArray)); + } + if (tryGlobal) { + this.needCommon = mergeBuilder.getNeedCommonFlag(); + this.needSendMaker = mergeBuilder.getNeedSendMakerFlag(); + } + } + RouteResultsetNode[] rrssArray = new RouteResultsetNode[rrssList.size()]; + rrssArray = rrssList.toArray(rrssArray); + buildMergeHandler(node, rrssArray, pdVisitor, mergeBuilder.isSimpleVisited()); + } catch (Exception e) { + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "", e); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/GlobalVisitor.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/GlobalVisitor.java new file mode 100644 index 000000000..54b116dcb --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/GlobalVisitor.java @@ -0,0 +1,323 @@ +package io.mycat.backend.mysql.nio.handler.builder.sqlvisitor; + +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.node.NoNameNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.TableNode; + +/** + * 标准sql生成器,node是什么就下发什么,因为node是global的 每一个node都得用一个新的globalvisitor对象来进行visit + * + * @author chenzifei + * @CreateTime 2014年12月10日 + */ +public class GlobalVisitor extends MysqlVisitor { + + public GlobalVisitor(PlanNode globalQuery, boolean isTopQuery) { + super(globalQuery, isTopQuery); + } + + public void visit() { + if (!visited) { + replaceableSqlBuilder.clear(); + sqlBuilder = replaceableSqlBuilder.getCurrentElement().getSb(); + switch (query.type()) { + case TABLE: + visit((TableNode) query); + break; + case JOIN: + visit((JoinNode) query); + break; + case QUERY: + visit((QueryNode) query); + break; + case MERGE: + visit((MergeNode) query); + break; + case NONAME: + visit((NoNameNode) query); + break; + default: + throw new RuntimeException("not implement yet!"); + } + visited = true; + } else { + // where的可替换仅针对tablenode,不可以迭代 + buildWhere(query); + } + } + + protected void visit(TableNode query) { + boolean parentIsQuery = query.getParent() != null && query.getParent().type() == PlanNodeType.QUERY; + if (query.isSubQuery() && !parentIsQuery && !isTopQuery) { + sqlBuilder.append(" ( "); + } + if (query.isSubQuery() || isTopQuery) { + buildSelect(query); + + if (query.getTableName() == null) + return; + sqlBuilder.append(" from "); + } + // 需要根据是否是下划表进行计算 + buildTableName(query, sqlBuilder); + if (query.isSubQuery() || isTopQuery) { + buildWhere(query); + buildGroupBy(query); + buildHaving(query); + buildOrderBy(query); + buildLimit(query); + } + + if (query.isSubQuery() && !parentIsQuery && !isTopQuery) { + sqlBuilder.append(" ) "); + if (query.getAlias() != null) { + sqlBuilder.append(" ").append(query.getAlias()).append(" "); + } + } + visited = true; + } + + protected void visit(NoNameNode query) { + // to fix:如果在viewoptimizr时,将noname的where和select进行了修改,则需要 + // 改成和tablenode类似的做法 + if (!isTopQuery) { + sqlBuilder.append(" ( "); + } + sqlBuilder.append(query.getSql()); + if (!isTopQuery) { + sqlBuilder.append(" ) "); + if (query.getAlias() != null) { + sqlBuilder.append(" ").append(query.getAlias()).append(" "); + } + } + } + + protected void visit(QueryNode query) { + if (query.isSubQuery() && !isTopQuery) { + sqlBuilder.append(" ( "); + } + if (query.isSubQuery() || isTopQuery) { + buildSelect(query); + sqlBuilder.append(" from "); + } + sqlBuilder.append('('); + PlanNode child = query.getChild(); + MysqlVisitor childVisitor = new GlobalVisitor(child, false); + childVisitor.visit(); + sqlBuilder.append(childVisitor.getSql()).append(") ").append(child.getAlias()); + if (query.isSubQuery() || isTopQuery) { + buildWhere(query); + buildGroupBy(query); + buildHaving(query); + buildOrderBy(query); + buildLimit(query); + } + + if (query.isSubQuery() && !isTopQuery) { + sqlBuilder.append(" ) "); + if (query.getAlias() != null) { + sqlBuilder.append(" ").append(query.getAlias()).append(" "); + } + } + } + + protected void visit(MergeNode merge) { + boolean isUnion = merge.isUnion(); + boolean isFirst = true; + for (PlanNode child : merge.getChildren()) { + if (isFirst) + isFirst = false; + else + sqlBuilder.append(isUnion ? " UNION " : " UNION ALL "); + MysqlVisitor childVisitor = new GlobalVisitor(child, true); + childVisitor.visit(); + sqlBuilder.append("(").append(childVisitor.getSql()).append(")"); + } + } + + protected void visit(JoinNode join) { + if (!isTopQuery) { + sqlBuilder.append(" ( "); + } + if (join.isSubQuery() || isTopQuery) { + buildSelect(join); + sqlBuilder.append(" from "); + } + + PlanNode left = join.getLeftNode(); + PlanNode right = join.getRightNode(); + MysqlVisitor leftVisitor = new GlobalVisitor(left, false); + leftVisitor.visit(); + sqlBuilder.append(leftVisitor.getSql()); + if (join.getLeftOuter() && join.getRightOuter()) { + throw new RuntimeException("full outter join 不支持"); + } else if (join.getLeftOuter() && !join.getRightOuter()) { + sqlBuilder.append(" left"); + } else if (join.getRightOuter() && !join.getLeftOuter()) { + sqlBuilder.append(" right"); + } + + sqlBuilder.append(" join "); + MysqlVisitor rightVisitor = new GlobalVisitor(right, false); + rightVisitor.visit(); + sqlBuilder.append(rightVisitor.getSql()); + StringBuilder joinOnFilterStr = new StringBuilder(); + boolean first = true; + for (int i = 0; i < join.getJoinFilter().size(); i++) { + Item filter = join.getJoinFilter().get(i); + if (first) { + sqlBuilder.append(" on "); + first = false; + } else + joinOnFilterStr.append(" and "); + joinOnFilterStr.append(filter); + } + + if (join.getOtherJoinOnFilter() != null) { + if (first) { + first = false; + } else { + joinOnFilterStr.append(" and "); + } + + joinOnFilterStr.append(join.getOtherJoinOnFilter()); + } + sqlBuilder.append(joinOnFilterStr.toString()); + if (join.isSubQuery() || isTopQuery) { + buildWhere(join); + buildGroupBy(join); + buildHaving(join); + buildOrderBy(join); + buildLimit(join); + } + + if (!isTopQuery) { + sqlBuilder.append(" ) "); + if (join.getAlias() != null) + sqlBuilder.append(" ").append(join.getAlias()).append(" "); + } + + } + + protected void buildSelect(PlanNode query) { + sqlBuilder.append("select "); + boolean hasDistinct = query.isDistinct(); + boolean first = true; + StringBuilder sb = new StringBuilder(); + for (Item selected : query.getColumnsSelected()) { + if (first) + first = false; + else + sb.append(","); + String pdName = visitPushDownNameSel(selected); + sb.append(pdName); + } + if (hasDistinct) + sqlBuilder.append(" distinct "); + sqlBuilder.append(sb); + } + + protected void buildGroupBy(PlanNode query) { + boolean first = true; + if (query.getGroupBys() != null && query.getGroupBys().size() > 0) { + sqlBuilder.append(" GROUP BY "); + for (Order group : query.getGroupBys()) { + if (first) + first = false; + else + sqlBuilder.append(","); + Item groupCol = group.getItem(); + String pdName = ""; + if (groupCol.basicConstItem()) + pdName = "'" + groupCol.toString() + "'"; + if (pdName.isEmpty()) + pdName = visitUnselPushDownName(groupCol, true); + sqlBuilder.append(pdName).append(" ").append(group.getSortOrder()); + } + } + } + + protected void buildHaving(PlanNode query) { + if (query.getHavingFilter() != null) { + Item filter = query.getHavingFilter(); + String pdName = visitUnselPushDownName(filter, true); + sqlBuilder.append(" having ").append(pdName); + } + } + + protected void buildOrderBy(PlanNode query) { + boolean first = true; + if (query.getOrderBys() != null && !query.getOrderBys().isEmpty()) { + sqlBuilder.append(" order by "); + for (Order order : query.getOrderBys()) { + if (first) { + first = false; + } else { + sqlBuilder.append(","); + } + + Item orderByCol = order.getItem(); + String pdName = ""; + if (orderByCol.basicConstItem()) + pdName = "'" + orderByCol.toString() + "'"; + if (pdName.isEmpty()) + pdName = visitUnselPushDownName(orderByCol, true); + sqlBuilder.append(pdName).append(" ").append(order.getSortOrder()); + } + } + } + + protected void buildLimit(PlanNode query) { + long limitFrom = query.getLimitFrom(); + long limitTo = query.getLimitTo(); + if (limitFrom == -1 && limitTo == -1) { + return; + } + sqlBuilder.append(" limit "); + if (limitFrom > -1) + sqlBuilder.append(limitFrom); + if (limitTo != -1) { + sqlBuilder.append(",").append(limitTo); + } + } + + /* -------------------------- help method ------------------------ */ + @Override + protected String visitPushDownNameSel(Item item) { + String orgPushDownName = item.getItemName(); + if(item.type().equals(ItemType.FIELD_ITEM)){ + orgPushDownName = "`"+item.getTableName()+"`.`"+orgPushDownName + "`"; + } + String pushAlias = null; + if(item.getPushDownName() != null) + //already set before + pushAlias = item.getPushDownName(); + else if (item.getAlias() != null) { + pushAlias = item.getAlias(); + if (pushAlias.startsWith(Item.FNAF)) + pushAlias = getRandomAliasName(); + } else if (orgPushDownName.length() > MAX_COL_LENGTH) {// 如果超出最大长度,则需要自定义别名 + pushAlias = getRandomAliasName(); + } + if (pushAlias == null) { + if (item.type().equals(ItemType.FIELD_ITEM)) { + pushNameMap.put(orgPushDownName, null); + } else { + item.setPushDownName(orgPushDownName); + pushNameMap.put(orgPushDownName, orgPushDownName); + } + return orgPushDownName; + } else { + item.setPushDownName(pushAlias); + pushNameMap.put(orgPushDownName, pushAlias); + return orgPushDownName + " as `" + pushAlias + "`"; + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/MysqlVisitor.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/MysqlVisitor.java new file mode 100644 index 000000000..94629e62b --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/MysqlVisitor.java @@ -0,0 +1,131 @@ +package io.mycat.backend.mysql.nio.handler.builder.sqlvisitor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.ast.SQLHint; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.ptr.StringPtr; +import io.mycat.plan.node.TableNode; + +/** + * 处理可以下发的查询节点,可以下发的情况有可能是全global表, 也有可能是部分global部分非global + * + * @author chenzifei + * @CreateTime 2014年12月10日 + */ +public abstract class MysqlVisitor { + // mysql支持的最长列长度 + protected static final int MAX_COL_LENGTH = 255; + // 记录sel name和push name之间的映射关系 + protected Map pushNameMap = new HashMap(); + protected boolean isTopQuery = false; + protected PlanNode query; + protected long randomIndex = 0L; + /* 是否存在不可下发的聚合函数,如果存在,所有函数都不下发,自己进行计算 */ + protected boolean existUnPushDownGroup = false; + protected boolean visited = false; + // -- start replaceable stringbuilder + protected ReplaceableStringBuilder replaceableSqlBuilder = new ReplaceableStringBuilder(); + // 临时记录的sql + protected StringBuilder sqlBuilder; + protected StringPtr replaceableWhere = new StringPtr(""); + + // 存储可替换的String + // -- end replaceable stringbuilder + + public MysqlVisitor(PlanNode query, boolean isTopQuery) { + this.query = query; + this.isTopQuery = isTopQuery; + } + + public ReplaceableStringBuilder getSql() { + return replaceableSqlBuilder; + } + + public abstract void visit(); + + /** + * @param query + */ + protected void buildTableName(TableNode query, StringBuilder sb) { + sb.append(" `").append(query.getPureName()).append("`"); + String subAlias = query.getSubAlias(); + if (subAlias != null) + sb.append(" `").append(query.getSubAlias()).append("`"); + List hintList = query.getHintList(); + if (hintList != null && !hintList.isEmpty()) { + sb.append(' '); + boolean isFirst = true; + for (SQLHint hint : hintList) { + if (isFirst) + isFirst = false; + else + sb.append(" "); + MySqlOutputVisitor ov = new MySqlOutputVisitor(sb); + hint.accept(ov); + } + } + } + + /* where修改为可替换的 */ + protected void buildWhere(PlanNode query) { + if (!visited) + replaceableSqlBuilder.getCurrentElement().setRepString(replaceableWhere); + StringBuilder whereBuilder = new StringBuilder(); + Item filter = query.getWhereFilter(); + if (filter != null) { + String pdName = visitUnselPushDownName(filter, false); + whereBuilder.append(" where ").append(pdName); + } + replaceableWhere.set(whereBuilder.toString()); + // refresh sqlbuilder + sqlBuilder = replaceableSqlBuilder.getCurrentElement().getSb(); + } + + public boolean isRandomAliasMade() { + return randomIndex != 0; + } + + // 生成自定义的聚合函数别名 + public static String getMadeAggAlias(String aggFuncName) { + StringBuilder builder = new StringBuilder(); + builder.append("_$").append(aggFuncName).append("$_"); + return builder.toString(); + } + + protected String getRandomAliasName() { + StringBuilder builder = new StringBuilder(); + builder.append("rpda_").append(randomIndex++); + return builder.toString(); + } + + /** + * 生成pushdown信息 + */ + protected abstract String visitPushDownNameSel(Item o); + + // 非sellist的下推name + public final String visitUnselPushDownName(Item item, boolean canUseAlias) { + String selName = item.getItemName(); + if (item.type().equals(ItemType.FIELD_ITEM)) { + selName = "`" + item.getTableName() + "`.`" + selName + "`"; + } + String nameInMap = pushNameMap.get(selName); + if (nameInMap != null) { + item.setPushDownName(nameInMap); + if (canUseAlias && !(query.type() == PlanNodeType.JOIN && item.type().equals(ItemType.FIELD_ITEM))) { + // join时 select t1.id,t2.id from t1,t2 order by t1.id + // 尽量用用户原始输入的group by,order by + selName = nameInMap; + } + } + return selName; + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/PushDownVisitor.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/PushDownVisitor.java new file mode 100644 index 000000000..cb9a51fbb --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/PushDownVisitor.java @@ -0,0 +1,348 @@ +package io.mycat.backend.mysql.nio.handler.builder.sqlvisitor; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.TableNode; + +/** + * 处理那种可以下推部分sql,又和global表的下推不同的node类型 单个table er关系表 非全global表的下退等等 + * + * @author chenzifei + * + */ +public class PushDownVisitor extends MysqlVisitor { + + /* 用来记录真正被下发下去的orderby列表 */ + private List pushDownOrderBy; + /* 存储最后下推的列 */ + private List pushDownTableInfos; + + public PushDownVisitor(PlanNode pushDownQuery, boolean isTopQuery) { + super(pushDownQuery, isTopQuery); + this.existUnPushDownGroup = pushDownQuery.existUnPushDownGroup(); + pushDownOrderBy = new ArrayList(); + pushDownTableInfos = new ArrayList(); + } + + public void visit() { + if (!visited) { + replaceableSqlBuilder.clear(); + sqlBuilder = replaceableSqlBuilder.getCurrentElement().getSb(); + // 在已经visited的情况下,pushdownvisitor只要进行table名称的替换即可 + switch (query.type()) { + case TABLE: + visit((TableNode) query); + break; + case JOIN: + visit((JoinNode) query); + break; + default: + throw new RuntimeException("not implement yet!"); + } + visited = true; + } else { + // where的可替换仅针对tablenode,不可以迭代 + buildWhere(query); + } + } + + + + protected void visit(TableNode query) { + if (query.isSubQuery() && !isTopQuery) { + sqlBuilder.append(" ( "); + } + if (query.isSubQuery() || isTopQuery) { + buildSelect(query); + + if (query.getTableName() == null) + return; + sqlBuilder.append(" from "); + } + // 需要根据是否是下划表进行计算,生成可替换的String + buildTableName(query, sqlBuilder); + if (query.isSubQuery() || isTopQuery) { + buildWhere(query); + buildGroupBy(query); + buildHaving(query); + buildOrderBy(query); + buildLimit(query, sqlBuilder); + } + + if (query.isSubQuery() && !isTopQuery) { + sqlBuilder.append(" ) "); + if (query.getAlias() != null) { + sqlBuilder.append(" ").append(query.getAlias()).append(" "); + } + } + } + + protected void visit(JoinNode join) { + if (!isTopQuery) { + sqlBuilder.append(" ( "); + } + if (join.isSubQuery() || isTopQuery) { + buildSelect(join); + sqlBuilder.append(" from "); + } + + PlanNode left = join.getLeftNode(); + PlanNode right = join.getRightNode(); + MysqlVisitor leftVisitor = new GlobalVisitor(left, false); + leftVisitor.visit(); + replaceableSqlBuilder.append(leftVisitor.getSql()); + sqlBuilder = replaceableSqlBuilder.getCurrentElement().getSb(); + if (join.getLeftOuter() && join.getRightOuter()) { + throw new RuntimeException("full outter join 不支持"); + } else if (join.getLeftOuter() && !join.getRightOuter()) { + sqlBuilder.append(" left"); + } else if (join.getRightOuter() && !join.getLeftOuter()) { + sqlBuilder.append(" right"); + } + + sqlBuilder.append(" join "); + MysqlVisitor rightVisitor = new GlobalVisitor(right, false); + rightVisitor.visit(); + replaceableSqlBuilder.append(rightVisitor.getSql()); + sqlBuilder = replaceableSqlBuilder.getCurrentElement().getSb(); + StringBuilder joinOnFilterStr = new StringBuilder(); + boolean first = true; + for (int i = 0; i < join.getJoinFilter().size(); i++) { + Item filter = join.getJoinFilter().get(i); + if (first) { + sqlBuilder.append(" on "); + first = false; + } else + joinOnFilterStr.append(" and "); + joinOnFilterStr.append(filter); + } + + if (join.getOtherJoinOnFilter() != null) { + if (first) { + first = false; + } else { + joinOnFilterStr.append(" and "); + } + joinOnFilterStr.append(join.getOtherJoinOnFilter()); + } + sqlBuilder.append(joinOnFilterStr.toString()); + if (join.isSubQuery() || isTopQuery) { + buildWhere(join); + buildGroupBy(join); + buildHaving(join); + buildOrderBy(join); + buildLimit(join, sqlBuilder); + } + + if (!isTopQuery) { + sqlBuilder.append(" ) "); + if (join.getAlias() != null) + sqlBuilder.append(" ").append(join.getAlias()).append(" "); + } + + } + + protected void buildSelect(PlanNode query) { + boolean addPushDownTableInfo = pushDownTableInfos.isEmpty() && isTopQuery; + sqlBuilder.append("select "); + List columns = query.getColumnsRefered(); + if (query.isDistinct()) { + sqlBuilder.append("DISTINCT "); + } + for (Item col : columns) { + if (existUnPushDownGroup && col.type().equals(ItemType.SUM_FUNC_ITEM)) + continue; + if ((col.type().equals(ItemType.FUNC_ITEM) || col.type().equals(ItemType.COND_ITEM)) && col.withSumFunc) + continue; + String pdName = visitPushDownNameSel(col); + if (StringUtils.isEmpty(pdName))// 重复列 + continue; + if (col.type().equals(ItemType.SUM_FUNC_ITEM)) { + ItemSum funCol = (ItemSum) col; + String funName = funCol.funcName().toUpperCase(); + String colName = pdName; + switch (funCol.sumType()) { + case AVG_FUNC: { + String colNameSum = colName.replace(funName + "(", "SUM("); + colNameSum = colNameSum.replace(getMadeAggAlias(funName), getMadeAggAlias("SUM")); + String colNameCount = colName.replace(funName + "(", "COUNT("); + colNameCount = colNameCount.replace(getMadeAggAlias(funName), getMadeAggAlias("COUNT")); + sqlBuilder.append(colNameSum).append(",").append(colNameCount).append(","); + if (addPushDownTableInfo) { + pushDownTableInfos.add(null); + pushDownTableInfos.add(null); + } + } + continue; + case STD_FUNC: + case VARIANCE_FUNC: { + // variance:下发时 v[0]:count,v[1]:sum,v[2]:variance(局部) + String colNameCount = colName.replace(funName + "(", "COUNT("); + colNameCount = colNameCount.replace(getMadeAggAlias(funName), getMadeAggAlias("COUNT")); + String colNameSum = colName.replace(funName + "(", "SUM("); + colNameSum = colNameSum.replace(getMadeAggAlias(funName), getMadeAggAlias("SUM")); + String colNameVar = colName.replace(funName + "(", "VARIANCE("); + colNameVar = colNameVar.replace(getMadeAggAlias(funName), getMadeAggAlias("VARIANCE")); + // VARIANCE + sqlBuilder.append(colNameCount).append(",").append(colNameSum).append(",").append(colNameVar) + .append(","); + if (addPushDownTableInfo) { + pushDownTableInfos.add(null); + pushDownTableInfos.add(null); + pushDownTableInfos.add(null); + } + } + continue; + } + } + sqlBuilder.append(pdName); + if (addPushDownTableInfo) + pushDownTableInfos.add(col.getTableName()); + sqlBuilder.append(","); + } + sqlBuilder.deleteCharAt(sqlBuilder.length() - 1); + } + + protected void buildGroupBy(PlanNode query) { + if (nodeHasGroupBy(query)) { + // 可以下发整个group by的情形 + if (!existUnPushDownGroup) { + if (!query.getGroupBys().isEmpty()) { + sqlBuilder.append(" GROUP BY "); + for (Order group : query.getGroupBys()) { + // 记录下当前下推的结果集的排序 + pushDownOrderBy.add(group.copy()); + Item groupCol = group.getItem(); + String pdName = ""; + if (groupCol.basicConstItem()) + pdName = "'" + groupCol.toString() + "'"; + if (pdName.isEmpty()) + pdName = visitUnselPushDownName(groupCol, true); + sqlBuilder.append(pdName).append(" ").append(group.getSortOrder()).append(","); + } + sqlBuilder.deleteCharAt(sqlBuilder.length() - 1); + } + } else { + // 不可以下发group by的情况,转化为下发order + pushDownOrderBy.addAll(query.getGroupBys()); + if (pushDownOrderBy != null && pushDownOrderBy.size() > 0) { + sqlBuilder.append(" ORDER BY "); + for (Order order : pushDownOrderBy) { + Item orderSel = order.getItem(); + String pdName = ""; + if (orderSel.basicConstItem()) + pdName = "'" + orderSel.toString() + "'"; + if (pdName.isEmpty()) + pdName = visitUnselPushDownName(orderSel, true); + sqlBuilder.append(pdName).append(" ").append(order.getSortOrder()).append(","); + } + sqlBuilder.deleteCharAt(sqlBuilder.length() - 1); + } + } + } + } + + protected void buildHaving(PlanNode query) { + // having中由于可能存在聚合函数,而聚合函数需要merge之后结果才能出来,所以需要自己进行计算 + } + + protected void buildOrderBy(PlanNode query) { + /* 由于有groupby时,在merge的时候需要根据groupby的列进行排序merge,所以有groupby时不能下发order */ + boolean realPush = query.getGroupBys().isEmpty(); + if (query.getOrderBys().size() > 0) { + if (realPush) + sqlBuilder.append(" ORDER BY "); + for (Order order : query.getOrderBys()) { + Item orderByCol = order.getItem(); + String pdName = ""; + if (orderByCol.basicConstItem()) + pdName = "'" + orderByCol.toString() + "'"; + if (pdName.isEmpty()) + pdName = visitUnselPushDownName(orderByCol, true); + if (realPush) { + pushDownOrderBy.add(order.copy()); + sqlBuilder.append(pdName).append(" ").append(order.getSortOrder()).append(","); + } + } + if (realPush) + sqlBuilder.deleteCharAt(sqlBuilder.length() - 1); + } + } + + protected void buildLimit(PlanNode query, StringBuilder sb) { + /* groupby和limit共存时,是不可以下发limit的 */ + if (query.getGroupBys().isEmpty() && !existUnPushDownGroup) { + /* 只有order by可以下发时,limit才可以下发 */ + if (query.getLimitFrom() != -1 && query.getLimitTo() != -1) { + sb.append(" LIMIT ").append(query.getLimitFrom() + query.getLimitTo()); + } + } + } + + /** + * @return the pushDownTableInfos + */ + public List getPushDownTableInfos() { + return pushDownTableInfos; + } + + /* -------------------------- help method ------------------------ */ + + /* 判断node是否需要groupby */ + public static boolean nodeHasGroupBy(PlanNode node) { + return (node.sumFuncs.size() > 0 || node.getGroupBys().size() > 0); + } + + @Override + protected String visitPushDownNameSel(Item item) { + String orgPushDownName = item.getItemName(); + if (item.type().equals(ItemType.FIELD_ITEM)) { + orgPushDownName = "`" + item.getTableName() + "`.`" + orgPushDownName + "`"; + } + String pushAlias = null; + if (pushNameMap.containsKey(orgPushDownName)) { + // 重复的列不下发 + item.setPushDownName(pushNameMap.get(orgPushDownName)); + return null; + } + if (item.type().equals(ItemType.SUM_FUNC_ITEM)) { + // 聚合函数添加别名,但是需要表示出是哪个聚合函数 + String aggName = ((ItemSum) item).funcName().toUpperCase(); + pushAlias = getMadeAggAlias(aggName) + getRandomAliasName(); + } else if (item.getAlias() != null) { + pushAlias = item.getAlias(); + if (pushAlias.startsWith(Item.FNAF)) + pushAlias = getRandomAliasName(); + } else if (orgPushDownName.length() > MAX_COL_LENGTH) { + pushAlias = getRandomAliasName(); + } else if (isTopQuery && !item.type().equals(ItemType.FIELD_ITEM)) { + pushAlias = getRandomAliasName(); + } + if (pushAlias == null) { + if (item.type().equals(ItemType.FIELD_ITEM)) { + pushNameMap.put(orgPushDownName, null); + } else { + item.setPushDownName(orgPushDownName); + pushNameMap.put(orgPushDownName, orgPushDownName); + } + } else { + item.setPushDownName(pushAlias); + pushNameMap.put(orgPushDownName, pushAlias); + } + + if (pushAlias == null) + return orgPushDownName; + else + return orgPushDownName + " as `" + pushAlias + "`"; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/ReplaceableStringBuilder.java b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/ReplaceableStringBuilder.java new file mode 100644 index 000000000..0d221b498 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/builder/sqlvisitor/ReplaceableStringBuilder.java @@ -0,0 +1,93 @@ +package io.mycat.backend.mysql.nio.handler.builder.sqlvisitor; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.ptr.StringPtr; + +/** + * @author chenzifei + * @CreateTime 2015年12月15日 + */ +public class ReplaceableStringBuilder { + private List elements; + + public ReplaceableStringBuilder() { + elements = new ArrayList(); + } + + public Element getCurrentElement() { + Element curEle = null; + if (elements.isEmpty()) { + curEle = new Element(); + elements.add(curEle); + } else { + curEle = elements.get(elements.size() - 1); + if (curEle.getRepString() != null) { + curEle = new Element(); + elements.add(curEle); + } + } + return curEle; + } + + public ReplaceableStringBuilder append(ReplaceableStringBuilder other) { + if (other != null) + this.elements.addAll(other.elements); + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Element ele : elements) { + sb.append(ele.getSb()); + StringPtr rep = ele.getRepString(); + if (rep != null) + sb.append(rep.get()); + } + return sb.toString(); + } + + public final static class Element { + private final StringBuilder sb; + private StringPtr repString; + + public Element() { + sb = new StringBuilder(); + } + + /** + * @return the sb + */ + public StringBuilder getSb() { + return sb; + } + + /** + * @return the repString + */ + public StringPtr getRepString() { + return repString; + } + + /** + * @param repString + * the repString to set + */ + public void setRepString(StringPtr repString) { + if (this.repString != null) + throw new RuntimeException("error use"); + this.repString = repString; + } + + } + + /** + * like stringbuilder.setlength(0) + */ + public void clear() { + elements.clear(); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/BaseDMLHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/BaseDMLHandler.java new file mode 100644 index 000000000..714917297 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/BaseDMLHandler.java @@ -0,0 +1,128 @@ +package io.mycat.backend.mysql.nio.handler.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.log4j.Logger; + +import io.mycat.backend.BackendConnection; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.server.NonBlockingSession; +import io.mycat.util.ConcurrentHashSet; + +public abstract class BaseDMLHandler implements DMLResponseHandler { + private static Logger logger = Logger.getLogger(BaseDMLHandler.class); + protected final long id; + + /** + * 是否是处理所有的都pushdown了,包括函数 + */ + private boolean allPushDown = false; + + /** + * 从上一层hangdler接受到的fieldpackets集合 + */ + protected List fieldPackets = new ArrayList(); + protected BaseDMLHandler nextHandler = null; + protected boolean isLeft = false; + protected NonBlockingSession session; + protected AtomicBoolean terminate = new AtomicBoolean(false); + protected Set merges; + + public BaseDMLHandler(long id, NonBlockingSession session) { + this.id = id; + this.session = session; + this.merges = new ConcurrentHashSet(); + } + + @Override + public final BaseDMLHandler getNextHandler() { + return this.nextHandler; + } + + @Override + public final void setNextHandler(DMLResponseHandler next) { + this.nextHandler = (BaseDMLHandler) next; + DMLResponseHandler toAddMergesHandler = next; + do { + toAddMergesHandler.getMerges().addAll(this.getMerges()); + toAddMergesHandler = toAddMergesHandler.getNextHandler(); + } while (toAddMergesHandler != null); + } + + @Override + public void setLeft(boolean isLeft) { + this.isLeft = isLeft; + } + + @Override + public final Set getMerges() { + return this.merges; + } + + public boolean isAllPushDown() { + return allPushDown; + } + + public void setAllPushDown(boolean allPushDown) { + this.allPushDown = allPushDown; + } + + @Override + public final void terminate() { + if (terminate.compareAndSet(false, true)) { + try { + onTerminate(); + } catch (Exception e) { + logger.warn("handler terminate exception:", e); + } + } + } + + protected abstract void onTerminate() throws Exception; + + @Override + public void connectionError(Throwable e, BackendConnection conn) { + // TODO Auto-generated method stub + + } + + @Override + public void errorResponse(byte[] err, BackendConnection conn) { + nextHandler.errorResponse(err, conn); + } + + @Override + public void okResponse(byte[] ok, BackendConnection conn) { + } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + // TODO Auto-generated method stub + + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + // TODO Auto-generated method stub + + } + + @Override + public void writeQueueAvailable() { + // TODO Auto-generated method stub + + } + + @Override + public void connectionClose(BackendConnection conn, String reason) { + // TODO Auto-generated method stub + + } + @Override + public void connectionAcquired(BackendConnection conn) { + // TODO Auto-generated method stub + + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/DMLResponseHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/DMLResponseHandler.java new file mode 100644 index 000000000..b11b7eb9f --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/DMLResponseHandler.java @@ -0,0 +1,28 @@ +package io.mycat.backend.mysql.nio.handler.query; + +import java.util.Set; + +import io.mycat.backend.mysql.nio.handler.ResponseHandler; + +public interface DMLResponseHandler extends ResponseHandler { + public enum HandlerType { + DIRECT, TEMPTABLE, BASESEL, REFRESHFP, MERGE, JOIN, WHERE, GROUPBY, HAVING, ORDERBY, LIMIT, UNION, DISTINCT, SENDMAKER, FINAL + } + + HandlerType type(); + + DMLResponseHandler getNextHandler(); + + void setNextHandler(DMLResponseHandler next); + + Set getMerges(); + + boolean isAllPushDown(); + + void setAllPushDown(boolean allPushDown); + + void setLeft(boolean isLeft); + + void terminate(); + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/OwnThreadDMLHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/OwnThreadDMLHandler.java new file mode 100644 index 000000000..b78e9c3bd --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/OwnThreadDMLHandler.java @@ -0,0 +1,71 @@ +package io.mycat.backend.mysql.nio.handler.query; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.mycat.server.NonBlockingSession; + +/** + * 拥有自己的thread的dmlhandler + * + * @author chenzifei + * @CreateTime 2014年11月27日 + */ +public abstract class OwnThreadDMLHandler extends BaseDMLHandler { + /* 当前是否需要结束ownthread,ownthread运行中时为true */ + private AtomicBoolean ownJobFlag; + private Object ownThreadLock = new Object(); + private boolean preparedToRecycle; + + public OwnThreadDMLHandler(long id, NonBlockingSession session) { + super(id, session); + this.ownJobFlag = new AtomicBoolean(false); + this.preparedToRecycle = false; + } + + @Override + public final void onTerminate() throws Exception { + if (ownJobFlag.compareAndSet(false, true)) { + // thread未启动即进入了terminate + recycleResources(); + } else {// thread已经启动 + synchronized (ownThreadLock) { + if (!preparedToRecycle) { // 还未进入释放资源的地步 + terminateThread(); + } + } + } + } + + /** + * @param objects + * 有可能会用到的参数 + */ + protected final void startOwnThread(final Object... objects) { + session.getSource().getProcessor().getExecutor().execute(new Runnable() { + @Override + public void run() { + if (terminate.get()) + return; + if (ownJobFlag.compareAndSet(false, true)) { + try { + ownThreadJob(objects); + } finally { + synchronized (ownThreadLock) { + preparedToRecycle = true; + } + recycleResources(); + } + } + } + }); + } + + protected abstract void ownThreadJob(Object... objects); + + /* 通过一些动作,可以让running的thread终结 */ + protected abstract void terminateThread() throws Exception; + + /* 线程结束后需要执行的动作 */ + protected abstract void recycleResources(); + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/BaseSelectHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/BaseSelectHandler.java new file mode 100644 index 000000000..94bed616c --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/BaseSelectHandler.java @@ -0,0 +1,166 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.datasource.PhysicalDBNode; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.config.MycatConfig; +import io.mycat.net.mysql.ErrorPacket; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; + +/** + * 仅用来执行Sql,将接收到的数据转发到下一个handler + * + */ +public class BaseSelectHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(BaseSelectHandler.class); + + private final boolean autocommit; + private volatile int fieldCounts = -1; + + private RouteResultsetNode rrss; + + /** + * @param route + * @param autocommit + * @param orderBys + * @param session + */ + public BaseSelectHandler(long id, RouteResultsetNode rrss, boolean autocommit, NonBlockingSession session) { + super(id, session); + this.rrss = rrss; + this.autocommit = autocommit; + } + + public MySQLConnection initConnection() throws Exception { + if (session.closed()) { + return null; + } + + MySQLConnection exeConn = (MySQLConnection) session.getTarget(rrss); + if (session.tryExistsCon(exeConn, rrss)) { + return exeConn; + } else { + MycatConfig conf = MycatServer.getInstance().getConfig(); + PhysicalDBNode dn = conf.getDataNodes().get(rrss.getName()); + final BackendConnection newConn= dn.getConnection(dn.getDatabase(), session.getSource().isAutocommit()); + session.bindConnection(rrss, newConn); + return (MySQLConnection)newConn; + } + } + + public void execute(MySQLConnection conn) { + if (session.closed()) { + session.clearResources(true); + return; + } + conn.setResponseHandler(this); + if (logger.isInfoEnabled()) { + logger.info(conn.toString() + " send sql:" + rrss.getStatement()); + } + if (session.closed()) { + session.onQueryError("failed or cancelled by other thread".getBytes()); + return; + } + conn.execute(rrss, session.getSource(), autocommit); + } + + @Override + public void okResponse(byte[] ok, BackendConnection conn) { + conn.syncAndExcute(); + } + + @Override + public void fieldEofResponse(byte[] header, List fields, List fieldPacketsNull, byte[] eof, + boolean isLeft, BackendConnection conn) { + if (logger.isDebugEnabled()) { + logger.debug(conn.toString() + "'s field is reached."); + } + if (terminate.get()) { + return; + } + if (fieldCounts == -1) { + fieldCounts = fields.size(); + } + List fieldPackets = new ArrayList(); + + for (int i = 0; i < fields.size(); i++) { + FieldPacket field = new FieldPacket(); + field.read(fields.get(i)); + fieldPackets.add(field); + } + nextHandler.fieldEofResponse(null, null, fieldPackets, null, this.isLeft, conn); + } + + @Override + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + RowDataPacket rp = new RowDataPacket(fieldCounts); + rp.read(row); + nextHandler.rowResponse(null, rp, this.isLeft, conn); + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + if (logger.isDebugEnabled()) { + logger.debug(conn.toString() + " 's rowEof is reached."); + } + ((MySQLConnection)conn).setRunning(false); + if (this.terminate.get()) + return; + nextHandler.rowEofResponse(data, this.isLeft, conn); + } + + /** + * 1、if some connection's thread status is await. 2、if some connection's + * thread status is running. + */ + @Override + public void connectionError(Throwable e, BackendConnection conn) { + if (terminate.get()) + return; + logger.warn( + new StringBuilder().append(conn.toString()).append("|connectionError()|").append(e.getMessage()).toString()); + session.onQueryError(e.getMessage().getBytes()); + } + + @Override + public void errorResponse(byte[] err, BackendConnection conn) { + ((MySQLConnection)conn).setRunning(false); + ErrorPacket errPacket = new ErrorPacket(); + errPacket.read(err); + String errMsg; + try { + errMsg = new String(errPacket.message,conn.getCharset()); + } catch (UnsupportedEncodingException e) { + errMsg ="UnsupportedEncodingException:"+conn.getCharset(); + } + logger.warn(conn.toString() + errMsg); + if (terminate.get()) + return; + session.onQueryError(errMsg.getBytes()); + } + + @Override + protected void onTerminate() { + this.session.releaseConnections(false); + } + + @Override + public HandlerType type() { + return HandlerType.BASESEL; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/DistinctHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/DistinctHandler.java new file mode 100644 index 000000000..972c14fab --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/DistinctHandler.java @@ -0,0 +1,110 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.DistinctLocalResult; +import io.mycat.backend.mysql.store.LocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.server.NonBlockingSession; + +public class DistinctHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(DistinctHandler.class); + + private List sourceFields; + private LocalResult localResult; + private RowDataComparator cmptor; + private List fixedOrders; + private BufferPool pool; + /* if distincts is null, distinct the total row */ + private List distincts; + + public DistinctHandler(long id, NonBlockingSession session, List columns) { + this(id, session, columns, null); + } + + public DistinctHandler(long id, NonBlockingSession session, List columns, List fixedOrders) { + super(id, session); + this.distincts = columns; + this.fixedOrders = fixedOrders; + } + + @Override + public HandlerType type() { + return HandlerType.DISTINCT; + } + + /** + * 所有的上一级表传递过来的信息全部视作Field类型 + */ + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + if (this.pool == null) + this.pool = MycatServer.getInstance().getBufferPool(); + this.fieldPackets = fieldPackets; + this.sourceFields = HandlerTool.createFields(this.fieldPackets); + if (this.distincts == null) { + // 比如show tables这种语句 + this.distincts = new ArrayList(); + for (FieldPacket fp : this.fieldPackets) { + Item sel = HandlerTool.createItemField(fp); + this.distincts.add(sel); + } + } + List orders = this.fixedOrders; + if (orders == null) + orders = HandlerTool.makeOrder(this.distincts); + cmptor = new RowDataComparator(this.fieldPackets, orders, this.isAllPushDown(), type(), conn.getCharset()); + localResult = new DistinctLocalResult(pool, this.sourceFields.size(), cmptor, conn.getCharset()) + .setMemSizeController(session.getOtherBufferMC()); + nextHandler.fieldEofResponse(null, null, this.fieldPackets, null, this.isLeft, conn); + } + + /** + * 收到行数据包的响应处理 + */ + public boolean rowResponse(byte[] rownull, final RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + localResult.add(rowPacket); + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("roweof"); + if (terminate.get()) + return; + sendDistinctRowPacket(conn); + nextHandler.rowEofResponse(null, isLeft, conn); + } + + private void sendDistinctRowPacket(BackendConnection conn) { + localResult.done(); + RowDataPacket row = null; + while ((row = localResult.next()) != null) { + nextHandler.rowResponse(null, row, this.isLeft, conn); + } + } + + @Override + public void onTerminate() { + if (this.localResult != null) + this.localResult.close(); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/HavingHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/HavingHandler.java new file mode 100644 index 000000000..5978cae05 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/HavingHandler.java @@ -0,0 +1,91 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.server.NonBlockingSession; + +/** + * 目前having做成和where一样的处理 + * + * @author chenzifei + * + */ +public class HavingHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(HavingHandler.class); + + public HavingHandler(long id, NonBlockingSession session, Item having) { + super(id, session); + assert (having != null); + this.having = having; + } + + private Item having = null; + private Item havingItem = null; + private List sourceFields; + private ReentrantLock lock = new ReentrantLock(); + + @Override + public HandlerType type() { + return HandlerType.HAVING; + } + + /** + * 所有的上一级表传递过来的信息全部视作Field类型 + */ + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + this.fieldPackets = fieldPackets; + this.sourceFields = HandlerTool.createFields(this.fieldPackets); + /** + * having的函数我们基本算他不下发,因为他有可能带group by + */ + this.havingItem = HandlerTool.createItem(this.having, this.sourceFields, 0, false, this.type(), + conn.getCharset()); + nextHandler.fieldEofResponse(null, null, this.fieldPackets, null, this.isLeft, conn); + } + + /** + * 收到行数据包的响应处理 + */ + public boolean rowResponse(byte[] rownull, final RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + lock.lock(); + try { + HandlerTool.initFields(this.sourceFields, rowPacket.fieldValues); + /* 根据where条件进行过滤 */ + if (havingItem.valBool()) { + nextHandler.rowResponse(null, rowPacket, this.isLeft, conn); + } else { + // nothing + } + return false; + } finally { + lock.unlock(); + } + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("roweof"); + if (terminate.get()) + return; + nextHandler.rowEofResponse(data, isLeft, conn); + } + + @Override + public void onTerminate() { + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/LimitHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/LimitHandler.java new file mode 100644 index 000000000..cb4c5266e --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/LimitHandler.java @@ -0,0 +1,71 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.log4j.Logger; + +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.server.NonBlockingSession; + +/** + * 用来处理limit,仅作简单的统计过滤 + * + * @author chenzifei + * + */ +public class LimitHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(LimitHandler.class); + private long limitIndex; + private final long limitCount; + // current index + private AtomicLong curIndex = new AtomicLong(-1L); + + public LimitHandler(long id, NonBlockingSession session, long limitIndex, long limitCount) { + super(id, session); + this.limitIndex = limitIndex; + this.limitCount = limitCount; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("row eof"); + if (!terminate.get()) { + nextHandler.rowEofResponse(data, this.isLeft, conn); + } + } + + @Override + public HandlerType type() { + return HandlerType.LIMIT; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + nextHandler.fieldEofResponse(null, null, fieldPackets, null, this.isLeft, conn); + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) { + return true; + } + long curIndexTmp = curIndex.incrementAndGet(); + if (curIndexTmp < limitIndex) { + return false; + } else if (curIndexTmp >= limitIndex && curIndexTmp < limitIndex + limitCount) { + nextHandler.rowResponse(null, rowPacket, this.isLeft, conn); + } else { + return true; + } + return false; + } + + @Override + protected void onTerminate() { + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/MultiNodeMergeHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/MultiNodeMergeHandler.java new file mode 100644 index 000000000..e78faa665 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/MultiNodeMergeHandler.java @@ -0,0 +1,329 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.OwnThreadDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.ArrayMinHeap; +import io.mycat.backend.mysql.nio.handler.util.HeapItem; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.config.ErrorCode; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.route.RouteResultsetNode; +import io.mycat.server.NonBlockingSession; + +/** + * mergeHandler仅负责将从数据库采集回来的数据进行merge,如果有聚合函数的话,使用group byhandler进行处理 + * + * @author chenzifei + * + */ +public class MultiNodeMergeHandler extends OwnThreadDMLHandler { + private static final Logger logger = Logger.getLogger(MultiNodeMergeHandler.class); + + private final int queueSize; + private final ReentrantLock lock; + + private List exeHandlers; + // 对应MultiSource的row结果的blockingquene,if rowend, add NullHeapItem into queue; + private Map> queues; + private List orderBys; + private RowDataComparator rowComparator; + private RouteResultsetNode[] route; + private int reachedConCount; + private boolean isEasyMerge; + + public MultiNodeMergeHandler(long id, RouteResultsetNode[] route, boolean autocommit, NonBlockingSession session, + List orderBys) { + super(id, session); + this.exeHandlers = new ArrayList(); + this.lock = new ReentrantLock(); + if (route.length == 0) + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "can not execute empty rrss!"); + for (RouteResultsetNode rrss : route) { + BaseSelectHandler exeHandler = new BaseSelectHandler(id, rrss, autocommit, session); + exeHandler.setNextHandler(this); + this.exeHandlers.add(exeHandler); + } + this.route = route; + this.orderBys = orderBys; + this.queueSize = MycatServer.getInstance().getConfig().getSystem().getMergeQueueSize(); + if (route.length == 1 || (orderBys == null || orderBys.size() == 0)) { + this.isEasyMerge = true; + } else { + this.isEasyMerge = false; + } + this.queues = new ConcurrentHashMap>(); + this.merges.add(this); + } + +// /** +// * @param route +// * @param autocommit +// * @param orderBys +// * @param session +// */ +// public MultiNodeMergeHandler(long id, RouteResultsetNode[] route, boolean autocommit, NonBlockingSession session, +// List orderBys,List colTables) { +// super(id, session); +// this.exeHandlers = new ArrayList(); +// this.lock = new ReentrantLock(); +// if (route.length == 0) +// throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "can not execute empty rrss!"); +// for (RouteResultsetNode rrss : route) { +// BaseSelectHandler exeHandler = new BaseSelectHandler(id, rrss, autocommit, session); +// exeHandler.setNextHandler(this); +// this.exeHandlers.add(exeHandler); +// } +// this.route = route; +// this.orderBys = orderBys; +// this.queueSize = ProxyServer.getInstance().getConfig().getSystem().getMergeQueueSize(); +// if (route.length == 1 || (orderBys == null || orderBys.size() == 0)) { +// this.isEasyMerge = true; +// } else { +// this.isEasyMerge = false; +// } +// this.queues = new ConcurrentHashMap>(); +// this.merges.add(this); +// } + + public RouteResultsetNode[] getRouteSources() { + return this.route; + } + + public void execute() throws Exception { + synchronized (exeHandlers) { + if (terminate.get()) + return; + for (BaseSelectHandler exeHandler : exeHandlers) { + MySQLConnection exeConn = exeHandler.initConnection(); + if (exeConn != null) { + BlockingQueue queue = new LinkedBlockingQueue(queueSize); + queues.put(exeConn, queue); + exeHandler.execute(exeConn); + } + } + } + } + + @Override + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { + if (logger.isInfoEnabled()) { + logger.info(conn.toString() + "'s field is reached."); + } + // 保证连接及时中断 + if (terminate.get()) { + return; + } + lock.lock(); // for combine + try { + if (this.fieldPackets.isEmpty()) { + this.fieldPackets = fieldPackets; + rowComparator = makeRowDataSorter((MySQLConnection)conn); + nextHandler.fieldEofResponse(null, null, fieldPackets, null, this.isLeft, conn); + } + if (!isEasyMerge) { + if (++reachedConCount == route.length) { + startOwnThread(); + } + } + } finally { + lock.unlock(); + } + } + + @Override + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + + if (isEasyMerge) { + nextHandler.rowResponse(null, rowPacket, this.isLeft, conn); + } else { + BlockingQueue queue = queues.get(conn); + if (queue == null) + return true; + HeapItem item = new HeapItem(row, rowPacket, (MySQLConnection)conn); + try { + queue.put(item); + } catch (InterruptedException e) { + } + } + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + if (logger.isInfoEnabled()) { + logger.info(conn.toString() + " 's rowEof is reached."); + } + ((MySQLConnection)conn).setRunning(false); + if (this.terminate.get()) + return; + if (isEasyMerge) { + lock.lock(); + try { + if (++reachedConCount == route.length) + nextHandler.rowEofResponse(null, this.isLeft, conn); + } finally { + lock.unlock(); + } + } else { + BlockingQueue queue = queues.get(conn); + if (queue == null) + return; + try { + queue.put(HeapItem.NULLITEM()); + } catch (InterruptedException e) { + } + } + } + + @Override + protected void ownThreadJob(Object... objects) { + try { + ArrayMinHeap heap = new ArrayMinHeap(new Comparator() { + + @Override + public int compare(HeapItem o1, HeapItem o2) { + RowDataPacket row1 = o1.getRowPacket(); + RowDataPacket row2 = o2.getRowPacket(); + if (row1 == null || row2 == null) { + if (row1 == row2) + return 0; + if (row1 == null) + return -1; + return 1; + } + return rowComparator.compare(row1, row2); + } + }); + // init heap + for (MySQLConnection conn : queues.keySet()) { + HeapItem firstItem = queues.get(conn).take(); + heap.add(firstItem); + } + while (!heap.isEmpty()) { + if (terminate.get()) + return; + HeapItem top = heap.peak(); + if (top.IsNullItem()) { + heap.poll(); + } else { + BlockingQueue topitemQueue = queues.get(top.getIndex()); + HeapItem item = topitemQueue.take(); + heap.replaceTop(item); + if (nextHandler.rowResponse(top.getRowData(), top.getRowPacket(), this.isLeft, top.getIndex())) { + // should still send eof,so could not return + break; + } + } + } + if (logger.isInfoEnabled()) { + String executeSqls = getRoutesSql(route); + logger.info(executeSqls + " heap send eof: "); + } + nextHandler.rowEofResponse(null, this.isLeft, queues.keySet().iterator().next()); + } catch (Exception e) { + String msg = "Merge thread error, " + e.getLocalizedMessage(); + logger.warn(msg, e); + session.onQueryError(msg.getBytes()); + } + } + + @Override + protected void terminateThread() throws Exception { + Iterator>> iter = this.queues.entrySet().iterator(); + while (iter.hasNext()) { + Entry> entry = iter.next(); + // add EOF to signal atoMerge thread + entry.getValue().clear(); + entry.getValue().put(new HeapItem(null, null, entry.getKey())); + } + } + + @Override + protected void recycleResources() { + synchronized (exeHandlers) { + for (BaseSelectHandler exeHandler : exeHandlers) { + terminatePreHandler(exeHandler); + } + } + Iterator>> iter = this.queues.entrySet().iterator(); + while (iter.hasNext()) { + Entry> entry = iter.next(); + // fair lock queue,poll for clear + while (entry.getValue().poll() != null) + ; + iter.remove(); + } + } + + /** + * terminate前置handler + * + * @param handler + */ + private void terminatePreHandler(DMLResponseHandler handler) { + DMLResponseHandler current = handler; + while (current != null) { + if (current == this) + break; + current.terminate(); + current = current.getNextHandler(); + } + } + + private RowDataComparator makeRowDataSorter(MySQLConnection conn) { + if (!isEasyMerge) + return new RowDataComparator(this.fieldPackets, orderBys, this.isAllPushDown(), this.type(), + conn.getCharset()); + return null; + } + + @Override + public HandlerType type() { + return HandlerType.MERGE; + } + + private String getRoutesSql(RouteResultsetNode[] route) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Map> sqlMap = new HashMap>(); + for (RouteResultsetNode rrss : route) { + String sql = rrss.getStatement(); + if (!sqlMap.containsKey(sql)) { + List rrssList = new ArrayList(); + rrssList.add(rrss); + sqlMap.put(sql, rrssList); + } else { + List rrssList = sqlMap.get(sql); + rrssList.add(rrss); + } + } + for (String sql : sqlMap.keySet()) { + sb.append(sql).append(sqlMap.get(sql)).append(';'); + } + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/OrderByHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/OrderByHandler.java new file mode 100644 index 000000000..d1c6c63ae --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/OrderByHandler.java @@ -0,0 +1,146 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.OwnThreadDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.LocalResult; +import io.mycat.backend.mysql.store.SortedLocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.server.NonBlockingSession; +import io.mycat.util.TimeUtil; + +public class OrderByHandler extends OwnThreadDMLHandler { + private static final Logger logger = Logger.getLogger(OrderByHandler.class); + + private List orders; + private RowDataComparator cmp = null; + private BlockingQueue queue; + /* 排序对象,支持缓存、文件系统 */ + private LocalResult localResult; + private BufferPool pool; + private int queueSize; + + public OrderByHandler(long id, NonBlockingSession session, List orders) { + super(id, session); + this.orders = orders; + this.queueSize = MycatServer.getInstance().getConfig().getSystem().getOrderByQueueSize(); + this.queue = new LinkedBlockingDeque(queueSize); + } + + @Override + public HandlerType type() { + return HandlerType.ORDERBY; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, final BackendConnection conn) { + if (terminate.get()) + return; + if (this.pool == null) + this.pool = MycatServer.getInstance().getBufferPool(); + + this.fieldPackets = fieldPackets; + cmp = new RowDataComparator(this.fieldPackets, orders, isAllPushDown(), type(), conn.getCharset()); + localResult = new SortedLocalResult(pool, fieldPackets.size(), cmp, conn.getCharset()) + .setMemSizeController(session.getOrderBufferMC()); + nextHandler.fieldEofResponse(null, null, fieldPackets, null, this.isLeft, conn); + startOwnThread(conn); + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + try { + queue.put(rowPacket); + } catch (InterruptedException e) { + return true; + } + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("roweof"); + if (terminate.get()) + return; + try { + queue.put(new RowDataPacket(0)); + } catch (InterruptedException e) { + } + } + + @Override + protected void ownThreadJob(Object... objects) { + MySQLConnection conn = (MySQLConnection) objects[0]; + recordElapsedTime("order write start :"); + try { + while (true) { + if (terminate.get()) { + return; + } + RowDataPacket row = null; + try { + row = queue.take(); + } catch (InterruptedException e) { + } + if (row.fieldCount == 0) { + break; + } + localResult.add(row); + } + recordElapsedTime("order write end :"); + localResult.done(); + recordElapsedTime("order read start :"); + while (true) { + if (terminate.get()) { + return; + } + RowDataPacket row = localResult.next(); + if (row == null) { + break; + } + if (nextHandler.rowResponse(null, row, this.isLeft, conn)) + break; + } + recordElapsedTime("order read end:"); + nextHandler.rowEofResponse(null, this.isLeft, conn); + } catch (Exception e) { + String msg = "OrderBy thread error, " + e.getLocalizedMessage(); + logger.warn(msg, e); + session.onQueryError(msg.getBytes()); + } + } + + private void recordElapsedTime(String prefix) { + if (logger.isInfoEnabled()) { + logger.info(prefix + TimeUtil.currentTimeMillis()); + } + } + + @Override + protected void terminateThread() throws Exception { + this.queue.clear(); + this.queue.add(new RowDataPacket(0)); + } + + @Override + protected void recycleResources() { + this.queue.clear(); + if (this.localResult != null) + this.localResult.close(); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/OutputHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/OutputHandler.java new file mode 100644 index 000000000..41975bb46 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/OutputHandler.java @@ -0,0 +1,257 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.config.ErrorCode; +import io.mycat.net.mysql.BinaryRowDataPacket; +import io.mycat.net.mysql.EOFPacket; +import io.mycat.net.mysql.ErrorPacket; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.OkPacket; +import io.mycat.net.mysql.ResultSetHeaderPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.net.mysql.StatusFlags; +import io.mycat.server.NonBlockingSession; +import io.mycat.server.ServerConnection; + +/* + * 最终将数据返回给用户的hander处理 + */ +public class OutputHandler extends BaseDMLHandler { + private static Logger logger = Logger.getLogger(OutputHandler.class); + /** + * 回收资源和其他的response方法有可能同步 + */ + protected final ReentrantLock lock; + + private byte packetId; + private NonBlockingSession session; + private ByteBuffer buffer; + private boolean isBinary; + private boolean hasNext; + + public OutputHandler(long id, NonBlockingSession session, boolean hasNext) { + super(id, session); + session.setOutputHandler(this); + this.lock = new ReentrantLock(); + this.packetId = 0; + this.session = session; + this.hasNext = hasNext; + this.isBinary = session.isPrepared(); + this.buffer = session.getSource().allocate(); + } + + @Override + public HandlerType type() { + return HandlerType.FINAL; + } + + @Override + public void okResponse(byte[] ok, BackendConnection conn) { + OkPacket okPacket = new OkPacket(); + okPacket.read(ok); + ServerConnection source = session.getSource(); + lock.lock(); + try { + ok[3] = ++packetId; + if ((okPacket.serverStatus & StatusFlags.SERVER_MORE_RESULTS_EXISTS) > 0) { + buffer = source.writeToBuffer(ok, buffer); + } else { + HandlerTool.terminateHandlerTree(this); + if (hasNext) { + okPacket.serverStatus |= StatusFlags.SERVER_MORE_RESULTS_EXISTS; + } + buffer = source.writeToBuffer(ok, buffer); + if (hasNext) { + source.write(buffer); +// source.excuteNext(packetId, false); + } else { +// source.excuteNext(packetId, false); + source.write(buffer); + } + } + } finally { + lock.unlock(); + } + } + + @Override + public void errorResponse(byte[] err, BackendConnection conn) { + ErrorPacket errPacket = new ErrorPacket(); + errPacket.read(err); + logger.warn(new StringBuilder().append(conn.toString()).append("|errorResponse()|").append(errPacket.message) + .toString()); + lock.lock(); + try { + buffer = session.getSource().writeToBuffer(err, buffer); +// session.getSource().excuteNext(packetId, true); + session.getSource().write(buffer); + } finally { + lock.unlock(); + } + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) { + return; + } + lock.lock(); + try { + if (this.isBinary) + this.fieldPackets = fieldPackets; + ResultSetHeaderPacket hp = new ResultSetHeaderPacket(); + hp.fieldCount = fieldPackets.size(); + hp.packetId = ++packetId; + + ServerConnection source = session.getSource(); + buffer = hp.write(buffer, source, true); + for (FieldPacket fp : fieldPackets) { + fp.packetId = ++packetId; + buffer = fp.write(buffer, source, true); + } + EOFPacket ep = new EOFPacket(); + ep.packetId = ++packetId; + buffer = ep.write(buffer, source, true); + } finally { + lock.unlock(); + } + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) { + return true; + } + lock.lock(); + try { + byte[] row; + if (this.isBinary) { + BinaryRowDataPacket binRowPacket = new BinaryRowDataPacket(); + binRowPacket.read(this.fieldPackets, rowPacket); + binRowPacket.packetId = ++packetId; + buffer = binRowPacket.write(buffer, session.getSource(), true); + } else { + if (rowPacket != null) { + rowPacket.packetId = ++packetId; + buffer = rowPacket.write(buffer, session.getSource(), true); + } else { + row = rownull; + row[3] = ++packetId; + buffer = session.getSource().writeToBuffer(row, buffer); + } + } + } finally { + lock.unlock(); + } + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + if (terminate.get()) { + return; + } + logger.info("--------sql execute end!"); + ServerConnection source = session.getSource(); + lock.lock(); + try { + EOFPacket eofPacket = new EOFPacket(); + if (data != null) { + eofPacket.read(data); + } + eofPacket.packetId = ++packetId; + if (hasNext) { + eofPacket.status |= StatusFlags.SERVER_MORE_RESULTS_EXISTS; + } + HandlerTool.terminateHandlerTree(this); + byte[] eof = eofPacket.toBytes(); + buffer = source.writeToBuffer(eof, buffer); + if (hasNext) { + source.write(buffer); +// source.excuteNext(packetId, false); + } else { +// source.excuteNext(packetId, false); + source.write(buffer); + } + } finally { + lock.unlock(); + } + } + + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + lock.lock(); + try { + buffer = session.getSource().writeToBuffer(relayPacket, buffer); + } finally { + lock.unlock(); + } + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + lock.lock(); + try { + buffer = session.getSource().writeToBuffer(endPacket, buffer); + session.getSource().write(buffer); + } finally { + lock.unlock(); + } + } + + public void backendConnError(byte[] errMsg) { + if (terminate.compareAndSet(false, true)) { + ErrorPacket err = new ErrorPacket(); + err.errno = ErrorCode.ER_YES; + err.message = errMsg; + HandlerTool.terminateHandlerTree(this); + backendConnError(err); + } + } + + protected void backendConnError(ErrorPacket error) { + lock.lock(); + try { + recycleResources(); + if (error == null) { + error = new ErrorPacket(); + error.errno = ErrorCode.ER_YES; + error.message = "unknown error".getBytes(); + } + error.packetId = ++packetId; +// session.getSource().excuteNext(packetId, true); + session.getSource().write(error.toBytes()); + } finally { + lock.unlock(); + } + } + + private void recycleResources() { + if (buffer != null) { + if (buffer.position() > 0) { + session.getSource().write(buffer); + } else { + session.getSource().recycle(buffer); + buffer = null; + } + } + } + + @Override + protected void onTerminate() { + if (this.isBinary) { + if (this.fieldPackets != null) + this.fieldPackets.clear(); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/SendMakeHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/SendMakeHandler.java new file mode 100644 index 000000000..df87bc5a1 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/SendMakeHandler.java @@ -0,0 +1,126 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.lang.StringUtils; + +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.server.NonBlockingSession; + +/** + * 如果Item是Item_sum,那么Item肯定已经在GroupBy中生成过了,如果不是Item_sum,则有可能需要自己进行一次计算 + * + * + */ +public class SendMakeHandler extends BaseDMLHandler { + + private final ReentrantLock lock; + + private List sels; + private List sourceFields; + private List selItems; + /* 表的别名 */ + private String tbAlias; + + /** + * + * @param session + * @param sels + * 用户最终需要的sel集合 + */ + public SendMakeHandler(long id, NonBlockingSession session, List sels, String tableAlias) { + super(id, session); + lock = new ReentrantLock(); + this.sels = sels; + this.selItems = new ArrayList(); + this.tbAlias = tableAlias; + } + + @Override + public HandlerType type() { + return HandlerType.SENDMAKER; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + lock.lock(); + try { + if (terminate.get()) + return; + this.fieldPackets = fieldPackets; + this.sourceFields = HandlerTool.createFields(this.fieldPackets); + for (Item sel : sels) { + Item tmpItem = HandlerTool.createItem(sel, this.sourceFields, 0, isAllPushDown(), type(), + conn.getCharset()); + tmpItem.setItemName(sel.getItemName()); + if (sel.getAlias() != null || tbAlias != null) { + String selAlias = sel.getAlias(); + // 由于添加了FNAF,需要去掉 + if (StringUtils.indexOf(selAlias, Item.FNAF) == 0) + selAlias = StringUtils.substring(selAlias, Item.FNAF.length()); + tmpItem = HandlerTool.createRefItem(tmpItem, tbAlias, selAlias); + } + this.selItems.add(tmpItem); + } + List newFieldPackets = new ArrayList(); + for (Item selItem : this.selItems) { + FieldPacket tmpFp = new FieldPacket(); + selItem.makeField(tmpFp); + /* Keep things compatible for old clients */ + if (tmpFp.type == FieldTypes.MYSQL_TYPE_VARCHAR.numberValue()) + tmpFp.type = FieldTypes.MYSQL_TYPE_VAR_STRING.numberValue(); + newFieldPackets.add(tmpFp); + } + nextHandler.fieldEofResponse(null, null, newFieldPackets, null, this.isLeft, conn); + } finally { + lock.unlock(); + } + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + lock.lock(); + try { + if (terminate.get()) + return true; + HandlerTool.initFields(sourceFields, rowPacket.fieldValues); + RowDataPacket newRp = new RowDataPacket(selItems.size()); + for (Item selItem : selItems) { + byte[] b = selItem.getRowPacketByte(); + newRp.add(b); + } + nextHandler.rowResponse(null, newRp, this.isLeft, conn); + return false; + } finally { + lock.unlock(); + } + } + + @Override + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { + lock.lock(); + try { + if (terminate.get()) + return; + nextHandler.rowEofResponse(eof, this.isLeft, conn); + } finally { + lock.unlock(); + } + } + + @Override + public void onTerminate() { + } + + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/TempTableHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/TempTableHandler.java new file mode 100644 index 000000000..ff49c76e4 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/TempTableHandler.java @@ -0,0 +1,176 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.util.CallBackHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.store.UnSortedLocalResult; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.exception.TempTableException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.meta.TempTable; +import io.mycat.server.NonBlockingSession; + +/** + * 将结果集生成到临时表中 + * + */ +public class TempTableHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(TempTableHandler.class); + + private final ReentrantLock lock; + private final TempTable tempTable; + + private int maxPartSize = 2000; + private int maxConnSize = 4; + private int rowCount = 0; + private CallBackHandler tempDoneCallBack; + // 由tempHandler生成的Handler,还得由它来释放 + private DMLResponseHandler createdHandler; + + private int sourceSelIndex = -1; + private final Item sourceSel; + private Field sourceField; + private Set valueSet; + + public TempTableHandler(long id, NonBlockingSession session, Item sourceSel) { + super(id, session); + this.lock = new ReentrantLock(); + this.tempTable = new TempTable(); + this.maxPartSize = MycatServer.getInstance().getConfig().getSystem().getNestLoopRowsSize(); + this.maxConnSize = MycatServer.getInstance().getConfig().getSystem().getNestLoopConnSize(); + this.sourceSel = sourceSel; + this.valueSet = new HashSet(); + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) { + return; + } + lock.lock(); + try { + if (this.fieldPackets.isEmpty()) { + this.fieldPackets = fieldPackets; + tempTable.setFieldPackets(this.fieldPackets); + tempTable.setCharset(conn.getCharset()); + tempTable.setRowsStore(new UnSortedLocalResult(fieldPackets.size(), MycatServer.getInstance().getBufferPool(), + conn.getCharset()).setMemSizeController(session.getOtherBufferMC())); + List fields = HandlerTool.createFields(this.fieldPackets); + sourceSelIndex = HandlerTool.findField(sourceSel, fields, 0); + if (sourceSelIndex < 0) + throw new TempTableException( "sourcesel ["+sourceSel.toString()+"] not found in fields" ); + sourceField = fields.get(sourceSelIndex); + if (nextHandler != null) { + nextHandler.fieldEofResponse(headernull, fieldsnull, fieldPackets, eofnull, this.isLeft, conn); + } else { + throw new TempTableException("unexpected nextHandler is null"); + } + } + } finally { + lock.unlock(); + } + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + lock.lock(); + try { + if (terminate.get()) { + return true; + } + if (++rowCount > maxPartSize * maxConnSize) { + String errMessage = "temptable of ["+conn.toString()+"] too much rows,[rows="+rowCount+"]!"; + logger.warn(errMessage); + throw new TempTableException(errMessage); + } + RowDataPacket row = rowPacket; + if (row == null) { + row = new RowDataPacket(this.fieldPackets.size()); + row.read(rownull); + } + tempTable.addRow(row); + sourceField.setPtr(row.getValue(sourceSelIndex)); + valueSet.add(sourceField.valStr()); + } finally { + lock.unlock(); + } + return false; + } + + @Override + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { + lock.lock(); + try { + // terminate之后仍然进行callBack操作 + if (terminate.get()) { + return; + } + tempTable.dataEof(); + // onTerminate加了锁,避免了terminate的时候启动了 + tempDoneCallBack.call(); + RowDataPacket rp = null; + while ((rp = tempTable.nextRow()) != null) { + nextHandler.rowResponse(null, rp, this.isLeft, conn); + } + nextHandler.rowEofResponse(eof, this.isLeft, conn); + } catch (Exception e) { + logger.warn("rowEof exception!", e); + throw new TempTableException("rowEof exception!", e); + } finally { + lock.unlock(); + } + } + + @Override + protected void onTerminate() { + lock.lock(); + try { + this.tempTable.close(); + this.valueSet.clear(); + if (createdHandler != null) { + HandlerTool.terminateHandlerTree(createdHandler); + } + } finally { + lock.unlock(); + } + } + + public TempTable getTempTable() { + return tempTable; + } + + public void setTempDoneCallBack(CallBackHandler tempDoneCallBack) { + this.tempDoneCallBack = tempDoneCallBack; + } + + public void setCreatedHandler(DMLResponseHandler createdHandler) { + this.createdHandler = createdHandler; + } + + public Set getValueSet() { + return valueSet; + } + + public int getMaxPartSize() { + return maxPartSize; + } + + @Override + public HandlerType type() { + return HandlerType.TEMPTABLE; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/UnionHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/UnionHandler.java new file mode 100644 index 000000000..9edaef062 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/UnionHandler.java @@ -0,0 +1,159 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.FieldUtil; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.server.NonBlockingSession; + +/** + * union all语句的handler,如果是union语句的话,则在handlerbuilder时, + * 向unionallhandler后面添加distinctHandler + * + * @author chenzifei + * + */ +public class UnionHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(UnionHandler.class); + + public UnionHandler(long id, NonBlockingSession session, List sels, int nodecount) { + super(id, session); + this.sels = sels; + this.nodeCount = new AtomicInteger(nodecount); + this.nodeCountField = new AtomicInteger(nodecount); + } + + /** + * 因为union有可能是多个表,最终出去的节点仅按照第一个表的列名来 + */ + private List sels; + private AtomicInteger nodeCount; + /* 供fieldeof使用的 */ + private AtomicInteger nodeCountField; + private ReentrantLock lock = new ReentrantLock(); + private Condition conFieldSend = lock.newCondition(); + + @Override + public HandlerType type() { + return HandlerType.UNION; + } + + /** + * 所有的上一级表传递过来的信息全部视作Field类型 + */ + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + lock.lock(); + try { + if (this.fieldPackets == null || this.fieldPackets.size() == 0) { + this.fieldPackets = fieldPackets; + } else { + this.fieldPackets = unionFieldPackets(this.fieldPackets, fieldPackets); + } + if (nodeCountField.decrementAndGet() == 0) { + // 将fieldpackets赋成正确的fieldname + checkFieldPackets(); + nextHandler.fieldEofResponse(null, null, this.fieldPackets, null, this.isLeft, conn); + conFieldSend.signalAll(); + } else { + conFieldSend.await(); + } + } catch (Exception e) { + String msg = "Union field merge error, " + e.getLocalizedMessage(); + logger.warn(msg, e); + conFieldSend.signalAll(); + session.onQueryError(msg.getBytes()); + } finally { + lock.unlock(); + } + } + + private void checkFieldPackets() { + for (int i = 0; i < sels.size(); i++) { + FieldPacket fp = this.fieldPackets.get(i); + Item sel = sels.get(i); + fp.name = sel.getItemName().getBytes(); + // @fix: union语句没有表名,只要列名相等即可 + fp.table = null; + } + } + + /** + * 将fieldpakcets和fieldpackets2进行merge,比如说 + * 一个int的列和一个double的列union完了之后结果是一个double的列 + * + * @param fieldPackets + * @param fieldPackets2 + */ + private List unionFieldPackets(List fieldPackets, List fieldPackets2) { + List newFps = new ArrayList(); + for (int i = 0; i < fieldPackets.size(); i++) { + FieldPacket fp1 = fieldPackets.get(i); + FieldPacket fp2 = fieldPackets2.get(i); + FieldPacket newFp = unionFieldPacket(fp1, fp2); + newFps.add(newFp); + } + return newFps; + } + + private FieldPacket unionFieldPacket(FieldPacket fp1, FieldPacket fp2) { + FieldPacket union = new FieldPacket(); + union.catalog = fp1.catalog; + union.charsetIndex = fp1.charsetIndex; + union.db = fp1.db; + union.decimals = (byte) Math.max(fp1.decimals, fp2.decimals); + union.definition = fp1.definition; + union.flags = fp1.flags | fp2.flags; + union.length = Math.max(fp1.length, fp2.length); + FieldTypes field_type1 = FieldTypes.valueOf(fp1.type); + FieldTypes field_type2 = FieldTypes.valueOf(fp2.type); + FieldTypes merge_field_type = FieldUtil.field_type_merge(field_type1, field_type2); + union.type = merge_field_type.numberValue(); + return union; + } + + /** + * 收到行数据包的响应处理,这里需要等上面的field都merge完了才可以发送 + */ + public boolean rowResponse(byte[] rownull, final RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + nextHandler.rowResponse(null, rowPacket, this.isLeft, conn); + return false; + } + + /** + * 收到行数据包结束的响应处理 + */ + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + if (nodeCount.decrementAndGet() == 0) { + nextHandler.rowEofResponse(data, this.isLeft, conn); + } + } + + @Override + public void onTerminate() { + lock.lock(); + try { + this.conFieldSend.signalAll(); + } finally { + lock.unlock(); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/WhereHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/WhereHandler.java new file mode 100644 index 000000000..1525ea0cf --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/WhereHandler.java @@ -0,0 +1,83 @@ +package io.mycat.backend.mysql.nio.handler.query.impl; + +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.server.NonBlockingSession; + +public class WhereHandler extends BaseDMLHandler { + + public WhereHandler(long id, NonBlockingSession session, Item where) { + super(id, session); + assert (where != null); + this.where = where; + } + + private Item where = null; + private Item whereItem = null; + private List sourceFields; + // 因为merge在没有order by时会存在多线程并发rowresponse + private ReentrantLock lock = new ReentrantLock(); + + @Override + public HandlerType type() { + return HandlerType.WHERE; + } + + /** + * 所有的上一级表传递过来的信息全部视作Field类型 + */ + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + this.fieldPackets = fieldPackets; + this.sourceFields = HandlerTool.createFields(this.fieldPackets); + whereItem = HandlerTool.createItem(this.where, this.sourceFields, 0, this.isAllPushDown(), this.type(), + conn.getCharset()); + nextHandler.fieldEofResponse(null, null, this.fieldPackets, null, this.isLeft, conn); + } + + /** + * 收到行数据包的响应处理 + */ + public boolean rowResponse(byte[] rownull, final RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return true; + lock.lock(); + try { + HandlerTool.initFields(this.sourceFields, rowPacket.fieldValues); + /* 根据where条件进行过滤 */ + if (whereItem.valBool()) { + nextHandler.rowResponse(null, rowPacket, this.isLeft, conn); + } else { + // nothing + } + return false; + } finally { + lock.unlock(); + } + } + + /** + * 收到行数据包结束的响应处理 + */ + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + nextHandler.rowEofResponse(data, isLeft, conn); + } + + @Override + public void onTerminate() { + } + + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/DirectGroupByHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/DirectGroupByHandler.java new file mode 100644 index 000000000..ca775e9d0 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/DirectGroupByHandler.java @@ -0,0 +1,338 @@ +package io.mycat.backend.mysql.nio.handler.query.impl.groupby; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.OwnThreadDMLHandler; +import io.mycat.backend.mysql.nio.handler.query.impl.groupby.directgroupby.DGRowPacket; +import io.mycat.backend.mysql.nio.handler.query.impl.groupby.directgroupby.GroupByBucket; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.GroupByLocalResult; +import io.mycat.backend.mysql.store.LocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.sumfunc.Aggregator; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.server.NonBlockingSession; +import io.mycat.util.TimeUtil; + +/** + * groupBy的前提是已经经过了OrderBy + * 通过groupbylocalresult直接进行groupby计算,在localresult计算中,优先进行内存内部的groupby计算,然后再在 + * 内存中再次进行group by计算 这种计算不支持的情况如下: 1.sum函数存在distinct约束 2.sum函数存在groupconcat类的函数 + * + * @author chenzifei + * + */ +public class DirectGroupByHandler extends OwnThreadDMLHandler { + private static final Logger logger = Logger.getLogger(DirectGroupByHandler.class); + + private BlockingQueue queue; + + /* 接收到的参数 */ + private List groupBys; + private List referedSumFunctions; + + private RowDataComparator cmptor; + private BufferPool pool; + private LocalResult groupLocalResult; + private AtomicBoolean groupStart = new AtomicBoolean(false); + + /* 所有的sum函数集合 */ + private List sourceFields = new ArrayList(); + private List sums = new ArrayList(); + + private AtomicBoolean hasFirstRow = new AtomicBoolean(false); + + /* 下发到localresult中的ISelectable */ + private List localResultReferedSums; + /* 下发到localresult中的fieldPackets */ + private List localResultFps; + private int queueSize; + + private BlockingQueue outQueue; + int bucketSize = 10; + private List buckets; + + /** + * + * @param groupBys + * @param refers + * 涉及到的所有的sumfunction集合 + */ + public DirectGroupByHandler(long id, NonBlockingSession session, List groupBys, + List referedSumFunctions) { + super(id, session); + this.groupBys = groupBys; + this.referedSumFunctions = referedSumFunctions; + this.queueSize = MycatServer.getInstance().getConfig().getSystem().getMergeQueueSize(); + this.queue = new LinkedBlockingQueue(queueSize); + this.outQueue = new LinkedBlockingQueue(queueSize); + this.buckets = new ArrayList(); + } + + @Override + public HandlerType type() { + return HandlerType.GROUPBY; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + if (terminate.get()) + return; + if (this.pool == null) + this.pool = MycatServer.getInstance().getBufferPool(); + + this.fieldPackets = fieldPackets; + this.sourceFields = HandlerTool.createFields(this.fieldPackets); + for (int index = 0; index < referedSumFunctions.size(); index++) { + ItemSum sumFunc = referedSumFunctions.get(index); + ItemSum sum = (ItemSum) (HandlerTool.createItem(sumFunc, this.sourceFields, 0, this.isAllPushDown(), + this.type(), conn.getCharset())); + sums.add(sum); + } + prepare_sum_aggregators(sums, true); + setup_sum_funcs(sums); + /* group fieldpackets are front of the origin */ + sendGroupFieldPackets((MySQLConnection)conn); + // localresult中的row为DGRowPacket,比原始的rowdatapacket增加了聚合结果对象 + localResultFps = this.fieldPackets; + localResultReferedSums = referedSumFunctions; + cmptor = new RowDataComparator(this.localResultFps, this.groupBys, this.isAllPushDown(), this.type(), + conn.getCharset()); + groupLocalResult = new GroupByLocalResult(pool, localResultFps.size(), cmptor, localResultFps, + localResultReferedSums, this.isAllPushDown(), conn.getCharset()) + .setMemSizeController(session.getOtherBufferMC()); + for (int i = 0; i < bucketSize; i++) { + RowDataComparator tmpcmptor = new RowDataComparator(this.localResultFps, this.groupBys, + this.isAllPushDown(), this.type(), conn.getCharset()); + GroupByBucket bucket = new GroupByBucket(queue, outQueue, pool, localResultFps.size(), tmpcmptor, + localResultFps, localResultReferedSums, this.isAllPushDown(), conn.getCharset()); + bucket.setMemSizeController(session.getOtherBufferMC()); + buckets.add(bucket); + bucket.start(); + } + if (this.groupStart.compareAndSet(false, true)) { + startOwnThread(conn); + } + } + + /** + * 生成新的fieldPackets,包括生成的聚合函数以及原始的fieldpackets + */ + private List sendGroupFieldPackets(MySQLConnection conn) { + List newFps = new ArrayList(); + for (int i = 0; i < sums.size(); i++) { + Item sum = sums.get(i); + FieldPacket tmpfp = new FieldPacket(); + sum.makeField(tmpfp); + newFps.add(tmpfp); + } + newFps.addAll(this.fieldPackets); + nextHandler.fieldEofResponse(null, null, newFps, null, this.isLeft, conn); + return newFps; + } + + @Override + protected void ownThreadJob(Object... objects) { + MySQLConnection conn = (MySQLConnection) objects[0]; + recordElapsedTime("local group by thread is start:"); + try { + int eofCount = 0; + for (;;) { + RowDataPacket row = outQueue.take(); + if (row.fieldCount == 0) { + eofCount++; + if (eofCount == bucketSize) + break; + else + continue; + } + groupLocalResult.add(row); + } + recordElapsedTime("local group by thread is end:"); + groupLocalResult.done(); + recordElapsedTime("local group by thread is done for read:"); + if (!hasFirstRow.get()) { + if (HandlerTool.needSendNoRow(this.groupBys)) + sendNoRowGroupRowPacket(conn); + } else { + sendGroupRowPacket(conn); + } + nextHandler.rowEofResponse(null, this.isLeft, conn); + } catch (Exception e) { + String msg = "group by thread is error," + e.getLocalizedMessage(); + logger.warn(msg, e); + session.onQueryError(msg.getBytes()); + } + } + + private void recordElapsedTime(String prefix) { + if (logger.isInfoEnabled()) { + logger.info(prefix + TimeUtil.currentTimeMillis()); + } + } + + @Override + public boolean rowResponse(byte[] rownull, final RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + logger.debug("rowResponse"); + if (terminate.get()) + return true; + hasFirstRow.compareAndSet(false, true); + try { + DGRowPacket row = new DGRowPacket(rowPacket, this.referedSumFunctions.size()); + queue.put(row); + } catch (InterruptedException e) { + } + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("roweof"); + if (terminate.get()) + return; + try { + // @bug1042 + for (int i = 0; i < bucketSize; i++) + queue.put(new RowDataPacket(0)); + } catch (InterruptedException e) { + } + } + + /** + * 将一组group好的数据发送出去 + */ + private void sendGroupRowPacket(MySQLConnection conn) { + groupLocalResult.done(); + RowDataPacket row = null; + List localFields = HandlerTool.createFields(localResultFps); + List sendSums = new ArrayList(); + for (int i = 0; i < referedSumFunctions.size(); i++) { + ItemSum selSum = referedSumFunctions.get(i); + ItemSum sum = (ItemSum) HandlerTool.createItem(selSum, localFields, 0, false, HandlerType.GROUPBY, + conn.getCharset()); + sendSums.add(sum); + } + prepare_sum_aggregators(sendSums, true); + while ((row = groupLocalResult.next()) != null)// group函数已经在row中被计算过了 + { + if (sendGroupRowPacket(conn, row, sendSums)) + break; + } + } + + /** + * 将一组group好的数据发送出去 + */ + private boolean sendGroupRowPacket(MySQLConnection conn, RowDataPacket row, List sendSums) { + init_sum_functions(sendSums, row); + RowDataPacket newRp = new RowDataPacket(this.fieldPackets.size() + sendSums.size()); + /** + * 将自己生成的聚合函数的值放在前面,这样在tablenode时,如果用户语句如select count(*) from t + * 由于整个语句下发,所以最后生成的rowpacket顺序为 + * count(*){groupbyhandler生成的},count(*){下发到各个节点的,不是真实的值} + */ + for (int i = 0; i < sendSums.size(); i++) { + byte[] tmpb = sendSums.get(i).getRowPacketByte(); + newRp.add(tmpb); + } + for (int i = 0; i < row.fieldCount; i++) { + newRp.add(row.getValue(i)); + } + if (nextHandler.rowResponse(null, newRp, this.isLeft, conn)) + return true; + return false; + } + + /** + * 没有数据时,也要发送结果 比如select count(*) from t2 ,如果t2是一张空表的话,那么显示为0 + */ + private void sendNoRowGroupRowPacket(MySQLConnection conn) { + RowDataPacket newRp = new RowDataPacket(this.fieldPackets.size() + this.sums.size()); + for (int i = 0; i < this.sums.size(); i++) { + ItemSum sum = this.sums.get(i); + sum.noRowsInResult(); + byte[] tmpb = sum.getRowPacketByte(); + newRp.add(tmpb); + } + for (int i = 0; i < this.fieldPackets.size(); i++) { + newRp.add(null); + } + nextHandler.rowResponse(null, newRp, this.isLeft, conn); + } + + /** + * see Sql_executor.cc + * + * @return + */ + protected void prepare_sum_aggregators(List funcs, boolean need_distinct) { + logger.info("prepare_sum_aggregators"); + for (ItemSum func : funcs) { + func.setAggregator(need_distinct && func.has_with_distinct() + ? Aggregator.AggregatorType.DISTINCT_AGGREGATOR : Aggregator.AggregatorType.SIMPLE_AGGREGATOR, + null); + } + } + + /** + * Call ::setup for all sum functions. + * + * @param thd + * thread handler + * @param func_ptr + * sum function list + * @retval FALSE ok + * @retval TRUE error + */ + + protected boolean setup_sum_funcs(List funcs) { + logger.info("setup_sum_funcs"); + for (ItemSum func : funcs) { + if (func.aggregatorSetup()) + return true; + } + return false; + } + + protected void init_sum_functions(List funcs, RowDataPacket row) { + for (int index = 0; index < funcs.size(); index++) { + ItemSum sum = funcs.get(index); + Object transObj = ((DGRowPacket) row).getSumTran(index); + sum.resetAndAdd(row, transObj); + } + } + + @Override + protected void terminateThread() throws Exception { + this.queue.clear(); + for (int i = 0; i < bucketSize; i++) + queue.put(new RowDataPacket(0)); + } + + @Override + protected void recycleResources() { + this.queue.clear(); + if (this.groupLocalResult != null) + this.groupLocalResult.close(); + for (LocalResult bucket : buckets) { + bucket.close(); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/OrderedGroupByHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/OrderedGroupByHandler.java new file mode 100644 index 000000000..0f64bdfb4 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/OrderedGroupByHandler.java @@ -0,0 +1,261 @@ +package io.mycat.backend.mysql.nio.handler.query.impl.groupby; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.BaseDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.DistinctLocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.external.ResultStore; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.sumfunc.Aggregator.AggregatorType; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.server.NonBlockingSession; + +/** + * 1.处理已经依据groupby的列进行过排序的groupby 2.处理需要用到Aggregator_distinct的group by + * + * + */ +public class OrderedGroupByHandler extends BaseDMLHandler { + private static final Logger logger = Logger.getLogger(OrderedGroupByHandler.class); + /* 接收到的参数 */ + private List groupBys; + private List referedSumFunctions; + + private RowDataComparator cmptor; + + /* 所有的sum函数集合 */ + private List sourceFields = new ArrayList(); + private List sums = new ArrayList(); + + /* group组的原始rowpacket,目前保留第一条数据的值 */ + private RowDataPacket originRp = null; + + private boolean hasFirstRow = false; + + private BufferPool pool; + + private String charset = "UTF-8"; + + /** merge以及sendmaker现在都是多线程 **/ + private ReentrantLock lock = new ReentrantLock(); + + /* 例如count(distinct id)中用到的distinct store */ + private List distinctStores; + + /** + * + * @param groupBys + * @param refers + * 涉及到的所有的sumfunction集合 + */ + public OrderedGroupByHandler(long id, NonBlockingSession session, List groupBys, List referedSumFunctions) { + super(id, session); + this.groupBys = groupBys; + this.referedSumFunctions = referedSumFunctions; + this.distinctStores = new ArrayList(); + } + + @Override + public HandlerType type() { + return HandlerType.GROUPBY; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, BackendConnection conn) { + this.charset = conn.getCharset(); + if (terminate.get()) + return; + if (this.pool == null) + this.pool = MycatServer.getInstance().getBufferPool(); + this.fieldPackets = fieldPackets; + this.sourceFields = HandlerTool.createFields(this.fieldPackets); + for (int index = 0; index < referedSumFunctions.size(); index++) { + ItemSum sumFunc = referedSumFunctions.get(index); + ItemSum sum = (ItemSum) (HandlerTool.createItem(sumFunc, this.sourceFields, 0, this.isAllPushDown(), + this.type(), conn.getCharset())); + sums.add(sum); + } + cmptor = new RowDataComparator(this.fieldPackets, this.groupBys, this.isAllPushDown(), this.type(), + conn.getCharset()); + prepare_sum_aggregators(sums, this.referedSumFunctions, this.fieldPackets, this.isAllPushDown(), true, (MySQLConnection)conn); + setup_sum_funcs(sums); + sendGroupFieldPackets(conn); + } + + /** + * 生成新的fieldPackets,包括生成的聚合函数以及原始的fieldpackets + */ + private void sendGroupFieldPackets(BackendConnection conn) { + List newFps = new ArrayList(); + for (int i = 0; i < sums.size(); i++) { + Item sum = sums.get(i); + FieldPacket tmpfp = new FieldPacket(); + sum.makeField(tmpfp); + newFps.add(tmpfp); + } + newFps.addAll(this.fieldPackets); + nextHandler.fieldEofResponse(null, null, newFps, null, this.isLeft, conn); + } + + @Override + public boolean rowResponse(byte[] rownull, final RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + logger.debug("rowresponse"); + if (terminate.get()) + return true; + lock.lock(); + try { + if (!hasFirstRow) { + hasFirstRow = true; + originRp = rowPacket; + init_sum_functions(sums, rowPacket); + } else { + boolean sameGroupRow = this.groupBys.size() == 0 ? true : (cmptor.compare(originRp, rowPacket) == 0); + if (!sameGroupRow) { + // 需要将这一组数据发送出去 + sendGroupRowPacket((MySQLConnection)conn); + originRp = rowPacket; + init_sum_functions(sums, rowPacket); + } else { + update_sum_func(sums, rowPacket); + } + } + return false; + } finally { + lock.unlock(); + } + } + + /** + * 将一组group好的数据发送出去 + */ + private void sendGroupRowPacket(MySQLConnection conn) { + RowDataPacket newRp = new RowDataPacket(this.fieldPackets.size() + this.sums.size()); + /** + * 将自己生成的聚合函数的值放在前面,这样在tablenode时,如果用户语句如select count(*) from t + * 由于整个语句下发,所以最后生成的rowpacket顺序为 + * count(*){groupbyhandler生成的},count(*){下发到各个节点的,不是真实的值} + */ + for (int i = 0; i < this.sums.size(); i++) { + byte[] tmpb = this.sums.get(i).getRowPacketByte(); + newRp.add(tmpb); + } + for (int i = 0; i < originRp.fieldCount; i++) { + newRp.add(originRp.getValue(i)); + } + nextHandler.rowResponse(null, newRp, this.isLeft, conn); + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("row eof for orderby."); + if (terminate.get()) + return; + if (!hasFirstRow) { + if (HandlerTool.needSendNoRow(this.groupBys)) + sendNoRowGroupRowPacket((MySQLConnection)conn); + } else { + sendGroupRowPacket((MySQLConnection)conn); + } + nextHandler.rowEofResponse(data, this.isLeft, conn); + } + + /** + * 没有数据时,也要发送结果 比如select count(*) from t2 ,如果t2是一张空表的话,那么显示为0 + */ + private void sendNoRowGroupRowPacket(MySQLConnection conn) { + RowDataPacket newRp = new RowDataPacket(this.fieldPackets.size() + this.sums.size()); + // @bug 1050 + // sumfuncs are front + for (int i = 0; i < this.sums.size(); i++) { + ItemSum sum = this.sums.get(i); + sum.noRowsInResult(); + byte[] tmpb = sum.getRowPacketByte(); + newRp.add(tmpb); + } + for (int i = 0; i < this.fieldPackets.size(); i++) { + newRp.add(null); + } + originRp = null; + nextHandler.rowResponse(null, newRp, this.isLeft, conn); + } + + /** + * see Sql_executor.cc + * + * @return + */ + protected void prepare_sum_aggregators(List funcs, List sumfuncs, List packets, + boolean isAllPushDown, boolean need_distinct, MySQLConnection conn) { + logger.info("prepare_sum_aggregators"); + for (int i = 0; i < funcs.size(); i++) { + ItemSum func = funcs.get(i); + ResultStore store = null; + if (func.has_with_distinct()) { + ItemSum selFunc = sumfuncs.get(i); + List orders = HandlerTool.makeOrder(selFunc.arguments()); + RowDataComparator distinctCmp = new RowDataComparator(packets, orders, isAllPushDown, this.type(), + conn.getCharset()); + store = new DistinctLocalResult(pool, packets.size(), distinctCmp, this.charset) + .setMemSizeController(session.getOtherBufferMC()); + distinctStores.add(store); + } + func.setAggregator(need_distinct && func.has_with_distinct() + ? AggregatorType.DISTINCT_AGGREGATOR : AggregatorType.SIMPLE_AGGREGATOR, + store); + } + } + + /** + * Call ::setup for all sum functions. + * + * @param thd + * thread handler + * @param func_ptr + * sum function list + * @retval FALSE ok + * @retval TRUE error + */ + + protected boolean setup_sum_funcs(List funcs) { + logger.info("setup_sum_funcs"); + for (ItemSum func : funcs) { + if (func.aggregatorSetup()) + return true; + } + return false; + } + + protected void init_sum_functions(List funcs, RowDataPacket row) { + for (ItemSum func : funcs) { + func.resetAndAdd(row, null); + } + } + + protected void update_sum_func(List funcs, RowDataPacket row) { + for (ItemSum func : funcs) { + func.aggregatorAdd(row, null); + } + } + + @Override + public void onTerminate() { + for (ResultStore store : distinctStores) { + store.close(); + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/directgroupby/DGRowPacket.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/directgroupby/DGRowPacket.java new file mode 100644 index 000000000..d5de6fb2c --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/directgroupby/DGRowPacket.java @@ -0,0 +1,154 @@ +package io.mycat.backend.mysql.nio.handler.query.impl.groupby.directgroupby; + + +import java.io.Serializable; +import java.nio.ByteBuffer; + +import org.apache.commons.lang.SerializationUtils; + +import io.mycat.MycatServer; +import io.mycat.backend.mysql.BufferUtil; +import io.mycat.backend.mysql.ByteUtil; +import io.mycat.net.mysql.RowDataPacket; + +/** + * proxy层进行group by计算时用到的RowPacket,比传统的rowpacket多保存了聚合函数的结果 + * sum的结果存放在RowPacket的最前面 + * + */ +public class DGRowPacket extends RowDataPacket { + + private int sumSize; + + /** 保存的中间聚合对象 **/ + private Object[] sumTranObjs; + + /** 保存中间聚合结果的大小 **/ + private int[] sumByteSizes; + + public DGRowPacket(RowDataPacket innerRow, int sumSize) { + this(innerRow.fieldCount, sumSize); + this.addAll(innerRow.fieldValues); + } + + /** + * + * @param fieldCount + * 原始的field的个数 + * @param sumSize + * 要计算的sum的个数 + */ + public DGRowPacket(int fieldCount, int sumSize) { + super(fieldCount); + this.sumSize = sumSize; + sumTranObjs = new Object[sumSize]; + sumByteSizes = new int[sumSize]; + } + + public void setSumTran(int index, Object trans, int transSize) { + if (index >= sumSize) + throw new RuntimeException("Set sumTran out of sumSize index!"); + else { + sumTranObjs[index] = trans; + sumByteSizes[index] = transSize; + } + } + + public Object getSumTran(int index) { + if (index >= sumSize) + throw new RuntimeException("Set sumTran out of sumSize index!"); + else { + return sumTranObjs[index]; + } + } + + + @Override + /** + * 提供一个不准确的size + */ + public int calcPacketSize() { + int size = super.calcPacketSize(); + for (int i = 0; i < sumSize; i++) { + int byteSize = sumByteSizes[i]; + size += ByteUtil.decodeLength(byteSize) + byteSize; + } + return size; + } + + private int getRealSize() { + int size = super.calcPacketSize(); + for (int i = 0; i < sumSize; i++) { + byte[] v = null; + Object obj = sumTranObjs[i]; + if (obj != null) + v = SerializationUtils.serialize((Serializable) obj); + size += (v == null || v.length == 0) ? 1 : ByteUtil.decodeLength(v); + } + return size; + } + + @Override + public byte[] toBytes() { + int size = getRealSize(); + ByteBuffer buffer = MycatServer.getInstance().getBufferPool().allocate(size + packetHeaderSize); + BufferUtil.writeUB3(buffer, size); + buffer.put(packetId); + for (int i = 0; i < this.sumSize; i++) { + Object obj = sumTranObjs[i]; + byte[] ov = null; + if (obj != null) + ov = SerializationUtils.serialize((Serializable) obj); + if (ov == null) { + buffer.put(NULL_MARK); + } else if (ov.length == 0) { + buffer.put(EMPTY_MARK); + } else { + BufferUtil.writeWithLength(buffer, ov); + } + } + for (int i = 0; i < this.fieldCount; i++) { + byte[] fv = fieldValues.get(i); + if (fv == null) { + buffer.put(NULL_MARK); + } else if (fv.length == 0) { + buffer.put(EMPTY_MARK); + } else { + BufferUtil.writeWithLength(buffer, fv); + } + } + buffer.flip(); + byte[] data = new byte[buffer.limit()]; + buffer.get(data); + MycatServer.getInstance().getBufferPool().recycle(buffer); + return data; + } + + @Override + public String getPacketInfo() { + return "Direct Groupby RowData Packet"; + } + + public static void main(String[] args) { + DGRowPacket row = new DGRowPacket(2, 2); + row.add(new byte[1]); + row.add(new byte[1]); + row.setSumTran(0, 1, 4); + row.setSumTran(1, 2.2, 8); + byte[] bb = row.toBytes(); + RowDataPacket rp = new RowDataPacket(4); + rp.read(bb); + DGRowPacket dgRow = new DGRowPacket(2, 2); + for (int i = 0; i < 2; i++) { + byte[] b = rp.getValue(i); + if (b != null) { + Object obj = SerializationUtils.deserialize(b); + dgRow.setSumTran(i, obj, 4); + } + } + for (int i = 2; i < 4; i++) { + dgRow.add(rp.getValue(i)); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/directgroupby/GroupByBucket.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/directgroupby/GroupByBucket.java new file mode 100644 index 000000000..d8d0186d5 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/groupby/directgroupby/GroupByBucket.java @@ -0,0 +1,60 @@ +package io.mycat.backend.mysql.nio.handler.query.impl.groupby.directgroupby; + +import java.util.List; +import java.util.concurrent.BlockingQueue; + +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.GroupByLocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; + +/** + * 多线程的Group By桶,并发生成Group By的中间结果,最终再进行Group By,从而生成Group By的总结果 + * + */ +public class GroupByBucket extends GroupByLocalResult { + // 进行groupby的输入来源 + private BlockingQueue inData; + private BlockingQueue outData; + private Thread thread; + + public GroupByBucket(BlockingQueue sourceData, BlockingQueue outData, + BufferPool pool, int fieldsCount, RowDataComparator groupCmp, + List fieldPackets, List sumFunctions, + boolean isAllPushDown, String charset) { + super(pool, fieldsCount, groupCmp, fieldPackets, sumFunctions, + isAllPushDown, charset); + this.inData = sourceData; + this.outData = outData; + } + + /** + * 开启一个新的线程进行Group by工作 + */ + public void start() { + thread = new Thread(new Runnable() { + @Override + public void run() { + try { + while (true) { + RowDataPacket rp = inData.take(); + if (rp.fieldCount == 0) + break; + add(rp); + } + done(); + RowDataPacket groupedRow = null; + while ((groupedRow = next()) != null) + outData.put(groupedRow); + outData.put(new RowDataPacket((0))); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + thread.start(); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/join/JoinHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/join/JoinHandler.java new file mode 100644 index 000000000..ae2ac77cf --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/join/JoinHandler.java @@ -0,0 +1,390 @@ +package io.mycat.backend.mysql.nio.handler.query.impl.join; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.OwnThreadDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.nio.handler.util.TwoTableComparator; +import io.mycat.backend.mysql.store.LocalResult; +import io.mycat.backend.mysql.store.UnSortedLocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.server.NonBlockingSession; +import io.mycat.util.FairLinkedBlockingDeque; + +/** + * join的策略目前为sortmerge,在merge数据到达之前,已经按照merge列进行了排序 + * + * @author chenzifei + * + */ +public class JoinHandler extends OwnThreadDMLHandler { + protected Logger logger = Logger.getLogger(JoinHandler.class); + + protected boolean isLeftJoin = false; + protected FairLinkedBlockingDeque leftQueue; + protected FairLinkedBlockingDeque rightQueue; + protected List leftOrders; + protected List rightOrders; + protected List leftFieldPackets; + protected List rightFieldPackets; + private AtomicBoolean fieldSent = new AtomicBoolean(false); + private BufferPool pool; + private RowDataComparator leftCmptor; + private RowDataComparator rightCmptor; + // @bug 1097 + // only join columns same is not enough + private List joinRowFields; + private Item otherJoinOn; + private Item otherJoinOnItem; + private int queueSize; + // @bug 1208 + private String charset = "UTF-8"; + // prevent multi thread rowresponse + protected ReentrantLock leftLock = new ReentrantLock(); + protected ReentrantLock rightLock = new ReentrantLock(); + + public JoinHandler(long id, NonBlockingSession session, boolean isLeftJoin, List leftOrder, + List rightOrder, Item otherJoinOn) { + super(id, session); + this.isLeftJoin = isLeftJoin; + this.leftOrders = leftOrder; + this.rightOrders = rightOrder; + this.queueSize = MycatServer.getInstance().getConfig().getSystem().getJoinQueueSize(); + this.leftQueue = new FairLinkedBlockingDeque(queueSize); + this.rightQueue = new FairLinkedBlockingDeque(queueSize); + this.leftFieldPackets = new ArrayList(); + this.rightFieldPackets = new ArrayList(); + this.otherJoinOn = otherJoinOn; + } + + @Override + public HandlerType type() { + return HandlerType.JOIN; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, final BackendConnection conn) { + if (this.pool == null) + this.pool = MycatServer.getInstance().getBufferPool(); + + if (isLeft) { + // logger.debug("field eof left"); + leftFieldPackets = fieldPackets; + leftCmptor = new RowDataComparator(leftFieldPackets, leftOrders, this.isAllPushDown(), this.type(), + conn.getCharset()); + } else { + // logger.debug("field eof right"); + rightFieldPackets = fieldPackets; + rightCmptor = new RowDataComparator(rightFieldPackets, rightOrders, this.isAllPushDown(), this.type(), + conn.getCharset()); + } + if (!fieldSent.compareAndSet(false, true)) { + this.charset = conn.getCharset(); + List newFieldPacket = new ArrayList(); + newFieldPacket.addAll(leftFieldPackets); + newFieldPacket.addAll(rightFieldPackets); + nextHandler.fieldEofResponse(null, null, newFieldPacket, null, this.isLeft, conn); + otherJoinOnItem = makeOtherJoinOnItem(newFieldPacket, conn); + // logger.debug("all ready"); + startOwnThread(conn); + } + } + + /* 用来处理otherjoinonfilter的 */ + private Item makeOtherJoinOnItem(List rowpackets, BackendConnection conn) { + this.joinRowFields = HandlerTool.createFields(rowpackets); + if (otherJoinOn == null) + return null; + Item ret = HandlerTool.createItem(this.otherJoinOn, this.joinRowFields, 0, this.isAllPushDown(), this.type(), + conn.getCharset()); + return ret; + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + logger.debug("rowresponse"); + if (terminate.get()) { + return true; + } + try { + if (isLeft) { + leftLock.lock(); + try { + addRowToDeque(rowPacket, leftFieldPackets.size(), leftQueue, leftCmptor); + } finally { + leftLock.unlock(); + } + } else { + rightLock.lock(); + try { + addRowToDeque(rowPacket, rightFieldPackets.size(), rightQueue, rightCmptor); + } finally { + rightLock.unlock(); + } + } + } catch (InterruptedException e) { + logger.error("join row response exception", e); + return true; + } + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.debug("roweof"); + if (terminate.get()) { + return; + } + RowDataPacket eofRow = new RowDataPacket(0); + try { + if (isLeft) { + logger.debug("row eof left"); + addRowToDeque(eofRow, leftFieldPackets.size(), leftQueue, leftCmptor); + } else { + logger.debug("row eof right"); + addRowToDeque(eofRow, rightFieldPackets.size(), rightQueue, rightCmptor); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + protected void ownThreadJob(Object... objects) { + MySQLConnection conn = (MySQLConnection) objects[0]; + LocalResult leftLocal = null, rightLocal = null; + try { + Comparator joinCmptor = new TwoTableComparator(leftFieldPackets, rightFieldPackets, + leftOrders, rightOrders, this.isAllPushDown(), this.type(), conn.getCharset()); + + // logger.debug("merge Join start"); + leftLocal = takeFirst(leftQueue); + rightLocal = takeFirst(rightQueue); + while (true) { + if (terminate.get()) + return; + RowDataPacket leftRow = leftLocal.getLastRow(); + RowDataPacket rightRow = rightLocal.getLastRow(); + if (leftRow.fieldCount == 0) { + break; + } + if (rightRow.fieldCount == 0) { + if (isLeftJoin) { + if (connectLeftAndNull(leftLocal, conn)) + break; + leftLocal = takeFirst(leftQueue); + continue; + } else { + break; + } + } + int rs = joinCmptor.compare(leftRow, rightRow); + if (rs < 0) { + if (isLeftJoin) { + if (connectLeftAndNull(leftLocal, conn)) + break; + leftLocal = takeFirst(leftQueue); + continue; + } else { + leftLocal.close(); + leftLocal = takeFirst(leftQueue); + } + } else if (rs > 0) { + rightLocal.close(); + rightLocal = takeFirst(rightQueue); + } else { + if (connectLeftAndRight(leftLocal, rightLocal, conn)) + break; + leftLocal = takeFirst(leftQueue); + rightLocal = takeFirst(rightQueue); + } + } + nextHandler.rowEofResponse(null, isLeft, conn); + HandlerTool.terminateHandlerTree(this); + } catch (Exception e) { + String msg = "join thread error, " + e.getLocalizedMessage(); + logger.error(msg, e); + session.onQueryError(msg.getBytes()); + } finally { + if (leftLocal != null) + leftLocal.close(); + if (rightLocal != null) + rightLocal.close(); + } + } + + private LocalResult takeFirst(FairLinkedBlockingDeque deque) throws InterruptedException { + /** + * 前提条件是这个方法是个单线程 + */ + deque.waitUtilCount(1); + LocalResult result = deque.peekFirst(); + RowDataPacket lastRow = result.getLastRow(); + if (lastRow.fieldCount == 0) + return deque.takeFirst(); + else { + deque.waitUtilCount(2); + return deque.takeFirst(); + } + } + + /** + * + * @param leftRows + * @param rightRows + * @param conn + * @return if is interrupted by next handler ,return true,else false + * @throws Exception + */ + private boolean connectLeftAndRight(LocalResult leftRows, LocalResult rightRows, MySQLConnection conn) + throws Exception { + RowDataPacket leftRow = null; + RowDataPacket rightRow = null; + try { + while ((leftRow = leftRows.next()) != null) { + // @bug 1097 + int matchCount = 0; + while ((rightRow = rightRows.next()) != null) { + RowDataPacket rowPacket = new RowDataPacket(leftFieldPackets.size() + rightFieldPackets.size()); + for (byte[] value : leftRow.fieldValues) { + rowPacket.add(value); + } + for (byte[] value : rightRow.fieldValues) { + rowPacket.add(value); + } + if (otherJoinOnItem != null) { + HandlerTool.initFields(joinRowFields, rowPacket.fieldValues); + if (otherJoinOnItem.valBool() == false) + continue; + } + matchCount++; + if (nextHandler.rowResponse(null, rowPacket, isLeft, conn)) + return true; + } + // @bug 1097 + // condition: exist otherOnItem and no row match other condition + // send left row and null + if (matchCount == 0 && isLeftJoin) { + RowDataPacket rowPacket = new RowDataPacket(leftFieldPackets.size() + rightFieldPackets.size()); + for (byte[] value : leftRow.fieldValues) { + rowPacket.add(value); + } + for (int i = 0; i < rightFieldPackets.size(); i++) { + rowPacket.add(null); + } + if (nextHandler.rowResponse(null, rowPacket, isLeft, conn)) + return true; + } + rightRows.reset(); + } + return false; + } finally { + leftRows.close(); + rightRows.close(); + } + } + + private boolean connectLeftAndNull(LocalResult leftRows, MySQLConnection conn) throws Exception { + RowDataPacket leftRow = null; + try { + while ((leftRow = leftRows.next()) != null) { + RowDataPacket rowPacket = new RowDataPacket(leftFieldPackets.size() + rightFieldPackets.size()); + for (byte[] value : leftRow.fieldValues) { + rowPacket.add(value); + } + for (int i = 0; i < rightFieldPackets.size(); i++) { + rowPacket.add(null); + } + if (nextHandler.rowResponse(null, rowPacket, isLeft, conn)) + return true; + } + return false; + } finally { + leftRows.close(); + } + } + + private void addRowToDeque(RowDataPacket row, int columnCount, FairLinkedBlockingDeque deque, + RowDataComparator cmp) throws InterruptedException { + LocalResult localResult = deque.peekLast(); + if (localResult != null) { + RowDataPacket lastRow = localResult.getLastRow(); + if (lastRow.fieldCount == 0) { + // 有可能是terminateThread添加的eof + return; + } else if (row.fieldCount > 0 && cmp.compare(lastRow, row) == 0) { + localResult.add(row); + return; + } else { + localResult.done(); + } + } + LocalResult newLocalResult = new UnSortedLocalResult(columnCount, pool, this.charset) + .setMemSizeController(session.getJoinBufferMC()); + newLocalResult.add(row); + if (row.fieldCount == 0) + newLocalResult.done(); + deque.putLast(newLocalResult); + + } + + /** + * only for terminate. + * + * @param row + * @param columnCount + * @param deque + * @throws InterruptedException + */ + private void addEndRowToDeque(RowDataPacket row, int columnCount, FairLinkedBlockingDeque deque) + throws InterruptedException { + LocalResult newLocalResult = new UnSortedLocalResult(columnCount, pool, this.charset) + .setMemSizeController(session.getJoinBufferMC()); + newLocalResult.add(row); + newLocalResult.done(); + LocalResult localResult = deque.addOrReplaceLast(newLocalResult); + if (localResult != null) + localResult.close(); + } + + @Override + protected void terminateThread() throws Exception { + RowDataPacket eofRow = new RowDataPacket(0); + addEndRowToDeque(eofRow, leftFieldPackets.size(), leftQueue); + RowDataPacket eofRow2 = new RowDataPacket(0); + addEndRowToDeque(eofRow2, rightFieldPackets.size(), rightQueue); + } + + @Override + protected void recycleResources() { + clearDeque(this.leftQueue); + clearDeque(this.rightQueue); + } + + private void clearDeque(FairLinkedBlockingDeque deque) { + if (deque == null) + return; + LocalResult local = deque.poll(); + while (local != null) { + local.close(); + local = deque.poll(); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/join/NotInHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/join/NotInHandler.java new file mode 100644 index 000000000..c0bcb6c9c --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/query/impl/join/NotInHandler.java @@ -0,0 +1,262 @@ +package io.mycat.backend.mysql.nio.handler.query.impl.join; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.BackendConnection; +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.backend.mysql.nio.handler.query.OwnThreadDMLHandler; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.nio.handler.util.TwoTableComparator; +import io.mycat.backend.mysql.store.LocalResult; +import io.mycat.backend.mysql.store.UnSortedLocalResult; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.server.NonBlockingSession; +import io.mycat.util.FairLinkedBlockingDeque; + +public class NotInHandler extends OwnThreadDMLHandler { + private static final Logger logger = Logger.getLogger(NotInHandler.class); + + private FairLinkedBlockingDeque leftQueue; + private FairLinkedBlockingDeque rightQueue; + private List leftOrders; + private List rightOrders; + private List leftFieldPackets; + private List rightFieldPackets; + private BufferPool pool; + private RowDataComparator leftCmptor; + private RowDataComparator rightCmptor; + private AtomicBoolean fieldSent = new AtomicBoolean(false); + private int queueSize; + private String charset = "UTF-8"; + + public NotInHandler(long id, NonBlockingSession session, List leftOrder, List rightOrder) { + super(id, session); + this.leftOrders = leftOrder; + this.rightOrders = rightOrder; + this.queueSize = MycatServer.getInstance().getConfig().getSystem().getJoinQueueSize(); + this.leftQueue = new FairLinkedBlockingDeque(queueSize); + this.rightQueue = new FairLinkedBlockingDeque(queueSize); + this.leftFieldPackets = new ArrayList(); + this.rightFieldPackets = new ArrayList(); + } + + @Override + public HandlerType type() { + return HandlerType.JOIN; + } + + @Override + public void fieldEofResponse(byte[] headernull, List fieldsnull, final List fieldPackets, + byte[] eofnull, boolean isLeft, final BackendConnection conn) { + if (this.pool == null) + this.pool = MycatServer.getInstance().getBufferPool(); + + if (isLeft) { + // logger.debug("field eof left"); + leftFieldPackets = fieldPackets; + leftCmptor = new RowDataComparator(leftFieldPackets, leftOrders, this.isAllPushDown(), this.type(), + conn.getCharset()); + } else { + // logger.debug("field eof right"); + rightFieldPackets = fieldPackets; + rightCmptor = new RowDataComparator(rightFieldPackets, rightOrders, this.isAllPushDown(), this.type(), + conn.getCharset()); + } + if (!fieldSent.compareAndSet(false, true)) { + this.charset = conn.getCharset(); + nextHandler.fieldEofResponse(null, null, leftFieldPackets, null, this.isLeft, conn); + // logger.debug("all ready"); + startOwnThread(conn); + } + } + + @Override + public boolean rowResponse(byte[] rownull, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { + logger.debug("rowresponse"); + if (terminate.get()) { + return true; + } + try { + if (isLeft) { + addRowToDeque(rowPacket, leftFieldPackets.size(), leftQueue, leftCmptor); + } else { + addRowToDeque(rowPacket, rightFieldPackets.size(), rightQueue, rightCmptor); + } + } catch (InterruptedException e) { + logger.warn("not in row exception", e); + return true; + } + return false; + } + + @Override + public void rowEofResponse(byte[] data, boolean isLeft, BackendConnection conn) { + logger.info("roweof"); + if (terminate.get()) { + return; + } + RowDataPacket eofRow = new RowDataPacket(0); + try { + if (isLeft) { + // logger.debug("row eof left"); + addRowToDeque(eofRow, leftFieldPackets.size(), leftQueue, leftCmptor); + } else { + // logger.debug("row eof right"); + addRowToDeque(eofRow, rightFieldPackets.size(), rightQueue, rightCmptor); + } + } catch (Exception e) { + logger.warn("not in rowEof exception", e); + } + } + + @Override + protected void ownThreadJob(Object... objects) { + MySQLConnection conn = (MySQLConnection) objects[0]; + LocalResult leftLocal = null, rightLocal = null; + try { + Comparator notInCmptor = new TwoTableComparator(leftFieldPackets, rightFieldPackets, + leftOrders, rightOrders, this.isAllPushDown(), this.type(), conn.getCharset()); + + leftLocal = takeFirst(leftQueue); + rightLocal = takeFirst(rightQueue); + while (true) { + RowDataPacket leftRow = leftLocal.getLastRow(); + RowDataPacket rightRow = rightLocal.getLastRow(); + if (leftRow.fieldCount == 0) { + break; + } + if (rightRow.fieldCount == 0) { + sendLeft(leftLocal, conn); + leftLocal.close(); + leftLocal = takeFirst(leftQueue); + continue; + } + int rs = notInCmptor.compare(leftRow, rightRow); + if (rs < 0) { + sendLeft(leftLocal, conn); + leftLocal.close(); + leftLocal = takeFirst(leftQueue); + continue; + } else if (rs > 0) { + rightLocal.close(); + rightLocal = takeFirst(rightQueue); + } else { + // because not in, if equal left should move to next value + leftLocal.close(); + rightLocal.close(); + leftLocal = takeFirst(leftQueue); + rightLocal = takeFirst(rightQueue); + } + } + nextHandler.rowEofResponse(null, isLeft, conn); + HandlerTool.terminateHandlerTree(this); + } catch (Exception e) { + String msg = "notIn thread error, " + e.getLocalizedMessage(); + logger.warn(msg, e); + session.onQueryError(msg.getBytes()); + } finally { + if (leftLocal != null) + leftLocal.close(); + if (rightLocal != null) + rightLocal.close(); + } + } + + private LocalResult takeFirst(FairLinkedBlockingDeque deque) throws InterruptedException { + /** + * 前提条件是这个方法是个单线程 + */ + deque.waitUtilCount(1); + LocalResult result = deque.peekFirst(); + RowDataPacket lastRow = result.getLastRow(); + if (lastRow.fieldCount == 0) + return deque.takeFirst(); + else { + deque.waitUtilCount(2); + return deque.takeFirst(); + } + } + + private void sendLeft(LocalResult leftRows, MySQLConnection conn) throws Exception { + RowDataPacket leftRow = null; + while ((leftRow = leftRows.next()) != null) { + nextHandler.rowResponse(null, leftRow, isLeft, conn); + } + } + + private void addRowToDeque(RowDataPacket row, int columnCount, FairLinkedBlockingDeque deque, + RowDataComparator cmp) throws InterruptedException { + LocalResult localResult = deque.peekLast(); + if (localResult != null) { + RowDataPacket lastRow = localResult.getLastRow(); + if (lastRow.fieldCount == 0) { + // 有可能是terminateThread添加的eof + return; + } else if (row.fieldCount > 0 && cmp.compare(lastRow, row) == 0) { + localResult.add(row); + return; + } else { + localResult.done(); + } + } + LocalResult newLocalResult = new UnSortedLocalResult(columnCount, pool, this.charset) + .setMemSizeController(session.getJoinBufferMC()); + newLocalResult.add(row); + if (row.fieldCount == 0) + newLocalResult.done(); + deque.putLast(newLocalResult); + } + + /** + * only for terminate. + * + * @param row + * @param columnCount + * @param deque + * @throws InterruptedException + */ + private void addEndRowToDeque(RowDataPacket row, int columnCount, FairLinkedBlockingDeque deque) + throws InterruptedException { + LocalResult newLocalResult = new UnSortedLocalResult(columnCount, pool, this.charset) + .setMemSizeController(session.getJoinBufferMC()); + newLocalResult.add(row); + newLocalResult.done(); + LocalResult localResult = deque.addOrReplaceLast(newLocalResult); + if (localResult != null) + localResult.close(); + } + + @Override + protected void terminateThread() throws Exception { + RowDataPacket eofRow = new RowDataPacket(0); + addEndRowToDeque(eofRow, leftFieldPackets.size(), leftQueue); + RowDataPacket eofRow2 = new RowDataPacket(0); + addEndRowToDeque(eofRow2, rightFieldPackets.size(), rightQueue); + } + + @Override + protected void recycleResources() { + clearDeque(this.leftQueue); + clearDeque(this.rightQueue); + } + + private void clearDeque(FairLinkedBlockingDeque deque) { + if (deque == null) + return; + LocalResult local = deque.poll(); + while (local != null) { + local.close(); + local = deque.poll(); + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractCommitNodesHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractCommitNodesHandler.java index ccbaa6376..9afbb088f 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractCommitNodesHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractCommitNodesHandler.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import io.mycat.backend.BackendConnection; import io.mycat.backend.mysql.nio.MySQLConnection; import io.mycat.backend.mysql.nio.handler.MultiNodeHandler; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultsetNode; import io.mycat.server.NonBlockingSession; @@ -39,7 +41,7 @@ public abstract class AbstractCommitNodesHandler extends MultiNodeHandler imple } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); @@ -51,17 +53,19 @@ public abstract class AbstractCommitNodesHandler extends MultiNodeHandler imple } @Override - public void fieldEofResponse(byte[] header, List fields, byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); + return false; } @Override diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractRollbackNodesHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractRollbackNodesHandler.java index 7922d408e..f94948fc5 100644 --- a/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractRollbackNodesHandler.java +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/transaction/AbstractRollbackNodesHandler.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import io.mycat.backend.BackendConnection; import io.mycat.backend.mysql.nio.MySQLConnection; import io.mycat.backend.mysql.nio.handler.MultiNodeHandler; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultsetNode; import io.mycat.server.NonBlockingSession; @@ -39,7 +41,7 @@ public abstract class AbstractRollbackNodesHandler extends MultiNodeHandler impl } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); @@ -51,17 +53,19 @@ public abstract class AbstractRollbackNodesHandler extends MultiNodeHandler impl } @Override - public void fieldEofResponse(byte[] header, List fields, byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { LOGGER.error(new StringBuilder().append("unexpected packet for ") .append(conn).append(" bound by ").append(session.getSource()) .append(": field's eof").toString()); + return false; } @Override diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/ArrayMinHeap.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/ArrayMinHeap.java new file mode 100644 index 000000000..60cc26285 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/ArrayMinHeap.java @@ -0,0 +1,337 @@ +package io.mycat.backend.mysql.nio.handler.util; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import io.mycat.util.MinHeap; + +@SuppressWarnings("unchecked") +public class ArrayMinHeap implements MinHeap { + + private static final int DEFAULT_INITIAL_CAPACITY = 3; + + private Object[] heap; + private int size = 0; + private Comparator comparator; + + public ArrayMinHeap(Comparator comparator) { + this(DEFAULT_INITIAL_CAPACITY, comparator); + } + + public ArrayMinHeap(int initialCapacity, Comparator comparator) { + if (initialCapacity < 1) + throw new IllegalArgumentException(); + this.heap = new Object[initialCapacity]; + this.comparator = comparator; + } + + public E find(E e) { + if (e != null) { + for (int i = 0; i < size; i++) + if (comparator.compare(e, (E) heap[i]) == 0) + return (E) heap[i]; + } + return null; + } + + @Override + public int size() { + return this.size; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return indexOf(o) != -1; + } + + @Override + public Iterator iterator() { + return new Itr(); + } + + private final class Itr implements Iterator { + /** + * Index (into queue array) of element to be returned by subsequent call + * to next. + */ + private int cursor = 0; + + /** + * Index of element returned by most recent call to next, unless that + * element came from the forgetMeNot list. Set to -1 if element is + * deleted by a call to remove. + */ + private int lastRet = -1; + + /** + * A queue of elements that were moved from the unvisited portion of the + * heap into the visited portion as a result of "unlucky" element + * removals during the iteration. (Unlucky element removals are those + * that require a siftup instead of a siftdown.) We must visit all of + * the elements in this list to complete the iteration. We do this after + * we've completed the "normal" iteration. + * + * We expect that most iterations, even those involving removals, will + * not need to store elements in this field. + */ + private ArrayDeque forgetMeNot = null; + + /** + * Element returned by the most recent call to next iff that element was + * drawn from the forgetMeNot list. + */ + private E lastRetElt = null; + + public boolean hasNext() { + return cursor < size + || (forgetMeNot != null && !forgetMeNot.isEmpty()); + } + + public E next() { + if (cursor < size) + return (E) heap[lastRet = cursor++]; + if (forgetMeNot != null) { + lastRet = -1; + lastRetElt = forgetMeNot.poll(); + if (lastRetElt != null) + return lastRetElt; + } + throw new NoSuchElementException(); + } + + public void remove() { + if (lastRet != -1) { + E moved = ArrayMinHeap.this.removeAt(lastRet); + lastRet = -1; + if (moved == null) + cursor--; + else { + if (forgetMeNot == null) + forgetMeNot = new ArrayDeque(); + forgetMeNot.add(moved); + } + } else if (lastRetElt != null) { + ArrayMinHeap.this.removeEq(lastRetElt); + lastRetElt = null; + } else { + throw new IllegalStateException(); + } + } + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(heap, size); + } + + @Override + public T[] toArray(T[] a) { + if (a.length < size) + // Make a new array of a's runtime type, but my contents: + return (T[]) Arrays.copyOf(heap, size, a.getClass()); + System.arraycopy(heap, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + + private void grow(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + int oldCapacity = heap.length; + // Double size if small; else grow by 50% + int newCapacity = ((oldCapacity < 64) ? ((oldCapacity + 1) * 2) + : ((oldCapacity / 2) * 3)); + if (newCapacity < 0) // overflow + newCapacity = Integer.MAX_VALUE; + if (newCapacity < minCapacity) + newCapacity = minCapacity; + heap = Arrays.copyOf(heap, newCapacity); + } + + @Override + public boolean add(E e) { + return offer(e); + } + + @Override + public void replaceTop(E e) { + if(size == 0) + return; + + siftDown(0, e); + } + + public boolean offer(E e) { + if (e == null) + throw new NullPointerException(); + int i = size; + if (i >= heap.length) + grow(i + 1); + size = i + 1; + if (i == 0) + heap[0] = e; + else + siftUp(i, e); + return true; + } + + private void siftUp(int k, E x) { + while (k > 0) { + int parant = (k - 1) >>> 1; + Object e = heap[parant]; + if (comparator.compare(x, (E) e) >= 0) + break; + heap[k] = e; + k = parant; + } + heap[k] = x; + } + + private int indexOf(Object o) { + if (o != null) { + for (int i = 0; i < size; i++) + if (o.equals(heap[i])) + return i; + } + return -1; + } + + @Override + public E peak() { + if (size == 0) + return null; + return (E) heap[0]; + } + + @Override + public E poll() { + if (size == 0) + return null; + int s = --size; + E result = (E) heap[0]; + E x = (E) heap[s]; + heap[s] = null; + if (s != 0) + siftDown(0, x); + return result; + } + + private E removeAt(int i) { + assert i >= 0 && i < size; + int s = --size; + if (s == i) // removed last element + heap[i] = null; + else { + E moved = (E) heap[s]; + heap[s] = null; + siftDown(i, moved); + if (heap[i] == moved) { + siftUp(i, moved); + if (heap[i] != moved) + return moved; + } + } + return null; + } + + private void siftDown(int k, E x) { + // the last element's parent index + int half = size >>> 1; + while (k < half) { + int child = (k << 1) + 1; + Object c = heap[child]; + int right = child + 1; + if (right < size && comparator.compare((E) c, (E) heap[right]) > 0) + c = heap[child = right]; + if (comparator.compare(x, (E) c) <= 0) + break; + heap[k] = c; + k = child; + } + heap[k] = x; + } + + boolean removeEq(Object o) { + for (int i = 0; i < size; i++) { + if (o == heap[i]) { + removeAt(i); + return true; + } + } + return false; + } + + @Override + public boolean remove(Object o) { + int i = indexOf(o); + if (i == -1) + return false; + else { + removeAt(i); + return true; + } + } + + @Override + public boolean containsAll(Collection c) { + Iterator e = c.iterator(); + while (e.hasNext()) + if (!contains(e.next())) + return false; + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean modified = false; + Iterator e = c.iterator(); + while (e.hasNext()) { + if (add(e.next())) + modified = true; + } + return modified; + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + Iterator e = iterator(); + while (e.hasNext()) { + if (c.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } + + @Override + public boolean retainAll(Collection c) { + boolean modified = false; + Iterator e = iterator(); + while (e.hasNext()) { + if (!c.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } + + @Override + public void clear() { + while (poll() != null) + ; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/CallBackHandler.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/CallBackHandler.java new file mode 100644 index 000000000..d4a1dc76d --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/CallBackHandler.java @@ -0,0 +1,6 @@ +package io.mycat.backend.mysql.nio.handler.util; + + +public interface CallBackHandler { + void call() throws Exception; +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/HandlerTool.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/HandlerTool.java new file mode 100644 index 000000000..60492abfd --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/HandlerTool.java @@ -0,0 +1,339 @@ +package io.mycat.backend.mysql.nio.handler.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; + +import io.mycat.backend.mysql.nio.handler.builder.sqlvisitor.MysqlVisitor; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler; +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler.HandlerType; +import io.mycat.config.ErrorCode; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.field.FieldUtil; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.ItemRef; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; + +public class HandlerTool { + // 两端是单引号,并且中间不允许出现单引号 + // private static Pattern pat = Pattern.compile("^\'([^\']*?)\'$"); + + /** + * 停止以node为根节点的handler树 + * + * @param node + */ + public static void terminateHandlerTree(final DMLResponseHandler node) { + try { + if (node == null) + return; + Set merges = node.getMerges(); + for (DMLResponseHandler merge : merges) { + DMLResponseHandler currentHandler = merge; + while (currentHandler != node) { + currentHandler.terminate(); + currentHandler = currentHandler.getNextHandler(); + } + } + node.terminate(); + } catch (Exception e) { + Logger.getLogger(HandlerTool.class).error("terminate node exception:", e); + } + } + +// public static byte[] getEofBytes(MySQLConnection conn) { +// EOFPacket eof = new EOFPacket(); +// return eof.toByteBuffer(conn.getCharset()).array(); +// } + + public static Field createField(FieldPacket fp) { + Field field = Field.getFieldItem(fp.name, fp.table, fp.type, fp.charsetIndex, (int) fp.length, fp.decimals, + fp.flags); + return field; + } + + public static List createFields(List fps) { + List ret = new ArrayList(); + for (FieldPacket fp : fps) { + Field field = createField(fp); + ret.add(field); + } + return ret; + } + + /** + * 创建一个Item,并且Item内部的对象指向fields中的某个对象,当field的实际值改变时,Item的value也改变 + * + * @param sel + * @param fields + * @param type + * @return + */ + public static Item createItem(Item sel, List fields, int startIndex, boolean allPushDown, HandlerType type, + String charset) { + Item ret = null; + if (sel.basicConstItem()) + return sel; + switch (sel.type()) { + case FUNC_ITEM: + case COND_ITEM: + ItemFunc func = (ItemFunc) sel; + if (func.getPushDownName()==null ||func.getPushDownName().length()==0) { + // 自己计算 + ret = createFunctionItem(func, fields, startIndex, allPushDown, type, charset); + } else { + ret = createFieldItem(func, fields, startIndex); + } + break; + case SUM_FUNC_ITEM: + ItemSum sumFunc = (ItemSum) sel; + if (type != HandlerType.GROUPBY) { + ret = createFieldItem(sumFunc, fields, startIndex); + } else if (sumFunc.getPushDownName()==null ||sumFunc.getPushDownName().length()==0) { + ret = createSumItem(sumFunc, fields, startIndex, allPushDown, type, charset); + } else { + ret = createPushDownGroupBy(sumFunc, fields, startIndex); + } + break; + default: + ret = createFieldItem(sel, fields, startIndex); + } + if (ret == null) + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "item not found:" + sel); + if (ret.getItemName() == null) + ret.setItemName(sel.getPushDownName() == null ? sel.getItemName() : sel.getPushDownName()); + ret.fixFields(); + return ret; + } + + public static Item createRefItem(Item ref, String tbAlias, String fieldAlias) { + return new ItemRef(ref, tbAlias, fieldAlias); + } + + /** + * 将field进行复制 + * + * @param fields + * @param bs + */ + public static void initFields(List fields, List bs) { + FieldUtil.initFields(fields, bs); + } + + public static List getItemListBytes(List items) { + List ret = new ArrayList(); + for (Item item : items) { + byte[] b = item.getRowPacketByte(); + ret.add(b); + } + return ret; + } + + public static ItemField createItemField(FieldPacket fp) { + Field field = createField(fp); + return new ItemField(field); + } + + /* + * ------------------------------- helper methods ------------------------ + */ + /** + * 计算下发的聚合函数 1.count(id) 下发count(id) 之后 count(id) = sum[count(id) 0...n]; + * 2.sum(id) sum(id) = sum[sum(id) 0...n]; 3.avg(id) avg(id) = sum[sum(id) + * 0...n]/sum[count(id) 0...n]; + * + * @param sumfun + * 聚合函数的名称 + * @param fields + * 当前所有行的fields信息 + * @return + */ + protected static Item createPushDownGroupBy(ItemSum sumfun, List fields, int startIndex) { + String funName = sumfun.funcName().toUpperCase(); + String colName = sumfun.getItemName(); + String pdName = sumfun.getPushDownName(); + Item ret = null; + List args = new ArrayList(); + if (funName.equalsIgnoreCase("AVG")) { + String colNameSum = colName.replace(funName + "(", "SUM("); + String colNameCount = colName.replace(funName + "(", "COUNT("); + Item sumfunSum = new ItemField(null, null, colNameSum); + sumfunSum.setPushDownName( + pdName.replace(MysqlVisitor.getMadeAggAlias(funName), MysqlVisitor.getMadeAggAlias("SUM"))); + Item sumfunCount = new ItemField(null, null, colNameCount); + sumfunCount.setPushDownName( + pdName.replace(MysqlVisitor.getMadeAggAlias(funName), MysqlVisitor.getMadeAggAlias("COUNT"))); + Item itemSum = createFieldItem(sumfunSum, fields, startIndex); + Item itemCount = createFieldItem(sumfunCount, fields, startIndex); + args.add(itemSum); + args.add(itemCount); + } else if (funName.equalsIgnoreCase("STD") || funName.equalsIgnoreCase("STDDEV_POP") + || funName.equalsIgnoreCase("STDDEV_SAMP") || funName.equalsIgnoreCase("STDDEV") + || funName.equalsIgnoreCase("VAR_POP") || funName.equalsIgnoreCase("VAR_SAMP") + || funName.equalsIgnoreCase("VARIANCE")) { + // variance:下发时 v[0]:count,v[1]:sum,v[2]:variance(局部) + String colNameCount = colName.replace(funName + "(", "COUNT("); + String colNameSum = colName.replace(funName + "(", "SUM("); + String colNameVar = colName.replace(funName + "(", "VARIANCE("); + Item sumfunCount = new ItemField(null, null, colNameCount); + sumfunCount.setPushDownName( + pdName.replace(MysqlVisitor.getMadeAggAlias(funName), MysqlVisitor.getMadeAggAlias("COUNT"))); + Item sumfunSum = new ItemField(null, null, colNameSum); + sumfunSum.setPushDownName( + pdName.replace(MysqlVisitor.getMadeAggAlias(funName), MysqlVisitor.getMadeAggAlias("SUM"))); + Item sumfunVar = new ItemField(null, null, colNameVar); + sumfunVar.setPushDownName( + pdName.replace(MysqlVisitor.getMadeAggAlias(funName), MysqlVisitor.getMadeAggAlias("VARIANCE"))); + Item itemCount = createFieldItem(sumfunCount, fields, startIndex); + Item itemSum = createFieldItem(sumfunSum, fields, startIndex); + Item itemVar = createFieldItem(sumfunVar, fields, startIndex); + args.add(itemCount); + args.add(itemSum); + args.add(itemVar); + } else { + Item subItem = createFieldItem(sumfun, fields, startIndex); + args.add(subItem); + } + ret = sumfun.reStruct(args, true, fields); + ret.setItemName(sumfun.getPushDownName() == null ? sumfun.getItemName() : sumfun.getPushDownName()); + return ret; + } + + protected static ItemFunc createFunctionItem(ItemFunc f, List fields, int startIndex, boolean allPushDown, + HandlerType type, String charset) { + ItemFunc ret = null; + List args = new ArrayList(); + for (int index = 0; index < f.getArgCount(); index++) { + Item arg = f.arguments().get(index); + Item newArg = null; + if (arg.isWild()) + newArg = new ItemInt(0); + else + newArg = createItem(arg, fields, startIndex, allPushDown, type, charset); + if (newArg == null) + throw new RuntimeException("Function argument not found:" + arg); + args.add(newArg); + } + ret = (ItemFunc) f.reStruct(args, allPushDown, fields); + ret.setItemName(f.getPushDownName() == null ? f.getItemName() : f.getPushDownName()); + return ret; + } + + /** + * @param func + * @param fields + * @param startIndex + * @param allPushDown + * @param type + * @param charset + * @return + */ + private static ItemSum createSumItem(ItemSum f, List fields, int startIndex, boolean allPushDown, + HandlerType type, String charset) { + ItemSum ret = null; + List args = new ArrayList(); + for (int index = 0; index < f.getArgCount(); index++) { + Item arg = f.arguments().get(index); + Item newArg = null; + if (arg.isWild()) + newArg = new ItemInt(0); + else + newArg = createItem(arg, fields, startIndex, allPushDown, type, charset); + if (newArg == null) + throw new RuntimeException("Function argument not found:" + arg); + args.add(newArg); + } + ret = (ItemSum) f.reStruct(args, allPushDown, fields); + ret.setItemName(f.getPushDownName() == null ? f.getItemName() : f.getPushDownName()); + return ret; + } + + /** + * 查出col对应的field,所有的col对象不管是函数还是非函数均当做普通列处理,直接比较他们的表名和列名 + * + * @param col + * @param fields + * @return + */ + protected static ItemField createFieldItem(Item col, List fields, int startIndex) { + int index = findField(col, fields, startIndex); + if (index < 0) + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "field not found:" + col); + ItemField ret = new ItemField(fields.get(index)); + ret.setItemName(col.getPushDownName() == null ? col.getItemName() : col.getPushDownName()); + return ret; + } + + /** + * 查找sel在fields中的对应,包含start, + * + * @param sel + * @param fields + * @param startIndex + * @param endIndex + * @return + */ + public static int findField(Item sel, List fields, int startIndex) { + String selName = (sel.getPushDownName() == null ? sel.getItemName() : sel.getPushDownName()); + selName = selName.trim(); + String tableName = sel.getTableName(); + for (int index = startIndex; index < fields.size(); index++) { + Field field = fields.get(index); + // ''下发之后field.name==null + String colName2 = field.name == null ? null : field.name.trim(); + String tableName2 = field.table; + if (sel instanceof ItemField && !((tableName==null && tableName2==null)||tableName.equals(tableName2))) + continue; + if (selName.equalsIgnoreCase(colName2)) + return index; + } + return -1; + } + + /** + * 判断name是否是函数 + * + * @param name + * @param func + * @return + */ + public static boolean matchFunc(String name, String func) { + Pattern pt = Pattern.compile("^" + func + "\\(.*", Pattern.CASE_INSENSITIVE); + return pt.matcher(name).matches(); + } + + /** + * 根据distinct的列生成orderby + * + * @param sels + * @return + */ + public static List makeOrder(List sels) { + List orders = new ArrayList(); + for (Item sel : sels) { + Order order = new Order(sel, SQLOrderingSpecification.ASC); + orders.add(order); + } + return orders; + } + + // @bug 1086 + public static boolean needSendNoRow(List groupBys) { + if (groupBys == null || groupBys.size() == 0) { + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/HeapItem.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/HeapItem.java new file mode 100644 index 000000000..3a7f45732 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/HeapItem.java @@ -0,0 +1,42 @@ +package io.mycat.backend.mysql.nio.handler.util; + +import io.mycat.backend.mysql.nio.MySQLConnection; +import io.mycat.net.mysql.RowDataPacket; + +public class HeapItem { + private byte[] row; + private RowDataPacket rowPacket; + private MySQLConnection hashIndex; + private boolean isNullItem = false; + + public static HeapItem NULLITEM() { + HeapItem NULLITEM = new HeapItem(null, null, null); + NULLITEM.isNullItem = true; + return NULLITEM; + } + + public boolean IsNullItem() { + if (row == null && isNullItem == true) + return true; + return false; + } + + public HeapItem(byte[] row, RowDataPacket rdp, MySQLConnection index) { + this.row = row; + this.rowPacket = rdp; + this.hashIndex = index; + } + + public MySQLConnection getIndex() { + return hashIndex; + } + + public byte[] getRowData() { + return row; + } + + public RowDataPacket getRowPacket() { + return this.rowPacket; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/RBTMinHeap.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/RBTMinHeap.java new file mode 100644 index 000000000..cb52030a0 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/RBTMinHeap.java @@ -0,0 +1,541 @@ +package io.mycat.backend.mysql.nio.handler.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import io.mycat.util.MinHeap; + +@SuppressWarnings("unchecked") +public class RBTMinHeap implements MinHeap { + + private static final boolean RED = false; + private static final boolean BLACK = true; + + private RBTNode root; + private int size = 0; + private Comparator comparator; + + public RBTMinHeap(Comparator comparator) { + this.comparator = comparator; + } + + public E find(E e) { + RBTNode node = search(e); + if (node == null) + return null; + return node.value; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return search((E) o) == null; + } + + private RBTNode search(E e) { + RBTNode t = root; + if (t == null) + return t; + while (t != null) { + int cmp = comparator.compare(e, t.value); + if (cmp < 0) + t = t.left; + else if (cmp > 0) + t = t.right; + else + return t; + } + return t; + + } + + @Override + public Iterator iterator() { + throw new RuntimeException("unsupport iterator in RBTMinHeap"); + } + + @Override + public Object[] toArray() { + Object[] obj = inOrder(); + return Arrays.copyOf(obj, size); + } + + private void inOrder(RBTNode node, List list) { + if (node != null) { + inOrder(node.left, list); + list.add(node.getValue()); + inOrder(node.right, list); + } + } + + private Object[] inOrder() { + List list = new ArrayList(size); + inOrder(root, list); + return list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + Object[] obj = inOrder(); + if (a.length < size) + // Make a new array of a's runtime type, but my contents: + return (T[]) Arrays.copyOf(obj, size, a.getClass()); + System.arraycopy(obj, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + + @Override + public boolean add(E e) { + size++; + RBTNode node = new RBTNode(BLACK, e); + insert(node); + return true; + } + + private void insert(RBTNode node) { + if (root == null) { + root = node; + return; + } + + int cmp; + RBTNode x = this.root; + RBTNode parent; + do { + parent = x; + cmp = comparator.compare(node.getValue(), x.getValue()); + if (cmp < 0) + x = x.left; + else + x = x.right; + } while (x != null); + node.parent = parent; + if (cmp < 0) + parent.left = node; + else + parent.right = node; + + fixAfterInsertion(node); + } + + private void fixAfterInsertion(RBTNode x) { + x.color = RED; + while (x != null && x != root && x.parent.color == RED) { + // parent is grandparent's left child + if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { + + RBTNode y = rightOf(parentOf(parentOf(x))); + // uncle's color is red + if (colorOf(y) == RED) { + setColor(parentOf(x), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(x)), RED); + x = parentOf(parentOf(x)); + } else { + if (x == rightOf(parentOf(x))) { + x = parentOf(x); + rotateLeft(x); + } + setColor(parentOf(x), BLACK); + setColor(parentOf(parentOf(x)), RED); + rotateRight(parentOf(parentOf(x))); + } + } else { + RBTNode y = leftOf(parentOf(parentOf(x))); + if (colorOf(y) == RED) { + setColor(parentOf(x), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(x)), RED); + x = parentOf(parentOf(x)); + } else { + if (x == leftOf(parentOf(x))) { + x = parentOf(x); + rotateRight(x); + } + setColor(parentOf(x), BLACK); + setColor(parentOf(parentOf(x)), RED); + rotateLeft(parentOf(parentOf(x))); + } + } + } + root.color = BLACK; + } + + /** + *
+	 * 
+	 *      px                              px
+	 *     /                               /
+	 *    x                               y                
+	 *   /  \      --(rotate left)-.     / \                #
+	 *  lx   y                          x  ry     
+	 *     /   \                       /  \
+	 *    ly   ry                     lx  ly
+	 * 
+ * + * @param p + */ + private void rotateLeft(RBTNode p) { + if (p != null) { + RBTNode r = p.right; + p.right = r.left; + if (r.left != null) + r.left.parent = p; + r.parent = p.parent; + if (p.parent == null) + root = r; + else if (p.parent.left == p) + p.parent.left = r; + else + p.parent.right = r; + r.left = p; + p.parent = r; + } + } + + /** + *
+	 * 
+	 *            py                               py
+	 *           /                                /
+	 *          y                                x                  
+	 *         /  \      --(rotate right)-.     /  \                     #
+	 *        x   ry                           lx   y  
+	 *       / \                                   / \                   #
+	 *      lx  rx                                rx  ry
+	 * 
+ * + * @param p + */ + private void rotateRight(RBTNode p) { + if (p != null) { + RBTNode l = p.left; + p.left = l.right; + if (l.right != null) + l.right.parent = p; + l.parent = p.parent; + if (p.parent == null) + root = l; + else if (p.parent.right == p) + p.parent.right = l; + else + p.parent.left = l; + l.right = p; + p.parent = l; + } + } + + private boolean colorOf(RBTNode node) { + return (node == null ? BLACK : node.color); + } + + private RBTNode parentOf(RBTNode node) { + return (node == null ? null : node.parent); + } + + private void setColor(RBTNode node, boolean c) { + if (node != null) + node.color = c; + } + + private RBTNode leftOf(RBTNode node) { + return (node == null) ? null : node.left; + } + + private RBTNode rightOf(RBTNode node) { + return (node == null) ? null : node.right; + } + + /** + * unused current version + */ + @Override + public E peak() { + RBTNode minNode = findMin(root); + if (minNode == null) + return null; + E e = minNode.value; + return e; + } + + /** + * need optimizer, unused current version + */ + @Override + public void replaceTop(E e) { + // find minNode + RBTNode minNode = findMin(root); + if (minNode == null) + return; + // delete minNode + delete(minNode); + // add minNode + RBTNode node = new RBTNode(BLACK, e); + insert(node); + } + + @Override + public E poll() { + RBTNode minNode = findMin(root); + if (minNode == null) + return null; + size--; + E e = minNode.value; + delete(minNode); + return e; + } + + private RBTNode findMin(RBTNode node) { + if (node == null) + return null; + while (node.left != null) { + node = node.left; + } + return node; + } + + @Override + public boolean remove(Object o) { + size--; + RBTNode node = search((E) o); + if (node != null) + delete(node); + return true; + } + + /** + * find the minimum node which value >= t.value + * + * @param t + * @return + */ + private RBTNode successor(RBTNode t) { + if (t == null) + return null; + + if (t.right != null) { + RBTNode p = t.right; + while (p.left != null) + p = p.left; + return p; + } + + RBTNode p = t.parent; + RBTNode ch = t; + // only child is parent's left node can return parent + while (p != null && ch == p.right) { + ch = p; + p = p.parent; + } + return p; + } + + private void delete(RBTNode node) { + // If strictly internal, copy successor's element to node and then make + // p + // point to successor. + if (node.left != null && node.right != null) { + RBTNode s = successor(node); + node.value = s.value; + node = s; + } // node has 2 children + + // Start fixup at replacement node, if it exists. + RBTNode replacement = (node.left != null ? node.left : node.right); + + if (replacement != null) { + // Link replacement to parent + replacement.parent = node.parent; + if (node.parent == null) + root = replacement; + else if (node == node.parent.left) + node.parent.left = replacement; + else + node.parent.right = replacement; + + // Null out links so they are OK to use by fixAfterDeletion. + node.left = node.right = node.parent = null; + + // Fix replacement + if (node.color == BLACK) + fixAfterDeletion(replacement); + } else if (node.parent == null) { // return if we are the only node. + root = null; + } else { // No children. Use self as phantom replacement and unlink. + if (node.color == BLACK) + fixAfterDeletion(node); + + if (node.parent != null) { + if (node == node.parent.left) + node.parent.left = null; + else if (node == node.parent.right) + node.parent.right = null; + node.parent = null; + } + } + } + + private void fixAfterDeletion(RBTNode x) { + while (x != root && colorOf(x) == BLACK) { + if (x == leftOf(parentOf(x))) { + RBTNode sib = rightOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateLeft(parentOf(x)); + sib = rightOf(parentOf(x)); + } + + if (colorOf(leftOf(sib)) == BLACK + && colorOf(rightOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(rightOf(sib)) == BLACK) { + setColor(leftOf(sib), BLACK); + setColor(sib, RED); + rotateRight(sib); + sib = rightOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(rightOf(sib), BLACK); + rotateLeft(parentOf(x)); + x = root; + } + } else { // symmetric + RBTNode sib = leftOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateRight(parentOf(x)); + sib = leftOf(parentOf(x)); + } + + if (colorOf(rightOf(sib)) == BLACK + && colorOf(leftOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(leftOf(sib)) == BLACK) { + setColor(rightOf(sib), BLACK); + setColor(sib, RED); + rotateLeft(sib); + sib = leftOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(leftOf(sib), BLACK); + rotateRight(parentOf(x)); + x = root; + } + } + } + + setColor(x, BLACK); + } + + @Override + public boolean containsAll(Collection c) { + Iterator e = c.iterator(); + while (e.hasNext()) + if (!contains(e.next())) + return false; + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean modified = false; + Iterator e = c.iterator(); + while (e.hasNext()) { + if (add(e.next())) + modified = true; + } + return modified; + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + Iterator e = iterator(); + while (e.hasNext()) { + if (c.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } + + @Override + public boolean retainAll(Collection c) { + boolean modified = false; + Iterator e = iterator(); + while (e.hasNext()) { + if (!c.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } + + @Override + public void clear() { + destory(root); + root = null; + size = 0; + } + + private void destory(RBTNode node) { + if (node == null) + return; + + if (node.left != null) { + destory(node.left); + node.left = null; + } + if (node.right != null) { + destory(node.right); + node.right = null; + } + node.parent = null; + node.value = null; + } + + static class RBTNode { + private boolean color; + private E value; + private RBTNode left; + private RBTNode right; + private RBTNode parent; + + public RBTNode(boolean color, E value) { + this.color = color; + this.value = value; + } + + public E getValue() { + return value; + } + + } +} diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/RowDataComparator.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/RowDataComparator.java new file mode 100644 index 000000000..c2b49b32b --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/RowDataComparator.java @@ -0,0 +1,112 @@ +package io.mycat.backend.mysql.nio.handler.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler.HandlerType; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + +/** + * 根据OrderBy的item list进行行数据排序的比较器 + * + * + */ +public class RowDataComparator implements Comparator { + + private List sourceFields; + private List cmpItems; + + private List cmpFields; + private List ascs; + + // only for test unit + public RowDataComparator(List sourceFields, List cmpItems, List cmpFields, List ascs, + String charset) { + this.sourceFields = sourceFields; + this.cmpItems = cmpItems; + this.cmpFields = cmpFields; + this.ascs = ascs; + } + + public RowDataComparator(List fps, List orders, boolean allPushDown, HandlerType type, + String charset) { + sourceFields = HandlerTool.createFields(fps); + if (orders != null && orders.size() > 0) { + ascs = new ArrayList(); + cmpFields = new ArrayList(); + cmpItems = new ArrayList(); + for (Order order : orders) { + Item cmpItem = HandlerTool.createItem(order.getItem(), sourceFields, 0, allPushDown, type, charset); + cmpItems.add(cmpItem); + FieldPacket tmpFp = new FieldPacket(); + cmpItem.makeField(tmpFp); + Field cmpField = HandlerTool.createField(tmpFp); + cmpFields.add(cmpField); + ascs.add(order.getSortOrder() == SQLOrderingSpecification.ASC ? true : false); + } + } + } + + public int getSourceFieldCount() { + return sourceFields.size(); + } + + public void sort(List rows) { + Comparator c = new Comparator() { + + @Override + public int compare(RowDataPacket o1, RowDataPacket o2) { + if (RowDataComparator.this.ascs != null && RowDataComparator.this.ascs.size() > 0) + return RowDataComparator.this.compare(o1, o2); + else + // 无须比较,按照原始的数据输出 + return -1; + } + }; + Collections.sort(rows, c); + } + + @Override + /** + * 传递进来的是源生行的row数据 + */ + public int compare(RowDataPacket o1, RowDataPacket o2) { + if (this.ascs != null && this.ascs.size() > 0) { + int cmpValue = cmp(o1, o2, 0); + return cmpValue; + } else + // 无须比较,按照原始的数据输出 + return 0; + } + + private int cmp(RowDataPacket o1, RowDataPacket o2, int index) { + HandlerTool.initFields(sourceFields, o1.fieldValues); + List bo1 = HandlerTool.getItemListBytes(cmpItems); + HandlerTool.initFields(sourceFields, o2.fieldValues); + List bo2 = HandlerTool.getItemListBytes(cmpItems); + boolean isAsc = ascs.get(index); + Field field = cmpFields.get(index); + byte[] b1 = bo1.get(index); + byte[] b2 = bo2.get(index); + int rs; + if (isAsc) { + rs = field.compare(b1, b2); + } else { + rs = field.compare(b2, b1); + } + if (rs != 0 || cmpFields.size() == (index + 1)) { + return rs; + } else { + return cmp(o1, o2, index + 1); + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/nio/handler/util/TwoTableComparator.java b/src/main/java/io/mycat/backend/mysql/nio/handler/util/TwoTableComparator.java new file mode 100644 index 000000000..940034a52 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/nio/handler/util/TwoTableComparator.java @@ -0,0 +1,89 @@ +package io.mycat.backend.mysql.nio.handler.util; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler.HandlerType; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.cmpfunc.util.ArgComparator; + +/** + * _2TableComparator和RowDataComparator的区别在于,join比较的两列有可能是不同的类型,比如一个是整数, 一个是字符串等等 + * + * @author chenzifei + * + */ +public class TwoTableComparator implements Comparator { + + /* 用来存放左右原始数据值的容器,item计算前更新 */ + private List leftFields; + private List rightFields; + /* 左右compare的item对象 */ + private List leftCmpItems; + private List rightCmpItems; + /* 左右排序规则,必定相同,所以只保留一份 */ + private List cmptors; + private List ascs; + private HandlerType type; + private boolean isAllPushDown; + + public TwoTableComparator(List fps1, List fps2, List leftOrders, + List rightOrders, boolean isAllPushDown, HandlerType type, String charset) { + this.isAllPushDown = isAllPushDown; + this.type = type; + this.leftFields = HandlerTool.createFields(fps1); + this.rightFields = HandlerTool.createFields(fps2); + ascs = new ArrayList(); + for (Order order : leftOrders) { + ascs.add(order.getSortOrder() == SQLOrderingSpecification.ASC ? true : false); + } + leftCmpItems = new ArrayList(); + rightCmpItems = new ArrayList(); + cmptors = new ArrayList(); + for (int index = 0; index < ascs.size(); index++) { + Order leftOrder = leftOrders.get(index); + Order rightOrder = rightOrders.get(index); + Item leftCmpItem = HandlerTool.createItem(leftOrder.getItem(), leftFields, 0, this.isAllPushDown, this.type, + charset); + leftCmpItems.add(leftCmpItem); + Item rightCmpItem = HandlerTool.createItem(rightOrder.getItem(), rightFields, 0, this.isAllPushDown, + this.type, charset); + rightCmpItems.add(rightCmpItem); + ArgComparator cmptor = new ArgComparator(leftCmpItem, rightCmpItem); + cmptor.setCmpFunc(null, leftCmpItem, rightCmpItem, false); + cmptors.add(cmptor); + } + } + + @Override + public int compare(RowDataPacket o1, RowDataPacket o2) { + if (ascs == null || ascs.size() == 0) // no join column, all same + return 0; + return compareRecursion(o1, o2, 0); + } + + private int compareRecursion(RowDataPacket o1, RowDataPacket o2, int i) { + HandlerTool.initFields(leftFields, o1.fieldValues); + HandlerTool.initFields(rightFields, o2.fieldValues); + ArgComparator cmptor = cmptors.get(i); + boolean isAsc = ascs.get(i); + int rs; + if (isAsc) { + rs = cmptor.compare(); + } else { + rs = -cmptor.compare(); + } + if (rs != 0 || ascs.size() == (i + 1)) { + return rs; + } else { + return compareRecursion(o1, o2, i + 1); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/mycat/backend/mysql/store/DistinctLocalResult.java b/src/main/java/io/mycat/backend/mysql/store/DistinctLocalResult.java new file mode 100644 index 000000000..1673c5bce --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/DistinctLocalResult.java @@ -0,0 +1,74 @@ +package io.mycat.backend.mysql.store; + +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.diskbuffer.DistinctResultDiskBuffer; +import io.mycat.backend.mysql.store.result.ResultExternal; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.util.RBTreeList; + +/** + * localresult to distinct input rows + * + * @author chenzifei + * + */ +public class DistinctLocalResult extends LocalResult { + + private RowDataComparator distinctCmp; + + /** + * + * @param initialCapacity + * @param fieldsCount + * @param pool + * @param rowcmp + * distinct selectable compator + */ + public DistinctLocalResult(int initialCapacity, int fieldsCount, BufferPool pool, RowDataComparator distinctCmp, + String charset) { + super(initialCapacity, fieldsCount, pool, charset); + this.distinctCmp = distinctCmp; + this.rows = new RBTreeList(initialCapacity, distinctCmp); + } + + public DistinctLocalResult(BufferPool pool, int fieldsCount, RowDataComparator distinctCmp, String charset) { + this(DEFAULT_INITIAL_CAPACITY, fieldsCount, pool, distinctCmp, charset); + } + + @Override + protected ResultExternal makeExternal() { + return new DistinctResultDiskBuffer(pool, fieldsCount, distinctCmp, charset); + } + + /** + * add a row into distinct localresult,if rows.contains(row),do not add + * + * @param row + */ + @Override + public void add(RowDataPacket row) { + lock.lock(); + try { + if (isClosed) + return; + int index = rows.indexOf(row); + if (index >= 0) + return; + super.add(row); + } finally { + lock.unlock(); + } + } + + @Override + protected void doneOnlyMemory() { + // Collections.sort(rows, this.distinctCmp); + } + + @Override + protected void beforeFlushRows() { + // rbtree.toarray() is sorted,so do not need to sort again + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/FileCounter.java b/src/main/java/io/mycat/backend/mysql/store/FileCounter.java new file mode 100644 index 000000000..bd966aa62 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/FileCounter.java @@ -0,0 +1,56 @@ +package io.mycat.backend.mysql.store; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.memory.environment.Hardware; + +public class FileCounter { + private static final Logger logger = Logger.getLogger(FileCounter.class); + private static FileCounter fileCounter = new FileCounter(); + + private final Lock lock; + private final int maxFileSize; + private int currentNum; + + private FileCounter() { + this.lock = new ReentrantLock(); + long totalMem = Hardware.getSizeOfPhysicalMemory(); + long freeMem = Hardware.getFreeSizeOfPhysicalMemoryForLinux(); + long currentMem = Math.min(totalMem / 2, freeMem); + this.maxFileSize = (int)(currentMem / (MycatServer.getInstance().getConfig().getSystem().getMappedFileSize() / 1024)); + logger.info("current mem is " + currentMem + "kb. max file size is " + maxFileSize); + this.currentNum = 0; + } + + public static FileCounter getInstance() { + return fileCounter; + } + + public boolean increament() { + lock.lock(); + try { + if (this.currentNum >= maxFileSize) + return false; + this.currentNum++; + return true; + } finally { + lock.unlock(); + } + } + + public boolean decrement() { + lock.lock(); + try { + if (this.currentNum <= 0) + return false; + this.currentNum--; + return true; + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/FileStore.java b/src/main/java/io/mycat/backend/mysql/store/FileStore.java new file mode 100644 index 000000000..2dcf752ec --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/FileStore.java @@ -0,0 +1,386 @@ +package io.mycat.backend.mysql.store; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import io.mycat.backend.mysql.store.fs.FilePath; +import io.mycat.backend.mysql.store.fs.FileUtils; +import io.mycat.config.ErrorCode; +import io.mycat.util.exception.TmpFileException; + +public class FileStore { + private static final String SUFFIX_TEMP_FILE = ".temp.db"; + private static Logger logger = Logger.getLogger(FileStore.class); + + /** + * The file path name. + */ + protected String name; + + private List fileNames; + private List files; + private long filePos; + private long fileLength; + private final String mode; + private final int mappedFileSize; + private List locks; + + /** + * Create a new file using the given settings. + * + * @param handler + * the callback object + * @param name + * the file name + * @param mode + * the access mode ("r", "rw", "rws", "rwd") + */ + public FileStore(String name, String mode) { + this.name = name; + this.fileNames = new ArrayList(); + this.files = new ArrayList(); + this.locks = new ArrayList(); + this.mode = mode; + this.mappedFileSize = MycatServer.getInstance().getConfig().getSystem().getMappedFileSize(); + try { + createFile(); + } catch (IOException e) { + throw TmpFileException.get(ErrorCode.ER_FILE_INIT, e, name); + } + } + + /** + * Close the file. + */ + public void close() { + if (!this.files.isEmpty()) { + for (FileChannel file : this.files) { + try { + file.close(); + } catch (IOException e) { + logger.warn("close file error :", e); + } finally { + // QUESTION_TODO if IOException,memory is release or not + FileCounter.getInstance().decrement(); + } + } + this.files.clear(); + } + } + + /** + * Close the file without throwing any exceptions. Exceptions are simply + * ignored. + */ + public void closeSilently() { + try { + close(); + } catch (Exception e) { + // ignore + } + } + + public void delete() { + if (!this.fileNames.isEmpty()) { + try { + for (int i = 0; i < this.fileNames.size(); i++) { + String fileName = fileNames.get(i); + FileUtils.tryDelete(fileName); + } + } finally { + this.fileNames.clear(); + } + } + } + + /** + * Close the file (ignoring exceptions) and delete the file. + */ + public void closeAndDeleteSilently() { + if (!this.files.isEmpty()) { + closeSilently(); + delete(); + } + name = null; + } + + /** + * Read a number of bytes without decrypting. + * + * @param b + * the target buffer + * @param off + * the offset + * @param len + * the number of bytes to read + */ + protected void readFullyDirect(byte[] b, int off, int len) { + readFully(b, off, len); + } + + /** + * Read a number of bytes. + * + * @param b + * the target buffer + * @param off + * the offset + * @param len + * the number of bytes to read + */ + public void readFully(byte[] b, int off, int len) { + ByteBuffer buffer = ByteBuffer.wrap(b, off, len); + read(buffer); + } + + private int read(ByteBuffer buffer) { + int len = 0; + try { + do { + int index = (int) (filePos / mappedFileSize); + long offset = filePos % mappedFileSize; + if (index > files.size() - 1) + throw TmpFileException.get(ErrorCode.ER_FILE_READ, name); + files.get(index).position(offset); + int r = files.get(index).read(buffer); + len += r; + filePos += r; + if (filePos >= fileLength - 1) + break; + } while (buffer.hasRemaining()); + } catch (IOException e) { + throw TmpFileException.get(ErrorCode.ER_FILE_READ, e, name); + } + return len; + } + + public int read(ByteBuffer buffer, long endPos) { + long remained = endPos - filePos; + if (remained <= 0) + return 0; + + if (remained < buffer.remaining()) { + int newLimit = (int) (buffer.position() + remained); + buffer.limit(newLimit); + } + return read(buffer); + } + + /** + * Go to the specified file location. + * + * @param pos + * the location + */ + public void seek(long pos) { + filePos = pos; + } + + /** + * Write a number of bytes without encrypting. + * + * @param b + * the source buffer + * @param off + * the offset + * @param len + * the number of bytes to write + */ + protected void writeDirect(byte[] b, int off, int len) { + write(b, off, len); + } + + /** + * Write a number of bytes. + * + * @param b + * the source buffer + * @param off + * the offset + * @param len + * the number of bytes to write + */ + public void write(byte[] b, int off, int len) { + write(ByteBuffer.wrap(b, off, len)); + } + + public void write(ByteBuffer buffer) { + try { + do { + int index = (int) (filePos / mappedFileSize); + if (index > files.size() - 1) { + createFile(); + } + long offset = filePos % mappedFileSize; + files.get(index).position(offset); + int w = files.get(index).write(buffer); + filePos += w; + } while (buffer.remaining() > 0); + } catch (IOException e) { + closeFileSilently(); + throw TmpFileException.get(ErrorCode.ER_FILE_WRITE, e, name); + } + fileLength = Math.max(filePos, fileLength); + } + + private void createFile() throws IOException { + String newName = name; + int index = newName.indexOf(':'); + String scheme = newName.substring(0, index); + if (!FileCounter.getInstance().increament() && "nioMapped".equals(scheme)) { + newName = "nio:AresDisk"; + } + try { + FilePath path = FilePath.get(newName).createTempFile(SUFFIX_TEMP_FILE, true); + this.fileNames.add(path.toString()); + this.files.add(path.open(mode)); + } catch (IOException e) { + if (e.getCause() instanceof OutOfMemoryError) { + logger.info("no memory to mapped file,change to disk file"); + // memory is used by other user + FileCounter.getInstance().decrement(); + newName = "nio:AresDisk"; + FilePath path = FilePath.get(newName).createTempFile(SUFFIX_TEMP_FILE, true); + this.files.add(path.open(mode)); + this.fileNames.add(path.toString()); + } else { + logger.warn("create file error :", e); + throw e; + } + } + } + + /** + * Get the file size in bytes. + * + * @return the file size + */ + public long length() { + return fileLength; + } + + /** + * Get the current location of the file pointer. + * + * @return the location + */ + public long getFilePointer() { + return filePos; + } + + public void force(boolean metaData) { + try { + for (FileChannel file : this.files) { + file.force(metaData); + } + } catch (IOException e) { + closeFileSilently(); + throw TmpFileException.get(ErrorCode.ER_FILE_FORCE, e, name); + } + } + + /** + * Call fsync. Depending on the operating system and hardware, this may or + * may not in fact write the changes. + */ + public void sync() { + try { + for (FileChannel file : this.files) { + file.force(true); + } + } catch (IOException e) { + closeFileSilently(); + throw TmpFileException.get(ErrorCode.ER_FILE_SYNC, e, name); + } + } + + /** + * Close the file. + */ + public void closeFile() throws IOException { + for (FileChannel file : this.files) { + file.close(); + } + } + + /** + * Just close the file, without setting the reference to null. This method + * is called when writing failed. The reference is not set to null so that + * there are no NullPointerExceptions later on. + */ + private void closeFileSilently() { + try { + closeFile(); + } catch (IOException e) { + // ignore + } + } + + /** + * Re-open the file. The file pointer will be reset to the previous + * location. + */ + public void openFile() throws IOException { + if (this.files.isEmpty()) { + for (String fileName : fileNames) { + this.files.add(FilePath.get(fileName).open(mode)); + } + } + } + + /** + * Try to lock the file. + * + * @return true if successful + */ + public synchronized boolean tryLock() { + boolean isLocked = true; + try { + for (FileChannel file : this.files) { + FileLock lock = file.tryLock(); + if (lock == null) { + isLocked = false; + break; + } + locks.add(lock); + } + } catch (Exception e) { + // ignore OverlappingFileLockException + return false; + } finally { + if (!isLocked) { + for (FileLock lock : locks) { + try { + lock.release(); + } catch (IOException e) { + // ignore + } + } + } + } + return isLocked; + } + + /** + * Release the file lock. + */ + public synchronized void releaseLock() { + if (!files.isEmpty() && !locks.isEmpty()) { + for (FileLock lock : locks) { + try { + lock.release(); + } catch (IOException e) { + // ignore + } + } + locks.clear(); + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/GroupByLocalResult.java b/src/main/java/io/mycat/backend/mysql/store/GroupByLocalResult.java new file mode 100644 index 000000000..dd2688983 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/GroupByLocalResult.java @@ -0,0 +1,188 @@ +package io.mycat.backend.mysql.store; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler.HandlerType; +import io.mycat.backend.mysql.nio.handler.query.impl.groupby.directgroupby.DGRowPacket; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.diskbuffer.GroupResultDiskBuffer; +import io.mycat.backend.mysql.store.result.ResultExternal; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.function.sumfunc.Aggregator.AggregatorType; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.util.RBTreeList; + +/** + * groupby is some part like distinct,but it should group by some value when add + * a row + * + */ +public class GroupByLocalResult extends LocalResult { + + private RowDataComparator groupCmp; + /** + * the packets contains sums + */ + private List fieldPackets; + private List sumFunctions; + private boolean isAllPushDown; + /** + * store the origin row fields,(already contains the item_sum fields in + * rowpackets we should calculate the item_sums again when next() is + * called!) + */ + private final List fields; + private final List sums; + + /** + * + * @param pool + * @param fieldsCount + * fieldsCount contains sums + * @param groupCmp + * @param fieldPackets + * fieldPackets contains sums + * @param sumFunctions + * @param isAllPushDown + */ + public GroupByLocalResult(BufferPool pool, int fieldsCount, RowDataComparator groupCmp, + List fieldPackets, List sumFunctions, boolean isAllPushDown, String charset) { + this(DEFAULT_INITIAL_CAPACITY, fieldsCount, pool, groupCmp, fieldPackets, sumFunctions, isAllPushDown, charset); + } + + public GroupByLocalResult(int initialCapacity, int fieldsCount, BufferPool pool, RowDataComparator groupCmp, + List fieldPackets, List sumFunctions, boolean isAllPushDown, String charset) { + super(initialCapacity, fieldsCount, pool, charset); + this.groupCmp = groupCmp; + this.fieldPackets = fieldPackets; + this.sumFunctions = sumFunctions; + this.isAllPushDown = isAllPushDown; + this.rows = new RBTreeList(initialCapacity, groupCmp); + /* init item_sums */ + this.fields = HandlerTool.createFields(fieldPackets); + this.sums = new ArrayList(); + for (ItemSum sumFunc : sumFunctions) { + ItemSum sum = (ItemSum) (HandlerTool.createItem(sumFunc, this.fields, 0, this.isAllPushDown, + HandlerType.GROUPBY, charset)); + this.sums.add(sum); + } + prepare_sum_aggregators(this.sums, true); + } + + /* should group sumfunctions when find a row in rows */ + @Override + public void add(RowDataPacket row) { + lock.lock(); + try { + if (isClosed) + return; + int index = rows.indexOf(row); + int increSize = 0; + if (index >= 0)// find + { + RowDataPacket oldRow = rows.get(index); + int oldRowSizeBefore = getRowMemory(oldRow); + onFoundRow(oldRow, row); + int oldRowSizeAfter = getRowMemory(oldRow); + increSize = oldRowSizeAfter - oldRowSizeBefore; + } else { + onFirstGroupRow(row); + rows.add(row); + rowCount++; + increSize = getRowMemory(row); + } + currentMemory += increSize; + boolean needFlush = false; + if (bufferMC != null) { + if (bufferMC.addSize(increSize) != true) { + needFlush = true; + } + } + else if (!needFlush && currentMemory > maxMemory) { + needFlush = true; + } + if (needFlush) { + if (external == null) + external = makeExternal(); + addRowsToDisk(); + } + } finally { + lock.unlock(); + } + } + + @Override + protected ResultExternal makeExternal() { + return new GroupResultDiskBuffer(pool, fieldsCount, groupCmp, fieldPackets, sumFunctions, isAllPushDown, + charset); + } + + @Override + protected void doneOnlyMemory() { + Collections.sort(rows, this.groupCmp); + } + + @Override + protected void beforeFlushRows() { + // rbtree.toarray() is sorted,so do not need to sort again + } + + protected void onFoundRow(RowDataPacket oldRow, RowDataPacket row) { + // we need to calculate group by + init_sum_functions(this.sums, oldRow); + update_sum_func(this.sums, row); + for (int i = 0; i < this.sums.size(); i++) { + ItemSum sum = this.sums.get(i); + Object b = sum.getTransAggObj(); + int transSize = sum.getTransSize(); + ((DGRowPacket) oldRow).setSumTran(i, b, transSize); + } + } + + protected void onFirstGroupRow(RowDataPacket row) { + // we need to calculate group by + init_sum_functions(this.sums, row); + for (int i = 0; i < this.sums.size(); i++) { + ItemSum sum = this.sums.get(i); + Object b = sum.getTransAggObj(); + int transSize = sum.getTransSize(); + ((DGRowPacket) row).setSumTran(i, b, transSize); + } + } + + /** + * see Sql_executor.cc + * + * @return + */ + protected void prepare_sum_aggregators(List funcs, boolean need_distinct) { + for (ItemSum func : funcs) { + func.setAggregator(need_distinct && func.has_with_distinct() + ? AggregatorType.DISTINCT_AGGREGATOR : AggregatorType.SIMPLE_AGGREGATOR, + null); + } + } + + protected void init_sum_functions(List funcs, RowDataPacket row) { + for (int i = 0; i < funcs.size(); i++) { + ItemSum sum = funcs.get(i); + Object transObj = ((DGRowPacket) row).getSumTran(i); + sum.resetAndAdd(row, transObj); + } + } + + protected void update_sum_func(List funcs, RowDataPacket row) { + for (int index = 0; index < funcs.size(); index++) { + ItemSum sum = funcs.get(index); + Object transObj = ((DGRowPacket) row).getSumTran(index); + sum.aggregatorAdd(row, transObj); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/LocalResult.java b/src/main/java/io/mycat/backend/mysql/store/LocalResult.java new file mode 100644 index 000000000..82d99827e --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/LocalResult.java @@ -0,0 +1,221 @@ +package io.mycat.backend.mysql.store; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import io.mycat.backend.mysql.store.memalloc.MemSizeController; +import io.mycat.backend.mysql.store.result.ResultExternal; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.external.ResultStore; + +public abstract class LocalResult implements ResultStore { + + protected static final int DEFAULT_INITIAL_CAPACITY = 1024; + protected final int fieldsCount; + protected int maxMemory = 262144; + + protected BufferPool pool; + protected List rows; + protected ResultExternal external; + protected int rowId, rowCount; + protected int currentMemory; + protected RowDataPacket currentRow; + protected RowDataPacket lastRow; + protected boolean isClosed; + protected Lock lock; + /* @bug 1208 */ + protected String charset = "UTF-8"; + protected MemSizeController bufferMC; + + public LocalResult(int initialCapacity, int fieldsCount, BufferPool pool, String charset) { + this.rows = new ArrayList(initialCapacity); + this.fieldsCount = fieldsCount; + this.pool = pool; + init(); + this.isClosed = false; + this.lock = new ReentrantLock(); + this.charset = charset; + } + + /** + * add a row into localresult + * + * @param row + */ + public void add(RowDataPacket row) { + lock.lock(); + try { + if (this.isClosed) + return; + lastRow = row; + rows.add(row); + rowCount++; + int increSize = getRowMemory(row); + currentMemory += increSize; + boolean needFlush = false; + if (bufferMC != null) { + if (bufferMC.addSize(increSize) != true) { + needFlush = true; + } + } + else if (!needFlush && currentMemory > maxMemory) { + needFlush = true; + } + if (needFlush) { + if (external == null) + external = makeExternal(); + addRowsToDisk(); + } + } finally { + lock.unlock(); + } + } + + protected abstract ResultExternal makeExternal(); + + public RowDataPacket currentRow() { + return currentRow; + } + + public RowDataPacket getLastRow() { + return lastRow; + } + + public int getRowCount() { + return rowCount; + } + + public int getRowId() { + return rowId; + } + + /** + * + * @return next row + */ + public RowDataPacket next() { + lock.lock(); + try { + if (this.isClosed) + return null; + if (++rowId < rowCount) { + if (external != null) { + currentRow = external.next(); + } else { + currentRow = rows.get(rowId); + } + } else { + currentRow = null; + } + return currentRow; + } finally { + lock.unlock(); + } + } + + /** + * This method is called after all rows have been added. + */ + public void done() { + lock.lock(); + try { + if (this.isClosed) + return; + if (external == null) + doneOnlyMemory(); + else { + if (!rows.isEmpty()) + addRowsToDisk(); + external.done(); + } + reset(); + } finally { + lock.unlock(); + } + } + + protected abstract void doneOnlyMemory(); + + public void reset() { + lock.lock(); + try { + rowId = -1; + if (external != null) { + external.reset(); + } + } finally { + lock.unlock(); + } + } + + @Override + public void close() { + lock.lock(); + try { + if (this.isClosed) + return; + this.isClosed = true; + rows.clear(); + if (bufferMC != null) + bufferMC.subSize(currentMemory); + rows = null; + if (external != null) { + external.close(); + external = null; + } + } finally { + lock.unlock(); + } + } + + @Override + public void clear() { + lock.lock(); + try { + rows.clear(); + if (bufferMC != null) + bufferMC.subSize(currentMemory); + init(); + if (external != null) { + external.close(); + external = null; + } + } finally { + lock.unlock(); + } + } + + protected final void addRowsToDisk() { + beforeFlushRows(); + rowCount = external.addRows(rows); + rows.clear(); + if (bufferMC != null) + bufferMC.subSize(currentMemory); + currentMemory = 0; + } + + /** + * job to do before flush rows into disk + */ + protected abstract void beforeFlushRows(); + + protected int getRowMemory(RowDataPacket row) { + return row.calcPacketSize(); + } + + private void init() { + this.rowId = -1; + this.rowCount = 0; + this.currentMemory = 0; + this.currentRow = null; + this.lastRow = null; + } + + public LocalResult setMemSizeController(MemSizeController bufferMC) { + this.bufferMC = bufferMC; + return this; + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/SortedLocalResult.java b/src/main/java/io/mycat/backend/mysql/store/SortedLocalResult.java new file mode 100644 index 000000000..7e5d71607 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/SortedLocalResult.java @@ -0,0 +1,39 @@ +package io.mycat.backend.mysql.store; + +import java.util.Collections; + +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.diskbuffer.SortedResultDiskBuffer; +import io.mycat.backend.mysql.store.result.ResultExternal; +import io.mycat.buffer.BufferPool; + +public class SortedLocalResult extends LocalResult { + + protected RowDataComparator rowcmp; + + public SortedLocalResult(BufferPool pool, int fieldsCount, RowDataComparator rowcmp, String charset) { + this(DEFAULT_INITIAL_CAPACITY, fieldsCount, pool, rowcmp, charset); + } + + public SortedLocalResult(int initialCapacity, int fieldsCount, BufferPool pool, RowDataComparator rowcmp, + String charset) { + super(initialCapacity, fieldsCount, pool, charset); + this.rowcmp = rowcmp; + } + + @Override + protected ResultExternal makeExternal() { + return new SortedResultDiskBuffer(pool, fieldsCount, rowcmp, charset); + } + + @Override + protected void beforeFlushRows() { + Collections.sort(rows, this.rowcmp); + } + + @Override + protected void doneOnlyMemory() { + Collections.sort(rows, this.rowcmp); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/UnSortedLocalResult.java b/src/main/java/io/mycat/backend/mysql/store/UnSortedLocalResult.java new file mode 100644 index 000000000..18ed3f4a2 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/UnSortedLocalResult.java @@ -0,0 +1,32 @@ +package io.mycat.backend.mysql.store; + +import io.mycat.backend.mysql.store.diskbuffer.UnSortedResultDiskBuffer; +import io.mycat.backend.mysql.store.result.ResultExternal; +import io.mycat.buffer.BufferPool; + +public class UnSortedLocalResult extends LocalResult { + + public UnSortedLocalResult(int fieldsCount, BufferPool pool, String charset) { + this(DEFAULT_INITIAL_CAPACITY, fieldsCount, pool, charset); + } + + public UnSortedLocalResult(int initialCapacity, int fieldsCount, BufferPool pool, String charset) { + super(initialCapacity, fieldsCount, pool, charset); + } + + @Override + protected ResultExternal makeExternal() { + return new UnSortedResultDiskBuffer(pool, fieldsCount, charset); + } + + @Override + protected void beforeFlushRows() { + + } + + @Override + protected void doneOnlyMemory() { + + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/diskbuffer/DistinctResultDiskBuffer.java b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/DistinctResultDiskBuffer.java new file mode 100644 index 000000000..b4df29d37 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/DistinctResultDiskBuffer.java @@ -0,0 +1,72 @@ +package io.mycat.backend.mysql.store.diskbuffer; + +import io.mycat.backend.mysql.nio.handler.util.RBTMinHeap; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.RowDataPacket; + +/** + * disk result buffer which show the distinct row result + * + * @author chenzifei + * + */ +public class DistinctResultDiskBuffer extends SortedResultDiskBuffer { + + /** + * + * @param pool + * @param columnCount + * @param cmp + * distinct selectable compator + */ + public DistinctResultDiskBuffer(BufferPool pool, int columnCount, RowDataComparator cmp, String charset) { + super(pool, columnCount, cmp, charset); + } + + @Override + public RowDataPacket next() { + if (heap.isEmpty()) + return null; + TapeItem tapeItem = heap.poll(); + addToHeap(tapeItem.tape); + return tapeItem.row; + } + + /** + * if heap already contains row, no add into heap + * + * @param row + */ + protected void addToHeap(ResultDiskTape tape) { + while (true) { + RowDataPacket row = tape.nextRow(); + if (row == null) + return; + else { + TapeItem tapeItem = new TapeItem(row, tape); + TapeItem oldItem = heap.find(tapeItem); + if (oldItem == null) { + heap.add(tapeItem); + return; + } else { + onFoundRow(oldItem.row, row); + } + } + } + } + + protected void onFoundRow(RowDataPacket oldRow, RowDataPacket row) { + + } + + @Override + protected void resetHeap() { + if (heap == null) + this.heap = new RBTMinHeap(this.heapCmp); + heap.clear(); + for (ResultDiskTape tape : tapes) { + addToHeap(tape); + } + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/diskbuffer/GroupResultDiskBuffer.java b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/GroupResultDiskBuffer.java new file mode 100644 index 000000000..6fe666201 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/GroupResultDiskBuffer.java @@ -0,0 +1,148 @@ +package io.mycat.backend.mysql.store.diskbuffer; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.SerializationUtils; +import org.apache.log4j.Logger; + +import io.mycat.backend.mysql.nio.handler.query.DMLResponseHandler.HandlerType; +import io.mycat.backend.mysql.nio.handler.query.impl.groupby.directgroupby.DGRowPacket; +import io.mycat.backend.mysql.nio.handler.util.HandlerTool; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.backend.mysql.store.FileStore; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.function.sumfunc.Aggregator.AggregatorType; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; + +/** + * the disk buffer which need to group by all the tapes value of it + * + * @author chenzifei + * + */ +public class GroupResultDiskBuffer extends DistinctResultDiskBuffer { + private final Logger logger = Logger.getLogger(GroupResultDiskBuffer.class); + /** + * store the origin row fields,(already contains the item_sum fields in + * rowpackets we should calculate the item_sums again when next() is + * called!) + */ + private final List fields; + + private final List sums; + + /** + * + * @param pool + * @param columnCount + * @param cmp + * group by cmptor + * @param packets + * packets which already contain sum_function's fieldpacket, + * sum_packets are put in the front + * @param sumFunctions + */ + public GroupResultDiskBuffer(BufferPool pool, int fieldsCount, RowDataComparator cmp, List packets, + List sumFunctions, boolean isAllPushDown, String charset) { + super(pool, fieldsCount, cmp, charset); + this.fields = HandlerTool.createFields(packets); + this.sums = new ArrayList(); + for (ItemSum sumFunc : sumFunctions) { + ItemSum sum = (ItemSum) (HandlerTool.createItem(sumFunc, this.fields, 0, isAllPushDown, + HandlerType.GROUPBY, charset)); + this.sums.add(sum); + } + logger.info("prepare_sum_aggregators"); + prepare_sum_aggregators(this.sums, true); + } + + @Override + protected ResultDiskTape makeResultDiskTape() { + return new GroupResultDiskTape(pool, file, columnCount, sums.size()); + } + + @Override + protected void onFoundRow(RowDataPacket oldRow, RowDataPacket row) { + // we need to calculate group by + init_sum_functions(this.sums, oldRow); + update_sum_func(this.sums, row); + for (int i = 0; i < this.sums.size(); i++) { + ItemSum sum = this.sums.get(i); + Object b = sum.getTransAggObj(); + int transSize = sum.getTransSize(); + ((DGRowPacket) oldRow).setSumTran(i, b, transSize); + } + } + + /** + * see Sql_executor.cc + * + * @return + */ + protected void prepare_sum_aggregators(List funcs, boolean need_distinct) { + for (ItemSum func : funcs) { + func.setAggregator(need_distinct && func.has_with_distinct() + ? AggregatorType.DISTINCT_AGGREGATOR : AggregatorType.SIMPLE_AGGREGATOR, + null); + } + } + + protected void init_sum_functions(List funcs, RowDataPacket row) { + for (int i = 0; i < funcs.size(); i++) { + ItemSum sum = funcs.get(i); + Object transObj = ((DGRowPacket) row).getSumTran(i); + sum.resetAndAdd(row, transObj); + } + } + + protected void update_sum_func(List funcs, RowDataPacket row) { + for (int index = 0; index < funcs.size(); index++) { + ItemSum sum = funcs.get(index); + Object transObj = ((DGRowPacket) row).getSumTran(index); + sum.aggregatorAdd(row, transObj); + } + } + + /** + * 比原生的resultdisktape要添加sum结果的值 + * + * @author zhangyaohua + * @CreateTime 2015年5月20日 + */ + static class GroupResultDiskTape extends ResultDiskTape { + private final int orgFieldCount; + private final int sumSize; + + public GroupResultDiskTape(BufferPool pool, FileStore file, int fieldCount, int sumSize) { + super(pool, file, sumSize + fieldCount); + this.orgFieldCount = fieldCount; + this.sumSize = sumSize; + } + + @Override + public RowDataPacket nextRow() { + RowDataPacket rp = super.nextRow(); + if (rp == null) + return null; + else { + DGRowPacket newRow = new DGRowPacket(orgFieldCount, sumSize); + for (int index = 0; index < sumSize; index++) { + byte[] b = rp.getValue(index); + if (b != null) { + Object obj = SerializationUtils.deserialize(b); + newRow.setSumTran(index, obj, b.length); + } + } + for (int index = sumSize; index < this.fieldCount; index++) { + newRow.add(rp.getValue(index)); + } + return newRow; + } + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/diskbuffer/ResultDiskBuffer.java b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/ResultDiskBuffer.java new file mode 100644 index 000000000..0b62683e7 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/ResultDiskBuffer.java @@ -0,0 +1,217 @@ +package io.mycat.backend.mysql.store.diskbuffer; + +import java.nio.ByteBuffer; + +import io.mycat.backend.mysql.store.FileStore; +import io.mycat.backend.mysql.store.result.ResultExternal; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.util.exception.NotSupportException; + +/** + * a buffer used to store large amount of data on disk or virtual memory mapped + * on disk + * + * @author chenzifei + * + */ +public abstract class ResultDiskBuffer implements ResultExternal { + protected final int columnCount; + protected final BufferPool pool; + + protected ByteBuffer writeBuffer; + protected FileStore file; + protected int rowCount = 0; + /* @bug 1208 */ + protected String charset = "UTF-8"; + + public ResultDiskBuffer(BufferPool pool, int columnCount, String charset) { + this.pool = pool; + this.columnCount = columnCount; + this.writeBuffer = pool.allocate(); + this.file = new FileStore("nioMapped:AresMemory", "rw"); + if (charset != null) + this.charset = charset; + } + + @Override + public void done() { + this.file.seek(0); + } + + @Override + public int removeRow(RowDataPacket row) { + throw new NotSupportException("unsupportted remove row"); + } + + @Override + public boolean contains(RowDataPacket row) { + throw new NotSupportException("unsupportted contains"); + } + + @Override + public int addRow(RowDataPacket row) { + throw new NotSupportException("unsupportted addRow"); + } + + @Override + public ResultExternal createShallowCopy() { + throw new NotSupportException("unsupportted createShallowCopy"); + } + + @Override + public void close() { + if (file != null) + file.closeAndDeleteSilently(); + file = null; + pool.recycle(writeBuffer); + } + + protected ByteBuffer writeToBuffer(byte[] src, ByteBuffer buffer) { + int offset = 0; + int len = src.length; + int remaining = buffer.remaining(); + while (len > 0) { + if (remaining >= len) { + buffer.put(src, offset, len); + break; + } else { + buffer.put(src, offset, remaining); + buffer.flip(); + file.write(buffer); + buffer.clear(); + offset += remaining; + len -= remaining; + remaining = buffer.remaining(); + continue; + } + } + return buffer; + } + + static class TapeItem { + + RowDataPacket row; + ResultDiskTape tape; + + public TapeItem(RowDataPacket row, ResultDiskTape tape) { + this.row = row; + this.tape = tape; + } + } + + /** + * Represents a virtual disk tape for the merge sort algorithm. Each virtual + * disk tape is a region of the temp file. + */ + static class ResultDiskTape { + + BufferPool pool; + FileStore file; + int fieldCount; + long filePos; + long start; + long end; + long pos; + int readBufferOffset; + ByteBuffer readBuffer; + RowDataPacket currentRow; + + public ResultDiskTape(BufferPool pool, FileStore file, int fieldCount) { + this.pool = pool; + this.file = file; + this.fieldCount = fieldCount; + this.readBuffer = pool.allocate(); + } + + public boolean isEnd() { + return isReadAll() && this.currentRow == null; + } + + public RowDataPacket nextRow() { + if (isReadAll()) + return null; + byte[] row = getRow(); + RowDataPacket currentRow = new RowDataPacket(fieldCount); + currentRow.read(row); + return currentRow; + } + + private boolean isReadAll() { + return this.end == this.pos; + } + + private void readIntoBuffer() { + file.seek(filePos); + filePos += file.read(readBuffer, end); + } + + private byte[] getRow() { + int offset = readBufferOffset, length = 0, position = readBuffer.position(); + length = getPacketLength(readBuffer, offset); + while (length == -1 || position < offset + length) { + if (!readBuffer.hasRemaining()) { + checkReadBuffer(offset); + } + // read new data to buffer + readIntoBuffer(); + // get new offset for buffer compact + offset = readBufferOffset; + position = readBuffer.position(); + if (length == -1) { + length = getPacketLength(readBuffer, offset); + } + } + + readBuffer.position(offset); + byte[] data = new byte[length]; + readBuffer.get(data, 0, length); + offset += length; + pos += length; + if (position == offset) { + if (readBufferOffset != 0) { + readBufferOffset = 0; + } + readBuffer.clear(); + readIntoBuffer(); + } else { + readBufferOffset = offset; + readBuffer.position(position); + } + return data; + } + + private int getPacketLength(ByteBuffer buffer, int offset) { + if (buffer.position() < offset + 4) { + return -1; + } else { + int length = buffer.get(offset) & 0xff; + length |= (buffer.get(++offset) & 0xff) << 8; + length |= (buffer.get(++offset) & 0xff) << 16; + return length + 4; + } + } + + private void checkReadBuffer(int offset) { + // if offset is 0,then expend buffer; else set offset to 0,compact + // buffer + if (offset == 0) { + if (readBuffer.capacity() >= Integer.MAX_VALUE) { + throw new IllegalArgumentException("Packet size over the limit."); + } + int size = readBuffer.capacity() << 1; + size = (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : size; + ByteBuffer newBuffer = ByteBuffer.allocate(size); + readBuffer.position(offset); + newBuffer.put(readBuffer); + pool.recycle(readBuffer); + readBuffer = newBuffer; + } else { + readBuffer.position(offset); + readBuffer.compact(); + readBufferOffset = 0; + } + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/diskbuffer/SortedResultDiskBuffer.java b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/SortedResultDiskBuffer.java new file mode 100644 index 000000000..218eae5d0 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/SortedResultDiskBuffer.java @@ -0,0 +1,142 @@ +package io.mycat.backend.mysql.store.diskbuffer; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.apache.log4j.Logger; + +import io.mycat.backend.mysql.nio.handler.util.ArrayMinHeap; +import io.mycat.backend.mysql.nio.handler.util.RowDataComparator; +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.util.MinHeap; +import io.mycat.util.TimeUtil; + +/** + * sort need diskbuffer, when done() is called,users use next() to get the + * result rows which have been sorted already + * + * @author chenzifei + * + */ +public class SortedResultDiskBuffer extends ResultDiskBuffer { + private final Logger logger = Logger.getLogger(SortedResultDiskBuffer.class); + + /** + * the tapes to store data, which is sorted each, so we can use minheap to + * sort them + */ + protected final ArrayList tapes; + /** + * the sort cmptor + */ + private final RowDataComparator comparator; + /** + * the heap used for sorting the sorted tapes + */ + protected MinHeap heap; + protected Comparator heapCmp; + + public SortedResultDiskBuffer(BufferPool pool, int columnCount, RowDataComparator cmp, String charset) { + super(pool, columnCount, charset); + tapes = new ArrayList(); + this.comparator = cmp; + this.heapCmp = new Comparator() { + @Override + public int compare(TapeItem o1, TapeItem o2) { + RowDataPacket row1 = o1.row; + RowDataPacket row2 = o2.row; + if (row1 == null || row2 == null) { + if (row1 == row2) + return 0; + if (row1 == null) + return -1; + return 1; + } + return comparator.compare(row1, row2); + } + }; + } + + @Override + public final int TapeCount() { + return tapes.size(); + } + + @Override + public final int addRows(List rows) { + /** + * we should make rows sorted first, then write them into file + */ + if (logger.isDebugEnabled()) { + logger.debug(" convert list to array start:" + TimeUtil.currentTimeMillis()); + } + RowDataPacket[] rowArray = new RowDataPacket[rows.size()]; + rows.toArray(rowArray); + long start = file.getFilePointer(); + for (RowDataPacket row : rowArray) { + byte[] b = row.toBytes(); + writeBuffer = writeToBuffer(b, writeBuffer); + } + // help for gc + rowArray = null; + writeBuffer.flip(); + file.write(writeBuffer); + writeBuffer.clear(); + /* make a new tape */ + ResultDiskTape tape = makeResultDiskTape(); + tape.start = start; + tape.filePos = start; + tape.end = file.getFilePointer(); + tapes.add(tape); + rowCount += rows.size(); + if (logger.isDebugEnabled()) { + logger.debug("write rows to disk end:" + TimeUtil.currentTimeMillis()); + } + return rowCount; + } + + /** + * to override by group by + * + * @return + */ + protected ResultDiskTape makeResultDiskTape() { + return new ResultDiskTape(pool, file, columnCount); + } + + @Override + public RowDataPacket next() { + if (heap.isEmpty()) + return null; + TapeItem tapeItem = heap.poll(); + RowDataPacket newRow = tapeItem.tape.nextRow(); + if (newRow != null) { + heap.add(new TapeItem(newRow, tapeItem.tape)); + } + return tapeItem.row; + } + + @Override + public final void reset() { + for (ResultDiskTape tape : tapes) { + tape.filePos = tape.start; + tape.pos = tape.start; + tape.readBufferOffset = 0; + tape.readBuffer.clear(); + } + resetHeap(); + } + + protected void resetHeap() { + if (heap == null) + heap = new ArrayMinHeap(tapes.size(), this.heapCmp); + heap.clear(); + // init heap + for (int i = 0; i < tapes.size(); i++) { + heap.add(new TapeItem(tapes.get(i).nextRow(), tapes.get(i))); + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/diskbuffer/UnSortedResultDiskBuffer.java b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/UnSortedResultDiskBuffer.java new file mode 100644 index 000000000..81b6d994b --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/diskbuffer/UnSortedResultDiskBuffer.java @@ -0,0 +1,68 @@ +package io.mycat.backend.mysql.store.diskbuffer; + +import java.util.List; + +import org.apache.log4j.Logger; + +import io.mycat.buffer.BufferPool; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.util.TimeUtil; + +/** + * no sort need diskbuffer,when a new row come in,added it directly + * + * @author chenzifei + * + */ +public class UnSortedResultDiskBuffer extends ResultDiskBuffer { + private final Logger logger = Logger.getLogger(UnSortedResultDiskBuffer.class); + /** + * the tape to store unsorted data + */ + private final ResultDiskTape mainTape; + + public UnSortedResultDiskBuffer(BufferPool pool, int columnCount, String charset) { + super(pool, columnCount, charset); + mainTape = new ResultDiskTape(pool, file, columnCount); + } + + @Override + public int TapeCount() { + return 1; + } + + @Override + public int addRows(List rows) { + if (logger.isDebugEnabled()) { + logger.debug("addRows start:" + TimeUtil.currentTimeMillis()); + } + for (RowDataPacket row : rows) { + byte[] b = row.toBytes(); + writeBuffer = writeToBuffer(b, writeBuffer); + } + writeBuffer.flip(); + file.write(writeBuffer); + writeBuffer.clear(); + mainTape.end = file.getFilePointer(); + rowCount += rows.size(); + if (logger.isDebugEnabled()) { + logger.debug("write rows to disk end:" + TimeUtil.currentTimeMillis()); + } + return rowCount; + } + + @Override + public void reset() { + mainTape.pos = mainTape.start; + mainTape.filePos = mainTape.start; + mainTape.readBufferOffset = 0; + mainTape.readBuffer.clear(); + } + + @Override + public RowDataPacket next() { + file.seek(mainTape.pos); + return mainTape.nextRow(); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FileBase.java b/src/main/java/io/mycat/backend/mysql/store/fs/FileBase.java new file mode 100644 index 000000000..092f55325 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FileBase.java @@ -0,0 +1,69 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +/** + * @author zhangyaohua + * @CreateTime 2014-8-21 + */ +public abstract class FileBase extends FileChannel { + + @Override + public synchronized int read(ByteBuffer dst, long position) throws IOException { + long oldPos = position(); + position(position); + int len = read(dst); + position(oldPos); + return len; + } + + @Override + public synchronized int write(ByteBuffer src, long position) throws IOException { + long oldPos = position(); + position(position); + int len = write(src); + position(oldPos); + return len; + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FilePath.java b/src/main/java/io/mycat/backend/mysql/store/fs/FilePath.java new file mode 100644 index 000000000..f89f2714a --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FilePath.java @@ -0,0 +1,317 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + + +/** + * A path to a file. It similar to the Java 7 java.nio.file.Path, + * but simpler, and works with older versions of Java. It also implements the + * relevant methods found in java.nio.file.FileSystem and + * FileSystems + * + * @author zhangyaohua + * @createTime 2013-11-11 + */ +public abstract class FilePath { + + private static FilePath defaultProvider; + + private static Map providers; + + /** + * The prefix for temporary files. + */ + private static String tempRandom; + private static long tempSequence; + + /** + * The complete path (which may be absolute or relative, depending on the + * file system). + */ + protected String name; + + /** + * Get the file path object for the given path. Windows-style '\' is + * replaced with '/'. + * + * @param path + * the path + * @return the file path object + */ + public static FilePath get(String path) { + path = path.replace('\\', '/'); + int index = path.indexOf(':'); + registerDefaultProviders(); + if (index < 2) { + // use the default provider if no prefix or + // only a single character (drive name) + return defaultProvider.getPath(path); + } + String scheme = path.substring(0, index); + FilePath p = providers.get(scheme); + if (p == null) { + // provider not found - use the default + p = defaultProvider; + } + return p.getPath(path); + // return p; + } + + private static void registerDefaultProviders() { + if (providers == null || defaultProvider == null) { + Map map = Collections.synchronizedMap(new HashMap()); + for (String c : new String[] { "com.actionsky.ares.partition.store.fs.FilePathDisk", + "com.actionsky.ares.partition.store.fs.FilePathNio", + "com.actionsky.ares.partition.store.fs.FilePathNioMapped" }) { + try { + FilePath p = (FilePath) Class.forName(c).newInstance(); + map.put(p.getScheme(), p); + if (defaultProvider == null) { + defaultProvider = p; + } + } catch (Exception e) { + // ignore - the files may be excluded in purpose + } + } + providers = map; + } + } + + /** + * Register a file provider. + * + * @param provider + * the file provider + */ + public static void register(FilePath provider) { + registerDefaultProviders(); + providers.put(provider.getScheme(), provider); + } + + /** + * Unregister a file provider. + * + * @param provider + * the file provider + */ + public static void unregister(FilePath provider) { + registerDefaultProviders(); + providers.remove(provider.getScheme()); + } + + /** + * Get the size of a file in bytes + * + * @return the size in bytes + */ + public abstract long size(); + + /** + * Rename a file if this is allowed. + * + * @param newName + * the new fully qualified file name + */ + public abstract void moveTo(FilePath newName); + + /** + * Create a new file. + * + * @return true if creating was successful + */ + public abstract boolean createFile(); + + /** + * Checks if a file exists. + * + * @return true if it exists + */ + public abstract boolean exists(); + + /** + * Delete a file or directory if it exists. Directories may only be deleted + * if they are empty. + */ + public abstract void delete(); + + /** + * List the files and directories in the given directory. + * + * @return the list of fully qualified file names + */ + public abstract List newDirectoryStream(); + + /** + * Normalize a file name. + * + * @return the normalized file name + */ + public abstract FilePath toRealPath(); + + /** + * Get the parent directory of a file or directory. + * + * @return the parent directory name + */ + public abstract FilePath getParent(); + + /** + * Check if it is a file or a directory. + * + * @return true if it is a directory + */ + public abstract boolean isDirectory(); + + /** + * Check if the file name includes a path. + * + * @return if the file name is absolute + */ + public abstract boolean isAbsolute(); + + /** + * Get the last modified date of a file + * + * @return the last modified date + */ + public abstract long lastModified(); + + /** + * Check if the file is writable. + * + * @return if the file is writable + */ + public abstract boolean canWrite(); + + /** + * Create a directory (all required parent directories already exist). + */ + public abstract void createDirectory(); + + /** + * Get the file or directory name (the last element of the path). + * + * @return the last element of the path + */ + public String getName() { + int idx = Math.max(name.indexOf(':'), name.lastIndexOf('/')); + return idx < 0 ? name : name.substring(idx + 1); + } + + /** + * Create an output stream to write into the file. + * + * @param append + * if true, the file will grow, if false, the file will be + * truncated first + * @return the output stream + */ + public abstract OutputStream newOutputStream(boolean append) throws IOException; + + /** + * Open a random access file object. + * + * @param mode + * the access mode. Supported are r, rw, rws, rwd + * @return the file object + */ + public abstract FileChannel open(String mode) throws IOException; + + /** + * Create an input stream to read from the file. + * + * @return the input stream + */ + public abstract InputStream newInputStream() throws IOException; + + /** + * Disable the ability to write. + * + * @return true if the call was successful + */ + public abstract boolean setReadOnly(); + + /** + * Create a new temporary file. + * + * @param suffix + * the suffix + * @param deleteOnExit + * if the file should be deleted when the virtual machine exists + * @return the name of the created file + */ + public FilePath createTempFile(String suffix, boolean deleteOnExit) throws IOException { + while (true) { + FilePath p = getPath(name + getNextTempFileNamePart(false) + suffix); + if (p.exists()) { + // in theory, the random number could collide + getNextTempFileNamePart(true); + continue; + } + return p; + } + } + + /** + * Get the next temporary file name part (the part in the middle). + * + * @param newRandom + * if the random part of the filename should change + * @return the file name part + */ + protected static synchronized String getNextTempFileNamePart(boolean newRandom) { + if (newRandom || tempRandom == null) { + tempRandom = new Random().nextInt(Integer.MAX_VALUE) + "."; + } + return tempRandom + tempSequence++; + } + + /** + * Get the string representation. The returned string can be used to + * construct a new object. + * + * @return the path as a string + */ + @Override + public String toString() { + return name; + } + + /** + * Get the scheme (prefix) for this file provider. This is similar to + * java.nio.file.spi.FileSystemProvider.getScheme. + * + * @return the scheme + */ + public abstract String getScheme(); + + /** + * Convert a file to a path. This is similar to + * java.nio.file.spi.FileSystemProvider.getPath, but may return + * an object even if the scheme doesn't match in case of the the default + * file provider. + * + * @param path + * the path + * @return the file path object + */ + public abstract FilePath getPath(String path); + + /** + * Get the unwrapped file name (without wrapper prefixes if wrapping / + * delegating file systems are used). + * + * @return the unwrapped path + */ + public FilePath unwrap() { + return this; + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FilePathDisk.java b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathDisk.java new file mode 100644 index 000000000..fc5ca5c65 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathDisk.java @@ -0,0 +1,405 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import io.mycat.config.ErrorCode; +import io.mycat.util.exception.TmpFileException; + +/** + * @author zhangyaohua + * @CreateTime 2014-9-8 + */ +public class FilePathDisk extends FilePath { + + private static final String CLASSPATH_PREFIX = "classpath:"; + + @Override + public FileChannel open(String mode) throws IOException { + FileDisk f; + try { + f = new FileDisk(name, mode); + } catch (IOException e) { + freeMemoryAndFinalize(); + try { + f = new FileDisk(name, mode); + } catch (IOException e2) { + throw e; + } + } + return f; + } + + @Override + public String getScheme() { + return "file"; + } + + @Override + public FilePath getPath(String path) { + FilePathDisk p = new FilePathDisk(); + p.name = translateFileName(path); + return p; + } + + @Override + public long size() { + return new File(name).length(); + } + + /** + * Translate the file name to the native format. This will replace '\' with + * '/'. + * + * @param fileName + * the file name + * @return the native file name + */ + protected String translateFileName(String fileName) { + fileName = fileName.replace('\\', '/'); + if (fileName.startsWith("file:")) { + fileName = fileName.substring("file:".length()); + } + return fileName; + } + + @Override + public void moveTo(FilePath newName) { + File oldFile = new File(name); + File newFile = new File(newName.name); + if (oldFile.getAbsolutePath().equals(newFile.getAbsolutePath())) { + return; + } + if (!oldFile.exists()) { + throw TmpFileException.get(ErrorCode.ER_FILE_RENAME, name + " (not found)", newName.name); + } + if (newFile.exists()) { + throw TmpFileException.get(ErrorCode.ER_FILE_RENAME, name, newName + " (exists)"); + } + for (int i = 0; i < 2; i++) { + boolean ok = oldFile.renameTo(newFile); + if (ok) { + return; + } + wait(i); + } + throw TmpFileException.get(ErrorCode.ER_FILE_RENAME, new String[] { name, newName.name }); + } + + protected void wait(int i) { + if (i == 8) { + System.gc(); + } + // sleep at most 256 ms + long sleep = Math.min(256, i * i); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(sleep)); + } + + @Override + public boolean createFile() { + File file = new File(name); + for (int i = 0; i < 2; i++) { + try { + return file.createNewFile(); + } catch (IOException e) { + // 'access denied' is really a concurrent access problem + wait(i); + } + } + return false; + } + + @Override + public boolean exists() { + return new File(name).exists(); + } + + @Override + public void delete() { + File file = new File(name); + for (int i = 0; i < 2; i++) { + boolean ok = file.delete(); + if (ok || !file.exists()) { + return; + } + wait(i); + } + throw TmpFileException.get(ErrorCode.ER_FILE_DELETE, name); + } + + @Override + public List newDirectoryStream() { + ArrayList list = new ArrayList(); + File f = new File(name); + try { + String[] files = f.list(); + if (files != null) { + String base = f.getCanonicalPath(); + for (int i = 0, len = files.length; i < len; i++) { + list.add(getPath(base + files[i])); + } + } + return list; + } catch (IOException e) { + throw TmpFileException.convertIOException(ErrorCode.ER_IO_EXCEPTION, e, name); + } + } + + @Override + public boolean canWrite() { + return canWriteInternal(new File(name)); + } + + @Override + public boolean setReadOnly() { + File f = new File(name); + return f.setReadOnly(); + } + + @Override + public FilePath toRealPath() { + try { + String fileName = new File(name).getCanonicalPath(); + return getPath(fileName); + } catch (IOException e) { + throw TmpFileException.convertIOException(ErrorCode.ER_IO_EXCEPTION, e, name); + } + } + + @Override + public FilePath getParent() { + String p = new File(name).getParent(); + return p == null ? null : getPath(p); + } + + @Override + public boolean isDirectory() { + return new File(name).isDirectory(); + } + + @Override + public boolean isAbsolute() { + return new File(name).isAbsolute(); + } + + @Override + public long lastModified() { + return new File(name).lastModified(); + } + + private static boolean canWriteInternal(File file) { + try { + if (!file.canWrite()) { + return false; + } + } catch (Exception e) { + // workaround for GAE which throws a + // java.security.AccessControlException + return false; + } + // File.canWrite() does not respect windows user permissions, + // so we must try to open it using the mode "rw". + // See also http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4420020 + RandomAccessFile r = null; + try { + r = new RandomAccessFile(file, "rw"); + return true; + } catch (FileNotFoundException e) { + return false; + } finally { + if (r != null) { + try { + r.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + @Override + public void createDirectory() { + File dir = new File(name); + for (int i = 0; i < 2; i++) { + if (dir.exists()) { + if (dir.isDirectory()) { + return; + } + throw TmpFileException.get(ErrorCode.ER_FILE_CREATE, name + " (a file with this name is already exists)"); + } else if (dir.mkdir()) { + return; + } + wait(i); + } + throw TmpFileException.get(ErrorCode.ER_FILE_CREATE, name); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + try { + File file = new File(name); + File parent = file.getParentFile(); + if (parent != null) { + FileUtils.createDirectories(parent.getAbsolutePath()); + } + FileOutputStream out = new FileOutputStream(name, append); + return out; + } catch (IOException e) { + freeMemoryAndFinalize(); + return new FileOutputStream(name); + } + } + + @Override + public InputStream newInputStream() throws IOException { + int index = name.indexOf(':'); + if (index > 1 && index < 20) { + // if the ':' is in position 1, a windows file access is assumed: + // C:.. or D:, and if the ':' is not at the beginning, assume its a + // file name with a colon + if (name.startsWith(CLASSPATH_PREFIX)) { + String fileName = name.substring(CLASSPATH_PREFIX.length()); + if (!fileName.startsWith("/")) { + fileName = "/" + fileName; + } + InputStream in = getClass().getResourceAsStream(fileName); + if (in == null) { + in = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); + } + if (in == null) { + throw new FileNotFoundException("resource " + fileName); + } + return in; + } + // otherwise an URL is assumed + URL url = new URL(name); + InputStream in = url.openStream(); + return in; + } + FileInputStream in = new FileInputStream(name); + return in; + } + + /** + * Call the garbage collection and run finalization. This close all files + * that were not closed, and are no longer referenced. + */ + static void freeMemoryAndFinalize() { + Runtime rt = Runtime.getRuntime(); + long mem = rt.freeMemory(); + for (int i = 0; i < 16; i++) { + rt.gc(); + long now = rt.freeMemory(); + rt.runFinalization(); + if (now == mem) { + break; + } + mem = now; + } + } + + @Override + public FilePath createTempFile(String suffix, boolean deleteOnExit) throws IOException { + String fileName = name + "."; + File dir = new File(fileName).getAbsoluteFile().getParentFile(); + FileUtils.createDirectories(dir.getAbsolutePath()); + return super.createTempFile(suffix, deleteOnExit); + } + + /** + * Uses java.io.RandomAccessFile to access a file. + */ + class FileDisk extends FileBase { + + private final RandomAccessFile file; + private final String name; + private final boolean readOnly; + + FileDisk(String fileName, String mode) throws FileNotFoundException { + this.file = new RandomAccessFile(fileName, mode); + this.name = fileName; + this.readOnly = mode.equals("r"); + } + + @Override + public void force(boolean metaData) throws IOException { + file.getChannel().force(metaData); + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + // compatibility with JDK FileChannel#truncate + if (readOnly) { + throw new NonWritableChannelException(); + } + if (newLength < file.length()) { + file.setLength(newLength); + } + return this; + } + + @Override + public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { + return file.getChannel().tryLock(position, size, shared); + } + + @Override + public void implCloseChannel() throws IOException { + file.close(); + } + + @Override + public long position() throws IOException { + return file.getFilePointer(); + } + + @Override + public long size() throws IOException { + return file.length(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int len = file.read(dst.array(), dst.arrayOffset() + dst.position(), dst.remaining()); + if (len > 0) { + dst.position(dst.position() + len); + } + return len; + } + + @Override + public FileChannel position(long pos) throws IOException { + file.seek(pos); + return this; + } + + @Override + public int write(ByteBuffer src) throws IOException { + int len = src.remaining(); + file.write(src.array(), src.arrayOffset() + src.position(), len); + src.position(src.position() + len); + return len; + } + + @Override + public String toString() { + return name; + } + + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FilePathNio.java b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathNio.java new file mode 100644 index 000000000..c1a819656 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathNio.java @@ -0,0 +1,167 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; + +import io.mycat.MycatServer; + +/** + *
+ * This file system stores files on disk and uses java.nio to access the files.
+ * 
+ * + * @author zhangyaohua + * @CreateTime 2014-8-21 + */ +public class FilePathNio extends FilePathWrapper { + + @Override + public FileChannel open(String mode) throws IOException { + return new FileNio(name.substring(getScheme().length() + 1), mode); + } + + @Override + public String getScheme() { + return "nio"; + } +} + +/** + * File which uses NIO FileChannel. + */ +class FileNio extends FileBase { + + private final String name; + private final FileChannel channel; + private RandomAccessFile file; + private long fileLength; + private long pos; + + FileNio(String fileName, String mode) throws IOException { + this.name = fileName; + this.file = new RandomAccessFile(fileName, mode); + this.channel = file.getChannel(); + this.fileLength = MycatServer.getInstance().getConfig().getSystem().getMappedFileSize(); + this.pos = 0; + } + + @Override + public synchronized void implCloseChannel() throws IOException { + channel.close(); + } + + @Override + public long position() throws IOException { + return channel.position(); + } + + @Override + public long size() throws IOException { + return channel.size(); + } + + @Override + public synchronized int read(ByteBuffer dst) throws IOException { + int len = dst.remaining(); + if (len == 0) { + return 0; + } + len = (int) Math.min(len, fileLength - pos); + if (len <= 0) { + return -1; + } + int limit = dst.limit(); + dst.limit(dst.position() + len); + channel.read(dst); + pos += len; + dst.limit(limit); + return len; + } + + @Override + public FileChannel position(long pos) throws IOException { + channel.position(pos); + this.pos = (int) pos; + return this; + } + + @Override + public synchronized int read(ByteBuffer dst, long position) throws IOException { + return channel.read(dst, position); + } + + @Override + public synchronized int write(ByteBuffer src, long position) throws IOException { + return channel.write(src, position); + } + + @Override + public synchronized FileChannel truncate(long newLength) throws IOException { + try { + long size = channel.size(); + if (newLength < size) { + long pos = channel.position(); + channel.truncate(newLength); + long newPos = channel.position(); + if (pos < newLength) { + // position should stay + // in theory, this should not be needed + if (newPos != pos) { + channel.position(pos); + this.pos = pos; + } + } else if (newPos > newLength) { + // looks like a bug in this FileChannel implementation, as + // the documentation says the position needs to be changed + channel.position(newLength); + this.pos = newLength; + } + } + return this; + } catch (NonWritableChannelException e) { + throw new IOException("read only"); + } + } + + @Override + public void force(boolean metaData) throws IOException { + channel.force(metaData); + } + + @Override + public synchronized int write(ByteBuffer src) throws IOException { + try { + int len; + if (fileLength < pos + src.remaining()) { + int length = (int) (fileLength - pos); + int limit = src.limit(); + src.limit(length); + len = channel.write(src); + src.limit(limit); + pos += len; + return len; + } else { + len = channel.write(src); + pos += len; + return len; + } + } catch (NonWritableChannelException e) { + throw new IOException("read only"); + } + } + + @Override + public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + + @Override + public String toString() { + return "nio:" + name; + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FilePathNioMapped.java b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathNioMapped.java new file mode 100644 index 000000000..35d8a4493 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathNioMapped.java @@ -0,0 +1,280 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.EOFException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; + +import org.apache.log4j.Logger; + +import io.mycat.MycatServer; +import sun.nio.ch.DirectBuffer; + +/** + * @author zhangyaohua + * @CreateTime 2014-8-21 + */ +public class FilePathNioMapped extends FilePathWrapper { + + @Override + public FileChannel open(String mode) throws IOException { + return new FileNioMapped(name.substring(getScheme().length() + 1), mode); + } + + @Override + public String getScheme() { + return "nioMapped"; + } + +} + +/** + * Uses memory mapped files. The file size is limited to 2 GB. + */ +class FileNioMapped extends FileBase { + + private static Logger logger = Logger.getLogger(FileNioMapped.class); + private static final long GC_TIMEOUT_MS = 10000; + private final String name; + private final MapMode mode; + private RandomAccessFile file; + private MappedByteBuffer mapped; + private long fileLength; + private boolean NIO_LOAD_MAPPED = false; + /** + * The position within the file. Can't use the position of the mapped buffer + * because it doesn't support seeking past the end of the file. + */ + private int pos; + + FileNioMapped(String fileName, String mode) throws IOException { + if ("r".equals(mode)) { + this.mode = MapMode.READ_ONLY; + } else { + this.mode = MapMode.READ_WRITE; + } + this.name = fileName; + file = new RandomAccessFile(fileName, mode); + try { + reMap(); + } catch (IOException e) { + if (file != null) { + file.close(); + file = null; + } + throw e; + } + } + + private void unMap() throws IOException { + if (mapped == null) { + return; + } + // first write all data + // mapped.force(); + + // need to dispose old direct buffer, see bug + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038 + + boolean useSystemGc = true; + try { + Method cleanerMethod = mapped.getClass().getMethod("cleaner"); + cleanerMethod.setAccessible(true); + Object cleaner = cleanerMethod.invoke(mapped); + if (cleaner != null) { + Method clearMethod = cleaner.getClass().getMethod("clean"); + clearMethod.invoke(cleaner); + } + useSystemGc = false; + } catch (Throwable e) { + logger.warn("unmap byteBuffer error", e); + // useSystemGc is already true + } finally { + mapped = null; + } + + if (useSystemGc) { + WeakReference bufferWeakRef = new WeakReference(mapped); + mapped = null; + long start = System.currentTimeMillis(); + while (bufferWeakRef.get() != null) { + if (System.currentTimeMillis() - start > GC_TIMEOUT_MS) { + throw new IOException( + "Timeout (" + GC_TIMEOUT_MS + " ms) reached while trying to GC mapped buffer"); + } + System.gc(); + Thread.yield(); + } + } + } + + /** + * Re-map byte buffer into memory, called when file size has changed or file + * was created. + */ + private void reMap() throws IOException { + int oldPos = 0; + if (mapped != null) { + oldPos = pos; + unMap(); + } + fileLength = file.length(); + if (fileLength == 0) { + // fileLength = 1024*1024* 1024; + fileLength = MycatServer.getInstance().getConfig().getSystem().getMappedFileSize(); + } + checkFileSizeLimit(fileLength); + // maps new MappedByteBuffer; the old one is disposed during GC + mapped = file.getChannel().map(mode, 0, fileLength); + int limit = mapped.limit(); + int capacity = mapped.capacity(); + if (limit < fileLength || capacity < fileLength) { + throw new IOException("Unable to map: length=" + limit + " capacity=" + capacity + " length=" + fileLength); + } + if (NIO_LOAD_MAPPED) { + mapped.load(); + } + this.pos = Math.min(oldPos, (int) fileLength); + } + + private static void checkFileSizeLimit(long length) throws IOException { + if (length > Integer.MAX_VALUE) { + throw new IOException("File over 2GB is not supported yet when using this file system"); + } + } + + @Override + public synchronized void implCloseChannel() throws IOException { + if (file != null) { + unMap(); + file.close(); + file = null; + } + } + + @Override + public long position() { + return pos; + } + + @Override + public String toString() { + return "nioMapped:" + name; + } + + @Override + public synchronized long size() throws IOException { + return fileLength; + } + + @Override + public synchronized int read(ByteBuffer dst) throws IOException { + try { + int len = dst.remaining(); + if (len == 0) { + return 0; + } + len = (int) Math.min(len, fileLength - pos); + if (len <= 0) { + return -1; + } + mapped.position(pos); + if (dst instanceof DirectBuffer) { + byte[] temp = new byte[len]; + mapped.get(temp, 0, len); + dst.put(temp); + } else { + mapped.get(dst.array(), dst.arrayOffset() + dst.position(), len); + dst.position(dst.position() + len); + } + pos += len; + return len; + } catch (IllegalArgumentException e) { + EOFException e2 = new EOFException("EOF"); + e2.initCause(e); + throw e2; + } catch (BufferUnderflowException e) { + EOFException e2 = new EOFException("EOF"); + e2.initCause(e); + throw e2; + } + } + + @Override + public FileChannel position(long pos) throws IOException { + checkFileSizeLimit(pos); + this.pos = (int) pos; + return this; + } + + @Override + public synchronized FileChannel truncate(long newLength) throws IOException { + if (newLength < size()) { + setFileLength(newLength); + } + return this; + } + + public synchronized void setFileLength(long newLength) throws IOException { + checkFileSizeLimit(newLength); + int oldPos = pos; + unMap(); + for (int i = 0;; i++) { + try { + file.setLength(newLength); + break; + } catch (IOException e) { + if (i > 16 || e.toString().indexOf("user-mapped section open") < 0) { + throw e; + } + } + System.gc(); + } + reMap(); + pos = (int) Math.min(newLength, oldPos); + } + + @Override + public synchronized void force(boolean metaData) throws IOException { + mapped.force(); + file.getFD().sync(); + } + + /** + * don't expand + */ + @Override + public synchronized int write(ByteBuffer src) throws IOException { + int len = src.remaining(); + if (mapped.capacity() < pos + len) { + int offset = src.position(); + int length = mapped.capacity() - pos; + if (src instanceof DirectBuffer) { + byte[] temp = new byte[length]; + src.get(temp, 0, length); + mapped.put(temp, 0, length); + temp = null; + } else { + mapped.put(src.array(), offset, length); + } + src.position(offset + length); + pos += length; + return length; + } else { + mapped.put(src); + pos += len; + return len; + } + } + + @Override + public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { + return file.getChannel().tryLock(position, size, shared); + } +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FilePathWrapper.java b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathWrapper.java new file mode 100644 index 000000000..6deea7b1f --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FilePathWrapper.java @@ -0,0 +1,158 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.List; + +public abstract class FilePathWrapper extends FilePath { + + private FilePath base; + + @Override + public FilePathWrapper getPath(String path) { + return create(path, unwrap(path)); + } + + /** + * Create a wrapped path instance for the given base path. + * + * @param base + * the base path + * @return the wrapped path + */ + public FilePathWrapper wrap(FilePath base) { + return base == null ? null : create(getPrefix() + base.name, base); + } + + @Override + public FilePath unwrap() { + return unwrap(name); + } + + private FilePathWrapper create(String path, FilePath base) { + try { + FilePathWrapper p = getClass().newInstance(); + p.name = path; + p.base = base; + return p; + } catch (Exception e) { + throw new IllegalArgumentException("Path: " + path, e); + } + } + + protected String getPrefix() { + return getScheme() + ":"; + } + + /** + * Get the base path for the given wrapped path. + * + * @param path + * the path including the scheme prefix + * @return the base file path + */ + protected FilePath unwrap(String path) { + return FilePath.get(path.substring(getScheme().length() + 1)); + } + + protected FilePath getBase() { + return base; + } + + @Override + public boolean canWrite() { + return base.canWrite(); + } + + @Override + public void createDirectory() { + base.createDirectory(); + } + + @Override + public boolean createFile() { + return base.createFile(); + } + + @Override + public void delete() { + base.delete(); + } + + @Override + public boolean exists() { + return base.exists(); + } + + @Override + public FilePath getParent() { + return wrap(base.getParent()); + } + + @Override + public boolean isAbsolute() { + return base.isAbsolute(); + } + + @Override + public boolean isDirectory() { + return base.isDirectory(); + } + + @Override + public long lastModified() { + return base.lastModified(); + } + + @Override + public FilePath toRealPath() { + return wrap(base.toRealPath()); + } + + @Override + public List newDirectoryStream() { + List list = base.newDirectoryStream(); + for (int i = 0, len = list.size(); i < len; i++) { + list.set(i, wrap(list.get(i))); + } + return list; + } + + @Override + public void moveTo(FilePath newName) { + base.moveTo(((FilePathWrapper) newName).base); + } + + @Override + public InputStream newInputStream() throws IOException { + return base.newInputStream(); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + return base.newOutputStream(append); + } + + @Override + public FileChannel open(String mode) throws IOException { + return base.open(mode); + } + + @Override + public boolean setReadOnly() { + return base.setReadOnly(); + } + + @Override + public long size() { + return base.size(); + } + + @Override + public FilePath createTempFile(String suffix, boolean deleteOnExit) throws IOException { + return wrap(base.createTempFile(suffix, deleteOnExit)); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/fs/FileUtils.java b/src/main/java/io/mycat/backend/mysql/store/fs/FileUtils.java new file mode 100644 index 000000000..b5ec9eee6 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/fs/FileUtils.java @@ -0,0 +1,376 @@ +package io.mycat.backend.mysql.store.fs; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +/** + * @author zhangyaohua + * @createTime 2013-11-11 + */ +public class FileUtils { + /** + * Checks if a file exists. This method is similar to Java 7 + * java.nio.file.Path.exists. + * + * @param fileName + * the file name + * @return true if it exists + */ + public static boolean exists(String fileName) { + return FilePath.get(fileName).exists(); + } + + /** + * Create a directory (all required parent directories must already exist). + * This method is similar to Java 7 + * java.nio.file.Path.createDirectory. + * + * @param directoryName + * the directory name + */ + public static void createDirectory(String directoryName) { + FilePath.get(directoryName).createDirectory(); + } + + /** + * Create a new file. This method is similar to Java 7 + * java.nio.file.Path.createFile, but returns false instead of + * throwing a exception if the file already existed. + * + * @param fileName + * the file name + * @return true if creating was successful + */ + public static boolean createFile(String fileName) { + return FilePath.get(fileName).createFile(); + } + + /** + * Delete a file or directory if it exists. Directories may only be deleted + * if they are empty. This method is similar to Java 7 + * java.nio.file.Path.deleteIfExists. + * + * @param path + * the file or directory name + */ + public static void delete(String path) { + FilePath.get(path).delete(); + } + + /** + * Get the canonical file or directory name. This method is similar to Java + * 7 java.nio.file.Path.toRealPath. + * + * @param fileName + * the file name + * @return the normalized file name + */ + public static String toRealPath(String fileName) { + return FilePath.get(fileName).toRealPath().toString(); + } + + /** + * Get the parent directory of a file or directory. This method returns null + * if there is no parent. This method is similar to Java 7 + * java.nio.file.Path.getParent. + * + * @param fileName + * the file or directory name + * @return the parent directory name + */ + public static String getParent(String fileName) { + FilePath p = FilePath.get(fileName).getParent(); + return p == null ? null : p.toString(); + } + + /** + * Check if the file name includes a path. This method is similar to Java 7 + * java.nio.file.Path.isAbsolute. + * + * @param fileName + * the file name + * @return if the file name is absolute + */ + public static boolean isAbsolute(String fileName) { + return FilePath.get(fileName).isAbsolute(); + } + + /** + * Rename a file if this is allowed. This method is similar to Java 7 + * java.nio.file.Path.moveTo. + * + * @param oldName + * the old fully qualified file name + * @param newName + * the new fully qualified file name + */ + public static void moveTo(String oldName, String newName) { + FilePath.get(oldName).moveTo(FilePath.get(newName)); + } + + /** + * Get the file or directory name (the last element of the path). This + * method is similar to Java 7 java.nio.file.Path.getName. + * + * @param path + * the directory and file name + * @return just the file name + */ + public static String getName(String path) { + return FilePath.get(path).getName(); + } + + /** + * List the files and directories in the given directory. This method is + * similar to Java 7 java.nio.file.Path.newDirectoryStream. + * + * @param path + * the directory + * @return the list of fully qualified file names + */ + public static List newDirectoryStream(String path) { + List list = FilePath.get(path).newDirectoryStream(); + int len = list.size(); + List result = new ArrayList(len); + for (int i = 0; i < len; i++) { + result.add(list.get(i).toString()); + } + return result; + } + + /** + * Get the last modified date of a file. This method is similar to Java 7 + * java.nio.file.attribute.Attributes. + * readBasicFileAttributes(file).lastModified().toMillis() + * + * @param fileName + * the file name + * @return the last modified date + */ + public static long lastModified(String fileName) { + return FilePath.get(fileName).lastModified(); + } + + /** + * Get the size of a file in bytes This method is similar to Java 7 + * java.nio.file.attribute.Attributes. + * readBasicFileAttributes(file).size() + * + * @param fileName + * the file name + * @return the size in bytes + */ + public static long size(String fileName) { + return FilePath.get(fileName).size(); + } + + /** + * Check if it is a file or a directory. + * java.nio.file.attribute.Attributes. + * readBasicFileAttributes(file).isDirectory() + * + * @param fileName + * the file or directory name + * @return true if it is a directory + */ + public static boolean isDirectory(String fileName) { + return FilePath.get(fileName).isDirectory(); + } + + /** + * Open a random access file object. This method is similar to Java 7 + * java.nio.channels.FileChannel.open. + * + * @param fileName + * the file name + * @param mode + * the access mode. Supported are r, rw, rws, rwd + * @return the file object + */ + public static FileChannel open(String fileName, String mode) throws IOException { + return FilePath.get(fileName).open(mode); + } + + /** + * Create an input stream to read from the file. This method is similar to + * Java 7 java.nio.file.Path.newInputStream. + * + * @param fileName + * the file name + * @return the input stream + */ + public static InputStream newInputStream(String fileName) throws IOException { + return FilePath.get(fileName).newInputStream(); + } + + /** + * Create an output stream to write into the file. This method is similar to + * Java 7 java.nio.file.Path.newOutputStream. + * + * @param fileName + * the file name + * @param append + * if true, the file will grow, if false, the file will be + * truncated first + * @return the output stream + */ + public static OutputStream newOutputStream(String fileName, boolean append) throws IOException { + return FilePath.get(fileName).newOutputStream(append); + } + + /** + * Check if the file is writable. This method is similar to Java 7 + * java.nio.file.Path.checkAccess(AccessMode.WRITE) + * + * @param fileName + * the file name + * @return if the file is writable + */ + public static boolean canWrite(String fileName) { + return FilePath.get(fileName).canWrite(); + } + + // special methods ======================================= + + /** + * Disable the ability to write. The file can still be deleted afterwards. + * + * @param fileName + * the file name + * @return true if the call was successful + */ + public static boolean setReadOnly(String fileName) { + return FilePath.get(fileName).setReadOnly(); + } + + /** + * Get the unwrapped file name (without wrapper prefixes if wrapping / + * delegating file systems are used). + * + * @param fileName + * the file name + * @return the unwrapped + */ + public static String unwrap(String fileName) { + return FilePath.get(fileName).unwrap().toString(); + } + + // utility methods ======================================= + + /** + * Delete a directory or file and all subdirectories and files. + * + * @param path + * the path + * @param tryOnly + * whether errors should be ignored + */ + public static void deleteRecursive(String path, boolean tryOnly) { + if (exists(path)) { + if (isDirectory(path)) { + for (String s : newDirectoryStream(path)) { + deleteRecursive(s, tryOnly); + } + } + if (tryOnly) { + tryDelete(path); + } else { + delete(path); + } + } + } + + /** + * Create the directory and all required parent directories. + * + * @param dir + * the directory name + */ + public static void createDirectories(String dir) { + if (dir != null) { + if (exists(dir)) { + if (!isDirectory(dir)) { + // this will fail + createDirectory(dir); + } + } else { + String parent = getParent(dir); + createDirectories(parent); + createDirectory(dir); + } + } + } + + /** + * Try to delete a file (ignore errors). + * + * @param fileName + * the file name + * @return true if it worked + */ + public static boolean tryDelete(String fileName) { + try { + FilePath.get(fileName).delete(); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Create a new temporary file. + * + * @param prefix + * the prefix of the file name (including directory name if + * required) + * @param suffix + * the suffix + * @param deleteOnExit + * if the file should be deleted when the virtual machine exists + * @param inTempDir + * if the file should be stored in the temporary directory + * @return the name of the created file + */ + public static String createTempFile(String prefix, String suffix, boolean deleteOnExit) throws IOException { + return FilePath.get(prefix).createTempFile(suffix, deleteOnExit).toString(); + } + + /** + * Fully read from the file. This will read all remaining bytes, or throw an + * EOFException if not successful. + * + * @param channel + * the file channel + * @param dst + * the byte buffer + */ + public static void readFully(FileChannel channel, ByteBuffer dst) throws IOException { + do { + int r = channel.read(dst); + if (r < 0) { + throw new EOFException(); + } + } while (dst.remaining() > 0); + } + + /** + * Fully write to the file. This will write all remaining bytes. + * + * @param channel + * the file channel + * @param src + * the byte buffer + */ + public static void writeFully(FileChannel channel, ByteBuffer src) throws IOException { + do { + channel.write(src); + } while (src.remaining() > 0); + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/memalloc/MemSizeController.java b/src/main/java/io/mycat/backend/mysql/store/memalloc/MemSizeController.java new file mode 100644 index 000000000..df02c8d0c --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/memalloc/MemSizeController.java @@ -0,0 +1,55 @@ +package io.mycat.backend.mysql.store.memalloc; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * 内存使用大小控制器 + * + * @author chenzifei + * @CreateTime 2016年1月19日 + */ +public class MemSizeController { + // 如果剩余空间小于当前最小剩余空间时,也认为整个size控制器已经到达极限 + private static long minLeft = 32; + // 当前内存大小 + private AtomicLong size; + private long maxSize; + + public MemSizeController(long maxSize) { + this.size = new AtomicLong(); + this.maxSize = maxSize; + } + + /** + * 增加了大小 + * + * @param incre + * @return 是否已经到达内存控制器极限,true:当前是ok的, false:not ok, need flush to disk + */ + public boolean addSize(long incre) { + for (;;) { + long current = size.get(); + long next = current + incre; + if (size.compareAndSet(current, next)) { + if (next + minLeft >= maxSize) + return false; + else + return true; + } + } + } + + public void subSize(long decre) { + for (;;) { + long current = size.get(); + long next = current - decre; + if (next < 0) { + throw new RuntimeException("unexpected!"); + } + if (size.compareAndSet(current, next)) { + return; + } + } + } + +} diff --git a/src/main/java/io/mycat/backend/mysql/store/result/ResultExternal.java b/src/main/java/io/mycat/backend/mysql/store/result/ResultExternal.java new file mode 100644 index 000000000..b9aa4b707 --- /dev/null +++ b/src/main/java/io/mycat/backend/mysql/store/result/ResultExternal.java @@ -0,0 +1,80 @@ +package io.mycat.backend.mysql.store.result; + +import java.util.List; + +import io.mycat.net.mysql.RowDataPacket; + +public interface ResultExternal { + /** + * Reset the current position of this object. + */ + void reset(); + + /** + * Get the next row from the result. + * + * @return the next row or null + */ + RowDataPacket next(); + + /** + * Add a row to this object. + * + * @param values + * the row to add + * @return the new number of rows in this object + */ + int addRow(RowDataPacket row); + + /** + * Add a number of rows to the result. + * + * @param rows + * the list of rows to add + * @return the new number of rows in this object + */ + int addRows(List rows); + + /** + * This method is called after all rows have been added. + */ + void done(); + + /** + * Close this object and delete the temporary file. + */ + void close(); + + /** + * Remove the row with the given values from this object if such a row + * exists. + * + * @param values + * the row + * @return the new row count + */ + int removeRow(RowDataPacket row); + + /** + * Check if the given row exists in this object. + * + * @param values + * the row + * @return true if it exists + */ + boolean contains(RowDataPacket row); + + /** + * Create a shallow copy of this object if possible. + * + * @return the shallow copy, or null + */ + ResultExternal createShallowCopy(); + + /** + * count tapes split by resultExternal + * + * @return tape's count + */ + int TapeCount(); +} diff --git a/src/main/java/io/mycat/buffer/BufferPool.java b/src/main/java/io/mycat/buffer/BufferPool.java index e8715a372..a03aa7897 100644 --- a/src/main/java/io/mycat/buffer/BufferPool.java +++ b/src/main/java/io/mycat/buffer/BufferPool.java @@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; * @time 12:19 2016/5/23 */ public interface BufferPool { + ByteBuffer allocate(); public ByteBuffer allocate(int size); public void recycle(ByteBuffer theBuf); public long capacity(); diff --git a/src/main/java/io/mycat/buffer/ByteBufferArena.java b/src/main/java/io/mycat/buffer/ByteBufferArena.java deleted file mode 100644 index 87c74dabc..000000000 --- a/src/main/java/io/mycat/buffer/ByteBufferArena.java +++ /dev/null @@ -1,214 +0,0 @@ -package io.mycat.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -/** - * 仿照Netty的思路,针对MyCat内存缓冲策略优化 - * ByteBufferArena维护着锁还有所有list - * - * @author Hash Zhang - * @version 1.0 - * @time 17:19 2016/5/17 - * @see @https://github.com/netty/netty - */ -public class ByteBufferArena implements BufferPool { - private static final Logger LOGGER = LoggerFactory.getLogger(ByteBufferChunkList.class); - private final ByteBufferChunkList q[]; - - private final AtomicInteger chunkCount = new AtomicInteger(0); - private final AtomicInteger failCount = new AtomicInteger(0); - - private static final int FAIL_THRESHOLD = 1000; - private final int pageSize; - private final int chunkSize; - - private final AtomicLong capacity; - private final AtomicLong size; - - private final ConcurrentHashMap sharedOptsCount; - - /** - * 记录对线程ID->该线程的所使用Direct Buffer的size - */ - private final ConcurrentHashMap memoryUsage; - private final int conReadBuferChunk; - - public ByteBufferArena(int chunkSize, int pageSize, int chunkCount, int conReadBuferChunk) { - try { - this.chunkSize = chunkSize; - this.pageSize = pageSize; - this.chunkCount.set(chunkCount); - this.conReadBuferChunk = conReadBuferChunk; - - q = new ByteBufferChunkList[6]; - q[5] = new ByteBufferChunkList(100, Integer.MAX_VALUE, chunkSize, pageSize, 0); - q[4] = new ByteBufferChunkList(75, 100, chunkSize, pageSize, 0); - q[3] = new ByteBufferChunkList(50, 100, chunkSize, pageSize, 0); - q[2] = new ByteBufferChunkList(25, 75, chunkSize, pageSize, 0); - q[1] = new ByteBufferChunkList(1, 50, chunkSize, pageSize, 0); - q[0] = new ByteBufferChunkList(Integer.MIN_VALUE, 25, chunkSize, pageSize, chunkCount); - - q[0].nextList = q[1]; - q[1].nextList = q[2]; - q[2].nextList = q[3]; - q[3].nextList = q[4]; - q[4].nextList = q[5]; - q[5].nextList = null; - - q[5].prevList = q[4]; - q[4].prevList = q[3]; - q[3].prevList = q[2]; - q[2].prevList = q[1]; - q[1].prevList = q[0]; - q[0].prevList = null; - - capacity = new AtomicLong(6 * chunkCount * chunkSize); - size = new AtomicLong(6 * chunkCount * chunkSize); - sharedOptsCount = new ConcurrentHashMap<>(); - memoryUsage = new ConcurrentHashMap<>(); - } finally { - } - } - - @Override - public ByteBuffer allocate(int reqCapacity) { - try { - ByteBuffer byteBuffer = null; - int i = 0, count = 0; - while (byteBuffer == null) { - if (i > 5) { - i = 0; - count = failCount.incrementAndGet(); - if (count > FAIL_THRESHOLD) { - try { - expand(); - } finally { - } - } - } - byteBuffer = q[i].allocate(reqCapacity); - i++; - } -// if (count > 0) { -// System.out.println("count: " + count); -// System.out.println(failCount.get()); -// } -// printList(); - capacity.addAndGet(-reqCapacity); - final Thread thread = Thread.currentThread(); - final long threadId = thread.getId(); - - if (memoryUsage.containsKey(threadId)){ - memoryUsage.put(threadId,memoryUsage.get(thread.getId())+reqCapacity); - }else { - memoryUsage.put(threadId, (long) reqCapacity); - } - if (sharedOptsCount.contains(thread)) { - int currentCount = sharedOptsCount.get(thread); - currentCount++; - sharedOptsCount.put(thread,currentCount); - } else{ - sharedOptsCount.put(thread,0); - } - return byteBuffer; - } finally { - } - } - - private void expand() { - LOGGER.warn("Current Buffer Size is not enough! Expanding Byte buffer!"); - ByteBufferChunk byteBufferChunk = new ByteBufferChunk(pageSize, chunkSize); - q[0].byteBufferChunks.add(byteBufferChunk); - failCount.set(0); - } - - @Override - public void recycle(ByteBuffer byteBuffer) { - final long size = byteBuffer != null?byteBuffer.capacity():0; - try { - int i; - for (i = 0; i < 6; i++) { - if (q[i].free(byteBuffer)) { - break; - } - } - if (i > 5) { - LOGGER.warn("This ByteBuffer is not maintained in ByteBufferArena!"); - return; - } - final Thread thread = Thread.currentThread(); - final long threadId = thread.getId(); - - if (memoryUsage.containsKey(threadId)){ - memoryUsage.put(threadId,memoryUsage.get(thread.getId())-size); - } - if (sharedOptsCount.contains(thread)) { - int currentCount = sharedOptsCount.get(thread); - currentCount--; - sharedOptsCount.put(thread,currentCount); - } else{ - sharedOptsCount.put(thread,0); - } - capacity.addAndGet(byteBuffer.capacity()); - return; - } finally { - } - } - - private void printList() { - for (int i = 0; i < 6; i++) { - System.out.println(i + ":" + q[i].byteBufferChunks.toString()); - } - } - - @Override - public long capacity() { - return capacity.get(); - } - - @Override - public long size() { - return size.get(); - } - - @Override - public int getConReadBuferChunk() { - return conReadBuferChunk; - } - - @Override - public int getSharedOptsCount() { - final Set integers = (Set) sharedOptsCount.values(); - int count = 0; - for(int i : integers){ - count += i; - } - return count; - } - - /** - * 这里pageSize就是DirectByteBuffer的chunksize - * @return - */ - @Override - public int getChunkSize() { - return pageSize; - } - - @Override - public ConcurrentHashMap getNetDirectMemoryUsage() { - return memoryUsage; - } - - @Override - public BufferArray allocateArray() { - return new BufferArray(this); - } -} diff --git a/src/main/java/io/mycat/buffer/DirectByteBufferPool.java b/src/main/java/io/mycat/buffer/DirectByteBufferPool.java index 747b8237e..7ac8bbe37 100644 --- a/src/main/java/io/mycat/buffer/DirectByteBufferPool.java +++ b/src/main/java/io/mycat/buffer/DirectByteBufferPool.java @@ -65,7 +65,9 @@ public class DirectByteBufferPool implements BufferPool{ } return null; } - + public ByteBuffer allocate() { + return allocate(chunkSize); + } public ByteBuffer allocate(int size) { final int theChunkCount = size / chunkSize + (size % chunkSize == 0 ? 0 : 1); int selectedPage = prevAllocatedPage.incrementAndGet() % allPages.length; diff --git a/src/main/java/io/mycat/config/ErrorCode.java b/src/main/java/io/mycat/config/ErrorCode.java index 2bd3f3cd8..dfebd22b1 100644 --- a/src/main/java/io/mycat/config/ErrorCode.java +++ b/src/main/java/io/mycat/config/ErrorCode.java @@ -42,6 +42,19 @@ public interface ErrorCode { public static final int ERR_MULTI_NODE_FAILED = 3011; public static final int ERR_WRONG_USED = 3012; public static final int ERR_FOUND_EXCEPION = 3344; + public static final int ER_HANDLE_DATA = 4002; + public static final int ER_OPTIMIZER = 4004; + public static final int ER_QUERYHANDLER = 4005; + public static final int ER_NO_VALID_CONNECTION = 5004; + public static final int ER_FILE_INIT = 5301; + public static final int ER_FILE_FORCE = 5302; + public static final int ER_FILE_SYNC = 5303; + public static final int ER_FILE_READ = 5304; + public static final int ER_FILE_WRITE = 5305; + public static final int ER_FILE_RENAME = 5306; + public static final int ER_FILE_DELETE = 5307; + public static final int ER_IO_EXCEPTION = 5308; + public static final int ER_FILE_CREATE = 5313; // mysql error code public static final int ER_HASHCHK = 1000; public static final int ER_NISAMCHK = 1001; diff --git a/src/main/java/io/mycat/config/loader/xml/XMLSchemaLoader.java b/src/main/java/io/mycat/config/loader/xml/XMLSchemaLoader.java index c41a8bd51..ef4133536 100644 --- a/src/main/java/io/mycat/config/loader/xml/XMLSchemaLoader.java +++ b/src/main/java/io/mycat/config/loader/xml/XMLSchemaLoader.java @@ -47,6 +47,7 @@ import io.mycat.config.model.DataHostConfig; import io.mycat.config.model.DataNodeConfig; import io.mycat.config.model.SchemaConfig; import io.mycat.config.model.TableConfig; +import io.mycat.config.model.TableConfig.TableTypeEnum; import io.mycat.config.model.TableConfigMap; import io.mycat.config.model.rule.TableRuleConfig; import io.mycat.config.util.ConfigException; @@ -150,7 +151,6 @@ public class XMLSchemaLoader implements SchemaLoader { //读取各个属性 String name = schemaElement.getAttribute("name"); String dataNode = schemaElement.getAttribute("dataNode"); - String checkSQLSchemaStr = schemaElement.getAttribute("checkSQLschema"); String sqlMaxLimitStr = schemaElement.getAttribute("sqlMaxLimit"); String lowerCaseStr = schemaElement.getAttribute("lowerCase"); int sqlMaxLimit = -1; @@ -186,7 +186,7 @@ public class XMLSchemaLoader implements SchemaLoader { "schema " + name + " didn't config tables,so you must set dataNode property!"); } SchemaConfig schemaConfig = new SchemaConfig(name, dataNode, - tables, sqlMaxLimit, !"false".equalsIgnoreCase(checkSQLSchemaStr), lowerCase); + tables, sqlMaxLimit, lowerCase); schemas.put(name, schemaConfig); } } @@ -312,9 +312,9 @@ public class XMLSchemaLoader implements SchemaLoader { } //记录type,是否为global String tableTypeStr = tableElement.hasAttribute("type") ? tableElement.getAttribute("type") : null; - int tableType = TableConfig.TYPE_GLOBAL_DEFAULT; + TableTypeEnum tableType = TableTypeEnum.TYPE_DEFAULT; if ("global".equalsIgnoreCase(tableTypeStr)) { - tableType = TableConfig.TYPE_GLOBAL_TABLE; + tableType = TableTypeEnum.TYPE_GLOBAL_TABLE; } //记录dataNode,就是分布在哪些dataNode上 String dataNode = tableElement.getAttribute("dataNode"); @@ -342,8 +342,6 @@ public class XMLSchemaLoader implements SchemaLoader { if (distTableDns) { dataNode = dataNode.substring(distPrex.length(), dataNode.length() - 1); } - //分表功能 - String subTables = tableElement.getAttribute("subTables"); for (int j = 0; j < tableNames.length; j++) { @@ -353,7 +351,7 @@ public class XMLSchemaLoader implements SchemaLoader { TableConfig table = new TableConfig(tableName, primaryKey, autoIncrement, needAddLimit, tableType, dataNode, (tableRule != null) ? tableRule.getRule() : null, - ruleRequired, null, false, null, null,subTables); + ruleRequired, null, false, null, null); checkDataNodeExists(table.getDataNodes()); // 检查分片表分片规则配置是否合法 @@ -441,14 +439,13 @@ public class XMLSchemaLoader implements SchemaLoader { if (childTbElement.hasAttribute("needAddLimit")) { needAddLimit = Boolean.parseBoolean(childTbElement.getAttribute("needAddLimit")); } - String subTables = childTbElement.getAttribute("subTables"); //子表join键,和对应的parent的键,父子表通过这个关联 String joinKey = childTbElement.getAttribute("joinKey").toUpperCase(); String parentKey = childTbElement.getAttribute("parentKey").toUpperCase(); TableConfig table = new TableConfig(cdTbName, primaryKey, autoIncrement, needAddLimit, - TableConfig.TYPE_GLOBAL_DEFAULT, dataNodes, null, false, parentTable, true, - joinKey, parentKey, subTables); + TableTypeEnum.TYPE_DEFAULT, dataNodes, null, false, parentTable, true, + joinKey, parentKey); if (tables.containsKey(table.getName())) { throw new ConfigException("table " + table.getName() + " duplicated!"); diff --git a/src/main/java/io/mycat/config/model/ERTable.java b/src/main/java/io/mycat/config/model/ERTable.java new file mode 100644 index 000000000..0932ede58 --- /dev/null +++ b/src/main/java/io/mycat/config/model/ERTable.java @@ -0,0 +1,54 @@ +package io.mycat.config.model; + +public class ERTable { + private final String table; + private final String column; + private final String schema; + + public ERTable(String schema, String table, String column) { + if (schema == null) + throw new IllegalArgumentException("ERTable's schema can't be null"); + this.schema = schema; + if (table == null) + throw new IllegalArgumentException("ERTable's tableName can't be null"); + this.table = table; + if (column == null) + throw new IllegalArgumentException("ERTable's column can't be null"); + this.column = column; + } + + public String getTable() { + return table; + } + + public String getColumn() { + return column; + } + + public String getSchema() { + return schema; + } + + @Override + public int hashCode() { + final int constant = 37; + int hash = 17; + hash += constant * (schema == null ? 0 : schema.toLowerCase().hashCode()); + hash += constant * (table == null ? 0 : table.toLowerCase().hashCode()); + hash += constant * (column == null ? 0 : column.toLowerCase().hashCode()); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof ERTable) { + ERTable erTable = (ERTable) obj; + return this.schema.equalsIgnoreCase(erTable.getSchema()) + && this.table.equalsIgnoreCase(erTable.getTable()) + && this.column.equalsIgnoreCase(erTable.getColumn()); + } + return false; + } +} diff --git a/src/main/java/io/mycat/config/model/SchemaConfig.java b/src/main/java/io/mycat/config/model/SchemaConfig.java index 63431125c..0665ac3fa 100644 --- a/src/main/java/io/mycat/config/model/SchemaConfig.java +++ b/src/main/java/io/mycat/config/model/SchemaConfig.java @@ -45,7 +45,6 @@ public class SchemaConfig { * prevent memory problem when return a large result set */ private final int defaultMaxLimit; - private final boolean checkSQLSchema; private final int lowerCase; /** @@ -56,11 +55,9 @@ public class SchemaConfig { private final String[] allDataNodeStrArr; public SchemaConfig(String name, String dataNode, - Map tables, int defaultMaxLimit, - boolean checkSQLschema, int lowerCase) { + Map tables, int defaultMaxLimit, int lowerCase) { this.name = name; this.dataNode = dataNode; - this.checkSQLSchema = checkSQLschema; this.tables = tables; this.defaultMaxLimit = defaultMaxLimit; this.lowerCase = lowerCase; @@ -81,9 +78,9 @@ public class SchemaConfig { } } - public boolean isCheckSQLSchema() { - return checkSQLSchema; - } +// public boolean isCheckSQLSchema() { +// return checkSQLSchema; +// } public int getDefaultMaxLimit() { return defaultMaxLimit; diff --git a/src/main/java/io/mycat/config/model/SystemConfig.java b/src/main/java/io/mycat/config/model/SystemConfig.java index 6693ba509..73eaba8b2 100644 --- a/src/main/java/io/mycat/config/model/SystemConfig.java +++ b/src/main/java/io/mycat/config/model/SystemConfig.java @@ -80,9 +80,15 @@ public final class SystemConfig { private static final String DEFAULT_TRANSACTION_BASE_DIR = "txlogs"; private static final String DEFAULT_TRANSACTION_BASE_NAME = "mycat-tx"; private static final int DEFAULT_TRANSACTION_ROTATE_SIZE = 16; - private final static long CHECKTABLECONSISTENCYPERIOD = 30 * 60 * 1000; + private static final long CHECKTABLECONSISTENCYPERIOD = 30 * 60 * 1000; // 全局表一致性检测任务,默认24小时调度一次 private static final long DEFAULT_GLOBAL_TABLE_CHECK_PERIOD = 24 * 60 * 60 * 1000L; + private static final int DEFAULT_MERGE_QUEUE_SIZE = 1024; + private static final int DEFAULT_ORDERBY_QUEUE_SIZE = 1024; + private static final int DEFAULT_JOIN_QUEUE_SIZE = 1024; + private static final int DEFAULT_NESTLOOP_ROWS_SIZE = 2000; + private static final int DEFAULT_NESTLOOP_CONN_SIZE = 4; + private static final int DEFAULT_MAPPEDFILE_SIZE = 1024 * 1024 * 64; private int processorBufferPoolType = 0; private int processorBufferLocalPercent; @@ -197,6 +203,13 @@ public final class SystemConfig { private String transactionLogBaseDir; private String transactionLogBaseName; private int transactionRatateSize; + + private int mergeQueueSize; + private int orderByQueueSize; + private int joinQueueSize; + private int nestLoopRowsSize; + private int nestLoopConnSize; + private int mappedFileSize; /** * 排序时,内存不够时,将已经排序的结果集 * 写入到临时目录 @@ -250,6 +263,12 @@ public final class SystemConfig { this.transactionLogBaseDir = SystemConfig.getHomePath()+File.separatorChar+DEFAULT_TRANSACTION_BASE_DIR; this.transactionLogBaseName = DEFAULT_TRANSACTION_BASE_NAME; this.transactionRatateSize = DEFAULT_TRANSACTION_ROTATE_SIZE; + this.mergeQueueSize = DEFAULT_MERGE_QUEUE_SIZE; + this.orderByQueueSize = DEFAULT_ORDERBY_QUEUE_SIZE; + this.joinQueueSize = DEFAULT_JOIN_QUEUE_SIZE; + this.nestLoopRowsSize = DEFAULT_NESTLOOP_ROWS_SIZE; + this.nestLoopConnSize = DEFAULT_NESTLOOP_CONN_SIZE; + this.mappedFileSize = DEFAULT_MAPPEDFILE_SIZE; } public int getTransactionRatateSize() { @@ -949,6 +968,45 @@ public final class SystemConfig { public void setUseHandshakeV10(int useHandshakeV10) { this.useHandshakeV10 = useHandshakeV10; } - - + public int getNestLoopRowsSize() { + return nestLoopRowsSize; + } + + public void setNestLoopRowsSize(int nestLoopRowsSize) { + this.nestLoopRowsSize = nestLoopRowsSize; + } + public int getJoinQueueSize() { + return joinQueueSize; + } + public void setJoinQueueSize(int joinQueueSize) { + this.joinQueueSize = joinQueueSize; + } + public int getMergeQueueSize() { + return mergeQueueSize; + } + + public void setMergeQueueSize(int mergeQueueSize) { + this.mergeQueueSize = mergeQueueSize; + } + public int getMappedFileSize() { + return mappedFileSize; + } + + public void setMappedFileSize(int mappedFileSize) { + this.mappedFileSize = mappedFileSize; + } + public int getNestLoopConnSize() { + return nestLoopConnSize; + } + + public void setNestLoopConnSize(int nestLoopConnSize) { + this.nestLoopConnSize = nestLoopConnSize; + } + public int getOrderByQueueSize() { + return orderByQueueSize; + } + + public void setOrderByQueueSize(int orderByQueueSize) { + this.orderByQueueSize = orderByQueueSize; + } } diff --git a/src/main/java/io/mycat/config/model/TableConfig.java b/src/main/java/io/mycat/config/model/TableConfig.java index d0724af08..37dd72294 100644 --- a/src/main/java/io/mycat/config/model/TableConfig.java +++ b/src/main/java/io/mycat/config/model/TableConfig.java @@ -38,15 +38,17 @@ import io.mycat.util.SplitUtil; * @author mycat */ public class TableConfig { - public static final int TYPE_GLOBAL_TABLE = 1; - public static final int TYPE_GLOBAL_DEFAULT = 0; + public enum TableTypeEnum{ + TYPE_DEFAULT, TYPE_GLOBAL_TABLE + } +// public static final int TYPE_GLOBAL_TABLE = 1; +// public static final int TYPE_GLOBAL_DEFAULT = 0; private final String name; private final String primaryKey; private final boolean autoIncrement; private final boolean needAddLimit; - private final int tableType; + private final TableTypeEnum tableType; private final ArrayList dataNodes; - private final ArrayList distTables; private final RuleConfig rule; private final String partitionColumn; private final boolean ruleRequired; @@ -66,10 +68,10 @@ public class TableConfig { private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false); - public TableConfig(String name, String primaryKey, boolean autoIncrement,boolean needAddLimit, int tableType, + public TableConfig(String name, String primaryKey, boolean autoIncrement,boolean needAddLimit, TableTypeEnum tableType, String dataNode, RuleConfig rule, boolean ruleRequired, TableConfig parentTC, boolean isChildTable, String joinKey, - String parentKey,String subTables) { + String parentKey) { if (name == null) { throw new IllegalArgumentException("table name is null"); } else if (dataNode == null) { @@ -94,19 +96,6 @@ public class TableConfig { dataNodes.add(dn); } - if(subTables!=null && !subTables.equals("")){ - String sTables[] = SplitUtil.split(subTables, ',', '$', '-'); - if (sTables == null || sTables.length <= 0) { - throw new IllegalArgumentException("invalid table subTables"); - } - this.distTables = new ArrayList(sTables.length); - for (String table : sTables) { - distTables.add(table); - } - }else{ - this.distTables = new ArrayList(); - } - this.rule = rule; this.partitionColumn = (rule == null) ? null : rule.getColumn(); partionKeyIsPrimaryKey=(partitionColumn==null)?primaryKey==null:partitionColumn.equals(primaryKey); @@ -145,7 +134,7 @@ public class TableConfig { } public boolean isGlobalTable() { - return this.tableType == TableConfig.TYPE_GLOBAL_TABLE; + return this.tableType == TableTypeEnum.TYPE_GLOBAL_TABLE; } public String genLocateRootParentSQL() { @@ -187,7 +176,7 @@ public class TableConfig { return partitionColumn; } - public int getTableType() { + public TableTypeEnum getTableType() { return tableType; } @@ -254,17 +243,6 @@ public class TableConfig { return partionKeyIsPrimaryKey; } - public ArrayList getDistTables() { - return this.distTables; - } - - public boolean isDistTable(){ - if(this.distTables!=null && !this.distTables.isEmpty() ){ - return true; - } - return false; - } - public List getTableElementList() { return tableElementList; } diff --git a/src/main/java/io/mycat/memory/environment/Hardware.java b/src/main/java/io/mycat/memory/environment/Hardware.java index f4b559647..42a9f77ef 100644 --- a/src/main/java/io/mycat/memory/environment/Hardware.java +++ b/src/main/java/io/mycat/memory/environment/Hardware.java @@ -18,9 +18,6 @@ package io.mycat.memory.environment; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; @@ -28,6 +25,9 @@ import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Convenience class to extract hardware specifics of the computer executing this class */ @@ -38,6 +38,7 @@ public class Hardware { private static final String LINUX_MEMORY_INFO_PATH = "/proc/meminfo"; private static final Pattern LINUX_MEMORY_REGEX = Pattern.compile("^MemTotal:\\s*(\\d+)\\s+kB$"); + private static final Pattern LINUX__FREE_MEMORY_REGEX = Pattern.compile("^MemFree:\\s*(\\d+)\\s+kB$"); @@ -110,6 +111,37 @@ public class Hardware { return -1; } } + + /** + * Returns the size of the free physical memory in bytes on a Linux-based + * operating system. + * + * @return the size of the free physical memory in bytes or -1 if + * the size could not be determined + */ + public static long getFreeSizeOfPhysicalMemoryForLinux() { + try (BufferedReader lineReader = new BufferedReader(new FileReader(LINUX_MEMORY_INFO_PATH))) { + String line; + while ((line = lineReader.readLine()) != null) { + Matcher matcher = LINUX__FREE_MEMORY_REGEX.matcher(line); + if (matcher.matches()) { + String totalMemory = matcher.group(1); + return Long.parseLong(totalMemory) * 1024L; // Convert from kilobyte to byte + } + } + // expected line did not come + LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'). Unexpected format."); + return -1; + } + catch (NumberFormatException e) { + LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'). Unexpected format."); + return -1; + } + catch (Throwable t) { + LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'): " + t.getMessage(), t); + return -1; + } + } /** * Returns the size of the physical memory in bytes on a Mac OS-based diff --git a/src/main/java/io/mycat/net/mysql/BinaryRowDataPacket.java b/src/main/java/io/mycat/net/mysql/BinaryRowDataPacket.java index 77a906044..dad9efc09 100644 --- a/src/main/java/io/mycat/net/mysql/BinaryRowDataPacket.java +++ b/src/main/java/io/mycat/net/mysql/BinaryRowDataPacket.java @@ -7,6 +7,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.mycat.backend.mysql.BufferUtil; import io.mycat.config.Fields; import io.mycat.memory.unsafe.row.UnsafeRow; @@ -14,9 +17,6 @@ import io.mycat.net.FrontendConnection; import io.mycat.util.ByteUtil; import io.mycat.util.DateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * ProtocolBinary::ResultsetRow: * row of a binary resultset (COM_STMT_EXECUTE) @@ -254,7 +254,7 @@ public class BinaryRowDataPacket extends MySQLPacket { bb = conn.getProcessor().getBufferPool().allocate(totalSize); - BufferUtil.writeUB3(bb, calcPacketSize()); + BufferUtil.writeUB3(bb, size); bb.put(packetId); bb.put(packetHeader); // packet header [00] bb.put(nullBitMap); // NULL-Bitmap diff --git a/src/main/java/io/mycat/net/mysql/EOFPacket.java b/src/main/java/io/mycat/net/mysql/EOFPacket.java index c2ba29e6f..b374da91e 100644 --- a/src/main/java/io/mycat/net/mysql/EOFPacket.java +++ b/src/main/java/io/mycat/net/mysql/EOFPacket.java @@ -25,6 +25,7 @@ package io.mycat.net.mysql; import java.nio.ByteBuffer; +import io.mycat.MycatServer; import io.mycat.backend.mysql.BufferUtil; import io.mycat.backend.mysql.MySQLMessage; import io.mycat.buffer.BufferArray; @@ -95,5 +96,18 @@ public class EOFPacket extends MySQLPacket { BufferUtil.writeUB2(buffer, warningCount); BufferUtil.writeUB2(buffer, status); } - + public byte[] toBytes() { + int size = calcPacketSize(); + ByteBuffer buffer = MycatServer.getInstance().getBufferPool().allocate(size + packetHeaderSize); + BufferUtil.writeUB3(buffer, size); + buffer.put(packetId); + buffer.put(fieldCount); + BufferUtil.writeUB2(buffer, warningCount); + BufferUtil.writeUB2(buffer, status); + buffer.flip(); + byte[] data = new byte[buffer.limit()]; + buffer.get(data); + MycatServer.getInstance().getBufferPool().recycle(buffer); + return data; + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/net/mysql/ErrorPacket.java b/src/main/java/io/mycat/net/mysql/ErrorPacket.java index 6a67a7180..b6a138386 100644 --- a/src/main/java/io/mycat/net/mysql/ErrorPacket.java +++ b/src/main/java/io/mycat/net/mysql/ErrorPacket.java @@ -94,8 +94,8 @@ public class ErrorPacket extends MySQLPacket { return data; } public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(calcPacketSize()+4); int size = calcPacketSize(); + ByteBuffer buffer = MycatServer.getInstance().getBufferPool().allocate(size + packetHeaderSize); BufferUtil.writeUB3(buffer, size); buffer.put(packetId); buffer.put(fieldCount); diff --git a/src/main/java/io/mycat/net/mysql/OkPacket.java b/src/main/java/io/mycat/net/mysql/OkPacket.java index 674bc0117..0ea89ebfe 100644 --- a/src/main/java/io/mycat/net/mysql/OkPacket.java +++ b/src/main/java/io/mycat/net/mysql/OkPacket.java @@ -141,9 +141,9 @@ public class OkPacket extends MySQLPacket { } public byte[] toBytes() { - int totalSize = calcPacketSize() + packetHeaderSize; - ByteBuffer buffer = MycatServer.getInstance().getBufferPool().allocate(totalSize); - BufferUtil.writeUB3(buffer, calcPacketSize()); + int size = calcPacketSize(); + ByteBuffer buffer = MycatServer.getInstance().getBufferPool().allocate(size + packetHeaderSize); + BufferUtil.writeUB3(buffer, size); buffer.put(packetId); buffer.put(fieldCount); BufferUtil.writeLength(buffer, affectedRows); diff --git a/src/main/java/io/mycat/net/mysql/RowDataPacket.java b/src/main/java/io/mycat/net/mysql/RowDataPacket.java index 3de6c5b60..054ff8685 100644 --- a/src/main/java/io/mycat/net/mysql/RowDataPacket.java +++ b/src/main/java/io/mycat/net/mysql/RowDataPacket.java @@ -27,6 +27,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import io.mycat.MycatServer; import io.mycat.backend.mysql.BufferUtil; import io.mycat.backend.mysql.MySQLMessage; import io.mycat.buffer.BufferArray; @@ -55,8 +56,8 @@ import io.mycat.net.FrontendConnection; * @author mycat */ public class RowDataPacket extends MySQLPacket { - private static final byte NULL_MARK = (byte) 251; - private static final byte EMPTY_MARK = (byte) 0; + protected static final byte NULL_MARK = (byte) 251; + protected static final byte EMPTY_MARK = (byte) 0; public byte[] value; public int fieldCount; @@ -75,7 +76,15 @@ public class RowDataPacket extends MySQLPacket { //这里应该修改field fieldCount=fieldCount+add; } - + public void addAll(List values) { + fieldValues.addAll(values); + } + public byte[] getValue(int index) { + return fieldValues.get(index); + } + public void setValue(int index, byte[] value) { + fieldValues.set(index, value); + } public void read(byte[] data) { value = data; MySQLMessage mm = new MySQLMessage(data); @@ -147,5 +156,25 @@ public class RowDataPacket extends MySQLPacket { } } } - + public byte[] toBytes() { + int size = calcPacketSize(); + ByteBuffer buffer = MycatServer.getInstance().getBufferPool().allocate(size + packetHeaderSize); + BufferUtil.writeUB3(buffer, size); + buffer.put(packetId); + for (int i = 0; i < fieldCount; i++) { + byte[] fv = fieldValues.get(i); + if (fv == null) { + buffer.put(RowDataPacket.NULL_MARK); + } else if (fv.length == 0) { + buffer.put(RowDataPacket.EMPTY_MARK); + } else { + BufferUtil.writeWithLength(buffer, fv); + } + } + buffer.flip(); + byte[] data = new byte[buffer.limit()]; + buffer.get(data); + MycatServer.getInstance().getBufferPool().recycle(buffer); + return data; + } } \ No newline at end of file diff --git a/src/main/java/io/mycat/plan/NamedField.java b/src/main/java/io/mycat/plan/NamedField.java new file mode 100644 index 000000000..5943cefdf --- /dev/null +++ b/src/main/java/io/mycat/plan/NamedField.java @@ -0,0 +1,48 @@ +package io.mycat.plan; + +import org.apache.commons.lang.StringUtils; + +public class NamedField { + public String table; + public String name; + // 这个field隶属于哪个节点 + public final PlanNode planNode; + + public NamedField(PlanNode tableNode) { + this(null, null, tableNode); + } + + public NamedField(String table, String name, PlanNode planNode) { + this.table = table; + this.name = name; + this.planNode = planNode; + } + + @Override + public int hashCode() { + int prime = 2; + int hashCode = table == null ? 0 : table.hashCode(); + hashCode = hashCode * prime + (name == null ? 0 : name.toLowerCase().hashCode()); + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (!(obj instanceof NamedField)) + return false; + NamedField other = (NamedField) obj; + if (StringUtils.equals(table, other.table) && StringUtils.equalsIgnoreCase(name, other.name)) + return true; + else + return false; + } + + @Override + public String toString() { + return new StringBuilder().append("table:").append(table).append(",name:").append(name).toString(); + } +} diff --git a/src/main/java/io/mycat/plan/Order.java b/src/main/java/io/mycat/plan/Order.java new file mode 100644 index 000000000..5b000f5cb --- /dev/null +++ b/src/main/java/io/mycat/plan/Order.java @@ -0,0 +1,48 @@ +/** + * + */ +package io.mycat.plan; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; + +import io.mycat.plan.common.item.Item; + +public class Order { + private Item item; + private SQLOrderingSpecification sortOrder; + + public Order(Item item) { + this(item, SQLOrderingSpecification.ASC); + } + + public Order(Item item, SQLOrderingSpecification sortOrder) { + this.item = item; + this.sortOrder = sortOrder; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public SQLOrderingSpecification getSortOrder() { + return sortOrder; + } + + public void setSortOrder(SQLOrderingSpecification sortOrder) { + this.sortOrder = sortOrder; + } + + @Override + public String toString() { + return "order by " + item.toString() + " " + sortOrder; + } + + public Order copy() { + return new Order(item.cloneStruct(), sortOrder); + } + +} diff --git a/src/main/java/io/mycat/plan/PlanNode.java b/src/main/java/io/mycat/plan/PlanNode.java new file mode 100644 index 000000000..145f9e16a --- /dev/null +++ b/src/main/java/io/mycat/plan/PlanNode.java @@ -0,0 +1,685 @@ +package io.mycat.plan; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.apache.log4j.Logger; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.plan.common.item.subquery.ItemSubselect; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.TableNode; + +public abstract class PlanNode { + private static final Logger logger = Logger.getLogger(PlanNode.class); + public enum PlanNodeType { + NONAME, TABLE, JOIN, MERGE, QUERY, VIEW + } + + public abstract PlanNodeType type(); + + protected String sql; + + private boolean isDistinct = false; + + /** + * select查询中的列 + */ + protected List columnsSelected = new ArrayList(); + + /** + * 显式的由查询接口指定的orderBy,注意需要保证顺序 + */ + protected List orderBys = new LinkedList(); + + /** + * 显式的由查询接口指定的group by,注意需要保证顺序 + */ + protected List groups = new LinkedList(); + + /** + * having条件 + */ + protected Item havingFilter; + /** + * 上一层父节点,比如子查询会依赖父节点的字段信息 + * http://dev.mysql.com/doc/refman/5.0/en/correlated-subqueries.html + */ + private PlanNode parent; + + /** + * 子节点 + */ + protected List children = new ArrayList(); + + /** + * 从哪里开始 + */ + protected long limitFrom = -1; + + /** + * 到哪里结束 + */ + protected long limitTo = -1; + + /** + * filter in where + */ + protected Item whereFilter = null; + + /** + * 当前tn的别名,用于进行join等操作的时候辨别到底这一行是从哪个subNode来的。 + */ + protected String alias; + + /** + * 如果出现subQuery,内外都存在别名时,内部的别名为subAlias,外部使用的别名为alias + * tablenode自身的这个Alias属性和基类TreeNode的alias属性的作用如下: 按照原本tddl的做法无法区分 select * + * from (select* from test1 t1) t当中的两个alias + * 当tablenode的tableAlias属性有值时,我们就把这个语句带上tableAlias下发下去 + */ + protected String subAlias; + + /** + * 当前节点是否为子查询 + */ + protected boolean subQuery; + + protected boolean exsitView = false; + + /** + * 聚合函数集合 + */ + public HashSet sumFuncs = new HashSet(); + + /** + * 子查询集合 + */ + public List subSelects = new ArrayList(); + + protected List referedTableNodes = new ArrayList(); + + // inner field -> child field + protected Map innerFields = new LinkedHashMap(); + + protected Map outerFields = new LinkedHashMap(); + + protected NameResolutionContext nameContext; + + protected ReferContext referContext; + + protected PlanNode() { + nameContext = new NameResolutionContext(); + referContext = new ReferContext(); + nameContext.setPlanNode(this); + referContext.setPlanNode(this); + } + + /** + * 依赖的所有列,保存的是childnode->iselectable + */ + protected LoadingCache> columnsReferedCache= CacheBuilder.newBuilder() + .build(new CacheLoader>() { + @Override + public List load(PlanNode tn) { + return new ArrayList(); + } + }); + + + private List columnsReferList = new ArrayList(); + + private boolean existUnPushDownGroup = false; + + /** + * 是否可全局表优化 ex. TableNode: isGlobaled当且仅当tablename是globaltablename + * MergeNode: always false; QueryNode: true <-->subchild is true JoinNode: + * 当且仅当所有子节点最多只有一个非globaltable + */ + protected boolean isGlobaled = false; + // 这个node涉及到的unglobal的表的个数 + protected int unGlobalTableCount = 0; + + protected List nestLoopFilters = null; + + public abstract String getPureName(); + + /* 当前节点的高度 */ + public abstract int getHeight(); + + public final String getCombinedName() { + if (this.getAlias() != null) { + return this.getAlias(); + } + if (this.getSubAlias() != null) { + return this.getSubAlias(); + } + return this.getPureName(); + } + + public PlanNode getChild() { + if (children.isEmpty()) + return null; + else + return children.get(0); + } + + public void addChild(PlanNode childNode) { + childNode.setParent(this); + this.children.add(childNode); + } + + public PlanNode select(List columnSelected) { + this.columnsSelected = columnSelected; + return this; + } + + public PlanNode groupBy(Item c, SQLOrderingSpecification sortOrder) { + Order order = new Order(c, sortOrder); + this.groups.add(order); + return this; + } + + public PlanNode orderBy(Item c, SQLOrderingSpecification sortOrder) { + Order order = new Order(c, sortOrder); + if (!this.orderBys.contains(order)) { + this.orderBys.add(order); + } + return this; + } + + public void setUpFields() { + sumFuncs.clear(); + setUpInnerFields(); + setUpSelects(); + setUpWhere(); + setUpGroupBy(); + setUpHaving(); + setUpOrderBy(); + } + + // column refered start + public void setUpRefers(boolean isPushDownNode) { + sumFuncs.clear(); + referContext.setPushDownNode(isPushDownNode); + // select + for (Item sel : columnsSelected) { + setUpItemRefer(sel); + } + if (type() == PlanNodeType.JOIN) { + JoinNode jn = (JoinNode) this; + if (!isPushDownNode) { + for (Item bf : jn.getJoinFilter()) + setUpItemRefer(bf); + setUpItemRefer(jn.getOtherJoinOnFilter()); + } + } + // where, pushdown node时无须where条件 + if (!isPushDownNode) { + setUpItemRefer(whereFilter); + } + // group by + for (Order groupBy : groups) { + setUpItemRefer(groupBy.getItem()); + } + // having + setUpItemRefer(havingFilter); + // order by + for (Order orderBy : orderBys) { + setUpItemRefer(orderBy.getItem()); + } + // make list + for (List selSet : columnsReferedCache.asMap().values()) { + columnsReferList.addAll(selSet); + } + } + + // ==================== helper method ================= + + public abstract PlanNode copy(); + + protected final void copySelfTo(PlanNode to) { + to.setAlias(this.alias); + to.setSubAlias(this.subAlias); + to.setDistinct(this.isDistinct); + for (Item selected : this.getColumnsSelected()) { + Item copySel = selected.cloneItem(); + copySel.setItemName(selected.getItemName()); + to.columnsSelected.add(copySel); + } + for (Order groupBy : this.getGroupBys()) { + to.groups.add(groupBy.copy()); + } + for (Order orderBy : this.getOrderBys()) { + to.orderBys.add(orderBy.copy()); + } + to.whereFilter = this.whereFilter == null ? null : this.whereFilter.cloneItem(); + to.havingFilter = this.havingFilter == null ? null : havingFilter.cloneItem(); + to.setLimitFrom(this.limitFrom); + to.setLimitTo(this.limitTo); + to.setSql(this.getSql()); + to.setSubQuery(subQuery); + to.setUnGlobalTableCount(unGlobalTableCount); + } + + protected void setUpInnerFields() { + innerFields.clear(); + for (PlanNode child : children) { + child.setUpFields(); + for (NamedField coutField : child.outerFields.keySet()) { + NamedField tmpField = new NamedField(coutField.planNode); + tmpField.table = child.getAlias() == null ? coutField.table : child.getAlias(); + // view也会有subAlias + if (subAlias!= null && subAlias.length()!=0) + tmpField.table = subAlias; + tmpField.name = coutField.name; + innerFields.put(tmpField, coutField); + } + } + } + + protected void setUpSelects() { + if (columnsSelected.isEmpty()) { + columnsSelected.add(new ItemField(null, null, "*")); + } + boolean withWild = false; + for (Item sel : columnsSelected) { + if (sel.isWild()) + withWild = true; + } + if (withWild) + dealStarColumn(); + outerFields.clear(); + nameContext.setFindInSelect(false); + nameContext.setSelectFirst(false); + for (Item sel : columnsSelected) { + setUpItem(sel); + NamedField field = makeOutNamedField(sel); + if (outerFields.containsKey(field) && getParent() != null) + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "duplicate field"); + outerFields.put(field, sel); + } + } + + private void setUpWhere() { + nameContext.setFindInSelect(false); + nameContext.setSelectFirst(false); + whereFilter = setUpItem(whereFilter); + } + + private void setUpGroupBy() { + nameContext.setFindInSelect(true); + nameContext.setSelectFirst(false); + for (Order order : groups) { + Item item = order.getItem(); + if (item.type() == ItemType.INT_ITEM) { + int index = item.valInt().intValue(); + if (index >= 1 && index <= getColumnsSelected().size()) + order.setItem(getColumnsSelected().get(index - 1)); + else + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "Unknown column '" + index + "' in group statement"); + } else { + order.setItem(setUpItem(item)); + } + } + } + + private void setUpHaving() { + nameContext.setFindInSelect(true); + nameContext.setSelectFirst(true); + havingFilter = setUpItem(havingFilter); + } + + private void setUpOrderBy() { + nameContext.setFindInSelect(true); + nameContext.setSelectFirst(true); + for (Order order : orderBys) { + Item item = order.getItem(); + if (item.type() == ItemType.INT_ITEM) { + int index = item.valInt().intValue(); + if (index >= 1 && index <= getColumnsSelected().size()) + order.setItem(getColumnsSelected().get(index - 1)); + else + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "Unknown column '" + index + "' in order statement"); + } else { + order.setItem(setUpItem(item)); + } + } + } + + protected void dealStarColumn() { + List newSels = new ArrayList(); + for (Item selItem : columnsSelected) { + if (selItem.isWild()) { + ItemField wildField = (ItemField) selItem; + if (wildField.tableName==null || wildField.tableName.length()==0) { + for (NamedField field : innerFields.keySet()) { + ItemField col = new ItemField(null, field.table, field.name); + newSels.add(col); + } + } else { + String selTable = wildField.tableName; + boolean found = false; + for (NamedField field : innerFields.keySet()) { + if (selTable != null && selTable.equals(field.table) + || (selTable == null && field.table == null)) { + ItemField col = new ItemField(null, field.table, field.name); + newSels.add(col); + found = true; + } else if (found) { + // a.* ->a.id,a.id1,b.id 找到b.id时退出 + break; + } + } + if (!found) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "child table " + selTable + " not exist!"); + } + } + } else { + newSels.add(selItem); + } + } + columnsSelected = newSels; + } + + private NamedField makeOutNamedField(Item sel) { + NamedField tmpField = new NamedField(sel.getTableName(), sel.getItemName(), this); + if (subAlias != null) + tmpField.table = subAlias; + if (tmpField.table == null)// maybe function + tmpField.table = getPureName(); + if (sel.getAlias() != null) + tmpField.name = sel.getAlias(); + return tmpField; + } + + protected Item setUpItem(Item sel) { + if (sel == null) + return null; + return sel.fixFields(nameContext); + } + + private void setUpItemRefer(Item sel) { + if (sel != null) + sel.fixRefer(referContext); + } + + // --------------------------getter&setter--------------------------- + + public long getLimitFrom() { + return limitFrom; + } + + public PlanNode setLimitFrom(long limitFrom) { + this.limitFrom = limitFrom; + return this; + } + + public long getLimitTo() { + return limitTo; + } + + public PlanNode setLimitTo(long limitTo) { + this.limitTo = limitTo; + return this; + } + + public PlanNode limit(long i, long j) { + this.setLimitFrom(i); + this.setLimitTo(j); + return this; + } + + public Item getWhereFilter() { + return whereFilter; + } + + public PlanNode query(Item whereFilter) { + this.whereFilter = whereFilter; + return this; + } + + public String getAlias() { + return alias; + } + + public PlanNode alias(String alias) { + this.alias = alias; + return this; + } + + public PlanNode subAlias(String alias) { + this.subAlias = alias; + return this; + } + + public List getGroupBys() { + return this.groups; + } + + public PlanNode setGroupBys(List groups) { + this.groups = groups; + return this; + } + + public List getOrderBys() { + return orderBys; + } + + public void setOrderBys(List orderBys) { + this.orderBys = orderBys; + } + + public List getChildren() { + return this.children; + } + + public List getColumnsSelected() { + return columnsSelected; + } + + public PlanNode setColumnsSelected(List columnsSelected) { + this.columnsSelected = columnsSelected; + return this; + } + + public Map> getColumnsReferedMap() { + return this.columnsReferedCache.asMap(); + } + + public void addSelToReferedMap(PlanNode tn, Item sel) { + // 使得相同的refermap中的sel具有相同的columnname + try { + this.columnsReferedCache.get(tn).add(sel); + } catch (ExecutionException e) { + logger.warn("columnsReferedCache error", e); + } + } + + public List getColumnsRefered() { + return this.columnsReferList; + } + + /** + * 设置别名,表级别 + */ + public PlanNode setAlias(String string) { + this.alias(string); + return this; + } + + /** + * 设置别名,表级别 + */ + public PlanNode setSubAlias(String string) { + this.subAlias(string); + return this; + } + + public String getSubAlias() { + return subAlias; + } + + public boolean isSubQuery() { + return subQuery; + } + + public PlanNode setSubQuery(boolean subQuery) { + this.subQuery = subQuery; + return this; + } + + public PlanNode having(Item havingFilter) { + this.havingFilter = havingFilter; + return this; + } + + public Item getHavingFilter() { + return this.havingFilter; + } + + public void setWhereFilter(Item whereFilter) { + this.whereFilter = whereFilter; + } + + public boolean isGlobaled() { + return isGlobaled; + } + + public void setGlobaled(boolean isGlobaled) { + this.isGlobaled = isGlobaled; + } + + public int getUnGlobalTableCount() { + return unGlobalTableCount; + } + + public void setUnGlobalTableCount(int unGlobalTableCount) { + this.unGlobalTableCount = unGlobalTableCount; + } + + /* 获取改节点下的tablenode集合 */ + public List getReferedTableNodes() { + return referedTableNodes; + } + + public PlanNode getParent() { + return parent; + } + + public void setParent(PlanNode parent) { + this.parent = parent; + if (parent != null) { + parent.referedTableNodes.addAll(referedTableNodes); + parent.exsitView |= this.exsitView; + } + } + + /** + * @return the innerFields + */ + public Map getInnerFields() { + return innerFields; + } + + /** + * @return the outerFields + */ + public Map getOuterFields() { + return outerFields; + } + + /** + * @return the exsitView + */ + public boolean isExsitView() { + return exsitView; + } + + /** + * @param exsitView + * the exsitView to set + */ + public void setExsitView(boolean exsitView) { + this.exsitView = exsitView; + } + + public boolean existUnPushDownGroup() { + return existUnPushDownGroup; + } + + public void setExistUnPushDownGroup(boolean existUnPushDownGroup) { + this.existUnPushDownGroup = existUnPushDownGroup; + } + + /** + * @return the isDistinct + */ + public boolean isDistinct() { + return isDistinct; + } + + /** + * @param isDistinct + * the isDistinct to set + */ + public void setDistinct(boolean isDistinct) { + this.isDistinct = isDistinct; + } + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } + + /** + * @return the strategyFilters + */ + public List getNestLoopFilters() { + return nestLoopFilters; + } + + /** + * @param nestLoopFilters + * the strategyFilters to set + */ + public void setNestLoopFilters(List nestLoopFilters) { + this.nestLoopFilters = nestLoopFilters; + } + + @Override + public final String toString() { + return this.toString(0); + } + + /** + * show visualable plan in tree + * + * @param level + * @return + */ + public abstract String toString(int level); +} diff --git a/src/main/java/io/mycat/plan/common/CastTarget.java b/src/main/java/io/mycat/plan/common/CastTarget.java new file mode 100644 index 000000000..7ac687235 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/CastTarget.java @@ -0,0 +1,14 @@ +package io.mycat.plan.common; + +public enum CastTarget { + ITEM_CAST_BINARY, + ITEM_CAST_CHAR, + ITEM_CAST_DATE, + ITEM_CAST_DATETIME, + ITEM_CAST_DECIMAL, + //JSON + ITEM_CAST_NCHAR, + ITEM_CAST_SIGNED_INT, + ITEM_CAST_TIME, + ITEM_CAST_UNSIGNED_INT; +} diff --git a/src/main/java/io/mycat/plan/common/CastType.java b/src/main/java/io/mycat/plan/common/CastType.java new file mode 100644 index 000000000..eb70f291e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/CastType.java @@ -0,0 +1,12 @@ +/** + * + */ +package io.mycat.plan.common; + +public class CastType { + public CastTarget target; + public int length = -1; + public int dec = -1; + + +} diff --git a/src/main/java/io/mycat/plan/common/Ctype.java b/src/main/java/io/mycat/plan/common/Ctype.java new file mode 100644 index 000000000..ee12cec05 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/Ctype.java @@ -0,0 +1,68 @@ +package io.mycat.plan.common; + +public class Ctype { + public static int _MY_U = 01; /* Upper case */ + public static int _MY_L = 02; /* Lower case */ + public static int _MY_NMR = 04; /* Numeral (digit) */ + public static int _MY_SPC = 010; /* Spacing character */ + public static int _MY_PNT = 020; /* Punctuation */ + public static int _MY_CTR = 040; /* Control character */ + public static int _MY_B = 0100; /* Blank */ + public static int _MY_X = 0200; /* heXadecimal digit */ + + private static byte ctype_latin1[] = { 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 72, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, (byte) 132, (byte) 132, (byte) 132, (byte) 132, (byte) 132, (byte) 132, (byte) 132, (byte) 132, + (byte) 132, (byte) 132, 16, 16, 16, 16, 16, 16, 16, (byte) 129, (byte) 129, (byte) 129, (byte) 129, + (byte) 129, (byte) 129, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 16, 16, 16, 16, 16, + (byte) 130, (byte) 130, (byte) 130, (byte) 130, (byte) 130, (byte) 130, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 16, 16, 16, 16, 32, 16, 0, 16, 2, 16, 16, 16, 16, 16, 16, 1, 16, 1, 0, 1, 0, 0, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 2, 16, 2, 0, 2, 1, 72, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 16, 2, 2, 2, 2, 2, 2, 2, 2 }; + + /** + * 目前仅作latin的 + * + * @param charset + * @param c + * @return + */ + public static boolean isDigit( // String charset, + char c) { + int index = (int) c + 1; + return (ctype_latin1[index] & _MY_NMR) != 0; + } + + public static boolean my_isalpha(char c) { + int index = (int) c + 1; + return (ctype_latin1[index] & (_MY_U | _MY_L)) != 0; + } + + public static boolean spaceChar(char c) { + int index = (int) c + 1; + return (ctype_latin1[index] & _MY_SPC) != 0; + } + + public static boolean isPunct(char c) { + int index = (int) c + 1; + return (ctype_latin1[index] & _MY_PNT) != 0; + } + + /** + * compare cs[start~count]==cs2[start~count] see + * ctype-simple.c[my_strnncoll_simple] + * + * @param cs + * @param start + * @param count + * @param cs2 + * @param start + * @param count + */ + public static int my_strnncoll(char[] css, int sbegin, int slen, char[] cst, int tbegin, int tlen) { + return new String(css, sbegin, slen).compareTo(new String(cst, tbegin, tlen)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/MySQLcom.java b/src/main/java/io/mycat/plan/common/MySQLcom.java new file mode 100644 index 000000000..21452ccdb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/MySQLcom.java @@ -0,0 +1,379 @@ +package io.mycat.plan.common; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +import io.mycat.plan.common.field.FieldUtil; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimeStatus; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class MySQLcom { + public static final double M_PI = Math.PI; + public static final int DBL_DIG = 6; + public static final int FLT_DIG = 10; + public static final int DECIMAL_LONGLONG_DIGITS = 22; + + /** maximum length of buffer in our big digits (uint32). */ + public static final int DECIMAL_BUFF_LENGTH = 9; + + /* the number of digits that my_decimal can possibly contain */ + public static final int DECIMAL_MAX_POSSIBLE_PRECISION = (DECIMAL_BUFF_LENGTH * 9); + + /** + * maximum guaranteed precision of number in decimal digits (number of our + * digits * number of decimal digits in one our big digit - number of + * decimal digits in one our big digit decreased by 1 (because we always put + * decimal point on the border of our big digits)) + */ + public static final int DECIMAL_MAX_PRECISION = (DECIMAL_MAX_POSSIBLE_PRECISION - 8 * 2); + + public static final int DECIMAL_MAX_SCALE = 30; + public static final int DECIMAL_NOT_SPECIFIED = 31; + + public static BigInteger BI64BACK = new BigInteger("18446744073709551616"); + + public static String setInt(long num, boolean unsignedFlag) { + return String.valueOf(num); + } + + public static String setReal(double num, int decimals) { + BigDecimal bd = new BigDecimal(num); + bd.setScale(decimals); + double db = bd.doubleValue(); + return String.valueOf(db); + } + + public static BigDecimal int2Decimal(long num, boolean unsigned) { + return BigDecimal.valueOf(num); + } + + public static BigDecimal double2Decimal(double num, int decimals) { + BigDecimal bd = new BigDecimal(num); + return bd.setScale(decimals); + } + + public static double str2Double(String str) { + try { + double db = Double.parseDouble(str); + return db; + } catch (Exception e) { + return 0.0; + } + } + + public static long str2Long(String str) { + try { + long l = Long.parseLong(str); + return l; + } catch (Exception e) { + return 0; + } + } + + public static BigDecimal str2Decimal(String str) { + try { + double db = Double.parseDouble(str); + BigDecimal bd = BigDecimal.valueOf(db); + return bd; + } catch (Exception e) { + return BigDecimal.ZERO; + } + } + + /** + * str转换成longlong + * + * @param cs + * str的byte数组 + * @param start + * 起始位置,包含 + * @param end + * 结束位置,包含 + * @param error + * @return + */ + public static BigInteger my_strtoll10(char[] cs, int start, int end, BoolPtr error) { + String tmp = new String(cs, start, end - start + 1); + error.set(false); + try { + BigInteger bi = new BigInteger(tmp); + return bi; + } catch (Exception e) { + error.set(true); + return BigInteger.ZERO; + } + } + + /** + * binary compare, num need to <= b1.size && <= b2.size + * + * @param b1 + * @param b2 + * @param num + * @return + */ + public static int memcmp(byte[] b1, byte[] b2, int num) { + for (int i = 0; i < num; i++) { + if (b1[i] < b2[i]) { + return -1; + } else if (b1[i] == b2[i]) { + continue; + } else { + return 1; + } + } + return 0; + } + + public static BigInteger getUnsignedLong(long l) { + BigInteger bi = BigInteger.valueOf(l); + BigInteger bmask = new BigInteger("FFFFFFFFFFFFFFFF", 16); + return bi.and(bmask); + } + + /** + * @brief Convert date provided in a string to its packed temporal int + * representation. + * @param[in] thd thread handle + * @param[in] str a string to convert + * @param[in] warn_type type of the timestamp for issuing the warning + * @param[in] warn_name field name for issuing the warning + * @param[out] error_arg could not extract a DATE or DATETIME + * @details Convert date provided in the string str to the int + * representation. If the string contains wrong date or doesn't + * contain it at all then a warning is issued. The warn_type and + * the warn_name arguments are used as the name and the type of the + * field when issuing the warning. + * @return converted value. 0 on error and on zero-dates -- check 'failure' + */ + public static long get_date_from_str(String str, MySQLTimestampType warntype, BoolPtr error) { + MySQLTime ltime = new MySQLTime(); + MySQLTimeStatus status = new MySQLTimeStatus(); + error.set(MyTime.str_to_datetime(str, str.length(), ltime, MyTime.TIME_FUZZY_DATE, status)); + if (error.get()) + return 0; + return MyTime.TIME_to_longlong_datetime_packed(ltime); + + } + + /* + * Collects different types for comparison of first item with each other + * items + * + * SYNOPSIS collect_cmp_types() items Array of items to collect types from + * nitems Number of items in the array skip_nulls Don't collect types of + * NULL items if TRUE + * + * DESCRIPTION This function collects different result types for comparison + * of the first item in the list with each of the remaining items in the + * 'items' array. + * + * RETURN 0 - if row type incompatibility has been detected (see + * cmp_row_type) Bitmap of collected types - otherwise 可以表示出一共有几种type + */ + public static int collect_cmp_types(List items, boolean skipnulls) { + int foundtypes = 0; + ItemResult leftResult = items.get(0).resultType(); + for (int i = 1; i < items.size(); i++) { + if (skipnulls && items.get(i).type() == Item.ItemType.NULL_ITEM) + continue; + if (leftResult == ItemResult.ROW_RESULT || items.get(i).resultType() == ItemResult.ROW_RESULT + && cmpRowType(items.get(0), items.get(i)) != 0) + return 0; + foundtypes |= 1 << MySQLcom.item_cmp_type(leftResult, items.get(i).resultType()).ordinal(); + } + /* + * Even if all right-hand items are NULLs and we are skipping them all, + * we need at least one type bit in the found_type bitmask. + */ + if (skipnulls && foundtypes == 0) + foundtypes |= 1 << leftResult.ordinal(); + return foundtypes; + } + + public static int cmpRowType(Item item1, Item item2) { + // TODO + return 0; + } + + public static ItemResult item_cmp_type(ItemResult a, ItemResult b) { + + if (a == ItemResult.STRING_RESULT && b == ItemResult.STRING_RESULT) + return ItemResult.STRING_RESULT; + if (a == ItemResult.INT_RESULT && b == ItemResult.INT_RESULT) + return ItemResult.INT_RESULT; + if ((a == ItemResult.INT_RESULT || a == ItemResult.DECIMAL_RESULT) + && (b == ItemResult.INT_RESULT || b == ItemResult.DECIMAL_RESULT)) + return ItemResult.DECIMAL_RESULT; + return ItemResult.REAL_RESULT; + } + + public static FieldTypes agg_field_type(List items, int startIndex, int nitems) { + if (nitems == 0 || items.get(startIndex).resultType() == ItemResult.ROW_RESULT) + return FieldTypes.valueOf("-1"); + FieldTypes res = items.get(startIndex).fieldType(); + for (int i = 1; i < nitems; i++) + res = FieldUtil.field_type_merge(res, items.get(startIndex + i).fieldType()); + return res; + } + + public static ItemResult agg_result_type(List items, int startIndex, int size) { + ItemResult type = ItemResult.STRING_RESULT; + /* Skip beginning NULL items */ + int index = 0, index_end; + Item item; + for (index = startIndex, index_end = startIndex + size; index < index_end; index++) { + item = items.get(index); + if (item.type() != ItemType.NULL_ITEM) { + type = item.resultType(); + index++; + break; + } + } + /* Combine result types. Note: NULL items don't affect the result */ + for (; index < index_end; index++) { + item = items.get(index); + if (item.type() != ItemType.NULL_ITEM) + type = item_store_type(type, item); + } + return type; + } + + public static ItemResult item_store_type(ItemResult a, Item item) { + ItemResult b = item.resultType(); + + if (a == ItemResult.STRING_RESULT || b == ItemResult.STRING_RESULT) + return ItemResult.STRING_RESULT; + else if (a == ItemResult.REAL_RESULT || b == ItemResult.REAL_RESULT) + return ItemResult.REAL_RESULT; + else if (a == ItemResult.DECIMAL_RESULT || b == ItemResult.DECIMAL_RESULT) + return ItemResult.DECIMAL_RESULT; + else + return ItemResult.INT_RESULT; + } + + public static byte[] long2Byte(BigInteger bi) { + long x = -1; + if (bi.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) + x = bi.subtract(BI64BACK).longValue(); + else + x = bi.longValue(); + int retLen = -1; + byte[] bb = new byte[8]; + int index = -1; + bb[++index] = (byte) (x >> 56); + if (bb[index] != 0 && retLen == -1) + retLen = 8; + bb[++index] = (byte) (x >> 48); + if (bb[index] != 0 && retLen == -1) + retLen = 7; + bb[++index] = (byte) (x >> 40); + if (bb[index] != 0 && retLen == -1) + retLen = 6; + bb[++index] = (byte) (x >> 32); + if (bb[index] != 0 && retLen == -1) + retLen = 5; + bb[++index] = (byte) (x >> 24); + if (bb[index] != 0 && retLen == -1) + retLen = 4; + bb[++index] = (byte) (x >> 16); + if (bb[index] != 0 && retLen == -1) + retLen = 3; + bb[++index] = (byte) (x >> 8); + if (bb[index] != 0 && retLen == -1) + retLen = 2; + bb[++index] = (byte) (x >> 0); + if (retLen == -1) + retLen = 1; + return Arrays.copyOfRange(bb, bb.length - retLen, bb.length); + } + + public static int memcmp(byte[] a_ptr, byte[] b_ptr) { + int a_len = a_ptr.length, b_len = b_ptr.length; + if (a_len >= b_len) + return memcmp0(a_ptr, b_ptr); + else + return -memcmp(b_ptr, a_ptr); + } + + public static void memcpy(byte[] a_ptr, int a_start, byte[] b_ptr) { + assert (a_ptr.length - a_start + 1 == b_ptr.length); + for (int i = 0; i < b_ptr.length; i++) { + a_ptr[a_start + i] = b_ptr[i]; + } + } + + /** + * 比较两个byte数组的大小,其中a_ptr的长度>=b_ptr的长度 + * + * @param a_ptr + * @param b_ptr + * @return + */ + private static int memcmp0(byte[] a_ptr, byte[] b_ptr) { + int a_len = a_ptr.length, b_len = b_ptr.length; + for (int i = 0; i < a_len - b_len; i++) { + if (a_ptr[i] != 0) // a比b多出了值 + return 1; + } + int a_start = a_len - b_len; + for (int i = 0; i < b_len; i++) { + byte a_byte = a_ptr[a_start + i]; + byte b_byte = b_ptr[i]; + if (a_byte > b_byte) + return 1; + else if (a_byte < b_byte) + return -1; + } + return 0; + } + + /** + * 解析rowpacket时使用,rowpacket的数据全都是字符串 + * + * @param charsetIndex + * @param buff + * @return + * @throws UnsupportedEncodingException + */ + public static String getFullString(String charsetName, byte[] buff) throws UnsupportedEncodingException { + if (buff == null || charsetName == null) + return null; + if ((charsetName != null) && (Charset.isSupported(charsetName))) { + return new String(buff, charsetName); + } else { + String msg = "unsupported character set :" + charsetName; + throw new UnsupportedEncodingException(msg); + } + } + + public static long[] log_10_int = new long[] { 1, 10, 100, 1000, 10000L, 100000L, 1000000L, 10000000L, 100000000L, + 1000000000L, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, + 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; + + public static long pow10(int index) { + return (long) Math.pow(10, index); + } + + public static final String Nulls = null; + + public static int check_word(String[] nameArray, char[] cs, int offset, int count) { + String val = new String(cs, offset, count); + for(int index =0; index < nameArray.length; index++){ + if(val.equalsIgnoreCase(nameArray[index])) + return index; + } + return 0; + } +} diff --git a/src/main/java/io/mycat/plan/common/context/NameResolutionContext.java b/src/main/java/io/mycat/plan/common/context/NameResolutionContext.java new file mode 100644 index 000000000..613ba01a7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/context/NameResolutionContext.java @@ -0,0 +1,51 @@ +package io.mycat.plan.common.context; + +import io.mycat.plan.PlanNode; + + +public class NameResolutionContext { + /* + * The name resolution context to search in when an Item cannot be resolved + * in this context (the context of an outer select) + */ + NameResolutionContext outer_context; + + private PlanNode planNode; + + private boolean findInSelect = false; + + private boolean selectFirst = false; + + public NameResolutionContext getOuter_context() { + return outer_context; + } + + public void setOuter_context(NameResolutionContext outer_context) { + this.outer_context = outer_context; + } + + public PlanNode getPlanNode() { + return planNode; + } + + public void setPlanNode(PlanNode pn) { + this.planNode = pn; + } + + public boolean isFindInSelect() { + return findInSelect; + } + + public void setFindInSelect(boolean findInSelect) { + this.findInSelect = findInSelect; + } + + public boolean isSelectFirst() { + return selectFirst; + } + + public void setSelectFirst(boolean selectFirst) { + this.selectFirst = selectFirst; + } + +} diff --git a/src/main/java/io/mycat/plan/common/context/ReferContext.java b/src/main/java/io/mycat/plan/common/context/ReferContext.java new file mode 100644 index 000000000..b4e6d13c2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/context/ReferContext.java @@ -0,0 +1,31 @@ +package io.mycat.plan.common.context; + +import io.mycat.plan.PlanNode; + +public class ReferContext { + + private PlanNode planNode; + private boolean isPushDownNode; + + public ReferContext() { + this.planNode = null; + this.isPushDownNode = false; + } + + public PlanNode getPlanNode() { + return planNode; + } + + public void setPlanNode(PlanNode planNode) { + this.planNode = planNode; + } + + public boolean isPushDownNode() { + return isPushDownNode; + } + + public void setPushDownNode(boolean isPushDownNode) { + this.isPushDownNode = isPushDownNode; + } + +} diff --git a/src/main/java/io/mycat/plan/common/exception/MySQLOutPutException.java b/src/main/java/io/mycat/plan/common/exception/MySQLOutPutException.java new file mode 100644 index 000000000..bfb2ff349 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/exception/MySQLOutPutException.java @@ -0,0 +1,43 @@ +package io.mycat.plan.common.exception; + +/** + * 表示mysql的标准输出信息 + * + * @author chenzifei + * + */ +public class MySQLOutPutException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = -7089907072181836842L; + + private int errorCode = -1; + private String sqlState = ""; + + public MySQLOutPutException(int errorCode, String sqlState, String msg) { + super(msg); + this.errorCode = errorCode; + this.sqlState = sqlState; + } + + public MySQLOutPutException(int errorCode, String sqlState, String msg, Throwable cause) { + super(msg, cause); + this.errorCode = errorCode; + this.sqlState = sqlState; + } + + public int getErrorCode() { + return errorCode; + } + + public String getSqlState() { + return sqlState; + } + + public String toMysqlErrorMsg() { + return String.format("ERROR %d (%s): %s", errorCode, sqlState, getMessage()); + } + +} diff --git a/src/main/java/io/mycat/plan/common/exception/TempTableException.java b/src/main/java/io/mycat/plan/common/exception/TempTableException.java new file mode 100644 index 000000000..282cbd9ce --- /dev/null +++ b/src/main/java/io/mycat/plan/common/exception/TempTableException.java @@ -0,0 +1,18 @@ +package io.mycat.plan.common.exception; + +public class TempTableException extends RuntimeException { + + private static final long serialVersionUID = 2869994979718401423L; + + public TempTableException(String message, Throwable cause) { + super(message, cause); + } + + public TempTableException(String message) { + super(message); + } + + public TempTableException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/io/mycat/plan/common/external/ResultStore.java b/src/main/java/io/mycat/plan/common/external/ResultStore.java new file mode 100644 index 000000000..81b835b02 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/external/ResultStore.java @@ -0,0 +1,22 @@ +package io.mycat.plan.common.external; + +import io.mycat.net.mysql.RowDataPacket; + +public interface ResultStore { + /* add a new row */ + void add(RowDataPacket row); + + /* all rows added */ + void done(); + + /* visit all rows in the store */ + RowDataPacket next(); + + int getRowCount(); + + /* 关闭result */ + void close(); + + /* 清楚数据 */ + void clear(); +} diff --git a/src/main/java/io/mycat/plan/common/field/Field.java b/src/main/java/io/mycat/plan/common/field/Field.java new file mode 100644 index 000000000..933819500 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/Field.java @@ -0,0 +1,250 @@ +package io.mycat.plan.common.field; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.apache.log4j.Logger; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.num.FieldBit; +import io.mycat.plan.common.field.num.FieldDecimal; +import io.mycat.plan.common.field.num.FieldDouble; +import io.mycat.plan.common.field.num.FieldFloat; +import io.mycat.plan.common.field.num.FieldLong; +import io.mycat.plan.common.field.num.FieldLonglong; +import io.mycat.plan.common.field.num.FieldMedium; +import io.mycat.plan.common.field.num.FieldNewdecimal; +import io.mycat.plan.common.field.num.FieldShort; +import io.mycat.plan.common.field.num.FieldTiny; +import io.mycat.plan.common.field.string.FieldBlob; +import io.mycat.plan.common.field.string.FieldEnum; +import io.mycat.plan.common.field.string.FieldSet; +import io.mycat.plan.common.field.string.FieldString; +import io.mycat.plan.common.field.string.FieldVarchar; +import io.mycat.plan.common.field.string.FieldVarstring; +import io.mycat.plan.common.field.temporal.FieldDate; +import io.mycat.plan.common.field.temporal.FieldDatetime; +import io.mycat.plan.common.field.temporal.FieldTime; +import io.mycat.plan.common.field.temporal.FieldTimestamp; +import io.mycat.plan.common.field.temporal.FieldYear; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public abstract class Field { + public static Field getFieldItem(byte[] name, byte[] table, int type, int charsetIndex, int field_length, + int decimals, long flags) { + String charset = CharsetUtil.getJavaCharset(charsetIndex); + try { + return getFieldItem(new String(name, charset), table==null?null:new String(table, charset), type, charsetIndex, field_length, + decimals, flags); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("parser error ,charset :" + charset.toString() ); + } +// + } + public static Field getFieldItem(String name, String table, int type, int charsetIndex, int field_length, + int decimals, long flags) { + FieldTypes field_type = FieldTypes.valueOf(type); + switch (field_type) { + case MYSQL_TYPE_NEWDECIMAL: + // mysql use newdecimal after some version + return new FieldNewdecimal(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_DECIMAL: + return new FieldDecimal(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_TINY: + return new FieldTiny(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_SHORT: + return new FieldShort(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_LONG: + return new FieldLong(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_FLOAT: + return new FieldFloat(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_DOUBLE: + return new FieldDouble(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_NULL: + return FieldNull.getInstance(); + case MYSQL_TYPE_TIMESTAMP: + return new FieldTimestamp(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_LONGLONG: + return new FieldLonglong(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_INT24: + return new FieldMedium(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_NEWDATE: + return new FieldDate(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_TIME: + return new FieldTime(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_DATETIME: + return new FieldDatetime(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_YEAR: + return new FieldYear(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_VARCHAR: + return new FieldVarchar(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_BIT: + return new FieldBit(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_VAR_STRING: + return new FieldVarstring(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_STRING: + return new FieldString(name, table, charsetIndex, field_length, decimals, flags); + /** --下列的类型函数目前不支持,因为select *出来的mysql都转化成string了,无法知晓它们在数据库中的type-- **/ + case MYSQL_TYPE_ENUM: + return new FieldEnum(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_SET: + return new FieldSet(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + return new FieldBlob(name, table, charsetIndex, field_length, decimals, flags); + case MYSQL_TYPE_GEOMETRY: + default: + throw new RuntimeException("unsupported field type :" + field_type.toString() + "!"); + } + } + + protected static final Logger logger = Logger.getLogger(Field.class); + + /** -- field的长度 -- **/ + public String name; + public String table; + public String dbname;// TODO + public int charsetIndex; + public String charsetName; + public long flags; + public byte[] ptr; + public int fieldLength; + public int decimals; + + public Field(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + this.name = name; + this.table = table; + this.charsetIndex = charsetIndex; + this.fieldLength = field_length; + this.flags = flags; + this.decimals = decimals; + this.charsetName = CharsetUtil.getJavaCharset(charsetIndex); + } + + public abstract ItemResult resultType(); + + public ItemResult numericContextResultType() { + return resultType(); + } + + public abstract FieldTypes fieldType(); + + public ItemResult cmpType() { + return resultType(); + } + + public boolean isNull() { + return ptr == null; + } + + public void setPtr(byte[] ptr) { + this.ptr = ptr; + } + + public String valStr() { + String val = null; + try { + val = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + return val; + } + + /** + * 是否有可能为空 + * + * @return + */ + public boolean maybeNull() { + return (FieldUtil.NOT_NULL_FLAG & flags) == 0; + } + + public void makeField(FieldPacket fp) { + try { + fp.name = this.name.getBytes(charsetName); + fp.db = this.dbname!=null?this.dbname.getBytes(charsetName):null; + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + fp.charsetIndex = this.charsetIndex; + fp.length = this.fieldLength; + fp.flags = (int) this.flags; + fp.decimals = (byte) this.decimals; + fp.type = fieldType().numberValue(); + } + + public abstract BigInteger valInt(); + + public abstract BigDecimal valReal(); + + public abstract BigDecimal valDecimal(); + + public abstract int compareTo(final Field other); + + public abstract int compare(byte[] v1, byte[] v2); + + public boolean getDate(MySQLTime ltime, long fuzzydate) { + String res = valStr(); + return res == null || MyTime.str_to_datetime_with_warn(res, ltime, fuzzydate); + } + + public boolean getTime(MySQLTime ltime) { + String res = valStr(); + return res == null || MyTime.str_to_time_with_warn(res, ltime); + } + + /** + * 计算出实际的对象的内部值 + */ + protected abstract void internalJob(); + + public boolean equals(final Field other, boolean binary_cmp) { + if (other == null) + return false; + if (this == other) + return true; + if (this.compareTo(other) == 0) + return true; + return false; + } + + /** + * Returns DATE/DATETIME value in packed longlong format. This method should + * not be called for non-temporal types. Temporal field types override the + * default method. + */ + public long valDateTemporal() { + assert (false); + return 0; + } + + /** + * Returns TIME value in packed longlong format. This method should not be + * called for non-temporal types. Temporal field types override the default + * method. + */ + public long valTimeTemporal() { + assert (false); + return 0; + } + + @Override + public int hashCode() { + int h = 1; + if (ptr != null) { + for (int i = ptr.length - 1; i >= 0; i--) + h = 31 * h + (int) ptr[i]; + } + return h; + } +} diff --git a/src/main/java/io/mycat/plan/common/field/FieldNull.java b/src/main/java/io/mycat/plan/common/field/FieldNull.java new file mode 100644 index 000000000..294792efc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/FieldNull.java @@ -0,0 +1,72 @@ +package io.mycat.plan.common.field; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import io.mycat.plan.common.field.string.FieldStr; +import io.mycat.plan.common.item.FieldTypes; + +public class FieldNull extends FieldStr { + private static FieldNull _instance = new FieldNull("NULL", "", 63, 0, 0, 0); + + public static FieldNull getInstance() { + return _instance; + } + + FieldNull(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + this.ptr = null; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_NULL; + } + + @Override + public boolean equals(final Field other, boolean binary) { + if (this == other) + return true; + return false; + } + + @Override + public BigDecimal valReal() { + return BigDecimal.ZERO; + } + + @Override + public BigInteger valInt() { + return BigInteger.ZERO; + } + + @Override + public String valStr() { + return null; + } + + @Override + public BigDecimal valDecimal() { + return null; + } + + @Override + public boolean isNull() { + return true; + } + + @Override + protected void internalJob() { + } + + @Override + public int compareTo(Field other) { + return -1; + } + + @Override + public int compare(byte[] v1, byte[] v2) { + return -1; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/FieldUtil.java b/src/main/java/io/mycat/plan/common/field/FieldUtil.java new file mode 100644 index 000000000..17cda5f26 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/FieldUtil.java @@ -0,0 +1,1388 @@ +package io.mycat.plan.common.field; + +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; + +public class FieldUtil { + public static final int NOT_NULL_FLAG = 1; /* Field can't be NULL */ + public static final int PRI_KEY_FLAG = 2; /* + * Field is part of a primary key + */ + public static final int UNIQUE_KEY_FLAG = 4; /* + * Field is part of a unique + * key + */ + public static final int MULTIPLE_KEY_FLAG = 8; /* Field is part of a key */ + public static final int BLOB_FLAG = 16; /* Field is a blob */ + public static final int UNSIGNED_FLAG = 32; /* Field is unsigned */ + public static final int ZEROFILL_FLAG = 64; /* Field is zerofill */ + public static final int BINARY_FLAG = 128; /* Field is binary */ + + /** + * b1,b2代表的是整数型数字,进行比较,b1,b2非null + * + * @param b1 + * @param b2 + * @return + */ + public static int compareIntUsingStringBytes(byte[] b1, byte[] b2) { + char b1c0 = (char) b1[0]; + char b2c0 = (char) b2[0]; + if (b1c0 == '-') {// b1为负数 + if (b2c0 == '-') { // b2为负数 + return -compareUnIntUsingStringBytes(b1, 1, b2, 1); + } else { + return -1; + } + } else {// b1为正数 + if (b2c0 == '-') { + return 1; + } else { + return compareUnIntUsingStringBytes(b1, 0, b2, 0); + } + } + } + + /* 不考虑符号的b1,b2的比较 , b1,b2代表整数 */ + private static int compareUnIntUsingStringBytes(byte[] b1, int startb1, byte[] b2, int startb2) { + int b1len = b1.length - startb1; + int b2len = b2.length - startb2; + if (b1len < b2len) + return -1; + else if (b1len > b2len) + return 1; + else { + // 长度相等 + for (int i = 0; i < b1len; i++) { + byte bb1 = b1[startb1 + i]; + byte bb2 = b2[startb2 + i]; + if (bb1 > bb2) + return 1; + else if (bb1 < bb2) + return -1; + else + continue; + } + return 0; + } + } + + public static void main(String[] args) { + String s1 = "-1234"; + String s2 = "1234"; + byte[] b1 = s1.getBytes(); + byte[] b2 = s2.getBytes(); + System.out.println(compareIntUsingStringBytes(b1, b2)); + } + + public int get_enum_pack_length(int elements) { + return elements < 256 ? 1 : 2; + } + + public int get_set_pack_length(int elements) { + int len = (elements + 7) / 8; + return len > 4 ? 8 : len; + } + + public static void initFields(List fields, List bs) { + int size = fields.size(); + for (int index = 0; index < size; index++) { + fields.get(index).setPtr(bs.get(index)); + } + } + + public static boolean is_temporal_type(FieldTypes valuetype) { + switch (valuetype) { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_NEWDATE: + return true; + default: + return false; + } + } + + /** + * Tests if field real type is temporal, i.e. represents all existing + * implementations of DATE, TIME, DATETIME or TIMESTAMP types in SQL. + * + * @param type + * Field real type, as returned by field->real_type() + * @retval true If field real type is temporal + * @retval false If field real type is not temporal + */ + public static boolean is_temporal_real_type(FieldTypes type) { + switch (type) { + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_DATETIME2: + return true; + default: + return FieldUtil.is_temporal_type(type); + } + } + + public static boolean is_temporal_type_with_time(FieldTypes type) { + switch (type) { + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return true; + default: + return false; + } + } + + public static boolean is_temporal_type_with_date(FieldTypes valuetype) { + switch (valuetype) { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return true; + default: + return false; + } + } + + /** + * Tests if field type is temporal and has date and time parts, i.e. + * represents DATETIME or TIMESTAMP types in SQL. + * + * @param type + * Field type, as returned by field->type(). + * @retval true If field type is temporal type with date and time parts. + * @retval false If field type is not temporal type with date and time + * parts. + */ + public static boolean is_temporal_type_with_date_and_time(FieldTypes type) { + switch (type) { + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return true; + default: + return false; + } + } + + /** + * Recognizer for concrete data type (called real_type for some reason), + * returning true if it is one of the TIMESTAMP types. + */ + public static boolean is_timestamp_type(FieldTypes type) { + return type == FieldTypes.MYSQL_TYPE_TIMESTAMP || type == FieldTypes.MYSQL_TYPE_TIMESTAMP2; + } + + /** + * Convert temporal real types as retuned by field->real_type() to field + * type as returned by field->type(). + * + * @param real_type + * Real type. + * @retval Field type. + */ + public static FieldTypes real_type_to_type(FieldTypes real_type) { + switch (real_type) { + case MYSQL_TYPE_TIME2: + return FieldTypes.MYSQL_TYPE_TIME; + case MYSQL_TYPE_DATETIME2: + return FieldTypes.MYSQL_TYPE_DATETIME; + case MYSQL_TYPE_TIMESTAMP2: + return FieldTypes.MYSQL_TYPE_TIMESTAMP; + case MYSQL_TYPE_NEWDATE: + return FieldTypes.MYSQL_TYPE_DATE; + /* Note: NEWDECIMAL is a type, not only a real_type */ + default: + return real_type; + } + } + + /* + * Rules for merging different types of fields in UNION + * + * NOTE: to avoid 256*256 table, gap in table types numeration is skiped + * following #defines describe that gap and how to canculate number of + * fields and index of field in thia array. + */ + private static int FIELDTYPE_TEAR_FROM = (FieldTypes.MYSQL_TYPE_BIT.numberValue() + 1); + private static int FIELDTYPE_TEAR_TO = (FieldTypes.MYSQL_TYPE_NEWDECIMAL.numberValue() - 1); + + // private static int FIELDTYPE_NUM = (FIELDTYPE_TEAR_FROM + (255 - + // FIELDTYPE_TEAR_TO)); + + public static int field_type2index(FieldTypes field_type) { + field_type = real_type_to_type(field_type); + assert (field_type.numberValue() < FIELDTYPE_TEAR_FROM || field_type.numberValue() > FIELDTYPE_TEAR_TO); + return (field_type.numberValue() < FIELDTYPE_TEAR_FROM ? field_type.numberValue() + : ((int) FIELDTYPE_TEAR_FROM) + (field_type.numberValue() - FIELDTYPE_TEAR_TO) - 1); + } + + public static FieldTypes field_type_merge(FieldTypes a, FieldTypes b) { + return field_types_merge_rules[field_type2index(a)][field_type2index(b)]; + } + + private static FieldTypes[][] field_types_merge_rules = new FieldTypes[][] { + /* enum_field_types.MYSQL_TYPE_DECIMAL -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_NEWDECIMAL, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_NEWDECIMAL, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_DECIMAL, FieldTypes.MYSQL_TYPE_DECIMAL, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_TINY -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_TINY, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_SHORT, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_TINY, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_INT24, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_SHORT -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_SHORT, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_SHORT, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_SHORT, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_INT24, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_SHORT, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_LONG -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_LONG, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_LONG, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_FLOAT -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_FLOAT, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_FLOAT, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_FLOAT, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_DOUBLE -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_NULL -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_TINY, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_SHORT, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_NULL, FieldTypes.MYSQL_TYPE_TIMESTAMP, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_LONGLONG, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_TIME, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_YEAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_BIT, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_ENUM, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_SET, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_GEOMETRY }, + /* enum_field_types.MYSQL_TYPE_TIMESTAMP -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_TIMESTAMP, FieldTypes.MYSQL_TYPE_TIMESTAMP, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_LONGLONG -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_LONGLONG, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_LONGLONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_LONGLONG, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_INT24 -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_INT24, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_INT24, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_INT24, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_INT24, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_INT24, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_DATE -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_TIME -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_TIME, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_TIME, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_DATETIME -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_YEAR -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_DECIMAL, FieldTypes.MYSQL_TYPE_TINY, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_SHORT, FieldTypes.MYSQL_TYPE_LONG, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_FLOAT, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_YEAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONGLONG, FieldTypes.MYSQL_TYPE_INT24, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_YEAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_NEWDATE -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_DATETIME, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_DATETIME, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_NEWDATE, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_VARCHAR -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_BIT -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_BIT, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_BIT, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_NEWDECIMAL -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_NEWDECIMAL, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_NEWDECIMAL, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_DOUBLE, FieldTypes.MYSQL_TYPE_DOUBLE, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_NEWDECIMAL, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_NEWDECIMAL, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_NEWDECIMAL, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_ENUM -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_ENUM, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_SET -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_SET, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_TINY_BLOB -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_TINY_BLOB, FieldTypes.MYSQL_TYPE_TINY_BLOB }, + /* enum_field_types.MYSQL_TYPE_MEDIUM_BLOB -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_MEDIUM_BLOB }, + /* enum_field_types.MYSQL_TYPE_LONG_BLOB -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_LONG_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB }, + /* enum_field_types.MYSQL_TYPE_BLOB -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_BLOB }, + /* enum_field_types.MYSQL_TYPE_VAR_STRING -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR }, + /* enum_field_types.MYSQL_TYPE_STRING -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_STRING }, + /* enum_field_types.MYSQL_TYPE_GEOMETRY -> */ + { + // enum_field_types.MYSQL_TYPE_DECIMAL + // enum_field_types.MYSQL_TYPE_TINY + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SHORT + // enum_field_types.MYSQL_TYPE_LONG + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_FLOAT + // enum_field_types.MYSQL_TYPE_DOUBLE + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NULL + // enum_field_types.MYSQL_TYPE_TIMESTAMP + FieldTypes.MYSQL_TYPE_GEOMETRY, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_LONGLONG + // enum_field_types.MYSQL_TYPE_INT24 + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATE + // enum_field_types.MYSQL_TYPE_TIME + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_DATETIME + // enum_field_types.MYSQL_TYPE_YEAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDATE + // enum_field_types.MYSQL_TYPE_VARCHAR + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_BIT <16>-<245> + FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_NEWDECIMAL + // enum_field_types.MYSQL_TYPE_ENUM + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_SET + // enum_field_types.MYSQL_TYPE_TINY_BLOB + FieldTypes.MYSQL_TYPE_VARCHAR, FieldTypes.MYSQL_TYPE_TINY_BLOB, + // enum_field_types.MYSQL_TYPE_MEDIUM_BLOB + // enum_field_types.MYSQL_TYPE_LONG_BLOB + FieldTypes.MYSQL_TYPE_MEDIUM_BLOB, FieldTypes.MYSQL_TYPE_LONG_BLOB, + // enum_field_types.MYSQL_TYPE_BLOB + // enum_field_types.MYSQL_TYPE_VAR_STRING + FieldTypes.MYSQL_TYPE_BLOB, FieldTypes.MYSQL_TYPE_VARCHAR, + // enum_field_types.MYSQL_TYPE_STRING + // enum_field_types.MYSQL_TYPE_GEOMETRY + FieldTypes.MYSQL_TYPE_STRING, FieldTypes.MYSQL_TYPE_GEOMETRY } }; +} diff --git a/src/main/java/io/mycat/plan/common/field/TypeConversionStatus.java b/src/main/java/io/mycat/plan/common/field/TypeConversionStatus.java new file mode 100644 index 000000000..98774518d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/TypeConversionStatus.java @@ -0,0 +1,41 @@ +package io.mycat.plan.common.field; + +public enum TypeConversionStatus { + /// Storage/conversion went fine. + TYPE_OK, + /** + * A minor problem when converting between temporal values, e.g. if datetime + * is converted to date the time information is lost. + */ + TYPE_NOTE_TIME_TRUNCATED, + /** + * Value outside min/max limit of datatype. The min/max value is stored by + * Field::store() instead (if applicable) + */ + TYPE_WARN_OUT_OF_RANGE, + /** + * Value was stored, but something was cut. What was cut is considered + * insignificant enough to only issue a note. Example: trying to store a + * number with 5 decimal places into a field that can only store 3 decimals. + * The number rounded to 3 decimal places should be stored. Another example: + * storing the string "foo " into a VARCHAR(3). The string "foo" is stored + * in this case, so only whitespace is cut. + */ + TYPE_NOTE_TRUNCATED, + /** + * Value was stored, but something was cut. What was cut is considered + * significant enough to issue a warning. Example: storing the string "foo" + * into a VARCHAR(2). The string "fo" is stored in this case. Another + * example: storing the string "2010-01-01foo" into a DATE. The garbage in + * the end of the string is cut in this case. + */ + TYPE_WARN_TRUNCATED, + /// Trying to store NULL in a NOT NULL field. + TYPE_ERR_NULL_CONSTRAINT_VIOLATION, + /** + * Store/convert incompatible values, like converting "foo" to a date. + */ + TYPE_ERR_BAD_VALUE, + /// Out of memory + TYPE_ERR_OOM +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldBit.java b/src/main/java/io/mycat/plan/common/field/num/FieldBit.java new file mode 100644 index 000000000..d3218c2fc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldBit.java @@ -0,0 +1,109 @@ +package io.mycat.plan.common.field.num; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +public class FieldBit extends Field { + private BigInteger intValue = null; + + public FieldBit(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_BIT; + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public BigDecimal valReal() { + internalJob(); + return isNull() ? BigDecimal.ZERO : new BigDecimal(intValue); + } + + @Override + public BigInteger valInt() { + internalJob(); + return isNull() ? BigInteger.ZERO : intValue; + } + + @Override + public BigDecimal valDecimal() { + internalJob(); + return isNull() ? null : new BigDecimal(intValue); + } + + @Override + protected void internalJob() { + // 比如一个bit(16)的数据类型,存储的值为8737(=34*256+33)那么客户端传递给我们的byte[]为[34,33] + if (ptr != null) { + long lv = getBitInt(ptr); + intValue = BigInteger.valueOf(lv); + } + } + + @Override + public int compareTo(Field other) { + if (other == null || !(other instanceof FieldBit)) { + return 1; + } + FieldBit bOther = (FieldBit) other; + BigInteger intValue = this.valInt(); + BigInteger intValue2 = bOther.valInt(); + if (intValue == null && intValue2 == null) + return 0; + else if (intValue2 == null) + return 1; + else if (intValue == null) + return -1; + else + return intValue.compareTo(intValue2); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } else + try { + Long b1 = getBitInt(v1); + Long b2 = getBitInt(v2); + return b1.compareTo(b2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } + + /** + * 高位在前 + * + * @param b + * @return + */ + private long getBitInt(byte[] b) { + if (b == null || b.length == 0) + return 0; + int ret = 0; + int leftShift = 0; + for (int i = ptr.length - 1; i >= 0; i--) { + long lb = ptr[i] << leftShift; + ret += lb; + leftShift += 8; + } + return ret; + } +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldDecimal.java b/src/main/java/io/mycat/plan/common/field/num/FieldDecimal.java new file mode 100644 index 000000000..621a9156c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldDecimal.java @@ -0,0 +1,22 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; + +/** + * decimal(%d,%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldDecimal extends FieldReal { + + public FieldDecimal(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DECIMAL; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldDouble.java b/src/main/java/io/mycat/plan/common/field/num/FieldDouble.java new file mode 100644 index 000000000..79ea9aaa0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldDouble.java @@ -0,0 +1,22 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; + +/** + * bigint(%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldDouble extends FieldReal { + + public FieldDouble(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DOUBLE; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldFloat.java b/src/main/java/io/mycat/plan/common/field/num/FieldFloat.java new file mode 100644 index 000000000..ce1f164de --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldFloat.java @@ -0,0 +1,22 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; + +/** + * bigint(%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldFloat extends FieldReal { + + public FieldFloat(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_FLOAT; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldLong.java b/src/main/java/io/mycat/plan/common/field/num/FieldLong.java new file mode 100644 index 000000000..88bdbee54 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldLong.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.field.num; + +import java.math.BigDecimal; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +/** + * int(%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldLong extends FieldNum { + + public FieldLong(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_LONG; + } + + @Override + public BigDecimal valDecimal() { + return new BigDecimal(valInt()); + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldLonglong.java b/src/main/java/io/mycat/plan/common/field/num/FieldLonglong.java new file mode 100644 index 000000000..41492ec0d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldLonglong.java @@ -0,0 +1,28 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +/** + * bigint(%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldLonglong extends FieldNum { + + public FieldLonglong(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_LONGLONG; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldMedium.java b/src/main/java/io/mycat/plan/common/field/num/FieldMedium.java new file mode 100644 index 000000000..2052a417a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldMedium.java @@ -0,0 +1,28 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +/** + * mediumint(%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldMedium extends FieldNum { + + public FieldMedium(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_INT24; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldNewdecimal.java b/src/main/java/io/mycat/plan/common/field/num/FieldNewdecimal.java new file mode 100644 index 000000000..6a0aa663f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldNewdecimal.java @@ -0,0 +1,28 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +/** + * decimal(%d,%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldNewdecimal extends FieldDecimal { + + public FieldNewdecimal(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_NEWDECIMAL; + } + + @Override + public ItemResult resultType() { + return ItemResult.DECIMAL_RESULT; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldNum.java b/src/main/java/io/mycat/plan/common/field/num/FieldNum.java new file mode 100644 index 000000000..6ae8d4a61 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldNum.java @@ -0,0 +1,135 @@ +package io.mycat.plan.common.field.num; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.field.FieldUtil; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +/** + * 整数的基类 + * + * @author chenzifei + * + */ +public abstract class FieldNum extends Field { + + protected BigInteger intValue = null; + protected String zeroptr_str = null; + + public FieldNum(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + zerofill = (FieldUtil.ZEROFILL_FLAG & flags) != 0; + unsigned_flag = (FieldUtil.UNSIGNED_FLAG & flags) != 0; + } + + public boolean zerofill = false; + public boolean unsigned_flag = false; + + @Override + public ItemResult resultType() { + return ItemResult.REAL_RESULT; + } + + @Override + public String valStr() { + internalJob(); + return isNull() ? null : zeroptr_str; + } + + @Override + public BigInteger valInt() { + internalJob(); + return isNull() ? BigInteger.ZERO : intValue; + } + + @Override + public BigDecimal valReal() { + internalJob(); + return isNull() ? BigDecimal.ZERO : new BigDecimal(intValue); + } + + @Override + public BigDecimal valDecimal() { + internalJob(); + return isNull() ? null : new BigDecimal(intValue); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + internalJob(); + return isNull() ? true : MyTime.my_longlong_to_datetime_with_warn(intValue.longValue(), ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + internalJob(); + return isNull() ? true : MyTime.my_longlong_to_time_with_warn(intValue.longValue(), ltime); + } + + @Override + protected void internalJob() { + /** --计算出zero_ptrstr-- **/ + String res = null; + try { + res = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + if (res != null) + if (zerofill && res.length() < fieldLength) { + for (int i = 0; i < fieldLength - res.length(); i++) { + res = "0" + res; + } + } + zeroptr_str = res; + + /** -- 计算出intValue -- **/ + + if (res == null) + intValue = BigInteger.ZERO; + else + try { + intValue = new BigInteger(res); + } catch (Exception e) { + logger.info("String:" + res + " to BigInteger exception!", e); + intValue = BigInteger.ZERO; + } + } + + @Override + public int compareTo(Field other) { + if (other == null || !(other instanceof FieldNum)) { + return 1; + } + FieldNum bOther = (FieldNum) other; + BigInteger intValue = this.valInt(); + BigInteger intValue2 = bOther.valInt(); + if (intValue == null && intValue2 == null) + return 0; + else if (intValue2 == null) + return 1; + else if (intValue == null) + return -1; + else + return intValue.compareTo(intValue2); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } else + return FieldUtil.compareIntUsingStringBytes(v1, v2); + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldReal.java b/src/main/java/io/mycat/plan/common/field/num/FieldReal.java new file mode 100644 index 000000000..c5870fdd2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldReal.java @@ -0,0 +1,113 @@ +package io.mycat.plan.common.field.num; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public abstract class FieldReal extends FieldNum { + protected BigDecimal decValue = null; + + public FieldReal(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public String valStr() { + internalJob(); + return isNull() ? null : decValue.toString(); + } + + @Override + public BigInteger valInt() { + return isNull() ? BigInteger.ZERO : valReal().toBigInteger(); + } + + @Override + public BigDecimal valReal() { + internalJob(); + return isNull() ? BigDecimal.ZERO : decValue; + } + + /** + * 如果是null的对象则是Item_field_null对象,不会是这些对象,所以这里不会返回null + */ + @Override + public BigDecimal valDecimal() { + return isNull() ? null : valReal(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + internalJob(); + return isNull() ? true : MyTime.my_double_to_datetime_with_warn(decValue.doubleValue(), ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + internalJob(); + return isNull() ? true : MyTime.my_double_to_time_with_warn(decValue.doubleValue(), ltime); + } + + @Override + protected void internalJob() { + String res = null; + try { + res = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + if (res == null) + decValue = BigDecimal.ZERO; + else + try { + decValue = new BigDecimal(res); + } catch (Exception e) { + logger.info("String:" + res + " to BigDecimal exception!", e); + decValue = BigDecimal.ZERO; + } + } + + @Override + public int compareTo(Field other) { + if (other == null || !(other instanceof FieldReal)) { + return 1; + } + FieldReal bOther = (FieldReal) other; + BigDecimal dec = this.valReal(); + BigDecimal dec2 = bOther.valReal(); + if (dec == null && dec2 == null) + return 0; + else if (dec2 == null) + return 1; + else if (dec == null) + return -1; + else + return dec.compareTo(dec2); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } else + try { + String sval1 = MySQLcom.getFullString(charsetName, v1); + String sval2 = MySQLcom.getFullString(charsetName, v2); + BigDecimal b1 = new BigDecimal(sval1); + BigDecimal b2 = new BigDecimal(sval2); + return b1.compareTo(b2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldShort.java b/src/main/java/io/mycat/plan/common/field/num/FieldShort.java new file mode 100644 index 000000000..5cad71aad --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldShort.java @@ -0,0 +1,28 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +/** + * smallint(%d) |unsigned |zerofilled + * + * @author chenzifei + * + */ +public class FieldShort extends FieldNum { + + public FieldShort(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_SHORT; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/num/FieldTiny.java b/src/main/java/io/mycat/plan/common/field/num/FieldTiny.java new file mode 100644 index 000000000..ed5de3ddc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/num/FieldTiny.java @@ -0,0 +1,27 @@ +package io.mycat.plan.common.field.num; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item.ItemResult; + +/** + * tinyint(%d) |unsigned | zerofilled + * + * @author chenzifei + * + */ +public class FieldTiny extends FieldNum { + + public FieldTiny(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_TINY; + } +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldBlob.java b/src/main/java/io/mycat/plan/common/field/string/FieldBlob.java new file mode 100644 index 000000000..8979902ab --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldBlob.java @@ -0,0 +1,22 @@ +package io.mycat.plan.common.field.string; + +import io.mycat.plan.common.item.FieldTypes; + +/** + * 目前blob,enum等值只支持传递,不支持计算 + * + * @author chenzifei + * + */ +public class FieldBlob extends FieldLongstr { + + public FieldBlob(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_BLOB; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldEnum.java b/src/main/java/io/mycat/plan/common/field/string/FieldEnum.java new file mode 100644 index 000000000..bf80fb1d1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldEnum.java @@ -0,0 +1,16 @@ +package io.mycat.plan.common.field.string; + +import io.mycat.plan.common.item.FieldTypes; + +public class FieldEnum extends FieldStr { + + public FieldEnum(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_STRING; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldLongstr.java b/src/main/java/io/mycat/plan/common/field/string/FieldLongstr.java new file mode 100644 index 000000000..a12a80545 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldLongstr.java @@ -0,0 +1,15 @@ +package io.mycat.plan.common.field.string; + +/** + * base class for Field_string, Field_varstring and Field_blob + * + * @author chenzifei + * + */ +public abstract class FieldLongstr extends FieldStr { + + public FieldLongstr(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldSet.java b/src/main/java/io/mycat/plan/common/field/string/FieldSet.java new file mode 100644 index 000000000..b33dd91a7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldSet.java @@ -0,0 +1,16 @@ +package io.mycat.plan.common.field.string; + +import io.mycat.plan.common.item.FieldTypes; + +public class FieldSet extends FieldEnum { + + public FieldSet(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_SET; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldStr.java b/src/main/java/io/mycat/plan/common/field/string/FieldStr.java new file mode 100644 index 000000000..29842c10a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldStr.java @@ -0,0 +1,119 @@ +package io.mycat.plan.common.field.string; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item.ItemResult; + +public abstract class FieldStr extends Field { + + public FieldStr(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public BigInteger valInt() { + return valReal().toBigInteger(); + } + + @Override + public BigDecimal valReal() { + if (ptr == null) + return BigDecimal.ZERO; + else { + String ptr_str = null; + try { + ptr_str = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + return BigDecimal.ZERO; + } + try { + return new BigDecimal(ptr_str); + } catch (Exception e) { + logger.info("String:" + ptr_str + " to BigDecimal exception!", e); + return BigDecimal.ZERO; + } + } + } + + @Override + public BigDecimal valDecimal() { + if (ptr == null) + return null; + else { + String ptr_str = null; + try { + ptr_str = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + return null; + } + try { + return new BigDecimal(ptr_str); + } catch (Exception e) { + logger.info("String:" + ptr_str + " to BigDecimal exception!", e); + return null; + } + } + } + + @Override + public ItemResult numericContextResultType() { + return ItemResult.REAL_RESULT; + } + + public boolean binary() { + return false; + } + + @Override + protected void internalJob() { + } + + @Override + public int compareTo(final Field other) { + if (other == null || !(other instanceof FieldStr)) + return 1; + FieldStr other2 = (FieldStr) other; + String ptr_str = this.valStr(); + String ptr_str2 = other2.valStr(); + if (ptr_str == null && ptr_str2 == null) + return 0; + else if (ptr_str2 == null) + return 1; + else if (ptr_str == null) + return -1; + else + return ptr_str.compareTo(ptr_str2); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } + try { + // mysql order by,>,<字符串使用的是排序值,正常是用大写作为比较 + String sval1 = MySQLcom.getFullString(charsetName, v1).toUpperCase(); + String sval2 = MySQLcom.getFullString(charsetName, v2).toUpperCase(); + return sval1.compareTo(sval2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldString.java b/src/main/java/io/mycat/plan/common/field/string/FieldString.java new file mode 100644 index 000000000..6a2212c05 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldString.java @@ -0,0 +1,21 @@ +package io.mycat.plan.common.field.string; + +import io.mycat.plan.common.item.FieldTypes; + +/** + * char\binary + * + * @author chenzifei + * + */ +public class FieldString extends FieldLongstr { + public FieldString(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_STRING; + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldVarchar.java b/src/main/java/io/mycat/plan/common/field/string/FieldVarchar.java new file mode 100644 index 000000000..d9cb43cf5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldVarchar.java @@ -0,0 +1,14 @@ +package io.mycat.plan.common.field.string; + +import io.mycat.plan.common.item.FieldTypes; + +public class FieldVarchar extends FieldString { + public FieldVarchar(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_VARCHAR; + } +} diff --git a/src/main/java/io/mycat/plan/common/field/string/FieldVarstring.java b/src/main/java/io/mycat/plan/common/field/string/FieldVarstring.java new file mode 100644 index 000000000..c8b0e3141 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/string/FieldVarstring.java @@ -0,0 +1,14 @@ +package io.mycat.plan.common.field.string; + +import io.mycat.plan.common.item.FieldTypes; + +public class FieldVarstring extends FieldString { + public FieldVarstring(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_VAR_STRING; + } +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldDate.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldDate.java new file mode 100644 index 000000000..6c70ebcd9 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldDate.java @@ -0,0 +1,64 @@ +package io.mycat.plan.common.field.temporal; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class FieldDate extends FieldTemporaWithDate { + + public FieldDate(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DATE; + } + + @Override + protected void internalJob() { + String ptr_str = null; + try { + ptr_str = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + if (ptr_str != null) { + MyTime.str_to_datetime_with_warn(ptr_str, ltime, MyTime.TIME_FUZZY_DATE); + } + } + + @Override + public BigInteger valInt() { + internalJob(); + return isNull() ? BigInteger.ZERO : BigInteger.valueOf(MyTime.TIME_to_ulonglong_date(ltime)); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } else + try { + String sval1 = MySQLcom.getFullString(charsetName, v1); + String sval2 = MySQLcom.getFullString(charsetName, v2); + MySQLTime ltime1 = new MySQLTime(); + MySQLTime ltime2 = new MySQLTime(); + MyTime.str_to_datetime_with_warn(sval1, ltime1, MyTime.TIME_FUZZY_DATE); + MyTime.str_to_datetime_with_warn(sval2, ltime2, MyTime.TIME_FUZZY_DATE); + return ltime1.compareTo(ltime2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldDatetime.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldDatetime.java new file mode 100644 index 000000000..39a26fe98 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldDatetime.java @@ -0,0 +1,64 @@ +package io.mycat.plan.common.field.temporal; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class FieldDatetime extends FieldTemporalWithDateAndTime { + + public FieldDatetime(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DATETIME; + } + + @Override + protected void internalJob() { + String ptr_str = null; + try { + ptr_str = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + if (ptr_str != null) { + MyTime.str_to_datetime_with_warn(ptr_str, ltime, MyTime.TIME_FUZZY_DATE); + } + } + + @Override + public BigInteger valInt() { + internalJob(); + return isNull() ? BigInteger.ZERO : BigInteger.valueOf(MyTime.TIME_to_ulonglong_datetime(ltime)); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } else + try { + String sval1 = MySQLcom.getFullString(charsetName, v1); + String sval2 = MySQLcom.getFullString(charsetName, v2); + MySQLTime ltime1 = new MySQLTime(); + MySQLTime ltime2 = new MySQLTime(); + MyTime.str_to_datetime_with_warn(sval1, ltime1, MyTime.TIME_FUZZY_DATE); + MyTime.str_to_datetime_with_warn(sval2, ltime2, MyTime.TIME_FUZZY_DATE); + return ltime1.compareTo(ltime2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporaWithDate.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporaWithDate.java new file mode 100644 index 000000000..036a36d80 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporaWithDate.java @@ -0,0 +1,38 @@ +package io.mycat.plan.common.field.temporal; + +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +/** + * Abstract class for types with date with optional time, with or without + * fractional part: DATE, DATETIME, DATETIME(N), TIMESTAMP, TIMESTAMP(N). + * + * @author chenzifei + * + */ +public abstract class FieldTemporaWithDate extends FieldTemporal { + + public FieldTemporaWithDate(String name, String table, int charsetIndex, int field_length, int decimals, + long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public long valTimeTemporal() { + internalJob(); + return isNull() ? 0 : MyTime.TIME_to_longlong_time_packed(ltime); + } + + @Override + public long valDateTemporal() { + internalJob(); + return isNull() ? 0 : MyTime.TIME_to_longlong_datetime_packed(ltime); + } + + @Override + public boolean getTime(MySQLTime time) { + internalJob(); + return isNull() ? true : getDate(time, MyTime.TIME_FUZZY_DATE); + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporal.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporal.java new file mode 100644 index 000000000..5fc06f9fb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporal.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.field.temporal; + +import java.math.BigDecimal; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.time.MySQLTime; + +/** + * Abstract class for TIME, DATE, DATETIME, TIMESTAMP with and without + * fractional part. + * + * @author chenzifei + * + */ +public abstract class FieldTemporal extends Field { + protected MySQLTime ltime = new MySQLTime(); + + public FieldTemporal(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public ItemResult cmpType() { + return ItemResult.INT_RESULT; + } + + @Override + public BigDecimal valReal() { + return new BigDecimal(valInt()); + } + + @Override + public BigDecimal valDecimal() { + return isNull() ? null : new BigDecimal(valInt()); + } + + @Override + public int compareTo(final Field other) { + if (other == null || !(other instanceof FieldTemporal)) + return 1; + FieldTemporal other2 = (FieldTemporal) other; + this.internalJob(); + other2.internalJob(); + MySQLTime ltime2 = other2.ltime; + return ltime.compareTo(ltime2); + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporalWithDateAndTime.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporalWithDateAndTime.java new file mode 100644 index 000000000..96aa5edad --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldTemporalWithDateAndTime.java @@ -0,0 +1,10 @@ +package io.mycat.plan.common.field.temporal; + +public abstract class FieldTemporalWithDateAndTime extends FieldTemporaWithDate { + + public FieldTemporalWithDateAndTime(String name, String table, int charsetIndex, int field_length, + int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldTime.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldTime.java new file mode 100644 index 000000000..6b48f45e8 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldTime.java @@ -0,0 +1,71 @@ +package io.mycat.plan.common.field.temporal; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class FieldTime extends FieldTemporal { + + public FieldTime(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_TIME; + } + + @Override + protected void internalJob() { + String ptr_str = null; + try { + ptr_str = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + if (ptr_str != null) { + MyTime.str_to_time_with_warn(ptr_str, ltime); + } + } + + @Override + public BigInteger valInt() { + internalJob(); + return isNull() ? BigInteger.ZERO : BigInteger.valueOf(MyTime.TIME_to_ulonglong_time(ltime)); + } + + @Override + public long valTimeTemporal() { + internalJob(); + return isNull() ? 0 : MyTime.TIME_to_longlong_time_packed(ltime); + } + + @Override + public long valDateTemporal() { + internalJob(); + return isNull() ? 0 : MyTime.TIME_to_longlong_datetime_packed(ltime); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + try { + String sval1 = MySQLcom.getFullString(charsetName, v1); + String sval2 = MySQLcom.getFullString(charsetName, v2); + MySQLTime ltime1 = new MySQLTime(); + MySQLTime ltime2 = new MySQLTime(); + MyTime.str_to_time_with_warn(sval1, ltime1); + MyTime.str_to_time_with_warn(sval2, ltime2); + return ltime1.compareTo(ltime2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldTimestamp.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldTimestamp.java new file mode 100644 index 000000000..8cb53957b --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldTimestamp.java @@ -0,0 +1,63 @@ +package io.mycat.plan.common.field.temporal; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class FieldTimestamp extends FieldTemporalWithDateAndTime { + + public FieldTimestamp(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_TIMESTAMP; + } + + @Override + public BigInteger valInt() { + internalJob(); + return isNull() ? BigInteger.ZERO : BigInteger.valueOf(MyTime.TIME_to_ulonglong_datetime(ltime)); + } + + @Override + protected void internalJob() { + String ptr_str = null; + try { + ptr_str = MySQLcom.getFullString(charsetName, ptr); + } catch (UnsupportedEncodingException ue) { + logger.warn("parse string exception!", ue); + } + if (ptr_str != null) + MyTime.str_to_datetime_with_warn(ptr_str, ltime, MyTime.TIME_FUZZY_DATE); + } + + @Override + public int compare(byte[] v1, byte[] v2) { + if (v1 == null && v2 == null) + return 0; + else if (v1 == null) { + return -1; + } else if (v2 == null) { + return 1; + } else + try { + String sval1 = MySQLcom.getFullString(charsetName, v1); + String sval2 = MySQLcom.getFullString(charsetName, v2); + MySQLTime ltime1 = new MySQLTime(); + MySQLTime ltime2 = new MySQLTime(); + MyTime.str_to_datetime_with_warn(sval1, ltime1, MyTime.TIME_FUZZY_DATE); + MyTime.str_to_datetime_with_warn(sval2, ltime2, MyTime.TIME_FUZZY_DATE); + return ltime1.compareTo(ltime2); + } catch (Exception e) { + logger.info("String to biginteger exception!", e); + return -1; + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/field/temporal/FieldYear.java b/src/main/java/io/mycat/plan/common/field/temporal/FieldYear.java new file mode 100644 index 000000000..c3196e59f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/field/temporal/FieldYear.java @@ -0,0 +1,22 @@ +package io.mycat.plan.common.field.temporal; + +import io.mycat.plan.common.field.num.FieldTiny; +import io.mycat.plan.common.item.FieldTypes; + +/** + * + * @author chenzifei + * + */ +public class FieldYear extends FieldTiny { + + public FieldYear(String name, String table, int charsetIndex, int field_length, int decimals, long flags) { + super(name, table, charsetIndex, field_length, decimals, flags); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_YEAR; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/FieldTypes.java b/src/main/java/io/mycat/plan/common/item/FieldTypes.java new file mode 100644 index 000000000..ffb6109c0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/FieldTypes.java @@ -0,0 +1,58 @@ +package io.mycat.plan.common.item; + +public enum FieldTypes { + MYSQL_TYPE_DECIMAL(0), MYSQL_TYPE_TINY(1), MYSQL_TYPE_SHORT(2), MYSQL_TYPE_LONG(3), MYSQL_TYPE_FLOAT( + 4), MYSQL_TYPE_DOUBLE(5), MYSQL_TYPE_NULL(6), MYSQL_TYPE_TIMESTAMP(7), MYSQL_TYPE_LONGLONG( + 8), MYSQL_TYPE_INT24(9), MYSQL_TYPE_DATE(10), MYSQL_TYPE_TIME(11), MYSQL_TYPE_DATETIME( + 12), MYSQL_TYPE_YEAR(13), MYSQL_TYPE_NEWDATE(14), MYSQL_TYPE_VARCHAR(15), MYSQL_TYPE_BIT( + 16), MYSQL_TYPE_TIMESTAMP2(17), MYSQL_TYPE_DATETIME2(18), MYSQL_TYPE_TIME2( + 19), MYSQL_TYPE_NEWDECIMAL(246), MYSQL_TYPE_ENUM(247), MYSQL_TYPE_SET( + 248), MYSQL_TYPE_TINY_BLOB(249), MYSQL_TYPE_MEDIUM_BLOB( + 250), MYSQL_TYPE_LONG_BLOB(251), MYSQL_TYPE_BLOB( + 252), MYSQL_TYPE_VAR_STRING(253), MYSQL_TYPE_STRING( + 254), MYSQL_TYPE_GEOMETRY(255); + + private int i = 0; + + FieldTypes(int i) { + this.i = i; + } + + public int numberValue() { + return i; + } + + public static FieldTypes valueOf(int i) { + if (i < 0 || i >= MYSQL_TYPE_GEOMETRY.i) { + throw new IndexOutOfBoundsException("Invalid ordinal:" + i); + } else if (i < MYSQL_TYPE_TIME2.i) + return values()[i]; + else { + switch (i) { + case 246: + return MYSQL_TYPE_NEWDECIMAL; + case 247: + return MYSQL_TYPE_ENUM; + case 248: + return MYSQL_TYPE_SET; + case 249: + return MYSQL_TYPE_TINY_BLOB; + case 250: + return MYSQL_TYPE_MEDIUM_BLOB; + case 251: + return MYSQL_TYPE_LONG_BLOB; + case 252: + return MYSQL_TYPE_BLOB; + case 253: + return MYSQL_TYPE_VAR_STRING; + case 254: + return MYSQL_TYPE_STRING; + case 255: + return MYSQL_TYPE_GEOMETRY; + default: + throw new IndexOutOfBoundsException("Invalid ordinal:" + i); + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/mycat/plan/common/item/Item.java b/src/main/java/io/mycat/plan/common/item/Item.java new file mode 100644 index 000000000..8012a8acb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/Item.java @@ -0,0 +1,957 @@ +package io.mycat.plan.common.item; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.field.FieldUtil; +import io.mycat.plan.common.field.TypeConversionStatus; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimeStatus; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; +import io.mycat.plan.common.time.Timeval; + +public abstract class Item { + + protected static final Logger logger = Logger.getLogger(Item.class); + + public static final int NOT_FIXED_DEC = 31; + public static final int DECIMAL_MAX_SCALE = 30; + public static String FNAF = "$$_"; + + public enum ItemResult { + STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT, DECIMAL_RESULT + } + + public enum ItemType { + FIELD_ITEM, FUNC_ITEM, SUM_FUNC_ITEM, STRING_ITEM, INT_ITEM, REAL_ITEM, NULL_ITEM, VARBIN_ITEM, COPY_STR_ITEM, FIELD_AVG_ITEM, DEFAULT_VALUE_ITEM, PROC_ITEM, COND_ITEM, REF_ITEM, FIELD_STD_ITEM, FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM, SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER, PARAM_ITEM, TRIGGER_FIELD_ITEM, DECIMAL_ITEM, XPATH_NODESET, XPATH_NODESET_CMP, VIEW_FIXER_ITEM + }; + + protected String itemName; /* Name from visit */ + protected String pushDownName;/* name in child or db */ + protected String aliasName; /* name for alias */ + public int maxLength = 0; + public int decimals = NOT_FIXED_DEC; + public boolean maybeNull;/* If item may be null */ + public boolean nullValue; + public boolean withSumFunc; + public boolean withIsNull; + public boolean withSubQuery; + public boolean withUnValAble; + public boolean fixed; + public ItemResult cmpContext; + /* 默认charsetindex为my_charset_bin */ + public int charsetIndex = 63; + private HashSet referTables; + + public boolean fixFields() { + // We do not check fields which are fixed during construction + assert (fixed == false || basicConstItem()); + fixed = true; + return false; + } + + public ItemResult resultType() { + return ItemResult.REAL_RESULT; + } + + public List arguments() { + return null; + } + + public int getArgCount() { + return 0; + } + + /** + * Result type when an item appear in a numeric context. See + * Field::numeric_context_result_type() for more comments. + */ + public ItemResult numericContextResultType() { + if (isTemporal()) + return decimals != 0 ? ItemResult.DECIMAL_RESULT : ItemResult.INT_RESULT; + if (resultType() == ItemResult.STRING_RESULT) + return ItemResult.REAL_RESULT; + return resultType(); + } + + /** + * Similar to result_type() but makes DATE, DATETIME, TIMESTAMP pretend to + * be numbers rather than strings. + */ + public ItemResult temporalWithDateAsNumberResultType() { + return isTemporalWithDate() ? (decimals == 0 ? ItemResult.DECIMAL_RESULT : ItemResult.INT_RESULT) + : resultType(); + } + + public ItemResult castToIntType() { + return resultType(); + } + + public FieldTypes stringFieldType() { + FieldTypes f_type = FieldTypes.MYSQL_TYPE_VAR_STRING; + if (maxLength >= 16777216) + f_type = FieldTypes.MYSQL_TYPE_LONG_BLOB; + else if (maxLength >= 65536) + f_type = FieldTypes.MYSQL_TYPE_MEDIUM_BLOB; + return f_type; + } + + public FieldTypes fieldType() { + switch (resultType()) { + case STRING_RESULT: + return FieldTypes.MYSQL_TYPE_STRING; + case INT_RESULT: + return FieldTypes.MYSQL_TYPE_LONG; + case DECIMAL_RESULT: + return FieldTypes.MYSQL_TYPE_DECIMAL; + case REAL_RESULT: + return FieldTypes.MYSQL_TYPE_DOUBLE; + case ROW_RESULT: + default: + return FieldTypes.MYSQL_TYPE_STRING; + } + } + + public abstract ItemType type(); + + /** + * val_real和val_decimal的区别是,val_real不会返回null,返回null的情况下会返回BigDecimal.zero + * + * @return + */ + public abstract BigDecimal valReal(); + + /** + * 不会返回null,对应的返回BigInteger.zero + * + * @return + */ + public abstract BigInteger valInt(); + + /** + * Return date value of item in packed longlong format. + */ + public long valDateTemporal() { + MySQLTime ltime = new MySQLTime(); + if (nullValue = getDate(ltime, 1)) + return 0; + return MyTime.TIME_to_longlong_datetime_packed(ltime); + } + + /** + * Return time value of item in packed longlong format. + */ + public long valTimeTemporal() { + MySQLTime ltime = new MySQLTime(); + if (nullValue = getTime(ltime)) + return 0; + return MyTime.TIME_to_longlong_time_packed(ltime); + } + + public long valTemporalByFieldType() { + if (fieldType() == FieldTypes.MYSQL_TYPE_TIME) + return valTimeTemporal(); + assert (isTemporalWithDate()); + return valDateTemporal(); + } + + public abstract String valStr(); + + public abstract BigDecimal valDecimal(); + + /** + * ProtocolText::ResultsetRow: + * + * A row with the data for each column. + * + * NULL is sent as 0xfb + * + * everything else is converted into a string and is sent as + * Protocol::LengthEncodedString. + * + * + * + * @return + */ + public byte[] getRowPacketByte() { + byte[] result = null; + FieldTypes f_type; + + switch (f_type = fieldType()) { + default: + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_NEWDECIMAL: { + String res = null; + if ((res = valStr()) != null) + try { + result = res.getBytes(charset()); + } catch (UnsupportedEncodingException e) { + logger.error(e); + } + else { + assert (nullValue); + } + break; + } + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: { + BigInteger bi = valInt(); + if (!nullValue) + result = bi.toString().getBytes(); + break; + } + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: { + BigDecimal bd = valReal(); + if (!nullValue) + result = bd.toString().getBytes(); + break; + } + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIMESTAMP: { + MySQLTime tm = new MySQLTime(); + getDate(tm, MyTime.TIME_FUZZY_DATE); + if (!nullValue) { + if (f_type == FieldTypes.MYSQL_TYPE_DATE) { + result = MyTime.my_date_to_str(tm).getBytes(); + } else { + result = MyTime.my_datetime_to_str(tm, decimals).getBytes(); + } + } + break; + } + case MYSQL_TYPE_TIME: { + MySQLTime tm = new MySQLTime(); + getTime(tm); + if (!nullValue) + result = MyTime.my_time_to_str(tm, decimals).getBytes(); + break; + } + } + if (nullValue) + result = null; + return result; + } + + public boolean valBool() { + switch (resultType()) { + case INT_RESULT: + return valInt().longValue() != 0; + case DECIMAL_RESULT: { + BigDecimal val = valDecimal(); + if (val != null) + return val.compareTo(BigDecimal.ZERO) != 0; + return false; + } + case REAL_RESULT: + case STRING_RESULT: + return !(valReal().compareTo(BigDecimal.ZERO) == 0); + case ROW_RESULT: + default: + return false; // Wrong (but safe) + } + } + + /* + * Returns true if this is a simple constant item like an integer, not a + * constant expression. Used in the optimizer to propagate basic constants. + */ + public boolean basicConstItem() { + return false; + } + + public int maxCharLength() { + return maxLength / 1; + } + + public int floatLength(int decimals_par) { + return decimals != NOT_FIXED_DEC ? (MySQLcom.DBL_DIG + 2 + decimals_par) : MySQLcom.DBL_DIG + 8; + } + + /** + * + * @return + */ + public int decimalPrecision() { + // TODO + return MySQLcom.DBL_DIG + 10; + } + + public final int decimalIntPart() { + return decimalPrecision() - decimals; + } + + public Comparable getValueWithType(ItemResult type) { + switch (type) { + case REAL_RESULT: + return valReal(); + case DECIMAL_RESULT: + return valDecimal(); + case INT_RESULT: + return valInt(); + case STRING_RESULT: + return valStr(); + default: + break; + } + return null; + } + + public boolean isTemporalWithDate() { + return FieldUtil.is_temporal_type_with_date(fieldType()); + } + + public boolean isTemporalWithDateAndTime() { + return FieldUtil.is_temporal_type_with_date_and_time(fieldType()); + } + + public boolean isTemporalWithTime() { + return FieldUtil.is_temporal_type_with_time(fieldType()); + } + + public boolean isTemporal() { + return FieldUtil.is_temporal_type(fieldType()); + } + + public abstract boolean getDate(MySQLTime ltime, long fuzzydate); + + public abstract boolean getTime(MySQLTime ltime); + + public boolean isNull() { + return false; + } + + /* + * Make sure the null_value member has a correct value. + */ + public void updateNullValue() { + valInt(); + } + + public void fixCharLength(int maxCharLength) { + maxLength = maxCharLength; + } + + public void makeField(FieldPacket tmpFp) { + initMakeField(tmpFp, fieldType()); + } + + /* + * This implementation can lose str_value content, so if the Item uses + * str_value to store something, it should reimplement it's + * ::save_in_field() as Item_string, for example, does. + * + * Note: all Item_XXX::val_str(str) methods must NOT rely on the fact that + * str != str_value. For example, see fix for bug #44743. + */ + /** + * Save a temporal value in packed longlong format into a Field. Used in + * optimizer. + * + * @param[out] field The field to set the value to. + * @retval 0 On success. + * @retval >0 In error. + */ + public TypeConversionStatus saveInField(Field field, boolean noConversions) { + TypeConversionStatus error = null; + try { + if (resultType() == ItemResult.STRING_RESULT) { + String result = valStr(); + if (nullValue) { + field.setPtr(null); + error = TypeConversionStatus.TYPE_OK; + return error; + } + field.setPtr(result.getBytes(charset())); + } else if (resultType() == ItemResult.REAL_RESULT && field.resultType() == ItemResult.STRING_RESULT) { + BigDecimal nr = valReal(); + if (nullValue) { + field.setPtr(null); + error = TypeConversionStatus.TYPE_OK; + return error; + } + field.setPtr(nr.toString().getBytes()); + } else if (resultType() == ItemResult.REAL_RESULT) { + BigDecimal nr = valReal(); + if (nullValue) { + field.setPtr(null); + error = TypeConversionStatus.TYPE_OK; + return error; + } + field.setPtr(nr.toString().getBytes()); + } else if (resultType() == ItemResult.DECIMAL_RESULT) { + BigDecimal value = valDecimal(); + if (nullValue) { + field.setPtr(null); + error = TypeConversionStatus.TYPE_OK; + return error; + } + field.setPtr(value.toString().getBytes()); + } else { + BigInteger nr = valInt(); + if (nullValue) { + field.setPtr(null); + error = TypeConversionStatus.TYPE_OK; + return error; + } + field.setPtr(nr.toString().getBytes()); + } + return error != null ? error : TypeConversionStatus.TYPE_ERR_BAD_VALUE; + } catch (Exception e) { + return TypeConversionStatus.TYPE_ERR_BAD_VALUE; + } + } + + /*-------------------------helper funtion----------------------------*/ + protected String valStringFromReal() { + BigDecimal nr = valReal(); + if (nullValue) + return null; /* purecov: inspected */ + return nr.toString(); + } + + protected String valStringFromInt() { + return valInt().toString(); + } + + protected String valStringFromDecimal() { + BigDecimal bd = valDecimal(); + if (nullValue) + return null; /* purecov: inspected */ + if (bd == null) + return null; + return bd.toString(); + } + + protected String valStringFromDate() { + MySQLTime ltime = new MySQLTime(); + if (getDate(ltime, 1)) + return null; + return MyTime.my_date_to_str(ltime); + } + + protected String valStringFromTime() { + MySQLTime ltime = new MySQLTime(); + if (getTime(ltime)) + return null; + return MyTime.my_time_to_str(ltime, this.decimals); + } + + protected String valStringFromDatetime() { + MySQLTime ltime = new MySQLTime(); + if (getDate(ltime, 1)) + return null; + return MyTime.my_datetime_to_str(ltime, this.decimals); + } + + protected BigDecimal valDecimalFromReal() { + BigDecimal nr = valReal(); + if (nullValue) + return null; /* purecov: inspected */ + return nr; + } + + protected BigDecimal valDecimalFromInt() { + BigInteger nr = valInt(); + return new BigDecimal(nr); + } + + protected BigDecimal valDecimalFromString() { + String res = valStr(); + if (res == null) + return null; + try { + return new BigDecimal(res); + } catch (NumberFormatException ne) { + return null; + } + } + + protected BigDecimal valDecimalFromDate() { + MySQLTime ltime = new MySQLTime(); + return getDate(ltime, 1) ? null : MyTime.date2my_decimal(ltime); + } + + protected BigDecimal valDecimalFromTime() { + MySQLTime ltime = new MySQLTime(); + return getTime(ltime) ? null : MyTime.time2my_decimal(ltime); + } + + protected long valIntFromDecimal() { + BigDecimal bd = valDecimal(); + if (nullValue) + return 0; + return bd.longValue(); + } + + protected long valIntFromDate() { + MySQLTime ltime = new MySQLTime(); + return getDate(ltime, 1) ? 0L : (long) MyTime.TIME_to_ulonglong_date(ltime); + } + + protected long valIntFromDatetime() { + MySQLTime ltime = new MySQLTime(); + return getDate(ltime, 1) ? 0L : (long) MyTime.TIME_to_ulonglong_datetime_round(ltime); + } + + protected long valIntFromTime() { + MySQLTime ltime = new MySQLTime(); + return getTime(ltime) ? 0L : (long) MyTime.TIME_to_ulonglong_time_round(ltime); + } + + protected BigDecimal valRealFromDecimal() { + /* Note that fix_fields may not be called for Item_avg_field items */ + BigDecimal dec_val = valDecimal(); + if (nullValue) + return BigDecimal.ZERO; + return dec_val; + } + + protected boolean getDateFromString(MySQLTime ltime, long flags) { + String res = null; + if ((res = valStr()) == null) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); + return true; + } + MySQLTimeStatus status = new MySQLTimeStatus(); + return MyTime.str_to_datetime(res, res.length(), ltime, flags, status); + } + + protected boolean getDateFromReal(MySQLTime ltime, long flags) { + double value = valReal().doubleValue(); + if (nullValue) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); + return true; + } + return MyTime.my_double_to_datetime_with_warn(value, ltime, flags); + } + + protected boolean getDateFromDecimal(MySQLTime ltime, long flags) { + BigDecimal value = valDecimal(); + if (nullValue) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); + return true; + } + return MyTime.my_decimal_to_datetime_with_warn(value, ltime, flags); + } + + protected boolean getDateFromInt(MySQLTime ltime, long flags) { + long value = valInt().longValue(); + if (nullValue) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); + return true; + } + MyTime.TIME_from_longlong_datetime_packed(ltime, value); + return false; + } + + protected boolean getDateFromTime(MySQLTime ltime) { + MySQLTime tmp = new MySQLTime(); + if (getTime(tmp)) { + assert (nullValue); + return true; + } + MyTime.time_to_datetime(tmp, ltime); + return false; + } + + /** + * Convert a numeric type to date + */ + protected boolean getDateFromNumeric(MySQLTime ltime, long flags) { + switch (resultType()) { + case REAL_RESULT: + return getDateFromReal(ltime, flags); + case DECIMAL_RESULT: + return getDateFromDecimal(ltime, flags); + case INT_RESULT: + return getDateFromInt(ltime, flags); + case STRING_RESULT: + case ROW_RESULT: + assert (false); + } + return (nullValue = true); // Impossible result_type + } + + /** + * Convert a non-temporal type to date + */ + protected boolean getDateFromNonTemporal(MySQLTime ltime, long fuzzydate) { + assert (!isTemporal()); + switch (resultType()) { + case STRING_RESULT: + return getDateFromString(ltime, fuzzydate); + case REAL_RESULT: + return getDateFromReal(ltime, fuzzydate); + case DECIMAL_RESULT: + return getDateFromDecimal(ltime, fuzzydate); + case INT_RESULT: + return getDateFromInt(ltime, fuzzydate); + case ROW_RESULT: + assert (false); + } + return (nullValue = true); // Impossible result_type + } + + /** + * Convert val_str() to time in MYSQL_TIME + */ + protected boolean getTimeFromString(MySQLTime ltime) { + String res = valStr(); + if (res == null) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + return true; + } + return MyTime.str_to_time(res, res.length(), ltime, new MySQLTimeStatus()); + } + + /** + * Convert val_real() to time in MYSQL_TIME + */ + protected boolean getTimeFromReal(MySQLTime ltime) { + double value = valReal().doubleValue(); + if (nullValue) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + return true; + } + return MyTime.my_double_to_time_with_warn(value, ltime); + } + + /** + * Convert val_decimal() to time in MYSQL_TIME + */ + protected boolean getTimeFromDecimal(MySQLTime ltime) { + BigDecimal decimal = valDecimal(); + if (nullValue) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + return true; + } + return MyTime.my_decimal_to_time_with_warn(decimal, ltime); + } + + /** + * Convert val_int() to time in MYSQL_TIME + */ + protected boolean getTimeFromInt(MySQLTime ltime) { + long value = valInt().longValue(); + if (nullValue) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + return true; + } + MyTime.TIME_from_longlong_time_packed(ltime, value); + return false; + } + + /** + * Convert date to time + */ + protected boolean getTimeFromDate(MySQLTime ltime) { + if (getDate(ltime, 1)) // Need this check if NULL value + return true; + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + return false; + } + + /** + * Convert datetime to time + */ + protected boolean getTimeFromDatetime(MySQLTime ltime) { + if (getDate(ltime, 1)) + return true; + MyTime.datetime_to_time(ltime); + return false; + } + + /** + * Convert a numeric type to time + */ + protected boolean getTimeFromNumeric(MySQLTime ltime) { + assert (!isTemporal()); + switch (resultType()) { + case REAL_RESULT: + return getTimeFromReal(ltime); + case DECIMAL_RESULT: + return getTimeFromDecimal(ltime); + case INT_RESULT: + return getTimeFromInt(ltime); + case STRING_RESULT: + case ROW_RESULT: + assert (false); + } + return (nullValue = true); // Impossible result type + } + + /** + * Convert a non-temporal type to time + */ + protected boolean getTimeFromNonTemporal(MySQLTime ltime) { + assert (!isTemporal()); + switch (resultType()) { + case STRING_RESULT: + return getTimeFromString(ltime); + case REAL_RESULT: + return getTimeFromReal(ltime); + case DECIMAL_RESULT: + return getTimeFromDecimal(ltime); + case INT_RESULT: + return getTimeFromInt(ltime); + case ROW_RESULT: + assert (false); + } + return (nullValue = true); // Impossible result type + } + + /*-----------------------------end helper functions----------------*/ + + /** -- not so important functions -- **/ + public void fixLengthAndDecAndCharsetDatetime(int max_char_length_arg, int dec_arg) { + decimals = dec_arg; + fixCharLength(max_char_length_arg + (dec_arg != 0 ? dec_arg + 1 : 0)); + } + + public int datetimePrecision() { + if (resultType() == ItemResult.STRING_RESULT && !isTemporal()) { + MySQLTime ltime = new MySQLTime(); + String tmp = valStr(); + MySQLTimeStatus status = new MySQLTimeStatus(); + // Nanosecond rounding is not needed, for performance purposes + if ((tmp != null) && !MyTime.str_to_datetime(tmp, tmp.length(), ltime, + MyTime.TIME_NO_NSEC_ROUNDING | MyTime.TIME_FUZZY_DATE, status)) + return Math.min((int) status.fractional_digits, MyTime.DATETIME_MAX_DECIMALS); + } + return Math.min(decimals, MyTime.DATETIME_MAX_DECIMALS); + } + + /* + * - Return NULL if argument is NULL. - Return zero if argument is not NULL, + * but we could not convert it to DATETIME. - Return zero if argument is not + * NULL and represents a valid DATETIME value, but the value is out of the + * supported Unix timestamp range. + */ + public boolean getTimeval(Timeval tm) { + MySQLTime ltime = new MySQLTime(); + if (getDate(ltime, MyTime.TIME_FUZZY_DATE)) { + if (nullValue) + return true; /* Value is NULL */ + else { + tm.tv_sec = tm.tv_usec = 0; + return false; + } + } + if (MyTime.datetime_to_timeval(ltime, + tm)) { /* + * Value is out of the supported range + */ + tm.tv_sec = tm.tv_usec = 0; + return false; + } + return false; /* Value is a good Unix timestamp */ + } + + private void initMakeField(FieldPacket tmp_field, FieldTypes field_type) { + byte[] empty_name = new byte[]{}; + tmp_field.db = empty_name; + tmp_field.orgTable = empty_name; + tmp_field.orgName = empty_name; + tmp_field.charsetIndex = charsetIndex; + try { + tmp_field.name = (getAlias() == null ? getItemName() : getAlias()).getBytes(charset()); + } catch (UnsupportedEncodingException e) { + logger.warn("parse string exception!", e); + } + tmp_field.flags = (maybeNull ? 0 : FieldUtil.NOT_NULL_FLAG); + tmp_field.type = field_type.numberValue(); + tmp_field.length = maxLength; + tmp_field.decimals = (byte) decimals; + } + + private String charset() { + return CharsetUtil.getJavaCharset(charsetIndex); + } + + /** + * @return + */ + public int timePrecision() { + if (canValued() && resultType() == ItemResult.STRING_RESULT && !isTemporal()) { + MySQLTime ltime = new MySQLTime(); + String tmp = valStr(); + MySQLTimeStatus status = new MySQLTimeStatus(); + // Nanosecond rounding is not needed, for performance purposes + if (tmp != null && MyTime.str_to_time(tmp, tmp.length(), ltime, status) == false) + return Math.min((int) status.fractional_digits, MyTime.DATETIME_MAX_DECIMALS); + } + return Math.min(decimals, MyTime.DATETIME_MAX_DECIMALS); + } + + public boolean isWild() { + return false; + } + + public final String getAlias() { + return this.aliasName; + } + + public final void setAlias(String alias) { + this.aliasName = alias; + } + + public String getPushDownName() { + return pushDownName; + } + + public void setPushDownName(String pushDownName) { + this.pushDownName = pushDownName; + } + + //TODO:YHQ NEED CHECK + public final String getItemName() { + if (itemName == null || itemName.length() == 0) { + SQLExpr expr = toExpression(); + StringBuilder sb = new StringBuilder(); + MySqlOutputVisitor ov = new MySqlOutputVisitor(sb); + expr.accept(ov); + itemName = sb.toString(); + } + return itemName; + } + + public final void setItemName(String itemName) { + this.itemName = itemName; + } + + /** + * clone item include alias + * + * @return + */ + public final Item cloneItem() { + Item cloneItem = cloneStruct(false, null, false, null); + cloneItem.itemName = itemName; + cloneItem.aliasName = aliasName; + return cloneItem; + } + + public final HashSet getReferTables() { + // so if we don't use this method all the time, refertables is null + if (referTables == null) + referTables = new HashSet(2); + return referTables; + } + + public final boolean canValued() { + if (withUnValAble) + return false; + else { + fixFields(); + return true; + } + } + + public String getTableName() { + return null; + } + + public abstract Item fixFields(NameResolutionContext context); + + /** + * added to construct all refers in an item + * + * @param context + */ + public abstract void fixRefer(ReferContext context); + //TODO:YHQ NEED CHECK + public abstract SQLExpr toExpression(); + + public final Item cloneStruct() { + Item clone = cloneStruct(false, null, false, null); + clone.withSumFunc = withSumFunc; + clone.withIsNull = withIsNull; + clone.withSubQuery = withSubQuery; + clone.withUnValAble = withUnValAble; + clone.pushDownName = pushDownName; + clone.getReferTables().addAll(getReferTables()); + return clone; + } + + public final Item reStruct(List calArgs, boolean isPushDown, List fields) { + Item clone = cloneStruct(true, calArgs, isPushDown, fields); + // TODO + return clone; + } + + /** + * cloen item's struct,visitName is all empty + * + * @param forCalculate + * if true,then use arguments after;else,only for name setup + * @param calArgs + * used in queryHandler calculate + * @param isPushDown + * @param fields + * @return + */ + protected abstract Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, + List fields); + + //TODO:YHQ NEED CHECK + protected final List toExpressionList(List args) { + if (args == null) + return null; + List newList = new ArrayList(); + for (Item item : args) { + newList.add(item.toExpression()); + } + return newList; + } + + protected final List cloneStructList(List args) { + if (args == null) + return null; + List newList = new ArrayList(); + for (Item toClone : args) { + newList.add(toClone.cloneStruct()); + } + return newList; + } + + @Override + public String toString() { + return getItemName(); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemBasicConstant.java b/src/main/java/io/mycat/plan/common/item/ItemBasicConstant.java new file mode 100644 index 000000000..573eb2a81 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemBasicConstant.java @@ -0,0 +1,30 @@ +package io.mycat.plan.common.item; + +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; + +public abstract class ItemBasicConstant extends Item { + + protected ItemBasicConstant(){ + this.withUnValAble = false; + this.withIsNull= false; + this.withSubQuery=false; + this.withSumFunc=false; + } + + @Override + public boolean basicConstItem() { + return true; + } + + @Override + public final Item fixFields(NameResolutionContext context) { + return this; + } + + @Override + public final void fixRefer(ReferContext context) { + // constant no need to add in refer + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemField.java b/src/main/java/io/mycat/plan/common/item/ItemField.java new file mode 100644 index 000000000..87ec859b2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemField.java @@ -0,0 +1,300 @@ +package io.mycat.plan.common.item; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.config.ErrorCode; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.plan.NamedField; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemField extends ItemIdent { + + public Field field; + + /* index如果有值的话,代表这个Item_field真实的下标值,需要在调用val之前调用setField方法 */ + public int index = -1; + + public ItemField(String dbName, String tableName, String fieldName) { + super(dbName, tableName, fieldName); + } + + public ItemField(Field field) { + super(null, field.table, field.name); + set_field(field); + } + + /** + * 保存index + * + * @param index + */ + public ItemField(int index) { + super(null, "", ""); + this.index = index; + } + + public void setField(List fields) { + assert (fields != null); + set_field(fields.get(index)); + } + + @Override + public ItemType type() { + return ItemType.FIELD_ITEM; + } + + @Override + public ItemResult resultType() { + return field.resultType(); + } + + @Override + public ItemResult numericContextResultType() { + return field.numericContextResultType(); + } + + @Override + public FieldTypes fieldType() { + return field.fieldType(); + } + + @Override + public byte[] getRowPacketByte() { + return field.ptr; + } + + public ItemResult cmp_type() { + return field.cmpType(); + } + + @Override + public int hashCode() { + int prime = 31; + int hashCode = dbName == null ? 0 : dbName.hashCode(); + hashCode = hashCode * prime + (tableName == null ? 0 : tableName.hashCode()); + hashCode = hashCode * prime + (itemName == null ? 0 : itemName.hashCode()); + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemField)) + return false; + ItemField other = (ItemField) obj; + return StringUtils.equals(getTableName(), other.getTableName()) + && StringUtils.equalsIgnoreCase(getItemName(), other.getItemName()); + } + + @Override + public BigDecimal valReal() { + if (nullValue = field.isNull()) + return BigDecimal.ZERO; + return field.valReal(); + } + + @Override + public BigInteger valInt() { + if (nullValue = field.isNull()) + return BigInteger.ZERO; + return field.valInt(); + } + + @Override + public long valTimeTemporal() { + if ((nullValue = field.isNull())) + return 0; + return field.valTimeTemporal(); + } + + @Override + public long valDateTemporal() { + if ((nullValue = field.isNull())) + return 0; + return field.valDateTemporal(); + } + + @Override + public BigDecimal valDecimal() { + if (nullValue = field.isNull()) + return null; + return field.valDecimal(); + } + + @Override + public String valStr() { + if (nullValue = field.isNull()) + return null; + return field.valStr(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + if ((nullValue = field.isNull()) || field.getDate(ltime, fuzzydate)) { + ltime.set_zero_time(ltime.time_type); + return true; + } + return false; + } + + @Override + public boolean getTime(MySQLTime ltime) { + if ((nullValue = field.isNull()) || field.getTime(ltime)) { + ltime.set_zero_time(ltime.time_type); + return true; + } + return false; + } + + @Override + public boolean isNull() { + return field.isNull(); + } + + @Override + public void makeField(FieldPacket fp) { + field.makeField(fp); + try { + if (itemName != null) { + fp.name = itemName.getBytes(CharsetUtil.getJavaCharset(charsetIndex)); + } + if ((tableName != null)) { + fp.table = tableName.getBytes(CharsetUtil.getJavaCharset(charsetIndex)); + } + if (dbName != null) { + fp.db = dbName.getBytes(CharsetUtil.getJavaCharset(charsetIndex)); + } + } catch (UnsupportedEncodingException e) { + logger.warn("parse string exception!", e); + } + } + + protected void set_field(Field field) { + this.field = field; + maybeNull = field.maybeNull(); // 有可能为null + decimals = field.decimals; + tableName = field.table; + itemName = field.name; + dbName = field.dbname; + maxLength = field.fieldLength; + charsetIndex = field.charsetIndex; + fixed = true; + } + + public String getTableName() { + return tableName; + } + + @Override + public Item fixFields(NameResolutionContext context) { + if (this.isWild()) + return this; + NamedField tmpField = new NamedField(null); + tmpField.name = getItemName(); + PlanNode planNode = context.getPlanNode(); + Item column = null; + Item columnFromMeta = null; + if (context.getPlanNode().type() == PlanNodeType.MERGE) { + // select union only found in outerfields + if (StringUtils.isEmpty(getTableName())) { + column = planNode.getOuterFields().get(tmpField); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "42000", + "Table '" + getTableName() + "' from one of the SELECTs cannot be used in global ORDER clause"); + } + return column; + } + if (context.isFindInSelect()) { + // 尝试从selectlist中查找一次 + if (StringUtils.isEmpty(getTableName())) { + for (NamedField field : planNode.getOuterFields().keySet()) { + if (StringUtils.equalsIgnoreCase(tmpField.name, field.name)) { + if (column == null) { + column = planNode.getOuterFields().get(field); + } else + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "42S22", "duplicate column:" + this); + } + } + } else { + tmpField.table = getTableName(); + column = planNode.getOuterFields().get(tmpField); + } + } + if (column != null && context.isSelectFirst()) { + return column; + } + // find from inner fields + if (StringUtils.isEmpty(getTableName())) { + for (NamedField field : planNode.getInnerFields().keySet()) { + if (StringUtils.equalsIgnoreCase(tmpField.name, field.name)) { + if (columnFromMeta == null) { + tmpField.table = field.table; + NamedField coutField = planNode.getInnerFields().get(tmpField); + this.tableName = tmpField.table; + getReferTables().clear(); + this.getReferTables().add(coutField.planNode); + columnFromMeta = this; + } else + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "42S22", "duplicate column:" + this); + } + } + } else { + tmpField.table = getTableName(); + if (planNode.getInnerFields().containsKey(tmpField)) { + NamedField coutField = planNode.getInnerFields().get(tmpField); + getReferTables().clear(); + getReferTables().add(coutField.planNode); + this.tableName = tmpField.table; + columnFromMeta = this; + } + } + if (columnFromMeta != null) { + return columnFromMeta; + } else if (column == null) + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "42S22", "column " + this + " not found"); + else { + return column; + } + + } + + @Override + public void fixRefer(ReferContext context) { + if (isWild()) + return; + PlanNode node = context.getPlanNode(); + PlanNode tn = getReferTables().iterator().next(); + node.addSelToReferedMap(tn, this); + } + + @Override + public SQLExpr toExpression() { + SQLIdentifierExpr parent = tableName == null ? null : new SQLIdentifierExpr(tableName); + return new SQLPropertyExpr(parent, itemName); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + return new ItemField(dbName, tableName, itemName); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemFloat.java b/src/main/java/io/mycat/plan/common/item/ItemFloat.java new file mode 100644 index 000000000..6c9f4deda --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemFloat.java @@ -0,0 +1,101 @@ +package io.mycat.plan.common.item; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumberExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.num.ItemNum; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFloat extends ItemNum { + private BigDecimal value; + + public ItemFloat(BigDecimal value) { + this.value = value; + fixed = true; + decimals = this.value.scale() > 0 ? this.value.scale() : 0; + } + + @Override + public ItemType type() { + return ItemType.REAL_ITEM; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DOUBLE; + } + + @Override + public BigDecimal valReal() { + return value; + } + + @Override + public BigInteger valInt() { + return value.toBigInteger(); + } + + @Override + public BigDecimal valDecimal() { + return value; + } + + @Override + public String valStr() { + return value.toString(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromReal(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromReal(ltime); + } + + @Override + public ItemNum neg() { + value = value.negate(); + return this; + } + + @Override + public int hashCode() { + if (value == null) + return 0; + else + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemFloat)) + return false; + ItemFloat other = (ItemFloat) obj; + if (value == null || other.value == null) + return value == null && other.value == null; + else + return value.equals(other.value); + } + + @Override + public SQLExpr toExpression() { + return new SQLNumberExpr(value); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + return new ItemFloat(value); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemIdent.java b/src/main/java/io/mycat/plan/common/item/ItemIdent.java new file mode 100644 index 000000000..a186f6160 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemIdent.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item; + +import org.apache.commons.lang.StringUtils; + +public abstract class ItemIdent extends Item { + /* + * We have to store initial values of db_name, table_name and field_name to + * be able to restore them during cleanup() because they can be updated + * during fix_fields() to values from Field object and life-time of those is + * shorter than life-time of Item_field. + */ + protected String origDbName; + protected String origTableName; + protected String origFieldName; + + public String dbName; + public String tableName; + public boolean aliasNameUsed; + + public ItemIdent(final String dbNameArg, final String tableNameArg, final String fieldNameArg) { + this.dbName = dbNameArg; + this.tableName = tableNameArg; + this.itemName = fieldNameArg; + this.withUnValAble=true; + } + + @Override + public boolean isWild() { + if (StringUtils.equalsIgnoreCase(itemName, "*")) + return true; + return false; + } + + @Override + public String toString() { + return toExpression().toString(); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemInt.java b/src/main/java/io/mycat/plan/common/item/ItemInt.java new file mode 100644 index 000000000..bc21d16dc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemInt.java @@ -0,0 +1,119 @@ +package io.mycat.plan.common.item; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.num.ItemNum; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemInt extends ItemNum { + private BigInteger value; + + public ItemInt(long value) { + this.value = BigInteger.valueOf(value); + fixed = true; + maxLength = String.valueOf(value).length(); + } + + public ItemInt(Integer value) { + this.value = BigInteger.valueOf(value); + fixed = true; + maxLength = String.valueOf(value).length(); + } + + @Override + public ItemType type() { + return ItemType.INT_ITEM; + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_LONGLONG; + } + + @Override + public BigDecimal valReal() { + return new BigDecimal(value); + } + + @Override + public BigInteger valInt() { + return value; + } + + @Override + public String valStr() { + return value.toString(); + } + + @Override + public BigDecimal valDecimal() { + return valReal(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromInt(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromInt(ltime); + } + + @Override + public int decimalPrecision() { + return maxLength - (value.signum() == -1 ? 1 : 0); + } + + @Override + public ItemNum neg() { + this.value = this.value.negate(); + return this; + } + + @Override + public int hashCode() { + if (value == null) + return 0; + else + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemFloat)) + return false; + ItemInt other = (ItemInt) obj; + if (value == null || other.value == null) + return value == null && other.value == null; + else + return value.equals(other.value); + } + + @Override + public SQLExpr toExpression() { + return new SQLIntegerExpr(value); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + return new ItemInt(value.longValue()); + } + + +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemNull.java b/src/main/java/io/mycat/plan/common/item/ItemNull.java new file mode 100644 index 000000000..b2b5305cf --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemNull.java @@ -0,0 +1,113 @@ +package io.mycat.plan.common.item; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLNullExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemNull extends ItemBasicConstant { + private void init() { + maybeNull = nullValue = true; + maxLength = 0; + fixed = true; + itemName = "NULL"; + } + + public ItemNull() { + init(); + } + + @Override + public ItemType type() { + return ItemType.NULL_ITEM; + } + + @Override + public int hashCode() { + return "null".hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemNull)) + return false; + return true; + } + + @Override + public BigDecimal valReal() { + nullValue = true; + return BigDecimal.ZERO; + } + + @Override + public BigInteger valInt() { + nullValue = true; + return BigInteger.ZERO; + } + + @Override + public long valTimeTemporal() { + return valInt().longValue(); + } + + @Override + public long valDateTemporal() { + return valInt().longValue(); + } + + @Override + public String valStr() { + nullValue = true; + return null; + } + + @Override + public BigDecimal valDecimal() { + return null; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return true; + } + + @Override + public boolean getTime(MySQLTime ltime) { + return true; + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_NULL; + } + + @Override + public boolean isNull() { + return true; + } + + @Override + public SQLExpr toExpression() { + return new SQLNullExpr(); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + return new ItemNull(); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemRef.java b/src/main/java/io/mycat/plan/common/item/ItemRef.java new file mode 100644 index 000000000..155124b53 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemRef.java @@ -0,0 +1,155 @@ +package io.mycat.plan.common.item; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.config.ErrorCode; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemRef extends Item { + public enum Ref_Type { + REF, DIRECT_REF, VIEW_REF, OUTER_REF, AGGREGATE_REF + }; + + private Item ref; + private String table_alias = null; + private String field_alias = null; + + public ItemRef(Item ref, String table_alias, String field_alias) { + this.ref = ref; + this.table_alias = table_alias; + this.field_alias = field_alias; + } + + @Override + public ItemType type() { + return ItemType.REF_ITEM; + } + + @Override + public boolean fixFields() { + if (ref == null) { + // TODO + throw new RuntimeException("unexpected!"); + } + return ref.fixFields(); + } + + @Override + public BigDecimal valReal() { + return ref.valReal(); + } + + @Override + public BigInteger valInt() { + return ref.valInt(); + } + + @Override + public long valTimeTemporal() { + return ref.valTimeTemporal(); + } + + @Override + public long valDateTemporal() { + return ref.valDateTemporal(); + } + + @Override + public BigDecimal valDecimal() { + return ref.valDecimal(); + } + + @Override + public boolean valBool() { + return ref.valBool(); + } + + @Override + public String valStr() { + return ref.valStr(); + } + + @Override + public boolean isNull() { + return ref.isNull(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return ref.getDate(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return ref.getTime(ltime); + } + + @Override + public ItemResult resultType() { + return ref.resultType(); + } + + @Override + public FieldTypes fieldType() { + return ref.fieldType(); + } + + @Override + public byte[] getRowPacketByte() { + return ref.getRowPacketByte(); + } + + @Override + public void makeField(FieldPacket tmpFp) { + ref.makeField(tmpFp); + if (field_alias != null) { + tmpFp.orgName = tmpFp.name; + try { + tmpFp.name = field_alias.getBytes(CharsetUtil.getJavaCharset(charsetIndex)); + } catch (UnsupportedEncodingException e) { + logger.warn("parse string exception!", e); + } + } + if (table_alias != null) { + tmpFp.orgTable = tmpFp.table; + try { + tmpFp.table = table_alias.getBytes(CharsetUtil.getJavaCharset(charsetIndex)); + } catch (UnsupportedEncodingException e) { + logger.warn("parse string exception!", e); + } + } + } + + @Override + public Item fixFields(NameResolutionContext context) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected usage!"); + } + + @Override + public void fixRefer(ReferContext context) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected usage!"); + + } + + @Override + public SQLExpr toExpression() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected usage!"); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected usage!"); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemResultField.java b/src/main/java/io/mycat/plan/common/item/ItemResultField.java new file mode 100644 index 000000000..a96bf2bb9 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemResultField.java @@ -0,0 +1,23 @@ +package io.mycat.plan.common.item; + +import io.mycat.plan.common.field.Field; + +public abstract class ItemResultField extends Item { + public Field resultField;/* Save result here */ + + protected ItemResultField() { + this.withUnValAble = true; + } + + public void set_result_field(Field field) { + this.resultField = field; + } + + public void cleanup() { + resultField = null; + } + + public abstract void fixLengthAndDec(); + + public abstract String funcName(); +} diff --git a/src/main/java/io/mycat/plan/common/item/ItemString.java b/src/main/java/io/mycat/plan/common/item/ItemString.java new file mode 100644 index 000000000..24b851907 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/ItemString.java @@ -0,0 +1,113 @@ +package io.mycat.plan.common.item; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCharExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemString extends ItemBasicConstant { + private String value; + + public ItemString(String value) { + this.value = value; + maxLength = value.length(); + fixed = true; + decimals = NOT_FIXED_DEC; + } + + @Override + public ItemType type() { + return ItemType.STRING_ITEM; + } + + @Override + public BigDecimal valReal() { + BigDecimal decValue = BigDecimal.ZERO; + try { + decValue = new BigDecimal(value); + } catch (Exception e) { + logger.warn("convert string to decimal exception!", e); + } + return decValue; + } + + @Override + public BigInteger valInt() { + BigInteger intValue = BigInteger.ZERO; + try { + intValue = new BigInteger(value); + } catch (Exception e) { + logger.warn("convert string to int exception!", e); + } + return intValue; + } + + @Override + public String valStr() { + return value; + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromString(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromString(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromString(ltime); + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_VARCHAR; + } + + @Override + public int hashCode() { + if (value == null) + return 0; + else + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemFloat)) + return false; + ItemString other = (ItemString) obj; + if (value == null || other.value == null) + return value == null && other.value == null; + else + return value.equals(other.value); + } + + @Override + public SQLExpr toExpression() { + return new SQLCharExpr(value);//LiteralString(null, value, false); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + return new ItemString(value); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/ItemCreate.java b/src/main/java/io/mycat/plan/common/item/function/ItemCreate.java new file mode 100644 index 000000000..378e10b12 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/ItemCreate.java @@ -0,0 +1,370 @@ +package io.mycat.plan.common.item.function; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.CastTarget; +import io.mycat.plan.common.CastType; +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncBitCount; +import io.mycat.plan.common.item.function.castfunc.ItemDateTypecast; +import io.mycat.plan.common.item.function.castfunc.ItemDatetimeTypecast; +import io.mycat.plan.common.item.function.castfunc.ItemDecimalTypecast; +import io.mycat.plan.common.item.function.castfunc.ItemFuncBinary; +import io.mycat.plan.common.item.function.castfunc.ItemFuncSigned; +import io.mycat.plan.common.item.function.castfunc.ItemFuncUnsigned; +import io.mycat.plan.common.item.function.castfunc.ItemNCharTypecast; +import io.mycat.plan.common.item.function.castfunc.ItemTimeTypecast; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncAbs; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncAcos; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncAsin; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncAtan; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncCeiling; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncConv; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncCos; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncCot; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncCrc32; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncDegree; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncExp; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncFloor; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncLn; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncLog; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncLog10; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncLog2; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncMd5; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncPi; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncPow; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncRadians; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncRand; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncRound; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncSign; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncSin; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncSqrt; +import io.mycat.plan.common.item.function.mathsfunc.ItemFuncTan; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncCoalesce; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncGreatest; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIsnull; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncLeast; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncStrcmp; +import io.mycat.plan.common.item.function.operator.controlfunc.ItemFuncIfnull; +import io.mycat.plan.common.item.function.operator.controlfunc.ItemFuncNullif; +import io.mycat.plan.common.item.function.strfunc.ItemFuncBitLength; +import io.mycat.plan.common.item.function.strfunc.ItemFuncCharLength; +import io.mycat.plan.common.item.function.strfunc.ItemFuncConcat; +import io.mycat.plan.common.item.function.strfunc.ItemFuncConcatWs; +import io.mycat.plan.common.item.function.strfunc.ItemFuncElt; +import io.mycat.plan.common.item.function.strfunc.ItemFuncField; +import io.mycat.plan.common.item.function.strfunc.ItemFuncFindInSet; +import io.mycat.plan.common.item.function.strfunc.ItemFuncHex; +import io.mycat.plan.common.item.function.strfunc.ItemFuncInstr; +import io.mycat.plan.common.item.function.strfunc.ItemFuncLength; +import io.mycat.plan.common.item.function.strfunc.ItemFuncLocate; +import io.mycat.plan.common.item.function.strfunc.ItemFuncLower; +import io.mycat.plan.common.item.function.strfunc.ItemFuncLpad; +import io.mycat.plan.common.item.function.strfunc.ItemFuncReverse; +import io.mycat.plan.common.item.function.strfunc.ItemFuncRpad; +import io.mycat.plan.common.item.function.strfunc.ItemFuncSoundex; +import io.mycat.plan.common.item.function.strfunc.ItemFuncSpace; +import io.mycat.plan.common.item.function.strfunc.ItemFuncSubstrIndex; +import io.mycat.plan.common.item.function.strfunc.ItemFuncTrim; +import io.mycat.plan.common.item.function.strfunc.ItemFuncTrim.TRIM_TYPE_ENUM; +import io.mycat.plan.common.item.function.strfunc.ItemFuncUnhex; +import io.mycat.plan.common.item.function.strfunc.ItemFuncUpper; +import io.mycat.plan.common.item.function.timefunc.ItemFuncAddTime; +import io.mycat.plan.common.item.function.timefunc.ItemFuncConvTz; +import io.mycat.plan.common.item.function.timefunc.ItemFuncDateFormat; +import io.mycat.plan.common.item.function.timefunc.ItemFuncDatediff; +import io.mycat.plan.common.item.function.timefunc.ItemFuncDayname; +import io.mycat.plan.common.item.function.timefunc.ItemFuncDayofmonth; +import io.mycat.plan.common.item.function.timefunc.ItemFuncDayofweek; +import io.mycat.plan.common.item.function.timefunc.ItemFuncDayofyear; +import io.mycat.plan.common.item.function.timefunc.ItemFuncFromDays; +import io.mycat.plan.common.item.function.timefunc.ItemFuncFromUnixtime; +import io.mycat.plan.common.item.function.timefunc.ItemFuncLastDay; +import io.mycat.plan.common.item.function.timefunc.ItemFuncMakedate; +import io.mycat.plan.common.item.function.timefunc.ItemFuncMaketime; +import io.mycat.plan.common.item.function.timefunc.ItemFuncMonthname; +import io.mycat.plan.common.item.function.timefunc.ItemFuncPeriodAdd; +import io.mycat.plan.common.item.function.timefunc.ItemFuncPeriodDiff; +import io.mycat.plan.common.item.function.timefunc.ItemFuncSecToTime; +import io.mycat.plan.common.item.function.timefunc.ItemFuncStrToDate; +import io.mycat.plan.common.item.function.timefunc.ItemFuncTimeToSec; +import io.mycat.plan.common.item.function.timefunc.ItemFuncTimediff; +import io.mycat.plan.common.item.function.timefunc.ItemFuncToDays; +import io.mycat.plan.common.item.function.timefunc.ItemFuncToSeconds; +import io.mycat.plan.common.item.function.timefunc.ItemFuncUnixTimestamp; +import io.mycat.plan.common.item.function.timefunc.ItemFuncWeekday; +import io.mycat.plan.common.item.function.timefunc.ItemFuncWeekofyear; +import io.mycat.plan.common.item.function.timefunc.ItemFuncYearweek; +import io.mycat.plan.common.time.MyTime; + +public class ItemCreate { + private Map nativFuncs = new HashMap(); + private static ItemCreate _instance = null; + + protected ItemCreate() { + nativFuncs.put("ABS", new ItemFuncAbs(null)); + nativFuncs.put("ACOS", new ItemFuncAcos(null)); + nativFuncs.put("ADDTIME", new ItemFuncAddTime(null, false, false)); + // proFuncs.put("AES_DECRYPT", new Item_func_abs(null)); + // proFuncs.put("AES_ENCRYPT", new Item_func_abs(null)); + // proFuncs.put("ANY_VALUE", new Item_func_abs(null)); + // proFuncs.put("AREA", new Item_func_abs(null)); + // proFuncs.put("ASBINARY", new Item_func_abs(null)); + nativFuncs.put("ASIN", new ItemFuncAsin(null)); + // proFuncs.put("ASTEXT", new Item_func_abs(null)); + // proFuncs.put("ASWKB", new Item_func_abs(null)); + // proFuncs.put("ASWKT", new Item_func_abs(null)); + nativFuncs.put("ATAN", new ItemFuncAtan(null)); + nativFuncs.put("ATAN2", new ItemFuncAtan(null)); + // proFuncs.put("BENCHMARK", new Item_func_abs(null)); + nativFuncs.put("BIN", new ItemFuncConv(null)); + nativFuncs.put("BIT_COUNT", new ItemFuncBitCount(null)); + // proFuncs.put("BUFFER", new Item_func_abs(null)); + nativFuncs.put("BIT_LENGTH", new ItemFuncBitLength(null)); + nativFuncs.put("CEIL", new ItemFuncCeiling(null)); + nativFuncs.put("CEILING", new ItemFuncCeiling(null)); + // proFuncs.put("CENTROID", new Item_func_abs(null)); + nativFuncs.put("CHARACTER_LENGTH", new ItemFuncCharLength(null)); + nativFuncs.put("CHAR_LENGTH", new ItemFuncCharLength(null)); + // proFuncs.put("COERCIBILITY", new Item_func_abs(null)); + // proFuncs.put("COMPRESS", new Item_func_abs(null)); + nativFuncs.put("COALESCE", new ItemFuncCoalesce(null)); + nativFuncs.put("CONCAT", new ItemFuncConcat(null)); + nativFuncs.put("CONCAT_WS", new ItemFuncConcatWs(null)); + // proFuncs.put("CONNECTION_ID", new Item_func_abs(null)); + nativFuncs.put("CONV", new ItemFuncConv(null)); + nativFuncs.put("CONVERT_TZ", new ItemFuncConvTz(null)); + // proFuncs.put("CONVEXHULL", new Item_func_abs(null)); + nativFuncs.put("COS", new ItemFuncCos(null)); + nativFuncs.put("COT", new ItemFuncCot(null)); + nativFuncs.put("CRC32", new ItemFuncCrc32(null)); + // proFuncs.put("CROSSES", new Item_func_abs(null)); + nativFuncs.put("DATEDIFF", new ItemFuncDatediff(null)); + nativFuncs.put("DATE_FORMAT", new ItemFuncDateFormat(null, false)); + nativFuncs.put("DAYNAME", new ItemFuncDayname(null)); + nativFuncs.put("DAYOFMONTH", new ItemFuncDayofmonth(null)); + nativFuncs.put("DAYOFWEEK", new ItemFuncDayofweek(null)); + nativFuncs.put("DAYOFYEAR", new ItemFuncDayofyear(null)); + // proFuncs.put("DECODE", new Item_func_decode(null)); + nativFuncs.put("DEGREES", new ItemFuncDegree(null)); + // proFuncs.put("DES_DECRYPT", new Item_func_abs(null)); + // proFuncs.put("DES_ENCRYPT", new Item_func_abs(null)); + // proFuncs.put("DIMENSION", new Item_func_abs(null)); + // proFuncs.put("DISJOINT", new Item_func_abs(null)); + // proFuncs.put("DISTANCE", new Item_func_abs(null)); + nativFuncs.put("ELT", new ItemFuncElt(null)); + // proFuncs.put("ENCODE", new Item_func_abs(null)); + // proFuncs.put("ENCRYPT", new Item_func_abs(null)); + // proFuncs.put("ENDPOINT", new Item_func_abs(null)); + // proFuncs.put("ENVELOPE", new Item_func_abs(null)); + // proFuncs.put("EQUALS", new Item_func_abs(null)); + nativFuncs.put("EXP", new ItemFuncExp(null)); + // proFuncs.put("EXPORT_SET", new Item_func_abs(null)); + // proFuncs.put("EXTERIORRING", new Item_func_abs(null)); + // proFuncs.put("EXTRACTVALUE", new Item_func_abs(null)); + nativFuncs.put("FIELD", new ItemFuncField(null)); + nativFuncs.put("FIND_IN_SET", new ItemFuncFindInSet(null)); + nativFuncs.put("FLOOR", new ItemFuncFloor(null)); + // proFuncs.put("FOUND_ROWS", new Item_func_(null)); + // proFuncs.put("FROM_BASE64", new Item_func_(null)); + nativFuncs.put("FROM_DAYS", new ItemFuncFromDays(null)); + nativFuncs.put("FROM_UNIXTIME", new ItemFuncFromUnixtime(null)); + // proFuncs.put("GEOMCOLLFROMTEXT", new Item_func_abs(null)); + // proFuncs.put("GEOMCOLLFROMWKB", new Item_func_abs(null)); + // proFuncs.put("GEOMETRYCOLLECTIONFROMTE + // proFuncs.put("GEOMETRYCOLLECTIONFROMWK + // proFuncs.put("GEOMETRYFROMTEXT", new Item_func_abs(null)); + // proFuncs.put("GEOMETRYFROMWKB", new Item_func_abs(null)); + // proFuncs.put("GEOMETRYN", new Item_func_abs(null)); + // proFuncs.put("GEOMETRYTYPE", new Item_func_abs(null)); + // proFuncs.put("GEOMFROMTEXT", new Item_func_abs(null)); + // proFuncs.put("GEOMFROMWKB", new Item_func_abs(null)); + // proFuncs.put("GET_LOCK", new Item_func_abs(null)); + // proFuncs.put("GLENGTH", new Item_func_abs(null)); + nativFuncs.put("GREATEST", new ItemFuncGreatest(null)); + // proFuncs.put("GTID_SUBTRACT", new Item_func_abs(null)); + // proFuncs.put("GTID_SUBSET", new Item_func_abs(null)); + nativFuncs.put("HEX", new ItemFuncHex(null)); + nativFuncs.put("IFNULL", new ItemFuncIfnull(null)); + // proFuncs.put("INET_ATON", new Item_func_abs(null)); + // proFuncs.put("INET_NTOA", new Item_func_abs(null)); + // proFuncs.put("INET6_ATON", new Item_func_abs(null)); + // proFuncs.put("INET6_NTOA", new Item_func_abs(null)); + // proFuncs.put("IS_IPV4", new Item_func_abs(null)); + // proFuncs.put("IS_IPV6", new Item_func_abs(null)); + // proFuncs.put("IS_IPV4_COMPAT", new Item_func_abs(null)); + // proFuncs.put("IS_IPV4_MAPPED", new Item_func_abs(null)); + nativFuncs.put("INSTR", new ItemFuncInstr(null)); + // proFuncs.put("INTERIORRINGN", new Item_func_abs(null)); + // proFuncs.put("INTERSECTS", new Item_func_abs(null)); + // proFuncs.put("ISCLOSED", new Item_func_abs(null)); + // proFuncs.put("ISEMPTY", new Item_func_abs(null)); + nativFuncs.put("ISNULL", new ItemFuncIsnull(null)); + // proFuncs.put("ISSIMPLE", new Item_func_abs(null)); + // proFuncs.put("JSON_VALID", new Item_func_abs(null)); + // proFuncs.put("JSON_CONTAINS", new Item_func_abs(null)); + // proFuncs.put("JSON_CONTAINS_PATH", new Item_func_abs(null)); + // proFuncs.put("JSON_LENGTH", new Item_func_abs(null)); + // proFuncs.put("JSON_DEPTH", new Item_func_abs(null)); + // proFuncs.put("JSON_TYPE", new Item_func_abs(null)); + // proFuncs.put("JSON_KEYS", new Item_func_abs(null)); + // proFuncs.put("JSON_EXTRACT", new Item_func_abs(null)); + // proFuncs.put("JSON_ARRAY_APPEND", new Item_func_abs(null)); + // proFuncs.put("JSON_INSERT", new Item_func_abs(null)); + // proFuncs.put("JSON_ARRAY_INSERT", new Item_func_abs(null)); + // proFuncs.put("JSON_OBJECT", new Item_func_abs(null)); + // proFuncs.put("JSON_SEARCH", new Item_func_abs(null)); + // proFuncs.put("JSON_SET", new Item_func_abs(null)); + // proFuncs.put("JSON_REPLACE", new Item_func_abs(null)); + // proFuncs.put("JSON_ARRAY", new Item_func_abs(null)); + // proFuncs.put("JSON_REMOVE", new Item_func_abs(null)); + // proFuncs.put("JSON_MERGE", new Item_func_abs(null)); + // proFuncs.put("JSON_QUOTE", new Item_func_abs(null)); + // proFuncs.put("JSON_UNQUOTE", new Item_func_abs(null)); + // proFuncs.put("IS_FREE_LOCK", new Item_func_abs(null)); + // proFuncs.put("IS_USED_LOCK", new Item_func_abs(null)); + nativFuncs.put("LAST_DAY", new ItemFuncLastDay(null)); + // proFuncs.put("LAST_INSERT_ID", new Item_func_abs(null)); + nativFuncs.put("LCASE", new ItemFuncLower(null)); + nativFuncs.put("LEAST", new ItemFuncLeast(null)); + nativFuncs.put("LENGTH", new ItemFuncLength(null)); + + // proFuncs.put("LIKE_RANGE_MIN", new Item_func_(null)); + // proFuncs.put("LIKE_RANGE_MAX", new Item_func_abs(null)); + + // proFuncs.put("LINEFROMTEXT", new Item_func_abs(null)); + // proFuncs.put("LINEFROMWKB", new Item_func_abs(null)); + // proFuncs.put("LINESTRINGFROMTEXT", new Item_func_abs(null)); + // proFuncs.put("LINESTRINGFROMWKB", new Item_func_abs(null)); + nativFuncs.put("LN", new ItemFuncLn(null)); + // proFuncs.put("LOAD_FILE", new Item_func_abs(null)); + nativFuncs.put("LOCATE", new ItemFuncLocate(null)); + nativFuncs.put("LOG", new ItemFuncLog(null)); + nativFuncs.put("LOG10", new ItemFuncLog10(null)); + nativFuncs.put("LOG2", new ItemFuncLog2(null)); + nativFuncs.put("LOWER", new ItemFuncLower(null)); + nativFuncs.put("LPAD", new ItemFuncLpad(null)); + nativFuncs.put("LTRIM", new ItemFuncTrim(null, TRIM_TYPE_ENUM.LTRIM)); + nativFuncs.put("MAKEDATE", new ItemFuncMakedate(null)); + nativFuncs.put("MAKETIME", new ItemFuncMaketime(null)); + + nativFuncs.put("MD5", new ItemFuncMd5(null)); + nativFuncs.put("MONTHNAME", new ItemFuncMonthname(null)); + nativFuncs.put("NULLIF", new ItemFuncNullif(null, null)); + nativFuncs.put("OCT", new ItemFuncLog2(null)); + nativFuncs.put("PERIOD_ADD", new ItemFuncPeriodAdd(null)); + nativFuncs.put("PERIOD_DIFF", new ItemFuncPeriodDiff(null)); + nativFuncs.put("PI", new ItemFuncPi(null)); + nativFuncs.put("POW", new ItemFuncPow(null)); + nativFuncs.put("POWER", new ItemFuncPow(null)); + nativFuncs.put("RADIANS", new ItemFuncRadians(null)); + nativFuncs.put("RAND", new ItemFuncRand(null)); + nativFuncs.put("REVERSE", new ItemFuncReverse(null)); + nativFuncs.put("ROUND", new ItemFuncRound(null)); + nativFuncs.put("RPAD", new ItemFuncRpad(null)); + nativFuncs.put("RTRIM", new ItemFuncTrim(null, TRIM_TYPE_ENUM.RTRIM)); + nativFuncs.put("SEC_TO_TIME", new ItemFuncSecToTime(null)); + nativFuncs.put("SIGN", new ItemFuncSign(null)); + nativFuncs.put("SIN", new ItemFuncSin(null)); + + nativFuncs.put("SOUNDEX", new ItemFuncSoundex(null)); + nativFuncs.put("SPACE", new ItemFuncSpace(null)); + nativFuncs.put("SQRT", new ItemFuncSqrt(null)); + nativFuncs.put("STRCMP", new ItemFuncStrcmp(null, null)); + nativFuncs.put("STR_TO_DATE", new ItemFuncStrToDate(null)); + nativFuncs.put("SUBSTRING_INDEX", new ItemFuncSubstrIndex(null)); + nativFuncs.put("SUBTIME", new ItemFuncAddTime(null, false, true)); + nativFuncs.put("TAN", new ItemFuncTan(null)); + nativFuncs.put("TIMEDIFF", new ItemFuncTimediff(null)); + nativFuncs.put("TIME_FORMAT", new ItemFuncDateFormat(null, true)); + nativFuncs.put("TIME_TO_SEC", new ItemFuncTimeToSec(null)); + nativFuncs.put("TO_DAYS", new ItemFuncToDays(null)); + nativFuncs.put("TO_SECONDS", new ItemFuncToSeconds(null)); + nativFuncs.put("UCASE", new ItemFuncUpper(null)); + nativFuncs.put("UNHEX", new ItemFuncUnhex(null)); + nativFuncs.put("UNIX_TIMESTAMP", new ItemFuncUnixTimestamp(null)); + nativFuncs.put("UPPER", new ItemFuncUpper(null)); +// nativFuncs.put("VERSION", new ItemFuncVersion()); + nativFuncs.put("WEEKDAY", new ItemFuncWeekday(null)); + nativFuncs.put("WEEKOFYEAR", new ItemFuncWeekofyear(null)); + nativFuncs.put("YEARWEEK", new ItemFuncYearweek(null)); + } + + public static synchronized ItemCreate getInstance() { + if (_instance == null) + _instance = new ItemCreate(); + return _instance; + } + + public boolean isNativeFunc(String funcName) { + return nativFuncs.containsKey(funcName.toUpperCase()); + } + + public ItemFunc createNativeFunc(String funcName, List args) { + ItemFunc nf = nativFuncs.get(funcName.toUpperCase()); + return nf.nativeConstruct(args); + } + + public ItemFunc create_func_cast(Item a, CastType type) { + CastTarget cast_type = type.target; + ItemFunc res = null; + switch (cast_type) { + case ITEM_CAST_BINARY: + res = new ItemFuncBinary(a,type.length); + break; + case ITEM_CAST_SIGNED_INT: + res = new ItemFuncSigned(a); + break; + case ITEM_CAST_UNSIGNED_INT: + res = new ItemFuncUnsigned(a); + break; + case ITEM_CAST_DATE: + res = new ItemDateTypecast(a); + break; + case ITEM_CAST_TIME: + case ITEM_CAST_DATETIME: { + if (type.length > MyTime.DATETIME_MAX_DECIMALS) + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "too big precision in cast time/datetime,max 6,current:" + type.length); + res = (cast_type == CastTarget.ITEM_CAST_TIME) ? new ItemTimeTypecast(a, type.length) + : new ItemDatetimeTypecast(a, type.length); + break; + } + case ITEM_CAST_DECIMAL: { + if (type.length < type.dec) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "For float(m,d), double(m,d) or decimal(m,d), M must be >= d"); + } + if (type.length > MySQLcom.DECIMAL_MAX_PRECISION) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "Too big precision " + type.length + " max is " + MySQLcom.DECIMAL_MAX_PRECISION); + } + if (type.dec > MySQLcom.DECIMAL_MAX_SCALE) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "Too big scale " + type.dec + " max is " + MySQLcom.DECIMAL_MAX_SCALE); + } + res = new ItemDecimalTypecast(a, type.length, type.dec); + break; + } + case ITEM_CAST_NCHAR: { + int len = -1; + if (type.length > 0) + len = type.length; + res = new ItemNCharTypecast(a, len); + break; + } + default: { + assert (false); + break; + } + } + return res; + } + + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/ItemFunc.java b/src/main/java/io/mycat/plan/common/item/function/ItemFunc.java new file mode 100644 index 000000000..e9aa81c96 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/ItemFunc.java @@ -0,0 +1,328 @@ +package io.mycat.plan.common.item.function; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.druid.sql.ast.SQLExpr; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.node.JoinNode; + +public abstract class ItemFunc extends Item { + + public enum Functype { + UNKNOWN_FUNC, EQ_FUNC, EQUAL_FUNC, NE_FUNC, LT_FUNC, LE_FUNC, GE_FUNC, GT_FUNC, FT_FUNC, LIKE_FUNC, ISNULL_FUNC, ISNOTNULL_FUNC, COND_AND_FUNC, COND_OR_FUNC, XOR_FUNC, BETWEEN, IN_FUNC, MULT_EQUAL_FUNC, INTERVAL_FUNC, ISNOTNULLTEST_FUNC, SP_EQUALS_FUNC, SP_DISJOINT_FUNC, SP_INTERSECTS_FUNC, SP_TOUCHES_FUNC, SP_CROSSES_FUNC, SP_WITHIN_FUNC, SP_CONTAINS_FUNC, SP_OVERLAPS_FUNC, SP_STARTPOINT, SP_ENDPOINT, SP_EXTERIORRING, SP_POINTN, SP_GEOMETRYN, SP_INTERIORRINGN, NOT_FUNC, NOT_ALL_FUNC, NOW_FUNC, TRIG_COND_FUNC, SUSERVAR_FUNC, GUSERVAR_FUNC, COLLATE_FUNC, EXTRACT_FUNC, CHAR_TYPECAST_FUNC, FUNC_SP, UDF_FUNC, NEG_FUNC, GSYSVAR_FUNC + }; + + protected final List args; + + public ItemFunc(List args) { + this.args = args; + } + + @Override + public ItemType type() { + return ItemType.FUNC_ITEM; + } + + public final int getArgCount() { + if (args == null) + return 0; + return args.size(); + } + + public final List arguments() { + return args; + } + + public Functype functype() { + return Functype.UNKNOWN_FUNC; + } + + @Override + public int hashCode() { + int prime = 31; + int hashCode = funcName().hashCode(); + hashCode = hashCode * prime; + for (int index = 0; index < getArgCount(); index++) { + hashCode += args.get(index).hashCode(); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemFunc)) + return false; + ItemFunc other = (ItemFunc) obj; + if (!funcName().equals(other.funcName())) + return false; + if (getArgCount() != other.getArgCount()) + return false; + return StringUtils.equals(getItemName(), other.getItemName()); + } + + @Override + public boolean fixFields() { + if (!fixed) { + if (args != null && args.size() > 0) { + for (Item arg : args) { + if ((!arg.fixed && arg.fixFields())) + return true; /* purecov: inspected */ + if (arg.maybeNull) + maybeNull = true; + withSumFunc = withSumFunc || arg.withSumFunc; + withIsNull = withIsNull || arg.withIsNull; + withSubQuery = withSubQuery || arg.withSubQuery; + withUnValAble = withUnValAble || arg.withUnValAble; + } + } + fixLengthAndDec(); + } + fixed = true; + return false; + } + + @Override + public boolean isNull() { + updateNullValue(); + return nullValue; + } + + public void signalDivideByNull() { + logger.warn("divide by zero"); + nullValue = true; + } + + @Override + public BigDecimal valDecimal() { + BigInteger nr = valInt(); + if (nullValue) + return null; + return new BigDecimal(nr); + } + + public boolean hasTimestampArgs() { + assert (fixed == true); + if (args != null && args.size() > 0) + for (Item arg : args) { + if (arg.type() == ItemType.FIELD_ITEM && arg.fieldType() == FieldTypes.MYSQL_TYPE_TIMESTAMP) + return true; + } + return false; + } + + public boolean hasDateArgs() { + assert (fixed == true); + if (args != null && args.size() > 0) + for (Item arg : args) { + if (arg.type() == ItemType.FIELD_ITEM && (arg.fieldType() == FieldTypes.MYSQL_TYPE_DATE + || arg.fieldType() == FieldTypes.MYSQL_TYPE_DATETIME)) + return true; + } + return false; + } + + public boolean hasTimeArgs() { + assert (fixed == true); + if (args != null && args.size() > 0) + for (Item arg : args) { + if (arg.type() == ItemType.FIELD_ITEM && (arg.fieldType() == FieldTypes.MYSQL_TYPE_TIME + || arg.fieldType() == FieldTypes.MYSQL_TYPE_DATETIME)) + return true; + } + return false; + } + + public boolean hasDatetimeArgs() { + assert (fixed == true); + if (args != null && args.size() > 0) + for (Item arg : args) { + if (arg.type() == ItemType.FIELD_ITEM && arg.fieldType() == FieldTypes.MYSQL_TYPE_DATETIME) + return true; + } + return false; + } + + public abstract void fixLengthAndDec(); + + public ItemFunc nativeConstruct(List realArgs) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "no native function called!"); + } + + public abstract String funcName(); + + /** + * Set max_length/decimals of function if function is floating point and + * result length/precision depends on argument ones. + */ + public void countRealLength() { + int length = 0; + decimals = 0; + maxLength = 0; + for (int i = 0; i < args.size(); i++) { + if (decimals != NOT_FIXED_DEC) { + decimals = Math.max(decimals, args.get(i).decimals); + length = Math.max(length, args.get(i).maxLength - args.get(i).decimals); + } + maxLength = Math.max(maxLength, args.get(i).maxLength); + } + if (decimals != NOT_FIXED_DEC) { + maxLength = length; + length += decimals; + if (length < maxLength) // If previous operation gave overflow + maxLength = Integer.MAX_VALUE; + else + maxLength = length; + } + } + + /** + * Set max_length/decimals of function if function is fixed point and result + * length/precision depends on argument ones. + */ + + public void countDecimalLength() { + int maxIntPart = 0; + decimals = 0; + for (int i = 0; i < args.size(); i++) { + decimals = Math.max(decimals, args.get(i).decimals); + maxIntPart = Math.max(maxIntPart, args.get(i).decimalIntPart()); + } + int precision = maxIntPart + decimals; + maxLength = precision; + } + + /** + * Calculate max_length and decimals for STRING_RESULT functions. + * + * @param field_type + * Field type. + * @param items + * Argument array. + * @param nitems + * Number of arguments. + * @retval False on success, true on error. + */ + public boolean countStringResultLength() { + return false; + } + + public boolean getArg0Date(MySQLTime ltime, long fuzzy_date) { + return (nullValue = args.get(0).getDate(ltime, fuzzy_date)); + } + + public boolean getArg0Time(MySQLTime ltime) { + return (nullValue = args.get(0).getTime(ltime)); + } + + @Override + public final ItemFunc fixFields(NameResolutionContext context) { + getReferTables().clear(); + if (getArgCount() > 0) { + for (int index = 0; index < getArgCount(); index++) { + Item arg = args.get(index); + Item fixedArg = arg.fixFields(context); + if (fixedArg == null) + return null; + args.set(index, fixedArg); + getReferTables().addAll(fixedArg.getReferTables()); + withSumFunc = withSumFunc || fixedArg.withSumFunc; + withIsNull = withIsNull || fixedArg.withIsNull; + withSubQuery = withSubQuery || fixedArg.withSubQuery; + withUnValAble = withUnValAble || arg.withUnValAble; + } + } + return this; + } + + @Override + public final void fixRefer(ReferContext context) { + PlanNode planNode = context.getPlanNode(); + if (withSumFunc) { + planNode.addSelToReferedMap(planNode, this); + for (Item arg : args) { + arg.fixRefer(context); + } + } else if (getReferTables().isEmpty()) { + if (planNode.type() == PlanNodeType.TABLE) { + planNode.addSelToReferedMap(planNode, this); + } else { + planNode.addSelToReferedMap(planNode.getChild(), this); + } + } else { + if (getReferTables().size() == 1) { + PlanNode pn = getReferTables().iterator().next(); + boolean existUnpushIsNull = false; + if (withIsNull && planNode.type() == PlanNodeType.JOIN) { + JoinNode jn = (JoinNode) planNode; + if (jn.isLeftOuterJoin() && jn.getRightNode() == pn) + existUnpushIsNull = true; + } + if (!existUnpushIsNull) + planNode.addSelToReferedMap(pn, this); + else { + planNode.addSelToReferedMap(planNode, this); + for (Item arg : args) { + arg.fixRefer(context); + } + } + } else { + planNode.addSelToReferedMap(planNode, this); + if (!context.isPushDownNode()) { + for (Item arg : args) { + arg.fixRefer(context); + } + } + } + } + } + + @Override + public SQLExpr toExpression() { +// if (ItemCreate.getInstance().isNativeFunc(this.funcName())) { +// List exprList = toExpressionList(args); +// FunctionExpression nativeFe = MySQLFunctionManager.INSTANCE_MYSQL_DEFAULT +// .createFunctionExpression(this.funcName().toUpperCase(), exprList); +// return nativeFe; +// } else { +// throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected function:" + funcName()); +// } + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected function:" + funcName()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + if (ItemCreate.getInstance().isNativeFunc(this.funcName())) { + List argList = cloneStructList(this.args); + return ItemCreate.getInstance().createNativeFunc(funcName(), argList); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected function:" + funcName()); + } + } else { + if (ItemCreate.getInstance().isNativeFunc(this.funcName())) { + return ItemCreate.getInstance().createNativeFunc(funcName(), calArgs); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected function:" + funcName()); + } + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/ItemFuncKeyWord.java b/src/main/java/io/mycat/plan/common/item/function/ItemFuncKeyWord.java new file mode 100644 index 000000000..350e55012 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/ItemFuncKeyWord.java @@ -0,0 +1,9 @@ +package io.mycat.plan.common.item.function; + +public class ItemFuncKeyWord { + public static final String USING = "USING" ; + public static final String FROM ="FROM"; + public static final String TRIM_TYPE = "TRIM_TYPE"; + public static final String ORDER_BY = "ORDER BY"; + public static final String SEPARATOR = "SEPARATOR"; +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitAnd.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitAnd.java new file mode 100644 index 000000000..4e7caf2de --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitAnd.java @@ -0,0 +1,57 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncBit; + + +public class ItemFuncBitAnd extends ItemFuncBit { + + public ItemFuncBitAnd(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "&"; + } + + @Override + public BigInteger valInt() { + BigInteger arg1 = args.get(0).valInt(); + if (args.get(0).nullValue) { + nullValue = true; /* purecov: inspected */ + return BigInteger.ZERO; /* purecov: inspected */ + } + BigInteger arg2 = args.get(1).valInt(); + ; + if (args.get(1).nullValue) { + nullValue = true; + return BigInteger.ZERO; + } + nullValue = false; + return arg1.and(arg2); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.BitwiseAnd, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncBitAnd(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitCount.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitCount.java new file mode 100644 index 000000000..a02e7ba49 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitCount.java @@ -0,0 +1,46 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +/** + * 参数是任意类型
+ * 返回值是BIG INT + * + * @author Administrator + * + */ +public class ItemFuncBitCount extends ItemIntFunc { + + public ItemFuncBitCount(List args) { + super(args); + } + + @Override + public final String funcName() { + return "bit_count"; + } + + @Override + public BigInteger valInt() { + BigInteger value = args.get(0).valInt(); + if ((nullValue = args.get(0).nullValue)) + return BigInteger.ZERO; /* purecov: inspected */ + return BigInteger.valueOf(value.bitCount()); + } + + @Override + public void fixLengthAndDec() { + maxLength = 2; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncBitCount(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitInversion.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitInversion.java new file mode 100644 index 000000000..dcc69ee78 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitInversion.java @@ -0,0 +1,58 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLUnaryExpr; +import com.alibaba.druid.sql.ast.expr.SQLUnaryOperator; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncBit; + + +public class ItemFuncBitInversion extends ItemFuncBit { + + public ItemFuncBitInversion(Item a) { + super(a); + } + + @Override + public final String funcName() { + return "~"; + } + + @Override + public BigInteger valInt() { + BigInteger res = args.get(0).valInt(); + if (nullValue = args.get(0).nullValue) + return BigInteger.ZERO; + // select ~1 18446744073709551614 + if (res.compareTo(BigInteger.ZERO) > 0) { + return MySQLcom.BI64BACK.subtract(BigInteger.ONE).subtract(res); + } else if (res.compareTo(BigInteger.ZERO) == 0) { + return MySQLcom.BI64BACK.subtract(BigInteger.ONE); + } else { + // select ~-10; 9 + return res.negate().subtract(BigInteger.ONE); + } + } + + @Override + public SQLExpr toExpression() { + return new SQLUnaryExpr(SQLUnaryOperator.Compl, args.get(0).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncBitInversion(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitOr.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitOr.java new file mode 100644 index 000000000..a67bd22bc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitOr.java @@ -0,0 +1,58 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncBit; + + +public class ItemFuncBitOr extends ItemFuncBit { + + public ItemFuncBitOr(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "|"; + } + + @Override + public BigInteger valInt() { + BigInteger arg1 = args.get(0).valInt(); + if (args.get(0).nullValue) { + nullValue = true; /* purecov: inspected */ + return BigInteger.ZERO; /* purecov: inspected */ + } + BigInteger arg2 = args.get(1).valInt(); + ; + if (args.get(1).nullValue) { + nullValue = true; + return BigInteger.ZERO; + } + nullValue = false; + return arg1.or(arg2); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.BitwiseOr, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncBitOr(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitXor.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitXor.java new file mode 100644 index 000000000..ded4cfcd4 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncBitXor.java @@ -0,0 +1,48 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncBit; + +public class ItemFuncBitXor extends ItemFuncBit { + + public ItemFuncBitXor(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "^"; + } + + @Override + public BigInteger valInt() { + BigInteger arg1 = args.get(0).valInt(); + BigInteger arg2 = args.get(1).valInt(); + if (nullValue = (args.get(0).nullValue || args.get(1).nullValue)) + return BigInteger.ZERO; + return arg1.xor(arg2); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.BitwiseXor, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncBitXor(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncLeftShift.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncLeftShift.java new file mode 100644 index 000000000..32638e32e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncLeftShift.java @@ -0,0 +1,55 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncBit; + +public class ItemFuncLeftShift extends ItemFuncBit { + + public ItemFuncLeftShift(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "<<"; + } + + @Override + public BigInteger valInt() { + BigInteger arg1 = args.get(0).valInt(); + if (args.get(0).nullValue) { + nullValue = true; /* purecov: inspected */ + return BigInteger.ZERO; /* purecov: inspected */ + } + int shift = args.get(1).valInt().intValue(); + if (args.get(1).nullValue) { + nullValue = true; + return BigInteger.ZERO; + } + nullValue = false; + return shift < Long.SIZE * 8 ? arg1.shiftLeft(shift) : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.LeftShift, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncLeftShift(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncRightShift.java b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncRightShift.java new file mode 100644 index 000000000..92693a965 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/bitfunc/ItemFuncRightShift.java @@ -0,0 +1,56 @@ +package io.mycat.plan.common.item.function.bitfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncBit; + +public class ItemFuncRightShift extends ItemFuncBit { + + public ItemFuncRightShift(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return ">>"; + } + + @Override + public BigInteger valInt() { + BigInteger arg1 = args.get(0).valInt(); + if (args.get(0).nullValue) { + nullValue = true; /* purecov: inspected */ + return BigInteger.ZERO; /* purecov: inspected */ + } + int shift = args.get(1).valInt().intValue(); + if (args.get(1).nullValue) { + nullValue = true; + return BigInteger.ZERO; + } + nullValue = false; + return shift < Long.SIZE * 8 ? arg1.shiftRight(shift) : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.RightShift, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncRightShift(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemCharTypecast.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemCharTypecast.java new file mode 100644 index 000000000..ee08ab5b0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemCharTypecast.java @@ -0,0 +1,89 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.statement.SQLCharacterDataType; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; + + +public class ItemCharTypecast extends ItemStrFunc { + private int cast_length; + private String charSetName; + public ItemCharTypecast(Item a, int length_arg, String charSetName) { + super(new ArrayList()); + args.add(a); + this.cast_length = length_arg; + this.charSetName = charSetName; + } + + @Override + public final String funcName() { + return "cast_as_char"; + } + + @Override + public void fixLengthAndDec() { + fixCharLength(cast_length >= 0 ? cast_length : args.get(0).maxLength); + } + + @Override + public String valStr() { + assert (fixed == true && cast_length >= 0); + + String res = null; + if ((res = args.get(0).valStr()) == null) { + nullValue = true; + return null; + } + nullValue = false; + if (cast_length < res.length()){ + res = res.substring(0, cast_length); + } + if(charSetName != null){ + try { + res = new String(res.getBytes(),CharsetUtil.getJavaCharset(charSetName)); + } catch (UnsupportedEncodingException e) { + logger.warn("convert using charset exception", e); + nullValue = true; + return null; + } + } + return res; + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLCharacterDataType dataType = new SQLCharacterDataType(SQLCharacterDataType.CHAR_TYPE_CHAR); + dataType.addArgument(new SQLIntegerExpr(cast_length)); + cast.setDataType(dataType); + if (cast_length >= 0) { + dataType.addArgument(new SQLIntegerExpr(cast_length)); + } + if (charSetName != null) { + dataType.setName(charSetName); + } + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemCharTypecast(newArgs.get(0), cast_length, charSetName); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDateTypecast.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDateTypecast.java new file mode 100644 index 000000000..edea0ed18 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDateTypecast.java @@ -0,0 +1,56 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.timefunc.ItemDateFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class ItemDateTypecast extends ItemDateFunc { + public ItemDateTypecast(Item a) { + super(new ArrayList()); + args.add(a); + maybeNull = true; + } + + @Override + public final String funcName() { + return "cast_as_date"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + boolean res = getArg0Date(ltime, fuzzy_date | MyTime.TIME_NO_DATE_FRAC_WARN); + ltime.hour = ltime.minute = ltime.second = ltime.second_part = 0; + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + return res; + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("DATE"); + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemDateTypecast(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDatetimeTypecast.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDatetimeTypecast.java new file mode 100644 index 000000000..092aa4fd2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDatetimeTypecast.java @@ -0,0 +1,75 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.timefunc.ItemDatetimeFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + + +public class ItemDatetimeTypecast extends ItemDatetimeFunc { + public ItemDatetimeTypecast(Item a) { + super(new ArrayList()); + args.add(a); + } + + public ItemDatetimeTypecast(Item a, int dec_arg) { + super(new ArrayList()); + args.add(a); + this.decimals = dec_arg; + } + + @Override + public final String funcName() { + return "cast_as_datetime"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + if ((nullValue = args.get(0).getDate(ltime, fuzzy_date | MyTime.TIME_NO_DATE_FRAC_WARN))) + return true; + assert (ltime.time_type != MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; // In + // case + // it + // was + // DATE + return (nullValue = MyTime.my_datetime_round(ltime, decimals)); + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("DATETIME"); + if (decimals != NOT_FIXED_DEC) { + dataType.addArgument(new SQLIntegerExpr(decimals)); + } + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemDatetimeTypecast(newArgs.get(0), this.decimals); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDecimalTypecast.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDecimalTypecast.java new file mode 100644 index 000000000..193e20471 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemDecimalTypecast.java @@ -0,0 +1,122 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemDecimalTypecast extends ItemFunc { + BigDecimal decimal_value = null; + int precision; + int dec; + + public ItemDecimalTypecast(Item a, int precision, int dec) { + super(new ArrayList()); + args.add(a); + this.precision = precision; + this.dec = dec; + } + + @Override + public final String funcName() { + return "decimal_typecast"; + } + + @Override + public void fixLengthAndDec() { + } + + @Override + public BigDecimal valReal() { + BigDecimal tmp = valDecimal(); + if (nullValue) + return BigDecimal.ZERO; + return tmp; + } + + @Override + public BigInteger valInt() { + BigDecimal tmp = valDecimal(); + if (nullValue) + return BigInteger.ZERO; + return tmp.toBigInteger(); + } + + @Override + public String valStr() { + BigDecimal tmp = valDecimal(); + if (nullValue) + return null; + return tmp.toString(); + } + + @Override + public BigDecimal valDecimal() { + BigDecimal tmp = args.get(0).valDecimal(); + + if ((nullValue = args.get(0).nullValue)) + return null; + BigDecimal dec = tmp.setScale(this.dec, RoundingMode.HALF_UP); + return dec; + } + + @Override + public boolean getDate(MySQLTime ltime, long flags) { + return getDateFromDecimal(ltime, flags); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromDecimal(ltime); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DECIMAL; + } + + @Override + public ItemResult resultType() { + return ItemResult.DECIMAL_RESULT; + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("DECIMAL"); + if (precision >= 0) { + dataType.addArgument(new SQLIntegerExpr(precision)); + } + if (dec > 0) { + dataType.addArgument(new SQLIntegerExpr(dec)); + } + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) { + newArgs = cloneStructList(args); + } else { + newArgs = calArgs; + } + return new ItemDecimalTypecast(newArgs.get(0), precision, dec); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncBinary.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncBinary.java new file mode 100644 index 000000000..4659d5335 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncBinary.java @@ -0,0 +1,69 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; + + +public class ItemFuncBinary extends ItemStrFunc { + private int cast_length; + public ItemFuncBinary(Item a, int length_arg) { + super(new ArrayList()); + args.add(a); + this.cast_length = length_arg; + } + + @Override + public final String funcName() { + return "cast_as_binary"; + } + + @Override + public String valStr() { + assert (fixed == true && cast_length >= 0); + String res = null; + if ((res = args.get(0).valStr()) == null) { + nullValue = true; + return null; + } + nullValue = false; + if (cast_length < res.length()) + res = res.substring(0, cast_length); + return res; + } + + @Override + public void fixLengthAndDec() { + fixCharLength(cast_length >= 0 ? cast_length : args.get(0).maxLength); + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("BINARY"); + if(cast_length >=0){ + dataType.addArgument(new SQLIntegerExpr(cast_length)); + } + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncBinary(newArgs.get(0), cast_length); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncConvCharset.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncConvCharset.java new file mode 100644 index 000000000..ee57502f2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncConvCharset.java @@ -0,0 +1,72 @@ +/** + * + */ +package io.mycat.plan.common.item.function.castfunc; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFuncKeyWord; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; + +public class ItemFuncConvCharset extends ItemStrFunc { + private String mysqlCharset; + private String javaCharset; + + public ItemFuncConvCharset(Item a, String charset) { + super(a); + mysqlCharset = charset; + javaCharset = CharsetUtil.getJavaCharset(charset); + } + + @Override + public final String funcName() { + return "CONVERT"; + } + + @Override + public void fixLengthAndDec() { + + } + + @Override + public String valStr() { + String argVal = args.get(0).valStr(); + if (argVal == null) { + nullValue = true; + return null; + } + try { + return new String(argVal.getBytes(), javaCharset); + } catch (UnsupportedEncodingException e) { + logger.warn("convert using charset exception", e); + nullValue = true; + return null; + } + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + method.addParameter(args.get(0).toExpression()); + method.putAttribute(ItemFuncKeyWord.USING, mysqlCharset); + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncConvCharset(newArgs.get(0), mysqlCharset); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncSigned.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncSigned.java new file mode 100644 index 000000000..405aca281 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncSigned.java @@ -0,0 +1,100 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + +/** + * CAST(expr AS type)
+ * type:
+ * BINARY[(N)]
+ * CHAR[(N)]
+ * DATE
+ * DATETIME
+ * DECIMAL[(M[,D])]
+ * SIGNED [INTEGER]
+ * TIME
+ * UNSIGNED [INTEGER]
+ * + * @author Administrator + * + */ +public class ItemFuncSigned extends ItemIntFunc { + + public ItemFuncSigned(Item a) { + super(new ArrayList()); + args.add(a); + } + + @Override + public final String funcName() { + return "cast_as_signed"; + } + + @Override + public BigInteger valInt() { + BigInteger value = BigInteger.ZERO; + + if (args.get(0).castToIntType() != ItemResult.STRING_RESULT || args.get(0).isTemporal()) { + value = args.get(0).valInt(); + nullValue = args.get(0).nullValue; + return value; + } + + try { + value = val_int_from_str(); + } catch (Exception e) { + value = new BigInteger("-1"); + logger.error("Cast to signed converted positive out-of-range integer to " + "it's negative complement", e); + } + return value; + } + + protected BigInteger val_int_from_str() throws Exception { + /* + * For a string result, we must first get the string and then convert it + * to a longlong + */ + + String res = args.get(0).valStr(); + if (res == null) { + nullValue = true; + return BigInteger.ZERO; + } + + nullValue = false; + return new BigInteger(res); + } + + @Override + public int decimalPrecision() { + return args.get(0).decimalPrecision(); + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("SIGNED"); + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncSigned(newArgs.get(0)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncUnsigned.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncUnsigned.java new file mode 100644 index 000000000..2ca6dc6e8 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemFuncUnsigned.java @@ -0,0 +1,109 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + +/** + * CAST(expr AS type)
+ * type:
+ * BINARY[(N)]
+ * CHAR[(N)]
+ * DATE
+ * DATETIME
+ * DECIMAL[(M[,D])]
+ * SIGNED [INTEGER]
+ * TIME
+ * UNSIGNED [INTEGER]
+ * + * @author Administrator + * + */ +public class ItemFuncUnsigned extends ItemIntFunc { + + public ItemFuncUnsigned(Item a) { + super(new ArrayList()); + args.add(a); + } + + @Override + public final String funcName() { + return "cast_as_unsigned"; + } + + @Override + public BigInteger valInt() { + BigInteger value = BigInteger.ZERO; + + if (args.get(0).castToIntType() == ItemResult.DECIMAL_RESULT) { + BigDecimal dec = args.get(0).valDecimal(); + if (!(nullValue = args.get(0).nullValue)) + value = dec.toBigInteger(); + else + value = BigInteger.ZERO; + return value; + } else if (args.get(0).castToIntType() != ItemResult.STRING_RESULT || args.get(0).isTemporal()) { + value = args.get(0).valInt(); + nullValue = args.get(0).nullValue; + return value; + } + + try { + value = val_int_from_str(); + } catch (Exception e) { + value = new BigInteger("-1"); + logger.error("Cast to unsigned converted negative integer to it's " + "positive complement", e); + } + return value; + } + + protected BigInteger val_int_from_str() throws Exception { + /* + * For a string result, we must first get the string and then convert it + * to a longlong + */ + + String res = args.get(0).valStr(); + if (res == null) { + nullValue = true; + return BigInteger.ZERO; + } + + nullValue = false; + return new BigInteger(res); + } + + @Override + public int decimalPrecision() { + return args.get(0).decimalPrecision(); + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("UNSIGNED"); + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncUnsigned(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemNCharTypecast.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemNCharTypecast.java new file mode 100644 index 000000000..62e88f51c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemNCharTypecast.java @@ -0,0 +1,71 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; + + +public class ItemNCharTypecast extends ItemStrFunc { + private int cast_length; + public ItemNCharTypecast(Item a, int length_arg) { + super(new ArrayList()); + args.add(a); + this.cast_length = length_arg; + } + + @Override + public final String funcName() { + return "cast_as_nchar"; + } + + @Override + public void fixLengthAndDec() { + fixCharLength(cast_length >= 0 ? cast_length : args.get(0).maxLength); + } + + @Override + public String valStr() { + assert (fixed == true && cast_length >= 0); + + String res = null; + if ((res = args.get(0).valStr()) == null) { + nullValue = true; + return null; + } + nullValue = false; + if (cast_length < res.length()) + res = res.substring(0, cast_length); + return res; + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("NCAHR"); + if(cast_length >=0){ + dataType.addArgument(new SQLIntegerExpr(cast_length)); + } + cast.setDataType(dataType); + + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemNCharTypecast(newArgs.get(0), cast_length); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemTimeTypecast.java b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemTimeTypecast.java new file mode 100644 index 000000000..60cc1ac28 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/castfunc/ItemTimeTypecast.java @@ -0,0 +1,75 @@ +package io.mycat.plan.common.item.function.castfunc; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.timefunc.ItemTimeFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class ItemTimeTypecast extends ItemTimeFunc { + + public ItemTimeTypecast(Item a) { + super(new ArrayList()); + args.add(a); + } + + public ItemTimeTypecast(Item a, int dec_arg) { + super(new ArrayList()); + args.add(a); + decimals = dec_arg; + } + + @Override + public final String funcName() { + return "cast_as_time"; + } + + public boolean getTime(MySQLTime ltime) { + if (getArg0Time(ltime)) + return true; + MyTime.my_time_round(ltime, decimals); + /* + * For MYSQL_TIMESTAMP_TIME value we can have non-zero day part, which + * we should not lose. + */ + if (ltime.time_type != MySQLTimestampType.MYSQL_TIMESTAMP_TIME) + MyTime.datetime_to_time(ltime); + return false; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + } + + @Override + public SQLExpr toExpression() { + SQLCastExpr cast = new SQLCastExpr(); + cast.setExpr(args.get(0).toExpression()); + SQLDataTypeImpl dataType = new SQLDataTypeImpl("TIME"); + if (decimals != NOT_FIXED_DEC) { + dataType.addArgument(new SQLIntegerExpr(decimals)); + } + cast.setDataType(dataType); + return cast; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemTimeTypecast(newArgs.get(0), this.decimals); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAbs.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAbs.java new file mode 100644 index 000000000..25ba324f4 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAbs.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemFuncNum1; + +public class ItemFuncAbs extends ItemFuncNum1 { + + public ItemFuncAbs(List args) { + super(args); + } + + @Override + public final String funcName() { + return "abs"; + } + + public BigDecimal realOp() { + BigDecimal bd = args.get(0).valReal(); + return bd.abs(); + } + + @Override + public BigInteger intOp() { + BigInteger bi = args.get(0).valInt(); + return bi.abs(); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal bd = args.get(0).valDecimal(); + if (nullValue = args.get(0).isNull() != true) + return bd.abs(); + return null; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncAbs(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAcos.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAcos.java new file mode 100644 index 000000000..7c9f6e957 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAcos.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncAcos extends ItemDecFunc { + + public ItemFuncAcos(List args) { + super(args); + } + + @Override + public final String funcName() { + return "acos"; + } + + public BigDecimal valReal() { + double db = args.get(0).valReal().doubleValue(); + if (nullValue = (args.get(0).isNull() || (db < -1.0 || db > 1.0))) { + return BigDecimal.ZERO; + } else { + return new BigDecimal(Math.acos(db)); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncAcos(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAsin.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAsin.java new file mode 100644 index 000000000..b05578148 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAsin.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncAsin extends ItemDecFunc { + + public ItemFuncAsin(List args) { + super(args); + } + + @Override + public final String funcName() { + return "asin"; + } + + public BigDecimal valReal() { + double db = args.get(0).valReal().doubleValue(); + if (nullValue = (args.get(0).isNull() || (db < -1.0 || db > 1.0))) { + return BigDecimal.ZERO; + } else { + return new BigDecimal(Math.asin(db)); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncAsin(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAtan.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAtan.java new file mode 100644 index 000000000..0c7c800c6 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncAtan.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncAtan extends ItemDecFunc { + + public ItemFuncAtan(List args) { + super(args); + } + + @Override + public final String funcName() { + return args.size() == 1 ? "atan" : "atan2"; + } + + public BigDecimal valReal() { + double value = args.get(0).valReal().doubleValue(); + if ((nullValue = args.get(0).nullValue)) + return BigDecimal.ZERO; + if (args.size() == 2) { + double val2 = args.get(1).valReal().doubleValue(); + if ((nullValue = args.get(1).nullValue)) + return BigDecimal.ZERO; + return new BigDecimal(Math.atan2(value, val2)); + } + return new BigDecimal(Math.atan(value)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncAtan(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCeiling.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCeiling.java new file mode 100644 index 000000000..0d8212344 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCeiling.java @@ -0,0 +1,65 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncCeiling extends ItemFuncIntVal { + + public ItemFuncCeiling(List args) { + super(args); + } + + @Override + public final String funcName() { + return "ceiling"; + } + + @Override + public BigInteger intOp() { + BigInteger result; + switch (args.get(0).resultType()) { + case INT_RESULT: + result = args.get(0).valInt(); + nullValue = args.get(0).nullValue; + break; + case DECIMAL_RESULT: { + BigDecimal dec = decimalOp(); + if (dec == null) + result = BigInteger.ZERO; + else + result = dec.toBigInteger(); + break; + } + default: + result = realOp().toBigInteger(); + } + ; + return result; + } + + @Override + public BigDecimal realOp() { + double value = args.get(0).valReal().doubleValue(); + nullValue = args.get(0).nullValue; + return new BigDecimal(Math.ceil(value)); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal bd = args.get(0).valDecimal(); + if (nullValue = args.get(0).nullValue) + return null; + return bd.setScale(0, RoundingMode.CEILING); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncCeiling(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncConv.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncConv.java new file mode 100644 index 000000000..f2a12eb21 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncConv.java @@ -0,0 +1,72 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; + + +public class ItemFuncConv extends ItemStrFunc { + public ItemFuncConv(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "conv"; + } + + @Override + public String valStr() { + String res = args.get(0).valStr(); + int from_base = args.get(1).valInt().intValue(); + int to_base = args.get(2).valInt().intValue(); + long dec = 0; + + if (args.get(0).nullValue || args.get(1).nullValue || args.get(2).nullValue || Math.abs(to_base) > 36 + || Math.abs(to_base) < 2 || Math.abs(from_base) > 36 || Math.abs(from_base) < 2 || res.length() == 0) { + nullValue = true; + return null; + } + nullValue = false; + if (args.get(0).fieldType() == FieldTypes.MYSQL_TYPE_BIT) { + /* + * Special case: The string representation of BIT doesn't resemble + * the decimal representation, so we shouldn't change it to string + * and then to decimal. + */ + dec = args.get(0).valInt().longValue(); + } else { + if (from_base < 0) + from_base = -from_base; + try { + dec = Long.parseLong(res, from_base); + } catch (NumberFormatException ne) { + logger.info("long parse from radix error, string:" + res + ", radix:" + from_base); + } + } + + String str = null; + try { + str = Long.toString(dec, to_base); + } catch (Exception e) { + logger.warn("long to string failed ,value:" + dec + ", to_base:" + to_base); + nullValue = true; + } + return str; + } + + @Override + public void fixLengthAndDec() { + maxLength = 64; + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncConv(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCos.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCos.java new file mode 100644 index 000000000..398788915 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCos.java @@ -0,0 +1,36 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncCos extends ItemDecFunc { + + public ItemFuncCos(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "cos"; + } + + public BigDecimal valReal() { + double db = args.get(0).valReal().doubleValue(); + if (args.get(0).isNull()) { + this.nullValue = true; + return BigDecimal.ZERO; + } else { + return new BigDecimal(Math.cos(db)); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncCos(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCot.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCot.java new file mode 100644 index 000000000..9b7b32d76 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCot.java @@ -0,0 +1,41 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncCot extends ItemDecFunc { + + public ItemFuncCot(List args) { + super(args); + } + + @Override + public final String funcName() { + return "cot"; + } + + public BigDecimal valReal() { + double db = args.get(0).valReal().doubleValue(); + if (args.get(0).isNull()) { + this.nullValue = true; + return BigDecimal.ZERO; + } + double tan = Math.tan(db); + if (tan == 0.0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } else { + return new BigDecimal(1.0 / tan); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncCot(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCrc32.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCrc32.java new file mode 100644 index 000000000..edc6a21d7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncCrc32.java @@ -0,0 +1,51 @@ +/** + * + */ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigInteger; +import java.util.List; +import java.util.zip.CRC32; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + +public class ItemFuncCrc32 extends ItemIntFunc { + + /** + * @param name + * @param a + */ + public ItemFuncCrc32(Item a) { + super(a); + } + + @Override + public final String funcName(){ + return "crc32"; + } + + @Override + public void fixLengthAndDec() { + maxLength = 10; + } + + @Override + public BigInteger valInt() { + String res = args.get(0).valStr(); + if (res == null) { + nullValue = true; + return BigInteger.ZERO; + } + nullValue = false; + CRC32 crc = new CRC32(); + crc.update(res.getBytes()); + return BigInteger.valueOf(crc.getValue()); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncCrc32(realArgs.get(0)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncDegree.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncDegree.java new file mode 100644 index 000000000..4bc481661 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncDegree.java @@ -0,0 +1,26 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemFuncUnits; + + +public class ItemFuncDegree extends ItemFuncUnits { + + public ItemFuncDegree(List args) { + super(args, 180 / MySQLcom.M_PI, 0.0); + } + + @Override + public final String funcName() { + return "degrees"; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDegree(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncExp.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncExp.java new file mode 100644 index 000000000..ce11855e7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncExp.java @@ -0,0 +1,41 @@ +/** + * + */ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + +public class ItemFuncExp extends ItemDecFunc { + + /** + * @param name + * @param args + */ + public ItemFuncExp(List args) { + super(args); + } + + @Override + public final String funcName() { + return "exp"; + } + + @Override + public BigDecimal valReal() { + BigDecimal value = args.get(0).valReal(); + if (nullValue = args.get(0).nullValue) { + return BigDecimal.ZERO; + } + return BigDecimal.valueOf(Math.exp(value.doubleValue())); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncExp(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncFloor.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncFloor.java new file mode 100644 index 000000000..bf740b272 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncFloor.java @@ -0,0 +1,65 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncFloor extends ItemFuncIntVal { + + public ItemFuncFloor(List args) { + super(args); + } + + @Override + public final String funcName() { + return "floor"; + } + + @Override + public BigInteger intOp() { + BigInteger result; + switch (args.get(0).resultType()) { + case INT_RESULT: + result = args.get(0).valInt(); + nullValue = args.get(0).nullValue; + break; + case DECIMAL_RESULT: { + BigDecimal dec = decimalOp(); + if (dec == null) + result = BigInteger.ZERO; + else + result = dec.toBigInteger(); + break; + } + default: + result = realOp().toBigInteger(); + } + ; + return result; + } + + @Override + public BigDecimal realOp() { + double value = args.get(0).valReal().doubleValue(); + nullValue = args.get(0).nullValue; + return new BigDecimal(Math.floor(value)); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal bd = args.get(0).valDecimal(); + if (nullValue = args.get(0).nullValue) + return null; + return bd.setScale(0, RoundingMode.FLOOR); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncFloor(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncIntVal.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncIntVal.java new file mode 100644 index 000000000..c735ae3ff --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncIntVal.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncNum1; + + +public abstract class ItemFuncIntVal extends ItemFuncNum1 { + + public ItemFuncIntVal(List args) { + super(args); + } + + @Override + public void fixNumLengthAndDec() { + decimals = 0; + } + + @Override + public void findNumType() { + switch (hybrid_type = args.get(0).resultType()) { + case STRING_RESULT: + case REAL_RESULT: + hybrid_type = ItemResult.REAL_RESULT; + maxLength = floatLength(decimals); + break; + case INT_RESULT: + hybrid_type = ItemResult.INT_RESULT; + break; + case DECIMAL_RESULT: + hybrid_type = ItemResult.DECIMAL_RESULT; + break; + default: + assert (false); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLn.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLn.java new file mode 100644 index 000000000..788c0bd58 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLn.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncLn extends ItemDecFunc { + + public ItemFuncLn(List args) { + super(args); + } + + @Override + public final String funcName() { + return "ln"; + } + + public BigDecimal valReal() { + double db = args.get(0).valReal().doubleValue(); + if (nullValue = args.get(0).isNull()) { + return BigDecimal.ZERO; + } + if (db <= 0.0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } else { + return new BigDecimal(Math.log(db)); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLn(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog.java new file mode 100644 index 000000000..318e8bee1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog.java @@ -0,0 +1,53 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncLog extends ItemDecFunc { + + public ItemFuncLog(List args) { + super(args); + } + + @Override + public final String funcName() { + return "log"; + } + + /** + * Extended but so slower LOG function. + * + * We have to check if all values are > zero and first one is not one as + * these are the cases then result is not a number. + */ + public BigDecimal valReal() { + double value = args.get(0).valReal().doubleValue(); + if ((nullValue = args.get(0).nullValue)) + return BigDecimal.ZERO; + if (value <= 0.0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + if (args.size() == 2) { + double value2 = args.get(1).valReal().doubleValue(); + if ((nullValue = args.get(1).nullValue)) + return BigDecimal.ZERO; + if (value2 <= 0.0 || value == 1.0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + return new BigDecimal(Math.log(value2) / Math.log(value)); + } + return new BigDecimal(Math.log(value)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLog(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog10.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog10.java new file mode 100644 index 000000000..6859726fb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog10.java @@ -0,0 +1,38 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncLog10 extends ItemDecFunc { + + public ItemFuncLog10(List args) { + super(args); + } + + @Override + public final String funcName() { + return "log10"; + } + + public BigDecimal valReal() { + double value = args.get(0).valReal().doubleValue(); + + if ((nullValue = args.get(0).nullValue)) + return BigDecimal.ZERO; + if (value <= 0.0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + return new BigDecimal(Math.log10(value)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLog10(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog2.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog2.java new file mode 100644 index 000000000..207f6137e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncLog2.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncLog2 extends ItemDecFunc { + private static double M_LN2 = Math.log(2); + + public ItemFuncLog2(List args) { + super(args); + } + + @Override + public final String funcName() { + return "log2"; + } + + public BigDecimal valReal() { + double value = args.get(0).valReal().doubleValue(); + + if ((nullValue = args.get(0).nullValue)) + return BigDecimal.ZERO; + if (value <= 0.0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + return new BigDecimal(Math.log(value) / M_LN2); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLog2(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncMd5.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncMd5.java new file mode 100644 index 000000000..2bda9eac7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncMd5.java @@ -0,0 +1,43 @@ +/** + * + */ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; + + +public class ItemFuncMd5 extends ItemStrFunc { + + /** + * @param name + * @param a + */ + public ItemFuncMd5(Item a) { + super(a); + } + + @Override + public final String funcName() { + return "md5"; + } + + @Override + public String valStr() { + String value = args.get(0).valStr(); + if (value != null) { + nullValue = false; + + } + // TODO + return null; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncMd5(realArgs.get(0)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncPi.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncPi.java new file mode 100644 index 000000000..a6f079132 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncPi.java @@ -0,0 +1,31 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemRealFunc; + + +public class ItemFuncPi extends ItemRealFunc { + + public ItemFuncPi(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "pi"; + } + + public BigDecimal valReal() { + return new BigDecimal(MySQLcom.M_PI); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncPi(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncPow.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncPow.java new file mode 100644 index 000000000..a2037bfd9 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncPow.java @@ -0,0 +1,34 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncPow extends ItemDecFunc { + + public ItemFuncPow(List args) { + super(args); + } + + @Override + public final String funcName() { + return "pow"; + } + + public BigDecimal valReal() { + double value = args.get(0).valReal().doubleValue(); + double val2 = args.get(1).valReal().doubleValue(); + if ((nullValue = args.get(0).nullValue || args.get(1).nullValue)) + return BigDecimal.ZERO; + return new BigDecimal(Math.pow(value, val2)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncPow(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRadians.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRadians.java new file mode 100644 index 000000000..c65554b60 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRadians.java @@ -0,0 +1,26 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemFuncUnits; + + +public class ItemFuncRadians extends ItemFuncUnits { + + public ItemFuncRadians(List args) { + super(args, MySQLcom.M_PI / 180, 0.0); + } + + @Override + public final String funcName() { + return "radians"; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncRadians(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRand.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRand.java new file mode 100644 index 000000000..6011e4b31 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRand.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemRealFunc; + + +public class ItemFuncRand extends ItemRealFunc { + // 理论上应该是每个连接有一个单独的种子保存起来,我们这里使用一个全局种子模拟下 + // TODO + + boolean first_eval; // TRUE if val_real() is called 1st time + + public ItemFuncRand(List args) { + super(args); + } + + @Override + public final String funcName() { + return "rand"; + } + + public BigDecimal valReal() { + return new BigDecimal(Math.random()); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncRand(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRound.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRound.java new file mode 100644 index 000000000..bf7784288 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRound.java @@ -0,0 +1,20 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncRound extends ItemFuncRoundOrTruncate { + + public ItemFuncRound(List args) { + super(args, false); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncRound(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRoundOrTruncate.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRoundOrTruncate.java new file mode 100644 index 000000000..37659d44a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncRoundOrTruncate.java @@ -0,0 +1,135 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncNum1; + +/** + * round和truncate的父类 + + */ +public abstract class ItemFuncRoundOrTruncate extends ItemFuncNum1 { + boolean truncate = false; + + public ItemFuncRoundOrTruncate(List args, boolean truncate) { + super(args); + this.truncate = truncate; + } + + @Override + public final String funcName() { + return truncate ? "truncate" : "round"; + } + + @Override + public BigDecimal realOp() { + BigDecimal val0 = args.get(0).valReal(); + if (!(nullValue = args.get(0).isNull() || args.get(1).isNull())) { + int val1 = args.get(1).valInt().intValue(); + return getDecimalRound(val0, val1); + } + return BigDecimal.ZERO; + } + + @Override + public BigInteger intOp() { + /** + * round(1234,3) = 1234 round(1234,-1) = 1230 + */ + BigInteger val0 = args.get(0).valInt(); + int val1 = args.get(1).valInt().intValue(); + if ((nullValue = args.get(0).nullValue || args.get(1).nullValue)) + return BigInteger.ZERO; + return getIntRound(val0, val1); + } + + @Override + public BigDecimal decimalOp() { + hybrid_type = ItemResult.DECIMAL_RESULT; + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return null; + } + BigDecimal val0 = args.get(0).valDecimal(); + int val1 = args.get(1).valInt().intValue(); + return getDecimalRound(val0, val1); + } + + @Override + public void fixLengthAndDec() { + int decimals_to_set; + long val1 = args.get(1).valInt().longValue(); + if ((nullValue = args.get(1).isNull())) + return; + + if (val1 < 0) + decimals_to_set = 0; + else + decimals_to_set = (int) val1; + + if (args.get(0).decimals == NOT_FIXED_DEC) { + decimals = Math.min(decimals_to_set, NOT_FIXED_DEC); + maxLength = floatLength(decimals); + hybrid_type = ItemResult.REAL_RESULT; + return; + } + + switch (args.get(0).resultType()) { + case REAL_RESULT: + case STRING_RESULT: + hybrid_type = ItemResult.REAL_RESULT; + decimals = Math.min(decimals_to_set, NOT_FIXED_DEC); + maxLength = floatLength(decimals); + break; + case INT_RESULT: + /* Here we can keep INT_RESULT */ + hybrid_type = ItemResult.INT_RESULT; + decimals = 0; + break; + /* fall through */ + case DECIMAL_RESULT: { + hybrid_type = ItemResult.DECIMAL_RESULT; + decimals_to_set = Math.min(DECIMAL_MAX_SCALE, decimals_to_set); + decimals = Math.min(decimals_to_set, DECIMAL_MAX_SCALE); + break; + } + default: + assert (false); /* This result type isn't handled */ + } + } + + /** + * round(1234,3) = 1234 round(-1234,-1) = -1230 + * + * @param value + * @param round + * @return + */ + private BigInteger getIntRound(BigInteger value, int round) { + if (round >= 0) + return value; + round = -round; + String sval = value.toString(); + int maxLen = value.compareTo(BigInteger.ZERO) >= 0 ? sval.length() : sval.length() - 1; + if (round >= maxLen) + return BigInteger.ZERO; + String appendZero = org.apache.commons.lang.StringUtils.repeat("0", round); + String subVal0 = sval.substring(sval.length() - round); + String res = subVal0 + appendZero; + return new BigInteger(res); + } + + private BigDecimal getDecimalRound(BigDecimal value, int round) { + String sVal = value.toString(); + if (!sVal.contains(".") || round < 0) { + BigInteger bi = value.toBigInteger(); + return new BigDecimal(getIntRound(bi, round)); + } else { + return value.setScale(round, RoundingMode.FLOOR); + } + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSign.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSign.java new file mode 100644 index 000000000..853e87186 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSign.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncSign extends ItemIntFunc { + + public ItemFuncSign(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "sign"; + } + + @Override + public BigInteger valInt() { + double value = args.get(0).valReal().doubleValue(); + nullValue = args.get(0).nullValue; + return value < 0.0 ? BigInteger.ONE.negate() : (value > 0 ? BigInteger.ONE : BigInteger.ZERO); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSign(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSin.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSin.java new file mode 100644 index 000000000..ae5be8787 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSin.java @@ -0,0 +1,36 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncSin extends ItemDecFunc { + + public ItemFuncSin(List args) { + super(args); + } + + @Override + public final String funcName() { + return "sin"; + } + + public BigDecimal valReal() { + double db = args.get(0).valReal().doubleValue(); + if (args.get(0).isNull()) { + this.nullValue = true; + return BigDecimal.ZERO; + } else { + return new BigDecimal(Math.sin(db)); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSin(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSqrt.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSqrt.java new file mode 100644 index 000000000..4d0d77c7f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncSqrt.java @@ -0,0 +1,34 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncSqrt extends ItemDecFunc { + + public ItemFuncSqrt(List args) { + super(args); + } + + @Override + public final String funcName() { + return "sqrt"; + } + + public BigDecimal valReal() { + double value = args.get(0).valReal().doubleValue(); + + if ((nullValue = args.get(0).nullValue)) + return BigDecimal.ZERO; + return new BigDecimal(Math.sqrt(value)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSqrt(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncTan.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncTan.java new file mode 100644 index 000000000..d5e08d0e2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncTan.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemDecFunc; + + +public class ItemFuncTan extends ItemDecFunc { + + public ItemFuncTan(List args) { + super(args); + } + + @Override + public final String funcName() { + return "tan"; + } + + public BigDecimal valReal() { + double db0 = args.get(0).valReal().doubleValue(); + if (args.get(0).isNull()) { + this.nullValue = true; + return BigDecimal.ZERO; + } + return new BigDecimal(Math.tan(db0)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncTan(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncTruncate.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncTruncate.java new file mode 100644 index 000000000..82974729a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/ItemFuncTruncate.java @@ -0,0 +1,13 @@ +package io.mycat.plan.common.item.function.mathsfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + +public class ItemFuncTruncate extends ItemFuncRoundOrTruncate { + + public ItemFuncTruncate(List args) { + super(args, true); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncDiv.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncDiv.java new file mode 100644 index 000000000..5b6fbf5e0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncDiv.java @@ -0,0 +1,116 @@ +package io.mycat.plan.common.item.function.mathsfunc.operator; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemNumOp; + + +public class ItemFuncDiv extends ItemNumOp { + + /** + * 默认的bigdecimal的长度,正确的方式应该是从mysql自身的配置获取 + */ + private int prec_increment = 4; + + public ItemFuncDiv(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "/"; + } + + @Override + public void fixLengthAndDec() { + super.fixLengthAndDec(); + switch (hybrid_type) { + case REAL_RESULT: { + // see sql/item_func.cc Item_func_div::fix_length_and_dec() + decimals = Math.max(args.get(0).decimals, args.get(1).decimals) + prec_increment; + decimals = Math.min(decimals, NOT_FIXED_DEC); + int tmp = floatLength(decimals); + if (decimals == NOT_FIXED_DEC) + maxLength = tmp; + else { + maxLength = args.get(0).maxLength - args.get(1).decimals + decimals; + maxLength = Math.min(maxLength, tmp); + } + break; + } + case INT_RESULT: + hybrid_type = ItemResult.DECIMAL_RESULT; + result_precision(); + break; + case DECIMAL_RESULT: + result_precision(); + default: + break; + } + } + + @Override + public BigDecimal realOp() { + BigDecimal val0 = args.get(0).valReal(); + BigDecimal val1 = args.get(1).valReal(); + if ((this.nullValue = args.get(0).isNull() || args.get(1).isNull())) + return BigDecimal.ZERO; + if (val1.compareTo(BigDecimal.ZERO) == 0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + return val0.divide(val1, decimals, RoundingMode.HALF_UP); + } + + @Override + public BigInteger intOp() { + assert (false); + return BigInteger.ZERO; + } + + @Override + public BigDecimal decimalOp() { + BigDecimal val1 = args.get(0).valDecimal(); + if ((this.nullValue = args.get(0).isNull())) + return new BigDecimal(0); + BigDecimal val2 = args.get(1).valDecimal(); + if ((this.nullValue = args.get(1).isNull())) + return new BigDecimal(0); + if (val2.compareTo(BigDecimal.ZERO) == 0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + BigDecimal bd = val1.divide(val2, decimals, RoundingMode.HALF_UP); + return bd; + } + + @Override + public void result_precision() { + decimals = Math.min(args.get(0).decimals + prec_increment, DECIMAL_MAX_SCALE); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.Divide, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if(!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncDiv(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMinus.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMinus.java new file mode 100644 index 000000000..09f7297c5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMinus.java @@ -0,0 +1,68 @@ +package io.mycat.plan.common.item.function.mathsfunc.operator; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncAdditiveOp; + +public class ItemFuncMinus extends ItemFuncAdditiveOp { + + public ItemFuncMinus(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "-"; + } + + @Override + public BigDecimal realOp() { + BigDecimal val0 = args.get(0).valReal(); + BigDecimal val1 = args.get(1).valReal(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return BigDecimal.ZERO; + return val0.subtract(val1); + } + + @Override + public BigInteger intOp() { + BigInteger v0 = args.get(0).valInt(); + BigInteger v1 = args.get(1).valInt(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return BigInteger.ZERO; + return v0.subtract(v1); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal v0 = args.get(0).valDecimal(); + BigDecimal v1 = args.get(1).valDecimal(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return null; + return v0.subtract(v1); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.Subtract, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncMinus(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMod.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMod.java new file mode 100644 index 000000000..97559946e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMod.java @@ -0,0 +1,97 @@ +package io.mycat.plan.common.item.function.mathsfunc.operator; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemNumOp; + +public class ItemFuncMod extends ItemNumOp { + + public ItemFuncMod(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "%"; + } + + @Override + public void fixLengthAndDec() { + super.fixLengthAndDec(); + maybeNull = true; + } + + @Override + public BigDecimal realOp() { + BigDecimal val0 = args.get(0).valReal(); + BigDecimal val1 = args.get(1).valReal(); + if ((this.nullValue = args.get(0).isNull() || args.get(1).isNull())) + return BigDecimal.ZERO; + if (val1.compareTo(BigDecimal.ZERO) == 0) { + signalDivideByNull(); + return BigDecimal.ZERO; + } + BigInteger tmp = val0.toBigInteger().divide(val1.toBigInteger()); + BigDecimal tmpBd = new BigDecimal(tmp.multiply(val1.toBigInteger())); + return val0.subtract(tmpBd); + + } + + @Override + public BigInteger intOp() { + if (this.nullValue) + return BigInteger.ZERO; + BigInteger v0 = args.get(0).valInt(); + BigInteger v1 = args.get(1).valInt(); + if (v1.equals(BigInteger.ZERO)) { + signalDivideByNull(); + return BigInteger.ZERO; + } + return v0.divide(v1); + } + + @Override + public BigDecimal decimalOp() { + if (this.nullValue) + return new BigDecimal(0); + BigDecimal val0 = args.get(0).valDecimal(); + BigDecimal val1 = args.get(1).valDecimal(); + if (val1.compareTo(BigDecimal.ZERO) == 0) { + signalDivideByNull(); + return null; + } + BigInteger tmp = val0.toBigInteger().divide(val0.toBigInteger()); + BigDecimal tmpBd = new BigDecimal(tmp.multiply(val0.toBigInteger())); + return val0.subtract(tmpBd); + } + + @Override + public void result_precision() { + decimals = Math.max(args.get(0).decimals, args.get(1).decimals); + maxLength = Math.max(args.get(0).maxLength, args.get(1).maxLength); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.Mod, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncMod(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMul.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMul.java new file mode 100644 index 000000000..fa55fa8c4 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncMul.java @@ -0,0 +1,73 @@ +package io.mycat.plan.common.item.function.mathsfunc.operator; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemNumOp; + +public class ItemFuncMul extends ItemNumOp { + + public ItemFuncMul(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "*"; + } + + @Override + public BigDecimal realOp() { + BigDecimal val0 = args.get(0).valReal(); + BigDecimal val1 = args.get(1).valReal(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return BigDecimal.ZERO; + return val0.multiply(val1); + } + + @Override + public BigInteger intOp() { + BigInteger v0 = args.get(0).valInt(); + BigInteger v1 = args.get(1).valInt(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return BigInteger.ZERO; + return v0.multiply(v1); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal v0 = args.get(0).valDecimal(); + BigDecimal v1 = args.get(1).valDecimal(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return new BigDecimal(0); + return v0.multiply(v1); + } + + @Override + public void result_precision() { + decimals = Math.min(args.get(0).decimals + args.get(1).decimals, DECIMAL_MAX_SCALE); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.Multiply, args.get(1).toExpression()); + } + + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncMul(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncNeg.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncNeg.java new file mode 100644 index 000000000..8090fcd85 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncNeg.java @@ -0,0 +1,78 @@ +package io.mycat.plan.common.item.function.mathsfunc.operator; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLUnaryExpr; +import com.alibaba.druid.sql.ast.expr.SQLUnaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncNum1; + + +public class ItemFuncNeg extends ItemFuncNum1 { + + public ItemFuncNeg(Item a) { + super(new ArrayList()); + args.add(a); + } + + @Override + public final String funcName() { + return "-"; + } + + @Override + public BigInteger intOp() { + BigInteger bi = args.get(0).valInt(); + return bi.negate(); + } + + @Override + public BigDecimal realOp() { + BigDecimal bd = args.get(0).valReal(); + return bd.negate(); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal bd = args.get(0).valDecimal(); + if (nullValue = args.get(0).nullValue) + return null; + return bd.negate(); + } + + @Override + public void fixNumLengthAndDec() { + decimals = args.get(0).decimals; + } + + @Override + public int decimalPrecision() { + return args.get(0).decimalPrecision(); + } + + @Override + public Functype functype() { + return Functype.NEG_FUNC; + } + + @Override + public SQLExpr toExpression() { + return new SQLUnaryExpr(SQLUnaryOperator.Negative, args.get(0).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncNeg(newArgs.get(0)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncPlus.java b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncPlus.java new file mode 100644 index 000000000..7a3c0f5dc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/mathsfunc/operator/ItemFuncPlus.java @@ -0,0 +1,67 @@ +package io.mycat.plan.common.item.function.mathsfunc.operator; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemFuncAdditiveOp; + +public class ItemFuncPlus extends ItemFuncAdditiveOp { + + public ItemFuncPlus(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "+"; + } + + @Override + public BigDecimal realOp() { + BigDecimal bd1 = args.get(0).valReal(); + BigDecimal bd2 = args.get(1).valReal(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return BigDecimal.ZERO; + return bd1.add(bd2); + } + + @Override + public BigInteger intOp() { + BigInteger val0 = args.get(0).valInt(); + BigInteger val1 = args.get(1).valInt(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return BigInteger.ZERO; + return val0.add(val1); + } + + @Override + public BigDecimal decimalOp() { + BigDecimal v0 = args.get(0).valDecimal(); + BigDecimal v1 = args.get(1).valDecimal(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull())) + return new BigDecimal(0); + return v0.add(v1); + } + + @Override + public SQLExpr toExpression() { + return new SQLBinaryOpExpr(args.get(0).toExpression(), SQLBinaryOperator.Add, args.get(1).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncPlus(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/ItemBoolFunc2.java b/src/main/java/io/mycat/plan/common/item/function/operator/ItemBoolFunc2.java new file mode 100644 index 000000000..2c198fe32 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/ItemBoolFunc2.java @@ -0,0 +1,56 @@ +package io.mycat.plan.common.item.function.operator; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.cmpfunc.util.ArgComparator; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + +/** + * Bool with 2 string args + * + * + */ +public abstract class ItemBoolFunc2 extends ItemBoolFunc { + protected ArgComparator cmp; + protected boolean abort_on_null; + + public ItemBoolFunc2(Item a, Item b) { + super(a, b); + cmp = new ArgComparator(a, b); + abort_on_null = false; + } + + public int set_cmp_func() { + return cmp.setCmpFunc(this, args.get(0), args.get(1), true); + } + + @Override + public void fixLengthAndDec() { + maxLength = 1; // Function returns 0 or 1 + + /* + * As some compare functions are generated after sql_yacc, we have to + * check for out of memory conditions here + */ + if (args.get(0) == null || args.get(1) == null) + return; + + /* + * See agg_item_charsets() in item.cc for comments on character set and + * collation aggregation. + */ + + args.get(0).cmpContext = args.get(1).cmpContext = MySQLcom.item_cmp_type(args.get(0).resultType(), + args.get(1).resultType()); + // Make a special case of compare with fields to get nicer DATE + // comparisons + + set_cmp_func(); + return; + } + + @Override + public boolean isNull() { + return args.get(0).isNull() || args.get(1).isNull(); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncBetweenAnd.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncBetweenAnd.java new file mode 100644 index 000000000..b0edd190e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncBetweenAnd.java @@ -0,0 +1,235 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.cmpfunc.util.ArgComparator; +import io.mycat.plan.common.item.function.operator.cmpfunc.util.CmpUtil; +import io.mycat.plan.common.ptr.ItemResultPtr; + + +public class ItemFuncBetweenAnd extends ItemFuncOptNeg { + private ItemResult cmp_type; + String value0, value1, value2; + /* TRUE <=> arguments will be compared as dates. */ + boolean compare_as_dates_with_strings; + boolean compare_as_temporal_dates; + boolean compare_as_temporal_times; + + /* Comparators used for DATE/DATETIME comparison. */ + ArgComparator ge_cmp, le_cmp; + + /** + * select 'a' in ('a','b','c') args(0)为'a',[1]为'a',[2]为'b'。。。 + * + * @param args + */ + public ItemFuncBetweenAnd(Item a, Item b, Item c, boolean isNegation) { + super(new ArrayList(), isNegation); + args.add(a); + args.add(b); + args.add(c); + } + + @Override + public final String funcName() { + return "between"; + } + + @Override + public Functype functype() { + return Functype.BETWEEN; + } + + @Override + public BigInteger valInt() { + if (compare_as_dates_with_strings) { + int ge_res, le_res; + + ge_res = ge_cmp.compare(); + if ((nullValue = args.get(0).isNull())) + return BigInteger.ZERO; + le_res = le_cmp.compare(); + + if (!args.get(1).isNull() && !args.get(2).isNull()) + return ((ge_res >= 0 && le_res <= 0)) != negated ? BigInteger.ONE : BigInteger.ZERO; + else if (args.get(1).isNull()) { + nullValue = le_res > 0; // not null if false range. + } else { + nullValue = ge_res < 0; + } + } else if (cmp_type == ItemResult.STRING_RESULT) { + String value, a, b; + value = args.get(0).valStr(); + if (nullValue = args.get(0).isNull()) + return BigInteger.ZERO; + a = args.get(1).valStr(); + b = args.get(2).valStr(); + if (!args.get(1).isNull() && !args.get(2).isNull()) + return (value.compareTo(a) >= 0 && value.compareTo(b) <= 0) != negated ? BigInteger.ONE + : BigInteger.ZERO; + if (args.get(1).isNull() && args.get(2).isNull()) + nullValue = true; + else if (args.get(1).isNull()) { + // Set to not null if false range. + nullValue = value.compareTo(b) <= 0; + } else { + // Set to not null if false range. + nullValue = value.compareTo(a) >= 0; + } + } else if (cmp_type == ItemResult.INT_RESULT) { + long a, b, value; + value = compare_as_temporal_times ? args.get(0).valTimeTemporal() + : compare_as_temporal_dates ? args.get(0).valDateTemporal() : args.get(0).valInt().longValue(); + if (nullValue = args.get(0).isNull()) + return BigInteger.ZERO; /* purecov: inspected */ + if (compare_as_temporal_times) { + a = args.get(1).valTimeTemporal(); + b = args.get(2).valTimeTemporal(); + } else if (compare_as_temporal_dates) { + a = args.get(1).valDateTemporal(); + b = args.get(2).valDateTemporal(); + } else { + a = args.get(1).valInt().longValue(); + b = args.get(2).valInt().longValue(); + } + if (!args.get(1).isNull() && !args.get(2).isNull()) + return (value >= a && value <= b) != negated ? BigInteger.ONE : BigInteger.ZERO; + if (args.get(1).isNull() && args.get(2).isNull()) + nullValue = true; + else if (args.get(1).isNull()) { + nullValue = value <= b; // not null if false range. + } else { + nullValue = value >= a; + } + } else if (cmp_type == ItemResult.DECIMAL_RESULT) { + BigDecimal dec = args.get(0).valDecimal(); + BigDecimal a_dec, b_dec; + if (nullValue = args.get(0).isNull()) + return BigInteger.ZERO; /* purecov: inspected */ + a_dec = args.get(1).valDecimal(); + b_dec = args.get(2).valDecimal(); + if (!args.get(1).isNull() && !args.get(2).isNull()) + return (dec.compareTo(a_dec) >= 0 && dec.compareTo(b_dec) <= 0) != negated ? BigInteger.ONE + : BigInteger.ZERO; + if (args.get(1).isNull() && args.get(2).isNull()) + nullValue = true; + else if (args.get(1).isNull()) + nullValue = dec.compareTo(b_dec) <= 0; + else + nullValue = dec.compareTo(a_dec) >= 0; + } else { + double value = args.get(0).valReal().doubleValue(), a, b; + if (nullValue = args.get(0).isNull()) + return BigInteger.ZERO; /* purecov: inspected */ + a = args.get(1).valReal().doubleValue(); + b = args.get(2).valReal().doubleValue(); + if (!args.get(1).isNull() && !args.get(2).isNull()) + return (value >= a && value <= b) != negated ? BigInteger.ONE : BigInteger.ZERO; + if (args.get(1).isNull() && args.get(2).isNull()) + nullValue = true; + else if (args.get(1).isNull()) { + nullValue = value <= b; // not null if false range. + } else { + nullValue = value >= a; + } + } + return !nullValue ? BigInteger.ONE : BigInteger.ZERO; + + } + + @Override + public boolean fixFields() { + if (super.fixFields()) + return true; + + return false; + } + + @Override + public void fixLengthAndDec() { + maxLength = 1; + int i; + int datetime_items_found = 0; + int time_items_found = 0; + compare_as_dates_with_strings = false; + compare_as_temporal_times = compare_as_temporal_dates = false; + /* + * As some compare functions are generated after sql_yacc, we have to + * check for out of memory conditions here + */ + if (args.get(0) == null || args.get(1) == null || args.get(2) == null) + return; + if (CmpUtil.agg_cmp_type(new ItemResultPtr(cmp_type), args, 3) != 0) + return; + /* + * Detect the comparison of DATE/DATETIME items. At least one of items + * should be a DATE/DATETIME item and other items should return the + * STRING result. + */ + if (cmp_type == ItemResult.STRING_RESULT) { + for (i = 0; i < 3; i++) { + if (args.get(i).isTemporalWithDate()) + datetime_items_found++; + else if (args.get(i).fieldType() == FieldTypes.MYSQL_TYPE_TIME) + time_items_found++; + } + } + + if (datetime_items_found + time_items_found == 3) { + if (time_items_found == 3) { + // All items are TIME + cmp_type = ItemResult.INT_RESULT; + compare_as_temporal_times = true; + } else { + /* + * There is at least one DATE or DATETIME item, all other items + * are DATE, DATETIME or TIME. + */ + cmp_type = ItemResult.INT_RESULT; + compare_as_temporal_dates = true; + } + } else if (datetime_items_found > 0) { + /* + * There is at least one DATE or DATETIME item. All other items are + * DATE, DATETIME or strings. + */ + compare_as_dates_with_strings = true; + ge_cmp.setDatetimeCmpFunc(this, args.get(0), args.get(1)); + le_cmp.setDatetimeCmpFunc(this, args.get(0), args.get(2)); + } else { + // TODO + } + } + + @Override + public int decimalPrecision() { + return 1; + } + + @Override + public SQLExpr toExpression() { + SQLExpr first = args.get(0).toExpression(); + SQLExpr second = args.get(1).toExpression(); + SQLExpr third = args.get(2).toExpression(); + return new SQLBetweenExpr(first,this.negated, second, third); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncBetweenAnd(newArgs.get(0), newArgs.get(1), newArgs.get(2), this.negated); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncCoalesce.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncCoalesce.java new file mode 100644 index 000000000..5afcf5885 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncCoalesce.java @@ -0,0 +1,138 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemFuncNumhybrid; +import io.mycat.plan.common.time.MySQLTime; + + +/* + * 返回第一个不是null的值 + */ +public class ItemFuncCoalesce extends ItemFuncNumhybrid { + + protected FieldTypes cached_field_type; + + public ItemFuncCoalesce(List args) { + super(args); + } + + @Override + public String funcName() { + return "coalesce"; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncCoalesce(realArgs); + } + + @Override + public void fixLengthAndDec() { + cached_field_type = MySQLcom.agg_field_type(args, 0, args.size()); + hybrid_type = MySQLcom.agg_result_type(args, 0, args.size()); + switch (hybrid_type) { + case STRING_RESULT: + break; + case DECIMAL_RESULT: + countDecimalLength(); + break; + case REAL_RESULT: + countRealLength(); + break; + case INT_RESULT: + decimals = 0; + break; + case ROW_RESULT: + default: + assert (false); + } + } + + @Override + public void findNumType() { + } + + @Override + public BigInteger intOp() { + nullValue = false; + for (int i = 0; i < getArgCount(); i++) { + BigInteger res = args.get(i).valInt(); + if (!args.get(i).nullValue) + return res; + } + nullValue = true; + return BigInteger.ZERO; + } + + @Override + public BigDecimal realOp() { + nullValue = false; + for (int i = 0; i < getArgCount(); i++) { + BigDecimal res = args.get(i).valReal(); + if (!args.get(i).nullValue) + return res; + } + nullValue = true; + return BigDecimal.ZERO; + } + + @Override + public BigDecimal decimalOp() { + nullValue = false; + for (int i = 0; i < getArgCount(); i++) { + BigDecimal res = args.get(i).valDecimal(); + if (!args.get(i).nullValue) + return res; + } + nullValue = true; + return null; + } + + @Override + public String strOp() { + nullValue = false; + for (int i = 0; i < getArgCount(); i++) { + String res = args.get(i).valStr(); + if (res != null) + return res; + } + nullValue = true; + return null; + } + + @Override + public boolean dateOp(MySQLTime ltime, long fuzzydate) { + for (int i = 0; i < getArgCount(); i++) { + if (!args.get(i).getDate(ltime, fuzzydate)) + return (nullValue = false); + } + return (nullValue = true); + } + + @Override + public boolean timeOp(MySQLTime ltime) { + for (int i = 0; i < getArgCount(); i++) { + if (!args.get(i).getTime(ltime)) + return (nullValue = false); + } + return (nullValue = true); + } + + @Override + public ItemResult resultType() { + return hybrid_type; + } + + @Override + public FieldTypes fieldType() { + return cached_field_type; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncEqual.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncEqual.java new file mode 100644 index 000000000..650ed138d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncEqual.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncEqual extends ItemBoolFunc2 { + + public ItemFuncEqual(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "="; + } + + @Override + public Functype functype() { + return Functype.EQ_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value == 0 ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.Equality, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncEqual(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGe.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGe.java new file mode 100644 index 000000000..3f7c81500 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGe.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncGe extends ItemBoolFunc2 { + + public ItemFuncGe(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return ">="; + } + + @Override + public Functype functype() { + return Functype.GE_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value >= 0 ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.GreaterThanOrEqual, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncGe(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGreatest.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGreatest.java new file mode 100644 index 000000000..3cea3bba5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGreatest.java @@ -0,0 +1,31 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +/* + * select least(c1,c2,'5') from t,结果为5 + * select least(c1,c2,'a') from t,结果为'a' + * select least(c1,c2,'a')+1 from t,结果为1 + * 是否根据参数类型已经不能判断返回类型? + * calculate时是否应该保存最大值的index号,然后返回旧的argument? + */ +public class ItemFuncGreatest extends ItemFuncMinMax { + + public ItemFuncGreatest(List args) { + super(args, -1); + } + + @Override + public final String funcName() { + return "greatest"; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncGreatest(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGt.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGt.java new file mode 100644 index 000000000..c92fce617 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncGt.java @@ -0,0 +1,53 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncGt extends ItemBoolFunc2 { + + public ItemFuncGt(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return ">"; + } + + @Override + public Functype functype() { + return Functype.GT_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value > 0 ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.GreaterThan, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncGt(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIn.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIn.java new file mode 100644 index 000000000..3c3d99d75 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIn.java @@ -0,0 +1,95 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLInListExpr; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.cmpfunc.util.ArgComparator; + + +public class ItemFuncIn extends ItemFuncOptNeg { + private ItemResult left_result_type; + private boolean have_null = false; + + /** + * select 'a' in ('a','b','c') args(0)为'a',[1]为'a',[2]为'b'。。。 + * + * @param args + */ + public ItemFuncIn(List args, boolean isNegation) { + super(args, isNegation); + } + + @Override + public final String funcName() { + return "in"; + } + + @Override + public void fixLengthAndDec() { + for (int i = 1; i < args.size(); i++) { + args.get(i).cmpContext = MySQLcom.item_cmp_type(left_result_type, args.get(i).resultType()); + } + maxLength = 1; + } + + @Override + public BigInteger valInt() { + if ((nullValue = args.get(0).type() == Item.ItemType.NULL_ITEM)) + return BigInteger.ZERO; + Item left = args.get(0); + if (nullValue = left.type() == ItemType.NULL_ITEM) { + return BigInteger.ZERO; + } + have_null = false; + for (int i = 1; i < args.size(); i++) { + Item right = args.get(i); + if (right.type() == ItemType.NULL_ITEM) { + have_null = true; + continue; + } + left.valInt(); + if (nullValue = left.nullValue) + return BigInteger.ZERO; + ArgComparator cmp = new ArgComparator(left, right); + cmp.setCmpFunc(this, left, right, false); + if (cmp.compare() == 0 && !right.nullValue) + return !negated ? BigInteger.ONE : BigInteger.ZERO; + have_null |= right.isNull(); + } + nullValue = have_null; + return (!nullValue && negated) ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLInListExpr in = new SQLInListExpr(args.get(0).toExpression(), this.negated); + List targetList = new ArrayList(); + int index = 0; + for (Item item : args) { + if (index != 0) { + targetList.add(item.toExpression()); + } + index++; + } + in.setTargetList(targetList); + return in; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIn(newArgs, this.negated); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncInterval.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncInterval.java new file mode 100644 index 000000000..c4e782ab6 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncInterval.java @@ -0,0 +1,49 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +/* + * 至少两个参数, INTERVAL(N,N1,N2,N3,...). + * 假如N < N1,则返回值为0;假如N < N2 等等,则返回值为1;假如N 为NULL,则返回值为 -1. + */ + +public class ItemFuncInterval extends ItemIntFunc { + + public ItemFuncInterval(List args) { + super(args); + } + @Override + public final String funcName(){ + return "interval"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = false; + maxLength = 2; + } + + @Override + public BigInteger valInt() { + BigInteger arg0 = args.get(0).valInt(); + if (args.get(0).nullValue) + return BigInteger.ONE.negate(); + int i = 0; + for (i = 1; i < args.size(); i++) { + BigInteger tmp = args.get(i).valInt(); + if (arg0.compareTo(tmp) < 0) + break; + } + return BigInteger.valueOf(i); + } + + @Override + public int decimalPrecision() { + return 2; + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsfalse.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsfalse.java new file mode 100644 index 000000000..0d49bc0f0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsfalse.java @@ -0,0 +1,47 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLBooleanExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +/** + * This Item represents a X IS TRUE boolean predicate. + * + * + */ +public class ItemFuncIsfalse extends ItemFuncTruth { + + public ItemFuncIsfalse(Item a) { + super(a, false, true); + args.add(a); + } + + @Override + public final String funcName() { + return "isfalse"; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.Is, new SQLBooleanExpr(false)); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIsfalse(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnotfalse.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnotfalse.java new file mode 100644 index 000000000..757dd147f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnotfalse.java @@ -0,0 +1,46 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLBooleanExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +/** + * This Item represents a X IS TRUE boolean predicate. + * + * + */ +public class ItemFuncIsnotfalse extends ItemFuncTruth { + + public ItemFuncIsnotfalse(Item a) { + super(a, false, false); + } + + @Override + public final String funcName() { + return "isnotfalse"; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.IsNot, new SQLBooleanExpr(false)); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIsnotfalse(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnotnull.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnotnull.java new file mode 100644 index 000000000..d6ac49009 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnotnull.java @@ -0,0 +1,63 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + +public class ItemFuncIsnotnull extends ItemBoolFunc { + + public ItemFuncIsnotnull(Item a) { + super(a); + } + + @Override + public final String funcName() { + return "isnotnull"; + } + + @Override + public Functype functype() { + return Functype.ISNOTNULL_FUNC; + } + + @Override + public BigInteger valInt() { + if (args.get(0).isNull()) { + return BigInteger.ZERO; + } else { + return BigInteger.ONE; + } + } + + @Override + public void fixLengthAndDec() { + decimals = 0; + maxLength = 1; + maybeNull = false; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.IsNot, new SQLIdentifierExpr("UNKNOWN")); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIsnotnull(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnottrue.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnottrue.java new file mode 100644 index 000000000..655c820d2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnottrue.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLBooleanExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +/** + * This Item represents a X IS NOT TRUE boolean predicate. + * + */ +public class ItemFuncIsnottrue extends ItemFuncTruth { + + public ItemFuncIsnottrue(Item a) { + super(a, true, false); + } + + @Override + public final String funcName() { + return "isnottrue"; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.IsNot, new SQLBooleanExpr(true)); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIsnottrue(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnull.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnull.java new file mode 100644 index 000000000..4b770ac5a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIsnull.java @@ -0,0 +1,74 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + + +/* + * 当MySQL的sql_auto_is_null变量设为true,并且col_name为自增列时, + * select * from table_name where col_name is null返回last_insert_id + */ +public class ItemFuncIsnull extends ItemBoolFunc { + + public ItemFuncIsnull(Item a) { + super(a); + } + + @Override + public final String funcName() { + return "isnull"; + } + + @Override + public Functype functype() { + return Functype.ISNULL_FUNC; + } + + @Override + public BigInteger valInt() { + if (args.get(0).isNull()) { + return BigInteger.ONE; + } else { + return BigInteger.ZERO; + } + } + + @Override + public void fixLengthAndDec() { + decimals = 0; + maxLength = 1; + maybeNull = false; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.Is, new SQLIdentifierExpr("UNKNOWN")); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIsnull(newArgs.get(0)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncIsnull(realArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIstrue.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIstrue.java new file mode 100644 index 000000000..21d165340 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncIstrue.java @@ -0,0 +1,46 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLBooleanExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +/** + * This Item represents a X IS TRUE boolean predicate. + * + * + */ +public class ItemFuncIstrue extends ItemFuncTruth { + + public ItemFuncIstrue(Item a) { + super(a, true, true); + } + + @Override + public final String funcName() { + return "istrue"; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.Is, new SQLBooleanExpr(true)); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIstrue(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLe.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLe.java new file mode 100644 index 000000000..0f1c6af56 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLe.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncLe extends ItemBoolFunc2 { + + public ItemFuncLe(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "<="; + } + + @Override + public Functype functype() { + return Functype.LE_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value <= 0 && !nullValue ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.LessThanOrEqual, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if(!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncLe(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLeast.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLeast.java new file mode 100644 index 000000000..e80fb86cd --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLeast.java @@ -0,0 +1,28 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +/** + * mysql> select least('11', '2'), least('11', '2')+0, concat(least(11,2));
+ */ +public class ItemFuncLeast extends ItemFuncMinMax { + + public ItemFuncLeast(List args) { + super(args, 1); + } + + @Override + public final String funcName() { + return "least"; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLeast(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLike.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLike.java new file mode 100644 index 000000000..7312d60f4 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLike.java @@ -0,0 +1,90 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; +import io.mycat.util.CompareLike; + + +public class ItemFuncLike extends ItemBoolFunc2 { + private Item escape; + private boolean isNot; + + public ItemFuncLike(Item a, Item b, Item escape, boolean isNot) { + super(a, b); + this.escape = escape; + if (escape != null) + args.add(escape); + this.isNot = isNot; + } + + @Override + public final String funcName() { + return isNot ? "not like " : "like"; + } + + public Functype functype() { + return Functype.LIKE_FUNC; + } + + @Override + public BigInteger valInt() { + String str = args.get(0).valStr(); + if (args.get(0).isNull()) { + this.nullValue = true; + return BigInteger.ZERO; + } + String str2 = args.get(1).valStr(); + if (args.get(1).isNull()) { + this.nullValue = true; + return BigInteger.ZERO; + } + String escapeStr = null; + if (escape != null) + escapeStr = escape.valStr(); + this.nullValue = false; + CompareLike like = null; + if (escapeStr == null) + like = new CompareLike(str2); + else + like = new CompareLike(str2, escapeStr); + boolean isLike = like.compare(str); + return isNot ? (isLike ? BigInteger.ZERO : BigInteger.ONE) : (isLike ? BigInteger.ONE : BigInteger.ZERO); + } + + @Override + public SQLExpr toExpression() { + SQLExpr comparee = args.get(0).toExpression(); + SQLExpr pattern = args.get(1).toExpression(); + SQLExpr escape = this.escape == null ? null : this.escape.toExpression(); + SQLBinaryOpExpr like = null; + if (isNot) { + like = new SQLBinaryOpExpr(comparee, SQLBinaryOperator.NotLike, pattern); + } else { + like = new SQLBinaryOpExpr(comparee, SQLBinaryOperator.Like, pattern); + } + if (escape == null) { + return like; + } else { + return new SQLBinaryOpExpr(like, SQLBinaryOperator.Escape, escape); + } + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncLike(newArgs.get(0), newArgs.get(1), escape == null ? null : newArgs.get(2), this.isNot); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLt.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLt.java new file mode 100644 index 000000000..e81f817a8 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncLt.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncLt extends ItemBoolFunc2 { + + public ItemFuncLt(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "<"; + } + + @Override + public Functype functype() { + return Functype.LT_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value < 0 && !nullValue ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.LessThan, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if(!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncLt(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncMinMax.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncMinMax.java new file mode 100644 index 000000000..01f98a04c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncMinMax.java @@ -0,0 +1,366 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +/** + * min_max函数的父函数,通过cmp_sign来区分是min还是max函数 + * + * + */ +public abstract class ItemFuncMinMax extends ItemFunc { + ItemResult cmp_type; + String tmp_value; + int cmp_sign; + boolean compare_as_dates; + Item datetime_item; + + protected FieldTypes cached_field_type; + + /* + * Compare item arguments in the DATETIME context. + * + * SYNOPSIS cmp_datetimes() value [out] found least/greatest DATE/DATETIME + * value + * + * DESCRIPTION Compare item arguments as DATETIME values and return the + * index of the least/greatest argument in the arguments array. The correct + * integer DATE/DATETIME value of the found argument is stored to the value + * pointer, if latter is provided. + * + * RETURN 0 If one of arguments is NULL or there was a execution error # + * index of the least/greatest argument + */ + protected long cmp_datetimes(LongPtr value) { + long min_max = -1; + int min_max_idx = 0; + + for (int i = 0; i < args.size(); i++) { + long res = args.get(i).valDateTemporal(); + + if ((nullValue = args.get(i).isNull())) + return 0; + if (i == 0 || (res < min_max ? cmp_sign : -cmp_sign) > 0) { + min_max = res; + min_max_idx = i; + } + } + value.set(min_max); + return min_max_idx; + } + + protected long cmp_times(LongPtr value) { + long min_max = -1; + int min_max_idx = 0; + + for (int i = 0; i < args.size(); i++) { + long res = args.get(i).valTimeTemporal(); + + if ((nullValue = args.get(i).isNull())) + return 0; + if (i == 0 || (res < min_max ? cmp_sign : -cmp_sign) > 0) { + min_max = res; + min_max_idx = i; + } + } + value.set(min_max); + return min_max_idx; + } + + public ItemFuncMinMax(List args, int cmp_sign_arg) { + super(args); + this.cmp_sign = cmp_sign_arg; + cmp_type = ItemResult.INT_RESULT; + compare_as_dates = false; + datetime_item = null; + } + + @Override + public BigDecimal valReal() { + double value = 0.0; + if (compare_as_dates) { + LongPtr result = new LongPtr(0); + cmp_datetimes(result); + return new BigDecimal(MyTime.double_from_datetime_packed(datetime_item.fieldType(), result.get())); + } + for (int i = 0; i < args.size(); i++) { + if (i == 0) + value = args.get(i).valReal().doubleValue(); + else { + double tmp = args.get(i).valReal().doubleValue(); + if (!args.get(i).isNull() && (tmp < value ? cmp_sign : -cmp_sign) > 0) + value = tmp; + } + if ((nullValue = args.get(i).isNull())) + break; + } + return new BigDecimal(value); + } + + @Override + public BigInteger valInt() { + long value = 0; + if (compare_as_dates) { + LongPtr result = new LongPtr(0); + cmp_datetimes(result); + return BigInteger.valueOf(MyTime.longlong_from_datetime_packed(datetime_item.fieldType(), result.get())); + } + /* + * TS-TODO: val_str decides which type to use using cmp_type. val_int, + * val_decimal, val_real do not check cmp_type and decide data type + * according to the method type. This is probably not good: + * + * mysql> select least('11', '2'), least('11', '2')+0, + * concat(least(11,2)); + * +------------------+--------------------+---------------------+ | + * least('11', '2') | least('11', '2')+0 | concat(least(11,2)) | + * +------------------+--------------------+---------------------+ | 11 + * | 2 | 2 | + * +------------------+--------------------+---------------------+ 1 row + * in set (0.00 sec) + * + * Should not the second column return 11? I.e. compare as strings and + * return '11', then convert to number. + */ + for (int i = 0; i < args.size(); i++) { + if (i == 0) + value = args.get(i).valInt().longValue(); + else { + long tmp = args.get(i).valInt().longValue(); + if (!args.get(i).isNull() && (tmp < value ? cmp_sign : -cmp_sign) > 0) + value = tmp; + } + if ((nullValue = args.get(i).isNull())) + break; + } + return BigInteger.valueOf(value); + } + + @Override + public String valStr() { + if (compare_as_dates) { + if (isTemporal()) { + /* + * In case of temporal data types, we always return string value + * according the format of the data type. For example, in case + * of LEAST(time_column, datetime_column) the result date type + * is DATETIME, so we return a 'YYYY-MM-DD hh:mm:ss' string even + * if time_column wins (conversion from TIME to DATETIME happens + * in this case). + */ + LongPtr result = new LongPtr(0); + cmp_datetimes(result); + if (nullValue) + return null; + MySQLTime ltime = new MySQLTime(); + MyTime.TIME_from_longlong_packed(ltime, fieldType(), result.get()); + return MyTime.my_time_to_str(ltime, decimals); + + } else { + /* + * In case of VARCHAR result type we just return val_str() value + * of the winning item AS IS, without conversion. + */ + long min_max_idx = cmp_datetimes(new LongPtr(0)); + if (nullValue) + return null; + String str_res = args.get((int) min_max_idx).valStr(); + if (args.get((int) min_max_idx).nullValue) { + // check if the call to val_str() above returns a NULL value + nullValue = true; + return null; + } + return str_res; + } + } + + switch (cmp_type) { + case INT_RESULT: { + BigInteger nr = valInt(); + if (nullValue) + return null; + return nr.toString(); + } + case DECIMAL_RESULT: { + BigDecimal bd = valDecimal(); + if (nullValue) + return null; + return bd.toString(); + } + case REAL_RESULT: { + BigDecimal nr = valReal(); + if (nullValue) + return null; /* purecov: inspected */ + return nr.toString(); + } + case STRING_RESULT: { + String res = null; + for (int i = 0; i < args.size(); i++) { + if (i == 0) + res = args.get(i).valStr(); + else { + String res2 = args.get(i).valStr(); + if (res2 != null) { + int cmp = res.compareTo(res2); + if ((cmp_sign < 0 ? cmp : -cmp) < 0) + res = res2; + } + } + if ((nullValue = args.get(i).isNull())) + return null; + } + return res; + } + case ROW_RESULT: + default: + // This case should never be chosen + return null; + } + } + + @Override + public BigDecimal valDecimal() { + BigDecimal res = null, tmp; + + if (compare_as_dates) { + LongPtr value = new LongPtr(0); + cmp_datetimes(value); + return MyTime.my_decimal_from_datetime_packed(datetime_item.fieldType(), value.get()); + } + for (int i = 0; i < args.size(); i++) { + if (i == 0) + res = args.get(i).valDecimal(); + else { + tmp = args.get(i).valDecimal(); // Zero if NULL + if (tmp != null && tmp.compareTo(res) * cmp_sign < 0) { + res = tmp; + } + } + if ((nullValue = args.get(i).isNull())) { + res = null; + break; + } + } + return res; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + assert (fixed == true); + if (compare_as_dates) { + LongPtr result = new LongPtr(0); + cmp_datetimes(result); + if (nullValue) + return true; + MyTime.TIME_from_longlong_packed(ltime, datetime_item.fieldType(), result.get()); + LongPtr warnings = new LongPtr(0); + return MyTime.check_date(ltime, ltime.isNonZeroDate(), fuzzydate, warnings); + } + + switch (fieldType()) { + case MYSQL_TYPE_TIME: + return getDateFromTime(ltime); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + assert (false); // Should have been processed in "compare_as_dates" + // block. + default: + return getDateFromNonTemporal(ltime, fuzzydate); + } + } + + @Override + public boolean getTime(MySQLTime ltime) { + assert (fixed == true); + if (compare_as_dates) { + LongPtr result = new LongPtr(0); + cmp_datetimes(result); + if (nullValue) + return true; + MyTime.TIME_from_longlong_packed(ltime, datetime_item.fieldType(), result.get()); + MyTime.datetime_to_time(ltime); + return false; + } + + switch (fieldType()) { + case MYSQL_TYPE_TIME: { + LongPtr result = new LongPtr(0); + cmp_times(result); + if (nullValue) + return true; + MyTime.TIME_from_longlong_time_packed(ltime, result.get()); + return false; + } + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME: + assert (false); // Should have been processed in "compare_as_dates" + // block. + default: + return getTimeFromNonTemporal(ltime); + } + } + + @Override + public void fixLengthAndDec() { + int string_arg_count = 0; + boolean datetime_found = false; + decimals = 0; + maxLength = 0; + cmp_type = args.get(0).temporalWithDateAsNumberResultType(); + + for (int i = 0; i < args.size(); i++) { + maxLength = Math.max(maxLength, args.get(i).maxLength); + decimals = Math.max(decimals, args.get(i).decimals); + cmp_type = MySQLcom.item_cmp_type(cmp_type, args.get(i).temporalWithDateAsNumberResultType()); + if (args.get(i).resultType() == ItemResult.STRING_RESULT) + string_arg_count++; + if (args.get(i).resultType() != ItemResult.ROW_RESULT && args.get(i).isTemporalWithDate()) { + datetime_found = true; + if (datetime_item == null || args.get(i).fieldType() == FieldTypes.MYSQL_TYPE_DATETIME) + datetime_item = args.get(i); + } + } + + if (string_arg_count == args.size()) { + if (datetime_found) { + compare_as_dates = true; + /* + * We should not do this: cached_field_type= + * datetime_item->field_type(); count_datetime_length(args, + * arg_count); because compare_as_dates can be TRUE but result + * type can still be VARCHAR. + */ + } + } + cached_field_type = MySQLcom.agg_field_type(args, 0, args.size()); + } + + @Override + public ItemResult resultType() { + return compare_as_dates ? ItemResult.STRING_RESULT : cmp_type; + } + + @Override + public FieldTypes fieldType() { + return cached_field_type; + } + + public ItemResult castToIntType() { + /* + * make CAST(LEAST_OR_GREATEST(datetime_expr, varchar_expr)) return a + * number in format "YYYMMDDhhmmss". + */ + return compare_as_dates ? ItemResult.INT_RESULT : resultType(); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncNe.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncNe.java new file mode 100644 index 000000000..f4fac53cc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncNe.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncNe extends ItemBoolFunc2 { + + public ItemFuncNe(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "<>"; + } + + @Override + public Functype functype() { + return Functype.NE_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value != 0 && !nullValue ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.LessThanOrGreater, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncNe(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncOptNeg.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncOptNeg.java new file mode 100644 index 000000000..20d373bda --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncOptNeg.java @@ -0,0 +1,43 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +/* +The class Item_func_opt_neg is defined to factor out the functionality +common for the classes Item_func_between and Item_func_in. The objects +of these classes can express predicates or there negations. +The alternative approach would be to create pairs Item_func_between, +Item_func_notbetween and Item_func_in, Item_func_notin. + +*/ +public abstract class ItemFuncOptNeg extends ItemIntFunc { + + public boolean negated = false; /* <=> the item represents NOT */ + public boolean pred_level = false; /* + * <=> [NOT] is used on a + * predicate level + */ + + public ItemFuncOptNeg(List args, boolean isNegation) { + super(args); + if (isNegation) + negate(); + } + + public void negate() { + negated = !negated; + } + + public void top_level_item() { + pred_level = true; + } + + public boolean subst_argument_checker() { + return true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncRegex.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncRegex.java new file mode 100644 index 000000000..64df1e96a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncRegex.java @@ -0,0 +1,51 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + + +public class ItemFuncRegex extends ItemBoolFunc { + + public ItemFuncRegex(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "regexp"; + } + + @Override + public BigInteger valInt() { + String arg0 = args.get(0).valStr(); + String arg1 = args.get(1).valStr(); + if (nullValue = (args.get(0).nullValue || args.get(1).nullValue)) + return BigInteger.ZERO; + return arg0.matches(arg1) ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.RegExp, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncRegex(newArgs.get(0), newArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncStrcmp.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncStrcmp.java new file mode 100644 index 000000000..93c5b55bc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncStrcmp.java @@ -0,0 +1,47 @@ +/** + * + */ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncStrcmp extends ItemBoolFunc2 { + + /** + * @param name + * @param a + * @param b + */ + public ItemFuncStrcmp(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "strcmp"; + } + + @Override + public BigInteger valInt() { + String a = args.get(0).valStr(); + String b = args.get(1).valStr(); + if (a == null || b == null) { + nullValue = true; + return BigInteger.ZERO; + } + int value = a.compareTo(b); + nullValue = false; + return value == 0 ? BigInteger.ZERO : (value < 0 ? BigInteger.valueOf(-1) : BigInteger.ONE); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncStrcmp(realArgs.get(0), realArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncStrictEqual.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncStrictEqual.java new file mode 100644 index 000000000..c922ef3b2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncStrictEqual.java @@ -0,0 +1,60 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncStrictEqual extends ItemBoolFunc2 { + + public ItemFuncStrictEqual(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "<=>"; + } + + @Override + public Functype functype() { + return Functype.EQUAL_FUNC; + } + + @Override + public BigInteger valInt() { + int value = cmp.compare(); + return value == 0 ? BigInteger.ONE : BigInteger.ZERO; + } + + @Override + public void fixLengthAndDec() { + super.fixLengthAndDec(); + maybeNull = nullValue = false; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.LessThanOrEqualOrGreaterThan, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncStrictEqual(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncTruth.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncTruth.java new file mode 100644 index 000000000..ad2b3ca33 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/ItemFuncTruth.java @@ -0,0 +1,61 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc; + +import java.math.BigInteger; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + + +public abstract class ItemFuncTruth extends ItemBoolFunc { + + /** + * True for X IS [NOT] TRUE, false for + * X IS [NOT] FALSE predicates. + */ + final boolean value; + /** + * True for X IS Y, false for X IS NOT Y + * predicates. + */ + final boolean affirmative; + + public ItemFuncTruth(Item a, boolean avalue, boolean aaffirmative) { + super(a); + this.value = avalue; + this.affirmative = aaffirmative; + } + + @Override + public boolean valBool() { + boolean val = args.get(0).valBool(); + if (args.get(0).isNull()) { + /* + * NULL val IS {TRUE, FALSE} --> FALSE NULL val IS NOT {TRUE, FALSE} + * --> TRUE + */ + return (!affirmative); + } + + if (affirmative) { + /* {TRUE, FALSE} val IS {TRUE, FALSE} value */ + return (val == value); + } + + /* {TRUE, FALSE} val IS NOT {TRUE, FALSE} value */ + return (val != value); + } + + @Override + public BigInteger valInt() { + return (valBool() ? BigInteger.ONE : BigInteger.ZERO); + } + + @Override + public void fixLengthAndDec() { + maybeNull = false; + nullValue = false; + decimals = 0; + maxLength = 1; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/ArgComparator.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/ArgComparator.java new file mode 100644 index 000000000..0fb3f19b3 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/ArgComparator.java @@ -0,0 +1,602 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc.util; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncStrictEqual; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTimestampType; + + +public class ArgComparator { + private Item a, b; + private ItemFunc owner; + private argCmpFunc func; // compare function name,在mysql源代码中为函数指针 + double precision = 0.0; + /* Fields used in DATE/DATETIME comparison. */ + FieldTypes atype, btype; // Types of a and b items + boolean is_nulls_eq; // TRUE <=> compare for the EQUAL_FUNC + boolean setNull = true; // TRUE <=> set owner->null_value + // when one of arguments is NULL. + GetValueFunc getValueAFunc; // get_value_a_func name + GetValueFunc getValueBFunc; // get_value_b_func name + + boolean try_year_cmp_func(ItemResult type) { + if (type == ItemResult.ROW_RESULT) + return false; + boolean aisyear = a.fieldType() == FieldTypes.MYSQL_TYPE_YEAR; + boolean bisyear = b.fieldType() == FieldTypes.MYSQL_TYPE_YEAR; + if (!aisyear && !bisyear) + return false; + if (aisyear && bisyear) { + getValueAFunc = new GetYearValue(); + getValueBFunc = new GetYearValue(); + } else if (aisyear && b.isTemporalWithDate()) { + getValueAFunc = new GetYearValue(); + getValueBFunc = new GetDatetimeValue(); + } else if (bisyear && a.isTemporalWithDate()) { + getValueBFunc = new GetYearValue(); + getValueAFunc = new GetDatetimeValue(); + } else + return false; + is_nulls_eq = isOwnerEqualFunc(); + func = new CompareDatetime(); + setcmpcontextfordatetime(); + return true; + } + + /** + * Check if str_arg is a constant and convert it to datetime packed value. + * Note, const_value may stay untouched, so the caller is responsible to + * initialize it. + * + * @param dateArg + * date argument, it's name is used for error reporting. + * @param strArg + * string argument to get datetime value from. + * @param[out] const_value the converted value is stored here, if not NULL. + * @return true on error, false on success, false if str_arg is not a const. + */ + static boolean getDateFromConst(Item dateArg, Item strArg, LongPtr constValue) { + BoolPtr error = new BoolPtr(false); + long value = 0; + if (strArg.fieldType() == FieldTypes.MYSQL_TYPE_TIME) { + // Convert from TIME to DATETIME + value = strArg.valDateTemporal(); + if (strArg.nullValue) + return true; + } else { + // Convert from string to DATETIME + String strVal = strArg.valStr(); + MySQLTimestampType ttype = (dateArg.fieldType() == FieldTypes.MYSQL_TYPE_DATE + ? MySQLTimestampType.MYSQL_TIMESTAMP_DATE + : MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); + if (strArg.nullValue) { + return true; + } + value = MySQLcom.get_date_from_str(strVal, ttype, error); + if (error.get()) + return true; + } + if (constValue != null) + constValue.set(value); + return false; + } + + public ArgComparator() { + + } + + public ArgComparator(Item a, Item b) { + this.a = a; + this.b = b; + } + + public int setCompareFunc(ItemFunc ownerarg, ItemResult type) { + owner = ownerarg; + func = comparator_matrix[type.ordinal()][isOwnerEqualFunc() == true ? 1 : 0]; + switch (type) { + case ROW_RESULT: + // 未实现 + return 1; + case STRING_RESULT: { + if (func instanceof CompareString) + func = new CompareBinaryString(); + else if (func instanceof CompareEString) + func = new CompareEBinaryString(); + break; + } + case INT_RESULT: { + if (a.isTemporal() && b.isTemporal()) { + func = isOwnerEqualFunc() ? new CompareETimePacked() : new CompareTimePacked(); + } else if (func instanceof CompareIntSigned) { + // + } else if (func instanceof CompareEInt) { + // + } + break; + } + case DECIMAL_RESULT: + break; + case REAL_RESULT: { + if (a.decimals < Item.NOT_FIXED_DEC && b.decimals < Item.NOT_FIXED_DEC) { + precision = 5 / Math.pow(10, (Math.max(a.decimals, b.decimals) + 1)); + if (func instanceof CompareReal) + func = new CompareRealFixed(); + else if (func instanceof CompareEReal) + func = new CompareERealFixed(); + } + break; + } + default: + } + return 0; + } + + public int setCompareFunc(ItemFunc ownerarg) { + return setCompareFunc(ownerarg, MySQLcom.item_cmp_type(a.resultType(), b.resultType())); + } + + public int setCmpFunc(ItemFunc ownerarg, Item a1, Item a2, ItemResult type) { + LongPtr constvalue = new LongPtr(-1); + owner = ownerarg; + setNull = setNull && (ownerarg != null); + a = a1; + b = a2; + if (canCompareAsDates(a, b, constvalue)) { + atype = a.fieldType(); + btype = b.fieldType(); + is_nulls_eq = isOwnerEqualFunc(); + func = new CompareDatetime(); + getValueAFunc = new GetDatetimeValue(); + getValueBFunc = new GetDatetimeValue(); + setcmpcontextfordatetime(); + return 0; + } else if (type == ItemResult.STRING_RESULT && a.fieldType() == FieldTypes.MYSQL_TYPE_TIME + && b.fieldType() == FieldTypes.MYSQL_TYPE_TIME) { + is_nulls_eq = isOwnerEqualFunc(); + func = new CompareDatetime(); + getValueAFunc = new GetTimeValue(); + getValueBFunc = new GetTimeValue(); + setcmpcontextfordatetime(); + return 0; + } else if (type == ItemResult.STRING_RESULT && a.resultType() == ItemResult.STRING_RESULT + && b.resultType() == ItemResult.STRING_RESULT) { + // see item_cmpfunc.cc line1054 + } else if (try_year_cmp_func(type)) { + return 0; + } + return setCompareFunc(ownerarg, type); + } + + public int setCmpFunc(ItemFunc ownerarg, Item a1, Item a2, boolean setnullarg) { + setNull = setnullarg; + return setCmpFunc(ownerarg, a1, a2, MySQLcom.item_cmp_type(a1.resultType(), a2.resultType())); + } + + public int compare() { + return this.func.compare(this); + } + + public boolean isOwnerEqualFunc() { + if (this.owner != null) + return this.owner instanceof ItemFuncStrictEqual; + return false; + } + + public void setDatetimeCmpFunc(ItemFunc ownerArg, Item a1, Item a2) { + owner = ownerArg; + a = a1; + b = a2; + atype = a.fieldType(); + btype = b.fieldType(); + is_nulls_eq = false; + func = new CompareDatetime(); + getValueAFunc = new GetDatetimeValue(); + getValueBFunc = new GetDatetimeValue(); + setcmpcontextfordatetime(); + } + + /* + * Check whether compare_datetime() can be used to compare items. + * + * SYNOPSIS Arg_comparator::can_compare_as_dates() a, b [in] items to be + * compared const_value [out] converted value of the string constant, if any + * + * DESCRIPTION Check several cases when the DATE/DATETIME comparator should + * be used. The following cases are checked: 1. Both a and b is a + * DATE/DATETIME field/function returning string or int result. 2. Only a or + * b is a DATE/DATETIME field/function returning string or int result and + * the other item (b or a) is an item with string result. If the second item + * is a constant one then it's checked to be convertible to the + * DATE/DATETIME type. If the constant can't be converted to a DATE/DATETIME + * then the compare_datetime() comparator isn't used and the warning about + * wrong DATE/DATETIME value is issued. In all other cases + * (date-[int|real|decimal]/[int|real|decimal]-date) the comparison is + * handled by other comparators. If the datetime comparator can be used and + * one the operands of the comparison is a string constant that was + * successfully converted to a DATE/DATETIME type then the result of the + * conversion is returned in the const_value if it is provided. If there is + * no constant or compare_datetime() isn't applicable then the *const_value + * remains unchanged. + * + * @return true if can compare as dates, false otherwise. + */ + public static boolean canCompareAsDates(Item a, Item b, LongPtr constvalue) { + if (a.isTemporalWithDate()) { + if (b.isTemporalWithDate())// date[time] + date + { + return true; + } else if (b.resultType() == ItemResult.STRING_RESULT) {// date[time] + // + + // string + return !getDateFromConst(a, b, constvalue); + } else + return false; + } else if (b.isTemporalWithDate() && a.resultType() == ItemResult.STRING_RESULT) // string + // + + // date[time] + { + return !getDateFromConst(b, a, constvalue); + } else + return false;// No date[time] items found + } + + public static argCmpFunc[][] comparator_matrix = { { new CompareString(), new CompareEString() }, + { new CompareReal(), new CompareEReal() }, { new CompareIntSigned(), new CompareEInt() }, + { new CompareRow(), new CompareERow() }, { new CompareDecimal(), new CompareEDecimal() } }; + + public void setcmpcontextfordatetime() { + if (a.isTemporal()) + a.cmpContext = ItemResult.INT_RESULT; + if (b.isTemporal()) + b.cmpContext = ItemResult.INT_RESULT; + } + + /** + * compare function + * + * @author chenzifei + * + */ + private static interface argCmpFunc { + int compare(ArgComparator ac); + } + + private static class CompareString implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + String res1, res2; + if ((res1 = ac.a.valStr()) != null) { + if ((res2 = ac.b.valStr()) != null) { + if (ac.setNull && ac.owner != null) { + ac.owner.nullValue = false; + return res1.compareTo(res2); + } + } + } + if (ac.setNull) + ac.owner.nullValue = true; + return ac.a.nullValue ? -1 : 1; + } + } + + private static class CompareBinaryString implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + String res1, res2; + if ((res1 = ac.a.valStr()) != null) { + if ((res2 = ac.b.valStr()) != null) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (false); + byte[] res1b = res1.getBytes(); + byte[] res2b = res2.getBytes(); + int res1Len = res1b.length; + int res2Len = res2b.length; + int cmp = MySQLcom.memcmp(res1b, res2b, Math.min(res1Len, res2Len)); + return cmp != 0 ? cmp : (int) (res1Len - res2Len); + } + } + if (ac.setNull) + ac.owner.nullValue = (true); + return ac.a.nullValue ? -1 : 1; + + } + } + + private static class CompareReal implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BigDecimal val1, val2; + val1 = ac.a.valReal(); + if (!(ac.a.isNull())) { + val2 = ac.b.valReal(); + if (!(ac.b.isNull())) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (false); + if (val1.compareTo(val2) < 0) + return -1; + if (val1.compareTo(val2) == 0) + return 0; + return 1; + } + } + if (ac.setNull) + ac.owner.nullValue = true; + return ac.a.nullValue ? -1 : 1; + } + } + + private static class CompareDecimal implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BigDecimal val1 = ac.a.valDecimal(); + if (!ac.a.isNull()) { + BigDecimal val2 = ac.b.valDecimal(); + if (!ac.b.isNull()) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (false); + return val1.compareTo(val2); + } + } + if (ac.setNull) + ac.owner.nullValue = (true); + return ac.a.nullValue ? -1 : 1; + } + } + + private static class CompareIntSigned implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BigInteger val1 = ac.a.valInt(); + if (!ac.a.isNull()) { + BigInteger val2 = ac.b.valInt(); + if (!ac.b.isNull()) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (false); + if (val1.compareTo(val2) < 0) + return -1; + if (val1.compareTo(val2) == 0) + return 0; + return 1; + } + } + if (ac.setNull) + ac.owner.nullValue = (true); + return ac.a.nullValue ? -1 : 1; + } + } + + /** + * Compare arguments using numeric packed temporal representation. + */ + private static class CompareTimePacked implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + /* + * Note, we cannot do this: DBUG_ASSERT((*a)->field_type() == + * MYSQL_TYPE_TIME); DBUG_ASSERT((*b)->field_type() == + * MYSQL_TYPE_TIME); + * + * SELECT col_time_key FROM t1 WHERE col_time_key != UTC_DATE() AND + * col_time_key = MAKEDATE(43, -2852); + * + * is rewritten to: + * + * SELECT col_time_key FROM t1 WHERE MAKEDATE(43, -2852) != + * UTC_DATE() AND col_time_key = MAKEDATE(43, -2852); + */ + long val1 = ac.a.valDateTemporal(); + if (!ac.a.isNull()) { + long val2 = ac.b.valDateTemporal(); + if (!ac.b.isNull()) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (false); + return val1 < val2 ? -1 : val1 > val2 ? 1 : 0; + } + } + if (ac.setNull) + ac.owner.nullValue = (true); + return ac.a.nullValue ? -1 : 1; + } + } + + private static class CompareETimePacked implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + long val1 = ac.a.valDateTemporal(); + long val2 = ac.b.valDateTemporal(); + if (ac.a.isNull() || ac.b.isNull()) + return (ac.a.isNull() && ac.b.isNull()) ? 1 : 0; + return (val1 == val2) ? 1 : 0; + } + } + + private static class CompareRow implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + // TODO Auto-generated method stub + return 0; + } + } + + private static class CompareEString implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + String res1, res2; + res1 = ac.a.valStr(); + res2 = ac.b.valStr(); + if (res1 == null || res2 == null) + return (res1 == res2) ? 1 : 0; + return (res1.compareTo(res2) == 0) ? 1 : 0; + } + } + + private static class CompareEBinaryString implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + String res1, res2; + res1 = ac.a.valStr(); + res2 = ac.b.valStr(); + if (res1 == null || res2 == null) + return (res1 == res2) ? 1 : 0; + return MySQLcom.memcmp(res1.getBytes(), res2.getBytes(), Math.min(res1.length(), res2.length())) == 0 ? 1 + : 0; + } + } + + private static class CompareEReal implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BigDecimal val1 = ac.a.valReal(); + BigDecimal val2 = ac.b.valReal(); + if (ac.a.isNull() || ac.b.isNull()) + return (ac.a.isNull() && ac.b.isNull()) ? 1 : 0; + return val1.compareTo(val2) == 0 ? 1 : 0; + } + } + + private static class CompareEDecimal implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BigDecimal val1 = ac.a.valDecimal(); + BigDecimal val2 = ac.b.valDecimal(); + if (ac.a.isNull() || ac.b.isNull()) + return (ac.a.isNull() && ac.b.isNull()) ? 1 : 0; + return (val1.compareTo(val2) == 0) ? 1 : 0; + } + } + + private static class CompareEInt implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BigInteger val1 = ac.a.valInt(); + BigInteger val2 = ac.b.valInt(); + if (ac.a.isNull() || ac.b.isNull()) + return (ac.a.isNull() && ac.b.isNull()) ? 1 : 0; + return val1.compareTo(val2) == 0 ? 1 : 0; + } + } + + private static class CompareERow implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + // TODO Auto-generated method stub + return 0; + } + } + + private static class CompareRealFixed implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + /* + * Fix yet another manifestation of Bug#2338. 'Volatile' will + * instruct gcc to flush double values out of 80-bit Intel FPU + * registers before performing the comparison. + */ + BigDecimal val1, val2; + val1 = ac.a.valReal(); + if (!ac.a.isNull()) { + val2 = ac.b.valReal(); + if (!ac.b.isNull()) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (false); + if (val1.compareTo(val2) == 0 || Math.abs(val1.doubleValue() - val2.doubleValue()) < ac.precision) + return 0; + if (val1.compareTo(val2) < 0) + return -1; + return 1; + } + } + if (ac.setNull) + ac.owner.nullValue = (true); + return ac.a.nullValue ? -1 : 1; + } + } + + private static class CompareERealFixed implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + double val1 = ac.a.valReal().doubleValue(); + double val2 = ac.b.valReal().doubleValue(); + if (ac.a.isNull() || ac.b.isNull()) + return (ac.a.isNull() && ac.b.isNull()) ? 1 : 0; + return (val1 == val2 || Math.abs(val1 - val2) < ac.precision) ? 1 : 0; + } + } + + /** + * compare args[0] & args[1] as DATETIMEs SYNOPSIS + * Arg_comparator::compare_datetime() + * + * DESCRIPTION Compare items values as DATE/DATETIME for both EQUAL_FUNC and + * from other comparison functions. The correct DATETIME values are obtained + * with help of the get_datetime_value() function. + * + * RETURN If is_nulls_eq is TRUE: 1 if items are equal or both are null 0 + * otherwise If is_nulls_eq is FALSE: -1 a < b or at least one item is null + * 0 a == b 1 a > b See the table: is_nulls_eq | 1 | 1 | 1 | 1 | 0 | 0 | 0 | + * 0 | a_is_null | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | b_is_null | 1 | 1 | 0 | 0 + * | 1 | 1 | 0 | 0 | result | 1 | 0 | 0 |0/1|-1 |-1 |-1 |-1/0/1| + * + * @author chenzifei + * + */ + private static class CompareDatetime implements argCmpFunc { + + @Override + public int compare(ArgComparator ac) { + BoolPtr aIsNull = new BoolPtr(false); + BoolPtr bIsNull = new BoolPtr(false); + long a_value, b_value; + + /* Get DATE/DATETIME/TIME value of the 'a' item. */ + a_value = ac.getValueAFunc.get(ac.a, ac.b, aIsNull); + if (!ac.is_nulls_eq && aIsNull.get()) { + if (ac.setNull && ac.owner != null) + ac.owner.nullValue = (true); + return -1; + } + + /* Get DATE/DATETIME/TIME value of the 'b' item. */ + b_value = ac.getValueBFunc.get(ac.b, ac.a, bIsNull); + if (aIsNull.get() || bIsNull.get()) { + if (ac.setNull) + ac.owner.nullValue = (ac.is_nulls_eq ? false : true); + return ac.is_nulls_eq ? (aIsNull.get() == bIsNull.get()) ? 1 : 0 : -1; + } + + /* Here we have two not-NULL values. */ + if (ac.setNull) + ac.owner.nullValue = (false); + + /* Compare values. */ + if (ac.is_nulls_eq) + return a_value == (b_value) ? 1 : 0; + return a_value < b_value ? -1 : (a_value > b_value ? 1 : 0); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/CmpUtil.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/CmpUtil.java new file mode 100644 index 000000000..1748fb7c1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/CmpUtil.java @@ -0,0 +1,130 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc.util; + +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.common.ptr.ItemResultPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimeStatus; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +/** + * compare用到的一些公共方法 + * + + */ +public class CmpUtil { + /** + * Parse date provided in a string to a MYSQL_TIME. + * + * @param[in] thd Thread handle + * @param[in] str A string to convert + * @param[in] warn_type Type of the timestamp for issuing the warning + * @param[in] warn_name Field name for issuing the warning + * @param[out] l_time The MYSQL_TIME objects is initialized. + * + * Parses a date provided in the string str into a MYSQL_TIME + * object. If the string contains an incorrect date or doesn't + * correspond to a date at all then a warning is issued. The + * warn_type and the warn_name arguments are used as the name + * and the type of the field when issuing the warning. If any + * input was discarded (trailing or non-timestamp-y characters), + * return value will be TRUE. + * @return Status flag + * @retval FALSE Success. + * @retval True Indicates failure. + */ + + public static boolean get_mysql_time_from_str(String str, MySQLTimestampType warn_type, + final String warn_name, MySQLTime l_time) { + boolean value; + MySQLTimeStatus status = new MySQLTimeStatus(); + if (!MyTime.str_to_datetime(str, str.length(), l_time, MyTime.TIME_FUZZY_DATE, status) + && (l_time.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME + || l_time.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATE)) + /* + * Do not return yet, we may still want to throw a + * "trailing garbage" warning. + */ + value = false; + else { + value = true; + status.warnings = MyTime.MYSQL_TIME_WARN_TRUNCATED; /* + * force warning + */ + } + + if (status.warnings > 0) + ; + // make_truncated_value_warning(thd, Sql_condition::SL_WARNING, + // ErrConvString(str), warn_type, warn_name); + + return value; + } + + /** + * @brief Convert date provided in a string to its packed temporal int + * representation. + * @param[in] thd thread handle + * @param[in] str a string to convert + * @param[in] warn_type type of the timestamp for issuing the warning + * @param[in] warn_name field name for issuing the warning + * @param[out] error_arg could not extract a DATE or DATETIME + * @details Convert date provided in the string str to the int + * representation. If the string contains wrong date or doesn't + * contain it at all then a warning is issued. The warn_type and + * the warn_name arguments are used as the name and the type of the + * field when issuing the warning. + * @return converted value. 0 on error and on zero-dates -- check 'failure' + */ + public static long get_date_from_str(String str, MySQLTimestampType warn_type, String warn_name, + BoolPtr error_arg) { + MySQLTime l_time = new MySQLTime(); + error_arg.set(get_mysql_time_from_str(str, warn_type, warn_name, l_time)); + + if (error_arg.get()) + return 0; + return MyTime.TIME_to_longlong_datetime_packed(l_time); + } + + /** + * Aggregates result types from the array of items. + * + * SYNOPSIS: agg_cmp_type() type [out] the aggregated type items array of + * items to aggregate the type from nitems number of items in the array + * + * DESCRIPTION This function aggregates result types from the array of + * items. Found type supposed to be used later for comparison of values of + * these items. Aggregation itself is performed by the item_cmp_type() + * function. + * + * @param[out] type the aggregated type + * @param items + * array of items to aggregate the type from + * @param nitems + * number of items in the array + * @retval 1 type incompatibility has been detected + * @retval 0 otherwise + */ + public static int agg_cmp_type(ItemResultPtr type, List items, int nitems) { + int i; + type.set(items.get(0).resultType()); + for (i = 1; i < nitems; i++) { + type.set(MySQLcom.item_cmp_type(type.get(), items.get(i).resultType())); + /* + * When aggregating types of two row expressions we have to check + * that they have the same cardinality and that each component of + * the first row expression has a compatible row signature with the + * signature of the corresponding component of the second row + * expression. + */ + if (type.get() == ItemResult.ROW_RESULT && MySQLcom.cmpRowType(items.get(0), items.get(i)) != 0) + return 1; // error found: invalid usage of rows + } + return 0; + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetDatetimeValue.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetDatetimeValue.java new file mode 100644 index 000000000..7ca662e89 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetDatetimeValue.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc.util; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.common.time.MySQLTimestampType; + +public class GetDatetimeValue implements GetValueFunc { + + @Override + public long get(Item item, Item warn_item, BoolPtr is_null) { + long value = 0; + String str = null; + if (item.isTemporal()) { + value = item.valDateTemporal(); + is_null.set(item.nullValue); + } else { + str = item.valStr(); + is_null.set(item.nullValue); + } + if (is_null.get()) + return 0; + if (str != null) { + BoolPtr error = new BoolPtr(false); + FieldTypes f_type = warn_item.fieldType(); + MySQLTimestampType t_type = f_type == FieldTypes.MYSQL_TYPE_DATE + ? MySQLTimestampType.MYSQL_TIMESTAMP_DATE + : MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + value = MySQLcom.get_date_from_str(str, t_type, error); + } + return value; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetTimeValue.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetTimeValue.java new file mode 100644 index 000000000..1d4d7966d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetTimeValue.java @@ -0,0 +1,15 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc.util; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.ptr.BoolPtr; + +public class GetTimeValue implements GetValueFunc { + + @Override + public long get(Item item, Item warn, BoolPtr is_null) { + long value = item.valTimeTemporal(); + is_null.set(item.nullValue); + return value; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetValueFunc.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetValueFunc.java new file mode 100644 index 000000000..ff41ffdb2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetValueFunc.java @@ -0,0 +1,8 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc.util; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.ptr.BoolPtr; + +public interface GetValueFunc { + long get(Item arg, Item warnitem, BoolPtr isNull); +} \ No newline at end of file diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetYearValue.java b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetYearValue.java new file mode 100644 index 000000000..4b1403a73 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/cmpfunc/util/GetYearValue.java @@ -0,0 +1,61 @@ +package io.mycat.plan.common.item.function.operator.cmpfunc.util; + + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.common.time.MyTime; + +/* + Retrieves YEAR value of 19XX-00-00 00:00:00 form from given item. + + SYNOPSIS + get_year_value() + thd thread handle + item_arg [in/out] item to retrieve YEAR value from + cache_arg [in/out] pointer to place to store the caching item to + warn_item [in] item for issuing the conversion warning + is_null [out] TRUE <=> the item_arg is null + + DESCRIPTION + Retrieves the YEAR value of 19XX form from given item for comparison by the + compare_datetime() function. + Converts year to DATETIME of form YYYY-00-00 00:00:00 for the compatibility + with the get_datetime_value function result. + + RETURN + obtained value + */ +public class GetYearValue implements GetValueFunc { + + @Override + public long get(Item item, Item warnitem, BoolPtr is_null) { + long value = 0; + + value = item.valInt().longValue(); + is_null.set(item.nullValue); + if (is_null.get()) + return 0; + + /* + * Coerce value to the 19XX form in order to correctly compare YEAR(2) & + * YEAR(4) types. Here we are converting all item values but YEAR(4) + * fields since 1) YEAR(4) already has a regular YYYY form and 2) we + * don't want to convert zero/bad YEAR(4) values to the value of 2000. + */ + if (item.type() == ItemType.FIELD_ITEM) { + Field field = ((ItemField) item).field; + if (field.fieldType() == FieldTypes.MYSQL_TYPE_YEAR && field.fieldLength == 4) { + if (value < 70) + value += 100; + if (value <= 1900) + value += 1900; + } + } + /* Convert year to DATETIME packed format */ + return MyTime.year_to_longlong_datetime_packed(value); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncCase.java b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncCase.java new file mode 100644 index 000000000..68957e460 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncCase.java @@ -0,0 +1,236 @@ +package io.mycat.plan.common.item.function.operator.controlfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLCaseExpr; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.operator.cmpfunc.util.ArgComparator; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncCase extends ItemFunc { + + int first_expr_num, else_expr_num; + ItemResult cached_result_type, left_result_type; + int ncases; + ItemResult cmp_type; + FieldTypes cached_field_type; + + /** + * @param args + * @param first_expr_num + * -1 代表没有case表达式,否则代表case表达式在args中的index,case和else exp在队列的最后 + * @param else_expr_num + * else在args中的index + */ + public ItemFuncCase(List args, int ncases, int first_expr_num, int else_expr_num) { + super(args); + this.ncases = ncases; + this.first_expr_num = first_expr_num; + this.else_expr_num = else_expr_num; + this.cached_result_type = ItemResult.INT_RESULT; + this.left_result_type = ItemResult.INT_RESULT; + } + + @Override + public final String funcName() { + return "case"; + } + + @Override + public void fixLengthAndDec() { + List agg = new ArrayList(); + int nagg; + /* + * Aggregate all THEN and ELSE expression types and collations when + * string result + */ + + for (nagg = 0; nagg < ncases / 2; nagg++) + agg.add(args.get(nagg * 2 + 1)); + if (else_expr_num != -1) + agg.add(args.get(else_expr_num)); + cached_field_type = MySQLcom.agg_field_type(agg, 0, agg.size()); + cached_result_type = MySQLcom.agg_result_type(agg, 0, agg.size()); + if (first_expr_num != -1) + left_result_type = args.get(first_expr_num).resultType(); + } + + @Override + public ItemResult resultType() { + return cached_result_type; + } + + @Override + public FieldTypes fieldType() { + return cached_field_type; + } + + @Override + public BigDecimal valReal() { + Item item = findItem(); + if (item == null) { + nullValue = true; + return BigDecimal.ZERO; + } + BigDecimal res = item.valReal(); + nullValue = item.nullValue; + return res; + } + + @Override + public BigInteger valInt() { + Item item = findItem(); + if (item == null) { + nullValue = true; + return BigInteger.ZERO; + } + BigInteger res = item.valInt(); + nullValue = item.nullValue; + return res; + } + + @Override + public String valStr() { + switch (fieldType()) { + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return valStringFromDatetime(); + case MYSQL_TYPE_DATE: + return valStringFromDate(); + case MYSQL_TYPE_TIME: + return valStringFromTime(); + default: { + Item item = findItem(); + if (item != null) { + String res; + if ((res = item.valStr()) != null) { + nullValue = false; + return res; + } + } + } + } + nullValue = true; + return null; + } + + @Override + public BigDecimal valDecimal() { + Item item = findItem(); + if (item == null) { + nullValue = true; + return null; + } + BigDecimal res = item.valDecimal(); + nullValue = item.nullValue; + return res; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + Item item = findItem(); + if (item == null) + return (nullValue = true); + return (nullValue = item.getDate(ltime, fuzzydate)); + } + + @Override + public boolean getTime(MySQLTime ltime) { + Item item = findItem(); + if (item == null) + return (nullValue = true); + return (nullValue = item.getTime(ltime)); + } + + /** + * Find and return matching items for CASE or ELSE item if all compares are + * failed or NULL if ELSE item isn't defined. + * + * IMPLEMENTATION In order to do correct comparisons of the CASE expression + * (the expression between CASE and the first WHEN) with each WHEN + * expression several comparators are used. One for each result type. CASE + * expression can be evaluated up to # of different result types are used. + * To check whether the CASE expression already was evaluated for a + * particular result type a bit mapped variable value_added_map is used. + * Result types are mapped to it according to their int values i.e. + * STRING_RESULT is mapped to bit 0, REAL_RESULT to bit 1, so on. + * + * @retval NULL Nothing found and there is no ELSE expression defined + * @retval item Found item or ELSE item if defined and all comparisons are + * failed + */ + private Item findItem() { + if (first_expr_num == -1) { + for (int i = 0; i < ncases; i += 2) { + // No expression between CASE and the first WHEN + if (args.get(i).valBool()) + return args.get(i + 1); + continue; + } + } else { + /* Compare every WHEN argument with it and return the first match */ + Item leftCmpItem = args.get(first_expr_num); + if (leftCmpItem.isNull() || leftCmpItem.type() == ItemType.NULL_ITEM) { + return else_expr_num != -1 ? args.get(else_expr_num) : null; + } + for (int i = 0; i < ncases; i += 2) { + if (args.get(i).type() == ItemType.NULL_ITEM) + continue; + Item rightCmpItem = args.get(i); + ArgComparator cmptor = new ArgComparator(leftCmpItem, rightCmpItem); + cmptor.setCmpFunc(null, leftCmpItem, rightCmpItem, false); + if (cmptor.compare() == 0 && !rightCmpItem.nullValue) + return args.get(i + 1); + } + } + // No, WHEN clauses all missed, return ELSE expression + return else_expr_num != -1 ? args.get(else_expr_num) : null; + } + + // @Override + // protected Item cloneStruct() { + // List newArgList = cloneStructList(args); + // return new Item_func_case(newArgList, ncases, first_expr_num, + // else_expr_num); + // } + + @Override + public SQLExpr toExpression() { + SQLCaseExpr caseExpr = new SQLCaseExpr(); + List exprList = toExpressionList(args); + for (int index = 0; index < ncases;) { + SQLExpr exprCond = exprList.get(index++); + SQLExpr exprValue = exprList.get(index++); + + SQLCaseExpr.Item item = new SQLCaseExpr.Item(exprCond,exprValue); + caseExpr.addItem(item); + } + if (first_expr_num > 0) { + caseExpr.setValueExpr(exprList.get(first_expr_num)); + } + if (else_expr_num > 0) { + caseExpr.setElseExpr(exprList.get(else_expr_num)); + } + return caseExpr; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncCase(newArgs, ncases, first_expr_num, else_expr_num); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncIf.java b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncIf.java new file mode 100644 index 000000000..6e0f438cf --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncIf.java @@ -0,0 +1,157 @@ +package io.mycat.plan.common.item.function.operator.controlfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncIf extends ItemFunc { + ItemResult cached_result_type; + FieldTypes cached_field_type; + + public ItemFuncIf(List args) { + super(args); + } + + @Override + public final String funcName() { + return "if"; + } + + @Override + public final ItemResult resultType() { + return cached_result_type; + } + + @Override + public final FieldTypes fieldType() { + return cached_field_type; + } + + @Override + public int decimalPrecision() { + int arg1_prec = args.get(1).decimalIntPart(); + int arg2_prec = args.get(2).decimalIntPart(); + int precision = Math.max(arg1_prec, arg2_prec) + decimals; + return Math.min(precision, MySQLcom.DECIMAL_MAX_PRECISION); + } + + @Override + public void fixLengthAndDec() { + // Let IF(cond, expr, NULL) and IF(cond, NULL, expr) inherit type from + // expr. + if (args.get(1).type() == ItemType.NULL_ITEM) { + cache_type_info(args.get(2)); + maybeNull = true; + // If both arguments are NULL, make resulting type BINARY(0). + if (args.get(2).type() == ItemType.NULL_ITEM) + cached_field_type = FieldTypes.MYSQL_TYPE_STRING; + return; + } + if (args.get(2).type() == ItemType.NULL_ITEM) { + cache_type_info(args.get(1)); + maybeNull = true; + return; + } + cached_result_type = MySQLcom.agg_result_type(args, 1, 2); + cached_field_type = MySQLcom.agg_field_type(args, 1, 2); + maybeNull = args.get(1).maybeNull || args.get(2).maybeNull; + decimals = Math.max(args.get(1).decimals, args.get(2).decimals); + } + + @Override + public BigDecimal valReal() { + Item arg = args.get(0).valBool() ? args.get(1) : args.get(2); + BigDecimal value = arg.valReal(); + nullValue = arg.nullValue; + return value; + } + + @Override + public BigInteger valInt() { + Item arg = args.get(0).valBool() ? args.get(1) : args.get(2); + BigInteger value = arg.valInt(); + nullValue = arg.nullValue; + return value; + } + + @Override + public String valStr() { + switch (fieldType()) { + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return valStringFromDatetime(); + case MYSQL_TYPE_DATE: + return valStringFromDate(); + case MYSQL_TYPE_TIME: + return valStringFromTime(); + default: { + Item item = args.get(0).valBool() ? args.get(1) : args.get(2); + String res; + if ((res = item.valStr()) != null) { + nullValue = false; + return res; + } + } + } + nullValue = true; + return null; + } + + @Override + public BigDecimal valDecimal() { + Item arg = args.get(0).valBool() ? args.get(1) : args.get(2); + BigDecimal value = arg.valDecimal(); + nullValue = arg.nullValue; + return value; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + Item arg = args.get(0).valBool() ? args.get(1) : args.get(2); + return (nullValue = arg.getDate(ltime, fuzzydate)); + } + + @Override + public boolean getTime(MySQLTime ltime) { + Item arg = args.get(0).valBool() ? args.get(1) : args.get(2); + return (nullValue = arg.getTime(ltime)); + } + + private void cache_type_info(Item source) { + cached_field_type = source.fieldType(); + cached_result_type = source.resultType(); + decimals = source.decimals; + maxLength = source.maxLength; + maybeNull = source.maybeNull; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncIf(newArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncIfnull.java b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncIfnull.java new file mode 100644 index 000000000..300991253 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncIfnull.java @@ -0,0 +1,113 @@ +package io.mycat.plan.common.item.function.operator.controlfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncCoalesce; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncIfnull extends ItemFuncCoalesce { + protected boolean field_type_defined; + + public ItemFuncIfnull(List args) { + super(args); + } + + @Override + public final String funcName() { + return "ifnull"; + } + + @Override + public void fixLengthAndDec() { + hybrid_type = MySQLcom.agg_result_type(args, 0, 2); + cached_field_type = MySQLcom.agg_field_type(args, 0, 2); + maybeNull = args.get(1).maybeNull; + decimals = Math.max(args.get(0).decimals, args.get(1).decimals); + } + + @Override + public int decimalPrecision() { + int arg0_int_part = args.get(0).decimalIntPart(); + int arg1_int_part = args.get(1).decimalIntPart(); + int max_int_part = Math.max(arg0_int_part, arg1_int_part); + int precision = max_int_part + decimals; + return Math.min(precision, MySQLcom.DECIMAL_MAX_PRECISION); + } + + @Override + public BigDecimal realOp() { + BigDecimal value = args.get(0).valReal(); + if (!args.get(0).nullValue) { + nullValue = false; + return value; + } + value = args.get(1).valReal(); + if ((nullValue = args.get(1).nullValue)) + return BigDecimal.ZERO; + return value; + } + + @Override + public BigInteger intOp() { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) { + nullValue = false; + return value; + } + value = args.get(1).valInt(); + if ((nullValue = args.get(1).nullValue)) + return BigInteger.ZERO; + return value; + } + + @Override + public String strOp() { + String value = args.get(0).valStr(); + if (!args.get(0).nullValue) { + nullValue = false; + return value; + } + value = args.get(1).valStr(); + if ((nullValue = args.get(1).nullValue)) + return null; + return value; + } + + @Override + public BigDecimal decimalOp() { + BigDecimal value = args.get(0).valDecimal(); + if (!args.get(0).nullValue) { + nullValue = false; + return value; + } + value = args.get(1).valDecimal(); + if ((nullValue = args.get(1).nullValue)) + return null; + return value; + } + + @Override + public boolean dateOp(MySQLTime ltime, long fuzzydate) { + if (!args.get(0).getDate(ltime, fuzzydate)) + return (nullValue = false); + return (nullValue = args.get(1).getDate(ltime, fuzzydate)); + } + + @Override + public boolean timeOp(MySQLTime ltime) { + if (!args.get(0).getTime(ltime)) + return (nullValue = false); + return (nullValue = args.get(1).getTime(ltime)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncIfnull(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncNullif.java b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncNullif.java new file mode 100644 index 000000000..86b11de28 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/controlfunc/ItemFuncNullif.java @@ -0,0 +1,81 @@ +package io.mycat.plan.common.item.function.operator.controlfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncNullif extends ItemBoolFunc2 { + ItemResult cached_result_type; + + public ItemFuncNullif(Item a, Item b) { + super( a, b); + } + + @Override + public final String funcName(){ + return "nullif"; + } + + @Override + public BigDecimal valReal() { + BigDecimal value; + if (cmp.compare() == 0) { + nullValue = true; + return BigDecimal.ZERO; + } + value = args.get(0).valReal(); + nullValue = args.get(0).nullValue; + return value; + } + + @Override + public BigInteger valInt() { + BigInteger value; + if (cmp.compare() == 0) { + nullValue = true; + return BigInteger.ZERO; + } + value = args.get(0).valInt(); + nullValue = args.get(0).nullValue; + return value; + } + + @Override + public String valStr() { + String res; + if (cmp.compare() == 0) { + nullValue = true; + return null; + } + res = args.get(0).valStr(); + nullValue = args.get(0).nullValue; + return res; + } + + @Override + public BigDecimal valDecimal() { + BigDecimal value; + if (cmp.compare() == 0) { + nullValue = true; + return null; + } + value = args.get(0).valDecimal(); + nullValue = args.get(0).nullValue; + return value; + } + + @Override + public boolean isNull() { + return (nullValue = (cmp.compare() == 0 ? true : args.get(0).nullValue)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncNullif(realArgs.get(0), realArgs.get(1)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCond.java b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCond.java new file mode 100644 index 000000000..62f37de72 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCond.java @@ -0,0 +1,43 @@ +package io.mycat.plan.common.item.function.operator.logic; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + + +public abstract class ItemCond extends ItemBoolFunc { + protected boolean abort_on_null = true; + List list; + + public ItemCond(List args) { + super(args); + list = new ArrayList(); + list.addAll(args); + } + + public void add(Item item) { + list.add(item); + } + + public void add_at_head(Item item) { + list.add(0, item); + } + + public void add_at_head(List itemList) { + list.addAll(0, itemList); + } + + @Override + public ItemType type() { + return ItemType.COND_ITEM; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_LONGLONG; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCondAnd.java b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCondAnd.java new file mode 100644 index 000000000..8150a27eb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCondAnd.java @@ -0,0 +1,59 @@ +package io.mycat.plan.common.item.function.operator.logic; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemCondAnd extends ItemCond { + + public ItemCondAnd(List args) { + super(args); + } + + @Override + public final String funcName() { + return "and"; + } + + @Override + public Functype functype() { + return Functype.COND_AND_FUNC; + } + + @Override + public BigInteger valInt() { + nullValue = false; + for (Item item : list) { + if (!item.valBool()) { + if (abort_on_null || !(nullValue = item.nullValue)) + return BigInteger.ZERO; // return FALSE + } + } + return nullValue ? BigInteger.ZERO : BigInteger.ONE; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.BooleanAnd, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if(!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemCondAnd(newArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCondOr.java b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCondOr.java new file mode 100644 index 000000000..1ad93c8d6 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemCondOr.java @@ -0,0 +1,59 @@ +package io.mycat.plan.common.item.function.operator.logic; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + +public class ItemCondOr extends ItemCond { + + public ItemCondOr(List args) { + super(args); + } + + @Override + public final String funcName() { + return "or"; + } + + @Override + public Functype functype() { + return Functype.COND_OR_FUNC; + } + + @Override + public BigInteger valInt() { + for (Item item : list) { + if (item.valBool()) { + nullValue = false; + return BigInteger.ONE; + } + if (item.nullValue) + nullValue = true; + } + return BigInteger.ZERO; + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.BooleanOr, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if(!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemCondOr(newArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemFuncNot.java b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemFuncNot.java new file mode 100644 index 000000000..ace0c0dd7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemFuncNot.java @@ -0,0 +1,60 @@ +package io.mycat.plan.common.item.function.operator.logic; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLNotExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemBoolFunc; + + +/** + * This Item represents a X IS TRUE boolean predicate. + * + * @author chenzifei + * + */ +public class ItemFuncNot extends ItemBoolFunc { + + public ItemFuncNot(Item a) { + super(new ArrayList()); + args.add(a); + } + + @Override + public final String funcName() { + return "not"; + } + + @Override + public Functype functype() { + return Functype.NOT_FUNC; + } + + @Override + public BigInteger valInt() { + boolean value = args.get(0).valBool(); + this.nullValue = args.get(0).isNull(); + return ((!this.nullValue && value == false) ? BigInteger.ONE : BigInteger.ZERO); + } + + @Override + public SQLExpr toExpression() { + return new SQLNotExpr(args.get(0).toExpression()); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncNot(newArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemFuncXor.java b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemFuncXor.java new file mode 100644 index 000000000..82c8f8d6e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/operator/logic/ItemFuncXor.java @@ -0,0 +1,62 @@ +package io.mycat.plan.common.item.function.operator.logic; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; + + +public class ItemFuncXor extends ItemBoolFunc2 { + + public ItemFuncXor(Item a, Item b) { + super(a, b); + } + + @Override + public final String funcName() { + return "xor"; + } + + @Override + public Functype functype() { + return Functype.XOR_FUNC; + } + + @Override + public BigInteger valInt() { + int result = 0; + nullValue = false; + for (int i = 0; i < getArgCount(); i++) { + result ^= (args.get(i).valInt().compareTo(BigInteger.ZERO) != 0) ? 1 : 0; + if (args.get(i).nullValue) { + nullValue = true; + return BigInteger.ZERO; + } + } + return BigInteger.valueOf(result); + } + + @Override + public SQLExpr toExpression() { + SQLExpr left = args.get(0).toExpression(); + SQLExpr right = args.get(1).toExpression(); + return new SQLBinaryOpExpr(left, SQLBinaryOperator.BooleanXor, right); + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if(!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncXor(newArgs.get(0), newArgs.get(1)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemBoolFunc.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemBoolFunc.java new file mode 100644 index 000000000..8e23b7b8f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemBoolFunc.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.primary; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +/** + * 返回结果boolean的function算子的基类 + * + * + */ +public abstract class ItemBoolFunc extends ItemIntFunc { + + public ItemBoolFunc(Item a) { + this(new ArrayList()); + args.add(a); + } + + public ItemBoolFunc(Item a, Item b) { + this(new ArrayList()); + args.add(a); + args.add(b); + } + + public ItemBoolFunc(List args) { + super(args); + } + + @Override + public void fixLengthAndDec() { + decimals = 0; + maxLength = 1; + } + + @Override + public int decimalPrecision() { + return 1; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemDecFunc.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemDecFunc.java new file mode 100644 index 000000000..b766bedb5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemDecFunc.java @@ -0,0 +1,20 @@ +package io.mycat.plan.common.item.function.primary; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public abstract class ItemDecFunc extends ItemRealFunc { + + public ItemDecFunc(List args) { + super(args); + } + + @Override + public void fixLengthAndDec() { + decimals = NOT_FIXED_DEC; + maxLength = floatLength(decimals); + maybeNull = true; + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncAdditiveOp.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncAdditiveOp.java new file mode 100644 index 000000000..8a701bd0b --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncAdditiveOp.java @@ -0,0 +1,16 @@ +package io.mycat.plan.common.item.function.primary; + +import io.mycat.plan.common.item.Item; + +public abstract class ItemFuncAdditiveOp extends ItemNumOp { + + public ItemFuncAdditiveOp(Item a, Item b) { + super(a, b); + } + + @Override + public void result_precision() { + decimals = Math.max(args.get(0).decimals, args.get(1).decimals); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncBit.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncBit.java new file mode 100644 index 000000000..89cc42106 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncBit.java @@ -0,0 +1,15 @@ +package io.mycat.plan.common.item.function.primary; + +import io.mycat.plan.common.item.Item; + +public abstract class ItemFuncBit extends ItemIntFunc { + + public ItemFuncBit(Item a) { + super(a); + } + + public ItemFuncBit(Item a, Item b) { + super(a, b); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncNum1.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncNum1.java new file mode 100644 index 000000000..16c75baa0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncNum1.java @@ -0,0 +1,57 @@ +package io.mycat.plan.common.item.function.primary; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +/** + * function where type of result detected by first argument + * + * + */ +public abstract class ItemFuncNum1 extends ItemFuncNumhybrid { + + public ItemFuncNum1(List args) { + super(args); + } + + @Override + public void fixNumLengthAndDec() { + decimals = args.get(0).decimals; + this.maxLength = args.get(0).maxLength; + } + + @Override + public void findNumType() { + switch (hybrid_type = args.get(0).resultType()) { + case INT_RESULT: + break; + case STRING_RESULT: + case REAL_RESULT: + hybrid_type = ItemResult.REAL_RESULT; + maxLength = floatLength(decimals); + break; + case DECIMAL_RESULT: + break; + default: + assert (false); + } + } + + @Override + public String strOp() { + return null; + } + + @Override + public boolean dateOp(MySQLTime ltime, long flags) { + return false; + } + + @Override + public boolean timeOp(MySQLTime ltime) { + return false; + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncNumhybrid.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncNumhybrid.java new file mode 100644 index 000000000..6672e940a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncNumhybrid.java @@ -0,0 +1,281 @@ +package io.mycat.plan.common.item.function.primary; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +/** + * 类型不定的函数,参数是混合类型可能 + * + * + */ +public abstract class ItemFuncNumhybrid extends ItemFunc { + protected ItemResult hybrid_type; + + public ItemFuncNumhybrid(List args) { + super(args); + hybrid_type = ItemResult.REAL_RESULT; + } + + @Override + public ItemResult resultType() { + return hybrid_type; + } + + @Override + public void fixLengthAndDec() { + fixNumLengthAndDec(); + findNumType(); + } + + public void fixNumLengthAndDec() { + + } + + /* To be called from fix_length_and_dec */ + public abstract void findNumType(); + + @Override + public BigDecimal valReal() { + switch (hybrid_type) { + case DECIMAL_RESULT: { + BigDecimal val = decimalOp(); + if (val == null) + return BigDecimal.ZERO; // null is set + return val; + } + case INT_RESULT: { + BigInteger result = intOp(); + return new BigDecimal(result); + } + case REAL_RESULT: + return realOp(); + case STRING_RESULT: { + switch (fieldType()) { + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return valRealFromDecimal(); + default: + break; + } + String res = strOp(); + if (res == null) + return BigDecimal.ZERO; + else { + try { + return new BigDecimal(res); + } catch (Exception e) { + logger.error(res + " to BigDecimal error!", e); + } + } + } + default: + } + return BigDecimal.ZERO; + } + + @Override + public BigInteger valInt() { + switch (hybrid_type) { + case DECIMAL_RESULT: { + BigDecimal val = decimalOp(); + if (val == null) + return BigInteger.ZERO; + return val.toBigInteger(); + } + case INT_RESULT: + return intOp(); + case REAL_RESULT: + return realOp().toBigInteger(); + case STRING_RESULT: { + switch (fieldType()) { + case MYSQL_TYPE_DATE: + return new BigDecimal(valIntFromDate()).toBigInteger(); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return new BigDecimal(valIntFromDatetime()).toBigInteger(); + case MYSQL_TYPE_TIME: + return new BigDecimal(valIntFromTime()).toBigInteger(); + default: + break; + } + String res = strOp(); + if (res == null) + return BigInteger.ZERO; + try { + return new BigInteger(res); + } catch (Exception e) { + logger.error(res + " to BigInteger error!", e); + } + } + default: + } + return BigInteger.ZERO; + } + + @Override + public BigDecimal valDecimal() { + BigDecimal val = null; + switch (hybrid_type) { + case DECIMAL_RESULT: + val = decimalOp(); + break; + case INT_RESULT: { + BigInteger result = intOp(); + val = new BigDecimal(result); + break; + } + case REAL_RESULT: { + BigDecimal result = realOp(); + val = result; + break; + } + case STRING_RESULT: { + switch (fieldType()) { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return valDecimalFromDate(); + case MYSQL_TYPE_TIME: + return valDecimalFromTime(); + default: + break; + } + String res = strOp(); + if (res == null) + return null; + try { + val = new BigDecimal(res); + } catch (Exception e) { + val = null; + } + break; + } + case ROW_RESULT: + default: + } + return val; + } + + @Override + public String valStr() { + String str = null; + switch (hybrid_type) { + case DECIMAL_RESULT: { + BigDecimal val = decimalOp(); + if (val == null) + return null; // null is set + str = val.toString(); + break; + } + case INT_RESULT: { + BigInteger nr = intOp(); + if (nullValue) + return null; /* purecov: inspected */ + str = nr.toString(); + break; + } + case REAL_RESULT: { + BigDecimal nr = realOp(); + if (nullValue) + return null; /* purecov: inspected */ + str = nr.toString(); + break; + } + case STRING_RESULT: + switch (fieldType()) { + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return valStringFromDatetime(); + case MYSQL_TYPE_DATE: + return valStringFromDate(); + case MYSQL_TYPE_TIME: + return valStringFromTime(); + default: + break; + } + return strOp(); + default: + } + return str; + } + + @Override + public boolean getDate(MySQLTime ltime, long flags) { + assert (fixed == true); + switch (fieldType()) { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return dateOp(ltime, flags); + case MYSQL_TYPE_TIME: + return getDateFromTime(ltime); + default: + return getDateFromNonTemporal(ltime, flags); + } + } + + @Override + public boolean getTime(MySQLTime ltime) { + assert (fixed == true); + switch (fieldType()) { + case MYSQL_TYPE_TIME: + return timeOp(ltime); + case MYSQL_TYPE_DATE: + return getTimeFromDate(ltime); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return getTimeFromDatetime(ltime); + default: + return getTimeFromNonTemporal(ltime); + } + } + + /** + * @brief Performs the operation that this functions implements when the + * result type is INT. + * @return The result of the operation. + */ + public abstract BigInteger intOp(); + + /** + * @brief Performs the operation that this functions implements when the + * result type is REAL. + * @return The result of the operation. + */ + public abstract BigDecimal realOp(); + + /** + * @brief Performs the operation that this functions implements when the + * result type is DECIMAL. + * @param A + * pointer where the DECIMAL value will be allocated. + * @return - 0 If the result is NULL - The same pointer it was given, with + * the area initialized to the result of the operation. + */ + public abstract BigDecimal decimalOp(); + + /** + * @brief Performs the operation that this functions implements when the + * result type is a string type. + * @return The result of the operation. + */ + public abstract String strOp(); + + /** + * @brief Performs the operation that this functions implements when the + * result type is MYSQL_TYPE_DATE or MYSQL_TYPE_DATETIME. + * @return The result of the operation. + */ + public abstract boolean dateOp(MySQLTime ltime, long flags); + + public abstract boolean timeOp(MySQLTime ltime); + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncUnits.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncUnits.java new file mode 100644 index 000000000..749768f6c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemFuncUnits.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.primary; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public abstract class ItemFuncUnits extends ItemRealFunc { + + BigDecimal mul, add; + + public ItemFuncUnits(List args, double mul_arg, double add_arg) { + super(args); + mul = new BigDecimal(mul_arg); + add = new BigDecimal(add_arg); + } + + @Override + public BigDecimal valReal() { + BigDecimal value = args.get(0).valReal(); + if ((nullValue = args.get(0).nullValue)) + return BigDecimal.ZERO; + return value.multiply(mul).add(add); + } + + @Override + public void fixLengthAndDec() { + decimals = NOT_FIXED_DEC; + maxLength = floatLength(decimals); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemIntFunc.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemIntFunc.java new file mode 100644 index 000000000..81ecc16ef --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemIntFunc.java @@ -0,0 +1,64 @@ +package io.mycat.plan.common.item.function.primary; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemIntFunc extends ItemFunc { + + public ItemIntFunc(Item a) { + this(new ArrayList()); + args.add(a); + } + + public ItemIntFunc(Item a, Item b) { + this(new ArrayList()); + args.add(a); + args.add(b); + } + + public ItemIntFunc(List args) { + super(args); + } + + @Override + public BigDecimal valReal() { + BigInteger nr = valInt(); + return new BigDecimal(nr); + } + + @Override + public String valStr() { + BigInteger val = valInt(); + if (nullValue) + return null; + return val.toString(); + } + + @Override + public boolean getDate(MySQLTime ltime, long flags) { + return getDateFromInt(ltime, flags); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromInt(ltime); + } + + @Override + public ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public void fixLengthAndDec() { + + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemNumOp.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemNumOp.java new file mode 100644 index 000000000..511df57a1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemNumOp.java @@ -0,0 +1,69 @@ +package io.mycat.plan.common.item.function.primary; + +import java.util.ArrayList; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + +/** + * Base class for operations like '+', '-', '*' + * + * + */ +public abstract class ItemNumOp extends ItemFuncNumhybrid { + + public ItemNumOp(Item a, Item b) { + super(new ArrayList()); + args.add(a); + args.add(b); + } + + /** + * 计算结果总长度 + */ + public abstract void result_precision(); + + @Override + public void findNumType() { + ItemResult r0 = args.get(0).numericContextResultType(); + ItemResult r1 = args.get(1).numericContextResultType(); + + assert (r0 != ItemResult.STRING_RESULT && r1 != ItemResult.STRING_RESULT); + + if (r0 == ItemResult.REAL_RESULT || r1 == ItemResult.REAL_RESULT) { + /* + * Since DATE/TIME/DATETIME data types return + * INT_RESULT/DECIMAL_RESULT type codes, we should never get to here + * when both fields are temporal. + */ + assert (!args.get(0).isTemporal() || !args.get(1).isTemporal()); + countRealLength(); + maxLength = floatLength(decimals); + hybrid_type = ItemResult.REAL_RESULT; + } else if (r0 == ItemResult.DECIMAL_RESULT || r1 == ItemResult.DECIMAL_RESULT) { + hybrid_type = ItemResult.DECIMAL_RESULT; + result_precision(); + } else { + assert (r0 == ItemResult.INT_RESULT && r1 == ItemResult.INT_RESULT); + decimals = 0; + hybrid_type = ItemResult.INT_RESULT; + result_precision(); + } + } + + @Override + public String strOp() { + return null; + } + + @Override + public boolean dateOp(MySQLTime ltime, long flags) { + return false; + } + + @Override + public boolean timeOp(MySQLTime ltime) { + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/primary/ItemRealFunc.java b/src/main/java/io/mycat/plan/common/item/function/primary/ItemRealFunc.java new file mode 100644 index 000000000..38ec08868 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/primary/ItemRealFunc.java @@ -0,0 +1,59 @@ +package io.mycat.plan.common.item.function.primary; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemRealFunc extends ItemFunc { + + public ItemRealFunc(List args) { + super(args); + } + + @Override + public String valStr() { + BigDecimal nr = valReal(); + if (nullValue) + return null; + return nr.toString(); + } + + @Override + public BigInteger valInt() { + return valReal().toBigInteger(); + } + + @Override + public BigDecimal valDecimal() { + BigDecimal nr = valReal(); + if (nullValue) + return null; + return nr; + } + + @Override + public ItemResult resultType() { + return ItemResult.REAL_RESULT; + } + + @Override + public boolean getDate(MySQLTime ltime, long flags) { + return getDateFromReal(ltime, flags); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromReal(ltime); + } + + @Override + public void fixLengthAndDec() { + decimals = NOT_FIXED_DEC; + maxLength = floatLength(decimals); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncAscii.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncAscii.java new file mode 100644 index 000000000..d84d0b0fd --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncAscii.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncAscii extends ItemIntFunc { + + public ItemFuncAscii(List args) { + super(args); + } + + @Override + public final String funcName() { + return "ascii"; + } + + @Override + public BigInteger valInt() { + String s = args.get(0).valStr(); + if (args.get(0).isNull()) { + this.nullValue = true; + return BigInteger.ZERO; + } else { + if (s.length() == 0) { + return BigInteger.ZERO; + } else { + return BigInteger.valueOf((int) s.charAt(0)); + } + } + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncBitLength.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncBitLength.java new file mode 100644 index 000000000..bb26599f9 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncBitLength.java @@ -0,0 +1,37 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncBitLength extends ItemIntFunc { + + public ItemFuncBitLength(List args) { + super(args); + } + + @Override + public final String funcName() { + return "bit_length"; + } + + @Override + public BigInteger valInt() { + String res = args.get(0).valStr(); + if (res == null) { + nullValue = true; + return null; /* purecov: inspected */ + } + nullValue = false; + return BigInteger.valueOf(res.length() * 8); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncBitLength(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncChar.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncChar.java new file mode 100644 index 000000000..ae8d85580 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncChar.java @@ -0,0 +1,80 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.backend.mysql.CharsetUtil; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemFuncChar extends ItemStrFunc { + private String mysqlCharset; + private String javaCharset; + + public ItemFuncChar(List args) { + this(args, null); + } + + public ItemFuncChar(List args, String charset) { + super(args); + this.mysqlCharset = charset; + } + + @Override + public final String funcName() { + return "CHAR"; + } + + @Override + public void fixLengthAndDec() { + javaCharset = mysqlCharset == null ? null : CharsetUtil.getJavaCharset(mysqlCharset); + maxLength = args.size() * 4; + } + + @Override + public String valStr() { + byte[] b = new byte[args.size()]; + int count = 0; + for (Item arg : args) { + if (!arg.isNull()) { + byte c = (byte) arg.valInt().intValue(); + b[count++] = c; + } + } + b[count++] = 0; + try { + if (javaCharset == null) + return new String(b); + else + return new String(b, javaCharset); + } catch (UnsupportedEncodingException e) { + nullValue = true; + logger.warn("char() charset exception:", e); + return null; + } + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncChar(newArgs, mysqlCharset); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncCharLength.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncCharLength.java new file mode 100644 index 000000000..1bcc56271 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncCharLength.java @@ -0,0 +1,38 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncCharLength extends ItemIntFunc { + + public ItemFuncCharLength(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "char_length"; + } + + @Override + public BigInteger valInt() { + String s = args.get(0).valStr(); + if (s == null) { + this.nullValue = true; + return BigInteger.ZERO; + } else { + nullValue = false; + return BigInteger.valueOf(s.toCharArray().length); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncCharLength(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncConcat.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncConcat.java new file mode 100644 index 000000000..f0302fb1c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncConcat.java @@ -0,0 +1,38 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncConcat extends ItemStrFunc { + + public ItemFuncConcat(List args) { + super(args); + } + + @Override + public final String funcName() { + return "concat"; + } + + @Override + public String valStr() { + StringBuilder sb = new StringBuilder(); + for (Item arg : args) { + if (arg.isNull()) { + this.nullValue = true; + return EMPTY; + } + String s = arg.valStr(); + sb.append(s); + } + return sb.toString(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncConcat(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncConcatWs.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncConcatWs.java new file mode 100644 index 000000000..8df705204 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncConcatWs.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncConcatWs extends ItemStrFunc { + + public ItemFuncConcatWs(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "concat_ws"; + } + + @Override + public String valStr() { + StringBuilder sb = new StringBuilder(); + String sep = args.get(0).valStr(); + for (int i = 1; i < args.size(); i++) { + Item arg = args.get(i); + if (!arg.isNull()) { + String s = arg.valStr(); + sb.append(s); + if (i < args.size() - 1) { + sb.append(sep); + } + } + } + return sb.toString(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncConcatWs(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncElt.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncElt.java new file mode 100644 index 000000000..c9d6099f6 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncElt.java @@ -0,0 +1,40 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncElt extends ItemStrFunc { + + public ItemFuncElt(List args) { + super(args); + } + + @Override + public final String funcName() { + return "elt"; + } + + @Override + public String valStr() { + Long l = args.get(0).valInt().longValue(); + if (l < 1 || l >= args.size()) { + this.nullValue = true; + return EMPTY; + } + Item arg = args.get(l.intValue()); + if (arg.isNull()) { + this.nullValue = true; + return EMPTY; + } else { + return arg.valStr(); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncElt(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncField.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncField.java new file mode 100644 index 000000000..84a65baee --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncField.java @@ -0,0 +1,78 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + +public class ItemFuncField extends ItemIntFunc { + + ItemResult cmp_type; + + public ItemFuncField(List args) { + super(args); + } + + @Override + public final String funcName() { + return "field"; + } + + @Override + public BigInteger valInt() { + if (cmp_type == ItemResult.STRING_RESULT) { + String field; + if ((field = args.get(0).valStr()) == null) + return BigInteger.ZERO; + for (int i = 1; i < args.size(); i++) { + String tmp_value = args.get(i).valStr(); + if (tmp_value != null && field.compareTo(tmp_value) == 0) + return BigInteger.valueOf(i); + } + } else if (cmp_type == ItemResult.INT_RESULT) { + long val = args.get(0).valInt().longValue(); + if (args.get(0).nullValue) + return BigInteger.ZERO; + for (int i = 1; i < getArgCount(); i++) { + if (val == args.get(i).valInt().longValue() && !args.get(i).nullValue) + return BigInteger.valueOf(i); + } + } else if (cmp_type == ItemResult.DECIMAL_RESULT) { + BigDecimal dec = args.get(0).valDecimal(); + if (args.get(0).nullValue) + return BigInteger.ZERO; + for (int i = 1; i < getArgCount(); i++) { + BigDecimal dec_arg = args.get(i).valDecimal(); + if (!args.get(i).nullValue && dec_arg.compareTo(dec) == 0) + return BigInteger.valueOf(i); + } + } else { + double val = args.get(0).valReal().doubleValue(); + if (args.get(0).nullValue) + return BigInteger.ZERO; + for (int i = 1; i < getArgCount(); i++) { + if (val == args.get(i).valReal().doubleValue() && !args.get(i).nullValue) + return BigInteger.valueOf(i); + } + } + return BigInteger.ZERO; + } + + @Override + public void fixLengthAndDec() { + maybeNull = false; + maxLength = 3; + cmp_type = args.get(0).resultType(); + for (int i = 1; i < args.size(); i++) + cmp_type = MySQLcom.item_cmp_type(cmp_type, args.get(i).resultType()); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncField(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncFindInSet.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncFindInSet.java new file mode 100644 index 000000000..9385eb760 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncFindInSet.java @@ -0,0 +1,47 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncFindInSet extends ItemIntFunc { + + public ItemFuncFindInSet(List args) { + super(args); + } + + @Override + public final String funcName() { + return "find_in_set"; + } + + @Override + public BigInteger valInt() { + long index = 0; + String s = args.get(0).valStr(); + String source = args.get(1).valStr(); + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return BigInteger.ZERO; + } + if (source.isEmpty()) + return BigInteger.ZERO; + String[] ss = source.split(","); + for (int i = 0; i < ss.length; ++i) { + if (ss[i].equals(s)) { + index = i + 1; + break; + } + } + return BigInteger.valueOf(index); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncFindInSet(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncFormat.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncFormat.java new file mode 100644 index 000000000..e8de50b0a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncFormat.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; + +import io.mycat.plan.common.item.Item; + +public class ItemFuncFormat extends ItemStrFunc { + + public ItemFuncFormat(List args) { + super(args); + } + + @Override + public final String funcName() { + return "FORMAT"; + } + + @Override + public String valStr() { + BigDecimal bd = args.get(0).valDecimal(); + int pl = args.get(1).valInt().intValue(); + if (pl < 0) + pl = 0; + String local = "en_US"; + if (args.size() == 3) + local = args.get(2).valStr(); + Locale loc = new Locale(local); + NumberFormat f = DecimalFormat.getInstance(loc); + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return EMPTY; + } + BigDecimal bdnew = bd.setScale(pl, RoundingMode.HALF_UP); + return f.format(bdnew); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncHex.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncHex.java new file mode 100644 index 000000000..18233fd97 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncHex.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncHex extends ItemStrFunc { + + public ItemFuncHex(List args) { + super(args); + } + + @Override + public final String funcName() { + return "hex"; + } + + @Override + public String valStr() { + Long l = args.get(0).valInt().longValue(); + if (args.get(0).isNull()) { + this.nullValue = true; + return EMPTY; + } + return Long.toBinaryString(l); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncHex(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncInsert.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncInsert.java new file mode 100644 index 000000000..a5bbd2def --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncInsert.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncInsert extends ItemStrFunc { + + public ItemFuncInsert(List args) { + super(args); + } + + @Override + public final String funcName() { + return "insert"; + } + + @Override + public String valStr() { + String orgStr = args.get(0).valStr(); + long pos = args.get(1).valInt().longValue(); + long len = args.get(2).valInt().longValue(); + String newStr = args.get(3).valStr(); + if (args.get(0).isNull() || args.get(1).isNull() || args.get(2).isNull() || args.get(3).isNull()) { + this.nullValue = true; + return EMPTY; + } + long orgLen = orgStr.length(); + if (pos <= 0 || pos > orgLen) + return orgStr; + StringBuilder sb = new StringBuilder(orgStr); + if (len < 0 || pos + len > orgLen) + return sb.replace((int) pos - 1, (int) orgLen - 1, newStr).toString(); + else + return sb.replace((int) (pos - 1), (int) (pos - 1 + len), newStr).toString(); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncInstr.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncInstr.java new file mode 100644 index 000000000..c67de3eef --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncInstr.java @@ -0,0 +1,29 @@ +/** + * + */ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + +public class ItemFuncInstr extends ItemFuncLocate { + + /** + * @param args + */ + public ItemFuncInstr(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "instr"; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncInstr(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLeft.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLeft.java new file mode 100644 index 000000000..f7b6931ec --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLeft.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncLeft extends ItemStrFunc { + + public ItemFuncLeft(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "left"; + } + + @Override + public String valStr() { + String orgStr = args.get(0).valStr(); + long len = args.get(1).valInt().longValue(); + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return EMPTY; + } + int size = orgStr.length(); + if (len >= size) + return orgStr; + return orgStr.substring(0, (int) len); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLength.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLength.java new file mode 100644 index 000000000..1308ea3d1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLength.java @@ -0,0 +1,37 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncLength extends ItemIntFunc { + + public ItemFuncLength(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "length"; + } + + @Override + public BigInteger valInt() { + String res = args.get(0).valStr(); + if (res == null) { + nullValue = true; + return null; /* purecov: inspected */ + } + nullValue = false; + return BigInteger.valueOf(res.length()); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLength(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLocate.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLocate.java new file mode 100644 index 000000000..0b9d3d0aa --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLocate.java @@ -0,0 +1,51 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncLocate extends ItemIntFunc { + + public ItemFuncLocate(List args) { + super(args); + } + + @Override + public String funcName(){ + return "locate"; + } + + @Override + public BigInteger valInt() { + String sub = args.get(0).valStr(); + String str = args.get(1).valStr(); + int pos = -1; + if (args.size() == 3) { + pos = (int) args.get(2).valInt().intValue(); + } + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return BigInteger.ZERO; + } + if (pos <= 1) { + return BigInteger.valueOf(str.indexOf(sub) + 1); + } else { + String posStr = str.substring(pos - 1); + int find = posStr.indexOf(sub); + if (find < 0) + return BigInteger.ZERO; + else + return BigInteger.valueOf(pos + find); + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLocate(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLower.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLower.java new file mode 100644 index 000000000..2bdc9f48e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLower.java @@ -0,0 +1,32 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncLower extends ItemStrFunc { + + public ItemFuncLower(List args) { + super(args); + } + + @Override + public final String funcName() { + return "lower"; + } + + @Override + public String valStr() { + String orgStr = args.get(0).valStr(); + if (this.nullValue = args.get(0).isNull()) + return EMPTY; + return orgStr.toLowerCase(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLower(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLpad.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLpad.java new file mode 100644 index 000000000..c78ca8b58 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncLpad.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncLpad extends ItemStrFunc { + + public ItemFuncLpad(List args) { + super(args); + } + + @Override + public final String funcName() { + return "lpad"; + } + + @Override + public String valStr() { + String str = args.get(0).valStr(); + int count = args.get(1).valInt().intValue(); + String pad = args.get(2).valStr(); + if (args.get(0).isNull() || args.get(1).isNull() || count < 0 || args.get(2).isNull()) { + this.nullValue = true; + return EMPTY; + } + if (count < str.length()) { + return str.substring(0, (int) count); + } + int padLen = pad.length(); + if (padLen <= 0) { + this.nullValue = true; + return EMPTY; + } + StringBuilder sb = new StringBuilder(); + count -= str.length(); + while (count >= padLen) { + sb.append(pad); + count -= padLen; + } + if (count > 0) { + sb.append(pad.substring(0, (int) count)); + } + sb.append(str); + return sb.toString(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLpad(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncMakeSet.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncMakeSet.java new file mode 100644 index 000000000..446fbb1d7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncMakeSet.java @@ -0,0 +1,23 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncMakeSet extends ItemStrFunc { + + public ItemFuncMakeSet(List args) { + super(args); + } + + @Override + public final String funcName() { + return "make_set"; + } + + @Override + public String valStr() { + throw new RuntimeException("not supportted yet!"); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncOrd.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncOrd.java new file mode 100644 index 000000000..a10de37cf --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncOrd.java @@ -0,0 +1,46 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; + + +public class ItemFuncOrd extends ItemIntFunc { + + public ItemFuncOrd(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "ord"; + } + + + @Override + public BigInteger valInt() { + String sub = args.get(0).valStr(); + String str = args.get(1).valStr(); + int pos = -1; + if (args.size() == 3) { + pos = (int) args.get(2).valInt().intValue(); + } + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return BigInteger.ZERO; + } + if (pos <= 1) { + return BigInteger.valueOf(str.indexOf(sub) + 1); + } else { + String posStr = str.substring(pos - 1); + int find = posStr.indexOf(sub); + if (find < 0) + return BigInteger.ZERO; + else + return BigInteger.valueOf(pos + find); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncQuote.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncQuote.java new file mode 100644 index 000000000..ae3624d22 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncQuote.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncQuote extends ItemStrFunc { + + public ItemFuncQuote(List args) { + super(args); + } + + @Override + public final String funcName() { + return "quote"; + } + + @Override + public String valStr() { + String old = args.get(0).valStr(); + if (args.get(0).isNull()) { + this.nullValue = true; + return EMPTY; + } + StringBuilder newSb = new StringBuilder(); + for (char c : old.toCharArray()) { + switch (c) { + case 0: + case '\032': + case '\'': + case '\\': + newSb.append('\\').append(c); + break; + default: + newSb.append(c); + break; + } + } + return newSb.toString(); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRepeat.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRepeat.java new file mode 100644 index 000000000..6c7c773f8 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRepeat.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncRepeat extends ItemStrFunc { + + public ItemFuncRepeat(List args) { + super(args); + } + + @Override + public final String funcName() { + return "repeat"; + } + + @Override + public String valStr() { + String old = args.get(0).valStr(); + int count = args.get(1).valInt().intValue(); + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return EMPTY; + } + if (count < 1) + return EMPTY; + StringBuilder sb = new StringBuilder(); + for (long l = 0; l < count; l++) { + sb.append(old); + } + return sb.toString(); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncReplace.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncReplace.java new file mode 100644 index 000000000..d2c8b3e1c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncReplace.java @@ -0,0 +1,30 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncReplace extends ItemStrFunc { + + public ItemFuncReplace(List args) { + super(args); + } + + @Override + public final String funcName() { + return "replace"; + } + + @Override + public String valStr() { + String old = args.get(0).valStr(); + String from = args.get(1).valStr(); + String to = args.get(2).valStr(); + if (args.get(0).isNull() || args.get(1).isNull() || args.get(2).isNull()) { + this.nullValue = true; + return EMPTY; + } + return old.replace(from, to); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncReverse.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncReverse.java new file mode 100644 index 000000000..08d718699 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncReverse.java @@ -0,0 +1,35 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncReverse extends ItemStrFunc { + + public ItemFuncReverse(List args) { + super(args); + } + + @Override + public final String funcName() { + return "reverse"; + } + + @Override + public String valStr() { + String old = args.get(0).valStr(); + if (args.get(0).isNull()) { + this.nullValue = true; + return EMPTY; + } + StringBuilder sb = new StringBuilder(old); + return sb.reverse().toString(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncReverse(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRight.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRight.java new file mode 100644 index 000000000..e8f19c8b5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRight.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemFuncRight extends ItemStrFunc { + + public ItemFuncRight(List args) { + super(args); + } + + @Override + public final String funcName() { + return "right"; + } + + @Override + public String valStr() { + String orgStr = args.get(0).valStr(); + long len = args.get(1).valInt().longValue(); + if (args.get(0).isNull() || args.get(1).isNull()) { + this.nullValue = true; + return EMPTY; + } + int size = orgStr.length(); + if (len >= size) + return orgStr; + return orgStr.substring(size - (int) len, size); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRpad.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRpad.java new file mode 100644 index 000000000..ab29d42b3 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncRpad.java @@ -0,0 +1,53 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncRpad extends ItemStrFunc { + + public ItemFuncRpad(List args) { + super(args); + } + + @Override + public final String funcName() { + return "rpad"; + } + + @Override + public String valStr() { + String str = args.get(0).valStr(); + int count = args.get(1).valInt().intValue(); + String pad = args.get(2).valStr(); + if (args.get(0).isNull() || args.get(1).isNull() || count < 0 || args.get(2).isNull()) { + this.nullValue = true; + return EMPTY; + } + if (count < str.length()) { + return str.substring(0, (int) count); + } + int padLen = pad.length(); + if (padLen <= 0) { + this.nullValue = true; + return EMPTY; + } + StringBuilder sb = new StringBuilder(str); + count -= str.length(); + while (count >= padLen) { + sb.append(pad); + count -= padLen; + } + if (count > 0) { + sb.append(pad.substring(0, (int) count)); + } + return sb.toString(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncRpad(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSoundex.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSoundex.java new file mode 100644 index 000000000..ac6c4f25b --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSoundex.java @@ -0,0 +1,127 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncSoundex extends ItemStrFunc { + + public ItemFuncSoundex(Item a) { + super(a); + } + + @Override + public final String funcName() { + return "SOUNDEX"; + } + + @Override + public String valStr() { + String str = args.get(0).valStr(); + if (this.nullValue = args.get(0).isNull()) + return EMPTY; + return ItemFuncSoundex.soundex(str); + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + method.addParameter(args.get(0).toExpression()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncSoundex(newArgs.get(0)); + } + + public static String soundex(String s) { + char[] x = s.toUpperCase().toCharArray(); + char firstLetter = x[0]; + + // convert letters to numeric code + for (int i = 0; i < x.length; i++) { + switch (x[i]) { + case 'B': + case 'F': + case 'P': + case 'V': { + x[i] = '1'; + break; + } + + case 'C': + case 'G': + case 'J': + case 'K': + case 'Q': + case 'S': + case 'X': + case 'Z': { + x[i] = '2'; + break; + } + + case 'D': + case 'T': { + x[i] = '3'; + break; + } + + case 'L': { + x[i] = '4'; + break; + } + + case 'M': + case 'N': { + x[i] = '5'; + break; + } + + case 'R': { + x[i] = '6'; + break; + } + + default: { + x[i] = '0'; + break; + } + } + } + + // remove duplicates + String output = "" + firstLetter; + for (int i = 1; i < x.length; i++) + if (x[i] != x[i - 1] && x[i] != '0') + output += x[i]; + + // pad with 0's or truncate + if (output.length() < 4) { + output += "0000"; + return output.substring(0, 4); + } + return output; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSoundex(realArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSpace.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSpace.java new file mode 100644 index 000000000..ce707fdde --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSpace.java @@ -0,0 +1,37 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + +public class ItemFuncSpace extends ItemStrFunc { + + public ItemFuncSpace(List args) { + super(args); + } + + @Override + public final String funcName() { + return "SPACE"; + } + + @Override + public String valStr() { + int count = args.get(0).valInt().intValue(); + if (this.nullValue = args.get(0).isNull()) + return EMPTY; + if (count <= 0) + return EMPTY; + StringBuilder sb = new StringBuilder(); + for (long i = 0; i < count; i++) { + sb.append(' '); + } + return sb.toString(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSpace(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSubstr.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSubstr.java new file mode 100644 index 000000000..9656eba98 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSubstr.java @@ -0,0 +1,36 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + +public class ItemFuncSubstr extends ItemStrFunc { + + public ItemFuncSubstr(List args) { + super(args); + } + + @Override + public final String funcName() { + return "SUBSTRING"; + } + + @Override + public String valStr() { + String str = args.get(0).valStr(); + long start = args.get(1).valInt().longValue(); + long length = args.size() == 3 ? args.get(2).valInt().longValue() : Long.MAX_VALUE; + long tmp_length; + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull() + || (args.size() == 3 && args.get(2).isNull()))) + return EMPTY; + if (args.size() == 3 && length <= 0 && (length <= 0)) + return EMPTY; + start = (start < 0) ? str.length() + start : start - 1; + tmp_length = str.length() - start; + length = Math.min(length, tmp_length); + if (start == 0 && str.length() == length) + return EMPTY; + return str.substring((int) start, (int) (start + length)); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSubstrIndex.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSubstrIndex.java new file mode 100644 index 000000000..673498986 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncSubstrIndex.java @@ -0,0 +1,65 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + +public class ItemFuncSubstrIndex extends ItemStrFunc { + + public ItemFuncSubstrIndex(List args) { + super(args); + } + + @Override + public final String funcName() { + return "SUBSTRING_INDEX"; + } + + @Override + public String valStr() { + String str = args.get(0).valStr(); + String delim = args.get(1).valStr(); + int count = args.get(2).valInt().intValue(); + if (this.nullValue = (args.get(0).isNull() || args.get(1).isNull() || args.get(2).isNull())) + return EMPTY; + if (str.length() == 0 || delim.length() == 0 || count == 0) + return EMPTY; + + int delimLen = delim.length(); + + List subs = new ArrayList(); + while (true) { + int index = str.indexOf(delim); + if (index < 0) { + subs.add(str); + break; + } + if (index == 0) { + str = str.substring(index + delimLen); + } else { + subs.add(str.substring(0, index)); + str = str.substring(index + delimLen); + } + } + String ret = EMPTY; + if (count > 0) { + int trueCount = Math.min(subs.size(), count); + for (int i = 0; i < trueCount; i++) + ret += subs.get(i) + delim; + } else { + int trueCount = Math.min(subs.size(), -count); + for (int i = subs.size() - trueCount; i < subs.size(); i++) + ret += subs.get(i) + delim; + } + // 得删除最后一个delim + int lastDelim = ret.lastIndexOf(delim); + return ret.substring(0, lastDelim); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSubstrIndex(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncTrim.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncTrim.java new file mode 100644 index 000000000..7bec0c6a0 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncTrim.java @@ -0,0 +1,137 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.ItemFuncKeyWord; + + +public class ItemFuncTrim extends ItemStrFunc { + public enum TRIM_TYPE_ENUM{ + DEFAULT,BOTH,LEADING,TRAILING,LTRIM,RTRIM + } + private TRIM_TYPE_ENUM mTrimMode; + private boolean mTrimLeading; + private boolean mTrimTrailing; + + public ItemFuncTrim(Item a, Item b, TRIM_TYPE_ENUM tm) { + super(a, b); + this.mTrimMode = tm; + mTrimLeading = trimLeading(); + mTrimTrailing = trimTrailing(); + } + + public ItemFuncTrim(Item a, TRIM_TYPE_ENUM tm) { + super(a); + this.mTrimMode = tm; + mTrimLeading = trimLeading(); + mTrimTrailing = trimTrailing(); + } + + private final boolean trimLeading() { + return mTrimMode == TRIM_TYPE_ENUM.DEFAULT || mTrimMode == TRIM_TYPE_ENUM.BOTH || mTrimMode == TRIM_TYPE_ENUM.LEADING + || mTrimMode == TRIM_TYPE_ENUM.LTRIM; + } + + private final boolean trimTrailing() { + return mTrimMode == TRIM_TYPE_ENUM.DEFAULT || mTrimMode == TRIM_TYPE_ENUM.BOTH || mTrimMode == TRIM_TYPE_ENUM.TRAILING + || mTrimMode == TRIM_TYPE_ENUM.RTRIM; + } + + @Override + public final String funcName() { + switch (mTrimMode) { + case DEFAULT: + return "trim"; + case BOTH: + return "trim"; + case LEADING: + return "ltrim"; + case TRAILING: + return "rtrim"; + case LTRIM: + return "ltrim"; + case RTRIM: + return "rtrim"; + } + return null; + } + + @Override + public void fixLengthAndDec() { + + } + + @Override + public String valStr() { + String toTrim = args.get(0).valStr(); + if (nullValue = args.get(0).nullValue) + return null; + String remove = null; + if (getArgCount() == 2) { + remove = args.get(1).valStr(); + if (nullValue = args.get(1).nullValue) + return null; + } + String ret = null; + if (mTrimLeading) + ret = StringUtils.stripStart(toTrim, remove); + if (mTrimTrailing) + ret = StringUtils.stripEnd(toTrim, remove); + return ret; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(); + switch (mTrimMode) { + case LTRIM: + method.setMethodName("LTRIM"); + method.addParameter(args.get(0).toExpression()); + break; + case RTRIM: + method.setMethodName("RTRIM"); + method.addParameter(args.get(0).toExpression()); + break; + default: + method.setMethodName("TRIM"); + method.addParameter(args.get(0).toExpression()); + if (this.getArgCount() > 1){ + method.putAttribute(ItemFuncKeyWord.FROM, args.get(1).toExpression()); + } + if(mTrimMode!=TRIM_TYPE_ENUM.DEFAULT){ + method.putAttribute(ItemFuncKeyWord.TRIM_TYPE, mTrimMode.toString()); + } + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + if (getArgCount() == 2) + return new ItemFuncTrim(newArgs.get(0), newArgs.get(1), mTrimMode); + else + return new ItemFuncTrim(newArgs.get(0), mTrimMode); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + if (getArgCount() == 2) + return new ItemFuncTrim(realArgs.get(0), realArgs.get(1), mTrimMode); + else + return new ItemFuncTrim(realArgs.get(0), mTrimMode); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncUnhex.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncUnhex.java new file mode 100644 index 000000000..d033cdb7e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncUnhex.java @@ -0,0 +1,80 @@ +/** + * + */ +package io.mycat.plan.common.item.function.strfunc; + +import java.io.ByteArrayOutputStream; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + +public class ItemFuncUnhex extends ItemStrFunc { + + public ItemFuncUnhex(Item a) { + super(a); + } + + @Override + public final String funcName() { + return "unhex"; + } + + @Override + public void fixLengthAndDec() { + decimals = 0; + maxLength = (1 + args.get(0).maxLength) / 2; + } + + @Override + public String valStr() { + nullValue = true; + String hexString = args.get(0).valStr(); + if (args.get(0).nullValue) + return null; + ByteArrayOutputStream baos = new ByteArrayOutputStream(hexString.length() / 2); + for (int index = 0; index < hexString.length(); index += 2) { + int hexChar = 0; + int bValue = 0; + bValue = ((hexChar = hexchar_to_int(hexString.charAt(index))) << 4); + if (hexChar == -1) + return null; + bValue |= (hexChar = hexchar_to_int(hexString.charAt(index + 1))); + if (hexChar == -1) + return null; + baos.write(bValue); + } + nullValue = false; + return baos.toString(); + } + + /* + * 将16进制数字解码成字符串,适用于所有字符(包括中文) + */ + public static String decode(String hexString) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(hexString.length() / 2); + // 将每2位16进制整数组装成一个字节 + for (int i = 0; i < hexString.length(); i += 2) + baos.write((hexString.indexOf(hexString.charAt(i)) << 4 | hexString.indexOf(hexString.charAt(i + 1)))); + return new String(baos.toByteArray()); + } + + /** + * convert a hex digit into number. + */ + + public static int hexchar_to_int(char c) { + if (c <= '9' && c >= '0') + return c - '0'; + c |= 32; + if (c <= 'f' && c >= 'a') + return c - 'a' + 10; + return -1; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncUnhex(realArgs.get(0)); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncUpper.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncUpper.java new file mode 100644 index 000000000..155d533be --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemFuncUpper.java @@ -0,0 +1,32 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +public class ItemFuncUpper extends ItemStrFunc { + + public ItemFuncUpper(List args) { + super(args); + } + + @Override + public final String funcName() { + return "upper"; + } + + @Override + public String valStr() { + String orgStr = args.get(0).valStr(); + if (this.nullValue = args.get(0).isNull()) + return EMPTY; + return orgStr.toUpperCase(); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncUpper(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemStrFunc.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemStrFunc.java new file mode 100644 index 000000000..6bbc8f29f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemStrFunc.java @@ -0,0 +1,93 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemStrFunc extends ItemFunc { + + protected static final String EMPTY = new String(""); + + public ItemStrFunc() { + this(new ArrayList()); + } + + public ItemStrFunc(Item a) { + this(new ArrayList()); + args.add(a); + } + + public ItemStrFunc(Item a, Item b) { + this(new ArrayList()); + args.add(a); + args.add(b); + } + + public ItemStrFunc(List args) { + super(args); + decimals = NOT_FIXED_DEC; + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public void fixLengthAndDec() { + + } + + @Override + public BigDecimal valReal() { + String res = valStr(); + if (res == null) + return BigDecimal.ZERO; + try { + return new BigDecimal(res); + } catch (Exception e) { + return BigDecimal.ZERO; + } + } + + @Override + public BigInteger valInt() { + String res = valStr(); + if (res == null) + return BigInteger.ZERO; + try { + return new BigInteger(res); + } catch (Exception e) { + return BigInteger.ZERO; + } + } + + @Override + public BigDecimal valDecimal() { + String res = valStr(); + if (res == null) + return null; + try { + return new BigDecimal(res); + } catch (Exception e) { + return null; + } + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromString(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromString(ltime); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemfuncLoadFile.java b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemfuncLoadFile.java new file mode 100644 index 000000000..0fdca0c83 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/strfunc/ItemfuncLoadFile.java @@ -0,0 +1,23 @@ +package io.mycat.plan.common.item.function.strfunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; + + +public class ItemfuncLoadFile extends ItemStrFunc { + + public ItemfuncLoadFile(List args) { + super(args); + } + + @Override + public final String funcName() { + return "load_file"; + } + + @Override + public String valStr() { + throw new RuntimeException("LOAD_FILE function is not realized"); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/Aggregator.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/Aggregator.java new file mode 100644 index 000000000..beaf4987c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/Aggregator.java @@ -0,0 +1,76 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; + +import io.mycat.net.mysql.RowDataPacket; + + +public abstract class Aggregator { + + /* + * All members are protected as this class is not usable outside of an + * Item_sum descendant. + */ + /* the aggregate function class to act on */ + protected ItemSum item_sum; + + /** + * When feeding back the data in endup() from Unique/temp table back to + * Item_sum::add(List rowBytes) methods we must read the data from + * Unique (and not recalculate the functions that are given as arguments to + * the aggregate function. This flag is to tell the add(List + * rowBytes) methods to take the data from the Unique instead by + * calling the relevant val_..() method + */ + protected boolean use_distinct_values; + + public Aggregator(ItemSum arg) { + item_sum = (arg); + use_distinct_values = (false); + } + + public enum AggregatorType { + SIMPLE_AGGREGATOR, DISTINCT_AGGREGATOR + }; + + public abstract AggregatorType Aggrtype(); + + /** + * Called before adding the first row. Allocates and sets up the internal + * aggregation structures used, e.g. the Unique instance used to calculate + * distinct. + */ + public abstract boolean setup(); + + /** + * Called when we need to wipe out all the data from the aggregator : all + * the values acumulated and all the state. Cleans up the internal + * structures and resets them to their initial state. + */ + public abstract void clear(); + + /** + * Called when there's a new value to be aggregated. Updates the internal + * state of the aggregator to reflect the new value. + */ + public abstract boolean add(RowDataPacket row, Object transObj); + + /** + * Called when there are no more data and the final value is to be + * retrieved. Finalises the state of the aggregator, so the final result can + * be retrieved. + */ + public abstract void endup(); + + /** Decimal value of being-aggregated argument */ + public abstract BigDecimal arg_val_decimal(); + + /** Floating point value of being-aggregated argument */ + public abstract BigDecimal arg_val_real(); + + /** + * NULLness of being-aggregated argument; can be called only after + * arg_val_decimal() or arg_val_real(). + */ + public abstract boolean arg_is_null(); +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/AggregatorDistinct.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/AggregatorDistinct.java new file mode 100644 index 000000000..e4e5afbd3 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/AggregatorDistinct.java @@ -0,0 +1,118 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; + +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.external.ResultStore; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.field.FieldUtil; + + +public class AggregatorDistinct extends Aggregator { + private boolean endup_done = false; + private final ResultStore distinctRows; + private Field field = null; + + public AggregatorDistinct(ItemSum arg, ResultStore store) { + super(arg); + this.distinctRows = store; + } + + @Override + public AggregatorType Aggrtype() { + return AggregatorType.DISTINCT_AGGREGATOR; + } + + /***************************************************************************/ + /** + * Called before feeding the first row. Used to allocate/setup the internal + * structures used for aggregation. + * + * @param thd + * Thread descriptor + * @return status + * @throws UnsupportedEncodingException + * @retval FALSE success + * @retval TRUE faliure + * + * Prepares Aggregator_distinct to process the incoming stream. + * Creates the temporary table and the Unique class if needed. + * Called by Item_sum::aggregator_setup() + */ + @Override + public boolean setup(){ + endup_done = false; + if (item_sum.setup()) + return true; + // TODO see item_sum.cc for more + FieldPacket tmp = new FieldPacket(); + item_sum.getArg(0).makeField(tmp); + field = Field.getFieldItem(tmp.name, tmp.table, tmp.type, tmp.charsetIndex, (int) tmp.length, tmp.decimals, + tmp.flags); + return false; + } + + @Override + public void clear() { + endup_done = false; + distinctRows.clear(); + if (item_sum.sumType() == ItemSum.Sumfunctype.COUNT_FUNC + || item_sum.sumType() == ItemSum.Sumfunctype.COUNT_DISTINCT_FUNC) { + + } else { + item_sum.nullValue = true; + } + } + + /** + * add the distinct value into buffer, to use when endup() is called + */ + @Override + public boolean add(RowDataPacket row, Object transObj) { + distinctRows.add(row); + return false; + } + + @Override + public void endup() { + if (endup_done) + return; + item_sum.clear(); + if (distinctRows != null) { + distinctRows.done(); + if (distinctRows != null && !endup_done) { + use_distinct_values = true; + RowDataPacket row = null; + while ((row = distinctRows.next()) != null) { + // @bug1072 see arg_is_null() + FieldUtil.initFields(item_sum.sourceFields, row.fieldValues); + field.setPtr(item_sum.getArg(0).getRowPacketByte()); + if (item_sum.isPushDown) + item_sum.pushDownAdd(row); + else + item_sum.add(row, null); + } + use_distinct_values = false; + } + endup_done = true; + } + } + + @Override + public BigDecimal arg_val_decimal() { + return use_distinct_values ? field.valDecimal() : item_sum.getArg(0).valDecimal(); + } + + @Override + public BigDecimal arg_val_real() { + return use_distinct_values ? field.valReal() : item_sum.getArg(0).valReal(); + } + + @Override + public boolean arg_is_null() { + return use_distinct_values ? field.isNull() : item_sum.getArg(0).nullValue; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/AggregatorSimple.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/AggregatorSimple.java new file mode 100644 index 000000000..1bffb4adb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/AggregatorSimple.java @@ -0,0 +1,58 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.FieldUtil; + + +public class AggregatorSimple extends Aggregator { + + public AggregatorSimple(ItemSum arg) { + super(arg); + } + + @Override + public AggregatorType Aggrtype() { + return AggregatorType.SIMPLE_AGGREGATOR; + } + + @Override + public boolean setup() { + return item_sum.setup(); + } + + @Override + public void clear() { + item_sum.clear(); + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + FieldUtil.initFields(item_sum.sourceFields, row.fieldValues); + if (!item_sum.isPushDown) + return item_sum.add(row, transObj); + else + return item_sum.pushDownAdd(row); + } + + @Override + public void endup() { + } + + @Override + public BigDecimal arg_val_decimal() { + return item_sum.args.get(0).valDecimal(); + } + + @Override + public BigDecimal arg_val_real() { + return item_sum.args.get(0).valReal(); + } + + @Override + public boolean arg_is_null() { + return item_sum.args.get(0).nullValue; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemFuncGroupConcat.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemFuncGroupConcat.java new file mode 100644 index 000000000..f0b37aa5f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemFuncGroupConcat.java @@ -0,0 +1,213 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; +import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.Order; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFuncKeyWord; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncGroupConcat extends ItemSum { + protected StringBuilder resultSb; + protected String seperator; + protected boolean no_appended; + private List orders; + protected boolean always_null;// 如果参数存在null时 + + public ItemFuncGroupConcat(List selItems, boolean distinct, List orders, String is_separator, + boolean isPushDown, List fields) { + super(selItems, isPushDown, fields); + this.orders = orders; + seperator = is_separator; + this.resultSb = new StringBuilder(); + this.always_null = false; + setDistinct(distinct); + } + + @Override + public Sumfunctype sumType() { + return Sumfunctype.GROUP_CONCAT_FUNC; + } + + @Override + public String funcName() { + return "GROUP_CONCAT"; + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_VARCHAR; + } + + @Override + public void cleanup() { + super.cleanup(); + } + + @Override + public void clear() { + resultSb.setLength(0); + nullValue = true; + no_appended = true; + } + + @Override + public Object getTransAggObj() { + throw new RuntimeException("Group_concat should not use direct groupby!"); + } + + @Override + public int getTransSize() { + throw new RuntimeException("Group_concat should not use direct groupby!"); + } + + @Override + public boolean add(RowDataPacket row, Object tranObject) { + if (always_null) + return false; + String rowStr = new String(); + for (int i = 0; i < getArgCount(); i++) { + Item item = args.get(i); + String s = item.valStr(); + if (item.isNull()) + return false; + rowStr += s; + } + nullValue = false; + if (resultSb.length() > 0) + resultSb.append(seperator); + resultSb.append(rowStr); + return false; + } + + @Override + public boolean setup() { + always_null = false; + for (int i = 0; i < getArgCount(); i++) { + Item item = args.get(i); + if (item.canValued()) { + if (item.isNull()) { + always_null = true; + return false; + } + } + } + return false; + } + + @Override + public boolean fixFields() { + super.fixFields(); + nullValue = true; + fixed = true; + return false; + } + + @Override + public String valStr() { + if (aggr != null) + aggr.endup(); + if (nullValue) + return null; + return resultSb.toString(); + } + + @Override + public BigDecimal valReal() { + String res = valStr(); + if (res == null) + return BigDecimal.ZERO; + else + try { + return new BigDecimal(res); + } catch (Exception e) { + logger.info("group_concat val_real() convert exception, string value is: " + res); + return BigDecimal.ZERO; + } + } + + @Override + public BigInteger valInt() { + String res = valStr(); + if (res == null) + return BigInteger.ZERO; + else + try { + return new BigInteger(res); + } catch (Exception e) { + logger.info("group_concat val_int() convert exception, string value is: " + res); + return BigInteger.ZERO; + } + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromString(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromString(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromString(ltime); + } + + @Override + public boolean pushDownAdd(RowDataPacket row) { + throw new RuntimeException("not implement"); + } + + @Override + public SQLExpr toExpression() { + SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName()); + if (has_with_distinct()) { + aggregate.setOption(SQLAggregateOption.DISTINCT); + } + if (orders != null) { + SQLOrderBy orderBy = new SQLOrderBy(); + for (Order order : orders) { + SQLSelectOrderByItem orderItem = new SQLSelectOrderByItem(order.getItem().toExpression()); + orderItem.setType(order.getSortOrder()); + orderBy.addItem(orderItem); + } + aggregate.putAttribute(ItemFuncKeyWord.ORDER_BY, orderBy); + } + if (seperator != null) { + aggregate.putAttribute(ItemFuncKeyWord.SEPARATOR, seperator); + } + return aggregate; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List argList = cloneStructList(args); + return new ItemFuncGroupConcat(argList, has_with_distinct(), this.orders, this.seperator, + false, null); + } else { + return new ItemFuncGroupConcat(calArgs, has_with_distinct(), this.orders, this.seperator, isPushDown, + fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSum.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSum.java new file mode 100644 index 000000000..4f301115c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSum.java @@ -0,0 +1,343 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.external.ResultStore; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemResultField; +import io.mycat.plan.common.item.function.sumfunc.Aggregator.AggregatorType; +import io.mycat.plan.common.ptr.DoublePtr; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.util.PlanUtil; + + +public abstract class ItemSum extends ItemResultField { + /* 行fields集合 */ + protected List sourceFields; + public boolean isPushDown; + + public static final int FALSE = 0; + public static final int TRUE = 1; + + /** + * Aggregator class instance. Not set initially. Allocated only after it is + * determined if the incoming data are already distinct. + */ + protected Aggregator aggr; + + /** + * Indicates how the aggregate function was specified by the parser : 1 if + * it was written as AGGREGATE(DISTINCT), 0 if it was AGGREGATE() + */ + private boolean withDistinct = false; + + public final int getArgCount() { + if (args == null) + return 0; + return args.size(); + } + + public List arguments() { + return this.args; + } + + public final boolean has_with_distinct() { + return withDistinct; + } + + public enum Sumfunctype { + COUNT_FUNC, COUNT_DISTINCT_FUNC, SUM_FUNC, SUM_DISTINCT_FUNC, AVG_FUNC, AVG_DISTINCT_FUNC, MIN_FUNC, MAX_FUNC, STD_FUNC, VARIANCE_FUNC, SUM_BIT_FUNC, UDF_SUM_FUNC, GROUP_CONCAT_FUNC + }; + + public void markAsSumFunc() { + withSumFunc = true; + } + + protected List args; + + public boolean quick_group; /* If incremental update of fields */ + + public ItemSum(List args, boolean isPushDown, List fields) { + this.isPushDown = isPushDown; + this.args = args; + this.sourceFields = fields; + markAsSumFunc(); + initAggregator(); + } + + public final ItemType type() { + return ItemType.SUM_FUNC_ITEM; + } + + public abstract Sumfunctype sumType(); + + @Override + public int hashCode() { + int prime = 10; + int hashCode = funcName().hashCode(); + hashCode = hashCode * prime; + for (int index = 0; index < getArgCount(); index++) { + hashCode += args.get(index).hashCode(); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ItemSum)) + return false; + ItemSum other = (ItemSum) obj; + if (!sumType().equals(other.sumType())) + return false; + if (getArgCount() != other.getArgCount()) + return false; + return StringUtils.equals(getItemName(), other.getItemName()); + } + + /** + * Resets the aggregate value to its default and aggregates the current + * value of its attribute(s). + */ + public boolean resetAndAdd(RowDataPacket row, Object transObj) { + aggregatorClear(); + return aggregatorAdd(row, transObj); + }; + + /** + * 局部聚合生成的中间结果(transitional) + * + * @return notice:不能返回null,因为Group By的第一条数据的transAggObj是null, + * 如果聚合过的也返回null的话,将无法区分是聚合过的还是第一条数据 + */ + public abstract Object getTransAggObj(); + + /** + * 局部聚合结果的byte数组的预估大小 + * + * @return + */ + public abstract int getTransSize(); + + public void fixLengthAndDec() { + maybeNull = true; + nullValue = true; + } + + @Override + public boolean isNull() { + return nullValue; + } + + public void fixNumLengthAndDec() { + decimals = 0; + for (int i = 0; i < getArgCount(); i++) + decimals = Math.max(decimals, args.get(i).decimals); + maxLength = floatLength(decimals); + } + + /** + * Mark an aggregate as having no rows. + * + * This function is called by the execution engine to assign 'NO ROWS FOUND' + * value to an aggregate item, when the underlying result set has no rows. + * Such value, in a general case, may be different from the default value of + * the item after 'clear()': e.g. a numeric item may be initialized to 0 by + * clear() and to NULL by no_rows_in_result(). + */ + public void noRowsInResult() { + setAggregator(withDistinct ? AggregatorType.DISTINCT_AGGREGATOR : AggregatorType.SIMPLE_AGGREGATOR, null); + aggregatorClear(); + } + + public Item getArg(int i) { + return args.get(i); + } + + public Item setArg(int i, Item newVal) { + args.set(i, newVal); + return newVal; + } + + /* Initialization of distinct related members */ + public void initAggregator() { + aggr = null; + withDistinct = false; + } + + /** + * Called to initialize the aggregator. + */ + + public boolean aggregatorSetup() { + return aggr.setup(); + } + + /** + * Called to cleanup the aggregator. + */ + + public void aggregatorClear() { + aggr.clear(); + } + + /** + * Called to add value to the aggregator. + */ + + public boolean aggregatorAdd(RowDataPacket row, Object transObj) { + return aggr.add(row, transObj); + }; + + /* stores the declared DISTINCT flag (from the parser) */ + public void setDistinct(boolean distinct) { + withDistinct = distinct; + quick_group = withDistinct ? false : true; + } + + /* + * Set the type of aggregation : DISTINCT or not. + * + * May be called multiple times. + */ + + public int setAggregator(AggregatorType aggregator, ResultStore store) { + /* + * Dependent subselects may be executed multiple times, making + * set_aggregator to be called multiple times. The aggregator type will + * be the same, but it needs to be reset so that it is reevaluated with + * the new dependent data. This function may also be called multiple + * times during query optimization. In this case, the type may change, + * so we delete the old aggregator, and create a new one. + */ + if (aggr != null && aggregator == aggr.Aggrtype()) { + aggr.clear(); + return FALSE; + } + + aggr = null; + switch (aggregator) { + case DISTINCT_AGGREGATOR: + aggr = new AggregatorDistinct(this, store); + break; + case SIMPLE_AGGREGATOR: + aggr = new AggregatorSimple(this); + break; + } + ; + return aggr != null ? FALSE : TRUE; + } + + public abstract void clear(); + + /** + * 函数不下发时候的add方法,和mysql原生代码基本一致 + * + * @param rowbytes + * @return + */ + protected abstract boolean add(RowDataPacket row, Object transObj); + + /** + * 聚合函数被下发时的add方法 + * + * @return + */ + protected abstract boolean pushDownAdd(RowDataPacket row); + + public boolean setup() { + return false; + } + + /** + * Variance implementation for floating-point implementations, without + * catastrophic cancellation, from Knuth's _TAoCP_, 3rd ed, volume 2, pg232. + * This alters the value at m, s, and increments count. + */ + + /* + * These two functions are used by the Item_sum_variance and the + * Item_variance_field classes, which are unrelated, and each need to + * calculate variance. The difference between the two classes is that the + * first is used for a mundane SELECT, while the latter is used in a + * GROUPing SELECT. + */ + protected static void varianceFpRecurrenceNext(DoublePtr m, DoublePtr s, LongPtr count, double nr) { + count.incre(); + + if (count.get() == 1) { + m.set(nr); + s.set(0); + } else { + double m_kminusone = m.get(); + m.set(m_kminusone + (nr - m_kminusone) / (double) count.get()); + s.set(s.get() + (nr - m_kminusone) * (nr - m.get())); + } + } + + protected static double varianceFpRecurrenceResult(double s, long count, boolean is_sample_variance) { + if (count == 1) + return 0.0; + + if (is_sample_variance) + return s / (count - 1); + + /* else, is a population variance */ + return s / count; + } + + @Override + public final ItemSum fixFields(NameResolutionContext context) { + getReferTables().clear(); + for (int index = 0; index < getArgCount(); index++) { + Item arg = args.get(index); + Item fixedArg = arg.fixFields(context); + if (fixedArg == null) + return null; + args.set(index, fixedArg); + getReferTables().addAll(fixedArg.getReferTables()); + withIsNull = withIsNull || fixedArg.withIsNull; + withSubQuery = withSubQuery || fixedArg.withSubQuery; + } + return this; + } + + @Override + public final void fixRefer(ReferContext context) { + PlanNode planNode = context.getPlanNode(); + planNode.addSelToReferedMap(planNode, this); + boolean needAddArgToRefer = true; + if (context.isPushDownNode() && !planNode.existUnPushDownGroup()) { + boolean isUnpushSum = PlanUtil.isUnPushDownSum(this); + if (isUnpushSum) {// 这个函数是非下推函数 + planNode.setExistUnPushDownGroup(true); + needAddArgToRefer = true; + // 补上sunfuncs里面的arg参数 + for (ItemSum sumfunc : planNode.sumFuncs) { + for (Item sumArg : sumfunc.args) { + sumArg.fixRefer(context); + } + } + } else { + needAddArgToRefer = false; + } + } + if (needAddArgToRefer) { + for (Item arg : this.args) { + arg.fixRefer(context); + } + } + planNode.sumFuncs.add(this); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumAnd.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumAnd.java new file mode 100644 index 000000000..5d8397529 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumAnd.java @@ -0,0 +1,68 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumAnd extends ItemSumBit { + public ItemSumAnd(List item_par, boolean isPushDown, List fields) { + super(item_par, 1, isPushDown, fields); + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AggData other = (AggData) transObj; + if (!other.isNull) + bits = bits.and(other.bits); + } else { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) + bits = bits.and(value); + } + return false; + } + + /** + * and的pushdown为and + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) + bits = bits.and(value); + return false; + } + + @Override + public String funcName() { + return "BIT_AND"; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumAnd(newArgs, false, null); + } else { + return new ItemSumAnd(calArgs, isPushDown, fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumAvg.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumAvg.java new file mode 100644 index 000000000..23d83cb9b --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumAvg.java @@ -0,0 +1,162 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumAvg extends ItemSumSum { + public long count; + + public ItemSumAvg(List args, boolean distinct, boolean isPushDown, List fields) { + super(args, distinct, isPushDown, fields); + count = 0; + } + + @Override + public void fixLengthAndDec() { + super.fixLengthAndDec(); + maybeNull = nullValue = true; + } + + @Override + public Sumfunctype sumType() { + return has_with_distinct() ? Sumfunctype.AVG_DISTINCT_FUNC : Sumfunctype.AVG_FUNC; + } + + @Override + public void clear() { + super.clear(); + count = 0; + } + + private static class AvgAggData extends AggData { + + private static final long serialVersionUID = -1831762635995954526L; + public long count; + + public AvgAggData(BigDecimal sum, long count, boolean isNull) { + super(sum, isNull); + this.count = count; + } + + } + + @Override + public Object getTransAggObj() { + AvgAggData aggData = new AvgAggData(sum, count, nullValue); + return aggData; + } + + @Override + public int getTransSize() { + return 20; + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AvgAggData data = (AvgAggData) transObj; + if (super.add(row, data)) + return true; + if (!data.isNull) + count += data.count; + } else { + if (super.add(row, null)) + return true; + if (!aggr.arg_is_null()) + count++; + } + return false; + } + + @Override + public boolean pushDownAdd(RowDataPacket row) { + // 聚合函数下发的情况,avg(n)被下发成sum(n) 和 count(n); + assert (getArgCount() == 2); + count += args.get(1).valInt().longValue(); + return super.add(row, null); + } + + @Override + public BigDecimal valReal() { + if (aggr != null) + aggr.endup(); + if (count == 0) { + nullValue = true; + return BigDecimal.ZERO; + } + + return super.valReal().divide(new BigDecimal(count), decimals+4, RoundingMode.HALF_UP); + } + + @Override + public BigInteger valInt() { + return valReal().toBigInteger(); + } + + @Override + public BigDecimal valDecimal() { + if (aggr != null) + aggr.endup(); + if (count == 0) { + nullValue = true; + return null; + } + return valReal(); + } + + @Override + public String valStr() { + if (aggr != null) + aggr.endup(); + if (hybrid_type == ItemResult.DECIMAL_RESULT) + return valStringFromDecimal(); + return valStringFromReal(); + } + + @Override + public void cleanup() { + count = 0; + super.cleanup(); + } + + @Override + public final String funcName() { + return "AVG"; + } + + @Override + public void noRowsInResult() { + } + + @Override + public SQLExpr toExpression() { + Item arg0 = args.get(0); + SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName()); + aggregate.addArgument(arg0.toExpression()); + if(has_with_distinct()){ + aggregate.setOption(SQLAggregateOption.DISTINCT); + } + return aggregate; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumAvg(newArgs, has_with_distinct(), false, null); + } else { + return new ItemSumAvg(calArgs, has_with_distinct(), isPushDown, fields); + } + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumBit.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumBit.java new file mode 100644 index 000000000..b20e72020 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumBit.java @@ -0,0 +1,73 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public abstract class ItemSumBit extends ItemSumInt { + + protected BigInteger reset_bits, bits; + + public ItemSumBit(List item_par, long reset_arg, boolean isPushDown, List fields) { + super(item_par, isPushDown, fields); + reset_bits = BigInteger.valueOf(reset_arg); + bits = BigInteger.valueOf(reset_arg); + } + + public Sumfunctype sumType() { + return Sumfunctype.SUM_BIT_FUNC; + } + + @Override + public void clear() { + bits = reset_bits; + } + + @Override + public BigInteger valInt() { + return bits; + } + + @Override + public void fixLengthAndDec() { + decimals = 0; + maxLength = 21; + maybeNull = nullValue = false; + } + + @Override + public void cleanup() { + bits = reset_bits; + super.cleanup(); + } + + protected static class AggData implements Serializable { + + private static final long serialVersionUID = -5952130248997591472L; + + public BigInteger bits; + public boolean isNull; + + public AggData(BigInteger bits, boolean isNull) { + this.bits = bits; + this.isNull = isNull; + } + + } + + @Override + public Object getTransAggObj() { + AggData data = new AggData(bits, nullValue); + return data; + } + + @Override + public int getTransSize() { + return 15; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumCount.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumCount.java new file mode 100644 index 000000000..e7027bd37 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumCount.java @@ -0,0 +1,102 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumCount extends ItemSumInt { + long count; + + public ItemSumCount(List args, boolean distinct, boolean isPushDown, List fields) { + super(args, isPushDown, fields); + count = 0; + setDistinct(distinct); + } + + @Override + public Sumfunctype sumType() { + return has_with_distinct() ? Sumfunctype.COUNT_DISTINCT_FUNC : Sumfunctype.COUNT_FUNC; + } + + @Override + public Object getTransAggObj() { + return count; + } + + @Override + public int getTransSize() { + return 10; + } + + @Override + public void clear() { + count = 0; + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + long countOther = (Long) transObj; + count += countOther; + } else if (!args.get(0).isNull()) + count++; + return false; + } + + /** + * count(id)的pushdown为count(id)然后将他们的值进行相加 + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + if (!args.get(0).isNull()) { + long val = args.get(0).valInt().longValue(); + count += val; + } + return false; + } + + @Override + public String funcName() { + return "COUNT"; + } + + @Override + public BigInteger valInt() { + if (aggr != null) + aggr.endup(); + return BigInteger.valueOf(count); + } + + @Override + public SQLExpr toExpression() { + SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName()); + if (has_with_distinct()) { + for (Item arg : args) + aggregate.addArgument(arg.toExpression()); + aggregate.setOption(SQLAggregateOption.DISTINCT); + } else { + Item arg0 = getArg(0); + aggregate.addArgument(arg0.toExpression()); + } + return aggregate; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumCount(newArgs, has_with_distinct(), false, null); + } else { + return new ItemSumCount(calArgs, has_with_distinct(), isPushDown, fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumHybrid.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumHybrid.java new file mode 100644 index 000000000..1fc7c1848 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumHybrid.java @@ -0,0 +1,188 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.field.FieldUtil; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemSumHybrid extends ItemSum { + + protected Field value; + protected ItemResult hybrid_type; + protected FieldTypes hybrid_field_type; + protected int cmp_sign; + protected boolean was_values;// Set if we have found at least one row (for + // max/min only) + + public ItemSumHybrid(List args, boolean distinct, int sign, boolean isPushDown, List fields) { + super(args, isPushDown, fields); + hybrid_field_type = FieldTypes.MYSQL_TYPE_LONGLONG; + hybrid_type = ItemResult.INT_RESULT; + cmp_sign = sign; + was_values = true; + setDistinct(distinct); + } + + @Override + public boolean fixFields() { + Item item = args.get(0); + + // 'item' can be changed during fix_fields + if (!item.fixed && item.fixFields()) + return true; + item = args.get(0); + decimals = item.decimals; + value = Field.getFieldItem(funcName(), null, item.fieldType().numberValue(), item.charsetIndex, + item.maxLength, item.decimals, (item.maybeNull ? 0 : FieldUtil.NOT_NULL_FLAG)); + + switch (hybrid_type = item.resultType()) { + case INT_RESULT: + case DECIMAL_RESULT: + case STRING_RESULT: + maxLength = item.maxLength; + break; + case REAL_RESULT: + maxLength = floatLength(decimals); + break; + case ROW_RESULT: + default: + assert (false); + } + charsetIndex = item.charsetIndex; + /* + * MIN/MAX can return NULL for empty set indepedent of the used column + */ + maybeNull = true; + nullValue = true; + fixLengthAndDec(); + hybrid_field_type = item.fieldType(); + + fixed = true; + return false; + } + + @Override + public void clear() { + value.setPtr(null); + nullValue = true; + } + + @Override + public BigDecimal valReal() { + if (nullValue) + return BigDecimal.ZERO; + BigDecimal retval = value.valReal(); + if (nullValue = value.isNull()) + retval = BigDecimal.ZERO; + return retval; + } + + @Override + public BigInteger valInt() { + if (nullValue) + return BigInteger.ZERO; + BigInteger retval = value.valInt(); + if (nullValue = value.isNull()) + retval = BigInteger.ZERO; + return retval; + } + + @Override + public long valTimeTemporal() { + if (nullValue) + return 0; + long retval = value.valTimeTemporal(); + if (nullValue = value.isNull()) + retval = 0; + return retval; + } + + @Override + public long valDateTemporal() { + if (nullValue) + return 0; + long retval = value.valDateTemporal(); + if (nullValue = value.isNull()) + retval = 0; + return retval; + } + + @Override + public BigDecimal valDecimal() { + if (nullValue) + return null; + BigDecimal retval = value.valDecimal(); + if (nullValue = value.isNull()) + retval = null; + return retval; + } + + @Override + public int getTransSize() { + return value.fieldLength; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + if (nullValue) + return true; + return (nullValue = value.getDate(ltime, fuzzydate)); + } + + @Override + public boolean getTime(MySQLTime ltime) { + if (nullValue) + return true; + return (nullValue = value.getTime(ltime)); + } + + @Override + public String valStr() { + if (nullValue) + return null; + String retval = value.valStr(); + if (nullValue = value.isNull()) + retval = null; + return retval; + } + + @Override + public ItemResult resultType() { + return hybrid_type; + } + + @Override + public FieldTypes fieldType() { + return hybrid_field_type; + } + + @Override + public void cleanup() { + super.cleanup(); + /* + * by default it is TRUE to avoid TRUE reporting by + * Item_func_not_all/Item_func_nop_all if this item was never called. + * + * no_rows_in_result() set it to FALSE if was not results found. If some + * results found it will be left unchanged. + */ + was_values = true; + } + + public boolean any_value() { + return was_values; + } + + @Override + public void noRowsInResult() { + was_values = false; + clear(); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumInt.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumInt.java new file mode 100644 index 000000000..e931e78c6 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumInt.java @@ -0,0 +1,54 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemSumInt extends ItemSumNum { + + public ItemSumInt(List args, boolean isPushDown, List fields) { + super(args, isPushDown, fields); + } + + @Override + public BigDecimal valReal() { + return new BigDecimal(valInt()); + } + + @Override + public String valStr() { + return valStringFromInt(); + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromInt(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromInt(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromInt(ltime); + } + + @Override + public final ItemResult resultType() { + return ItemResult.INT_RESULT; + } + + @Override + public void fixLengthAndDec() { + decimals = 0; + maxLength = 21; + maybeNull = nullValue = false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumMax.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumMax.java new file mode 100644 index 000000000..a50ba7a3d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumMax.java @@ -0,0 +1,104 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.io.Serializable; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumMax extends ItemSumHybrid { + public ItemSumMax(List args, boolean distinct, boolean isPushDown, List fields) { + super(args, distinct, -1, isPushDown, fields); + } + + @Override + public Sumfunctype sumType() { + return Sumfunctype.MAX_FUNC; + } + + private static class AggData implements Serializable { + + private static final long serialVersionUID = 5265691791812484350L; + public byte[] ptr; + public boolean isNull = false; + + public AggData(byte[] ptr, boolean isNull) { + this.ptr = ptr; + this.isNull = isNull; + } + + } + + @Override + public Object getTransAggObj() { + AggData data = new AggData(value.ptr, nullValue); + return data; + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AggData data = (AggData) transObj; + byte[] b1 = data.ptr; + byte[] b0 = value.ptr; + if (!data.isNull && (nullValue || value.compare(b0, b1) < 0)) { + value.setPtr(b1); + nullValue = false; + } + } else { + byte[] b1 = args.get(0).getRowPacketByte(); + byte[] b0 = value.ptr; + if (!args.get(0).isNull() && (nullValue || value.compare(b0, b1) < 0)) { + value.setPtr(b1); + nullValue = false; + } + } + return false; + } + + /** + * max(id)的pushdown为max(id) + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + byte[] b1 = args.get(0).getRowPacketByte(); + byte[] b0 = value.ptr; + if (!args.get(0).isNull() && (nullValue || value.compare(b0, b1) < 0)) { + value.setPtr(b1); + nullValue = false; + } + return false; + } + + @Override + public String funcName() { + return "MAX"; + } + + @Override + public SQLExpr toExpression() { + Item arg0 = args.get(0); + SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName()); + aggregate.addArgument(arg0.toExpression()); + if(has_with_distinct()){ + aggregate.setOption(SQLAggregateOption.DISTINCT); + } + return aggregate; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumMax(newArgs, has_with_distinct(), false, null); + } else { + return new ItemSumMax(calArgs, has_with_distinct(), isPushDown, fields); + } + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumMin.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumMin.java new file mode 100644 index 000000000..cfa39896a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumMin.java @@ -0,0 +1,106 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.io.Serializable; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumMin extends ItemSumHybrid { + + public ItemSumMin(List args, boolean distinct, boolean isPushDown, List fields) { + super(args, distinct, 1, isPushDown, fields); + } + + @Override + public Sumfunctype sumType() { + return Sumfunctype.MIN_FUNC; + } + + private static class AggData implements Serializable { + + private static final long serialVersionUID = 5265691791812484350L; + public byte[] ptr; + public boolean isNull = false; + + public AggData(byte[] ptr, boolean isNull) { + this.ptr = ptr; + this.isNull = isNull; + } + + } + + @Override + public Object getTransAggObj() { + AggData data = new AggData(value.ptr, nullValue); + return data; + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AggData data = (AggData) transObj; + byte[] b1 = data.ptr; + byte[] b0 = value.ptr; + if (!data.isNull && (nullValue || value.compare(b0, b1) > 0)) { + value.setPtr(b1); + nullValue = false; + } + } else { + byte[] b1 = args.get(0).getRowPacketByte(); + byte[] b0 = value.ptr; + if (!args.get(0).isNull() && (nullValue || value.compare(b0, b1) > 0)) { + value.setPtr(b1); + nullValue = false; + } + } + return false; + } + + /** + * min(id)的pushdown为min(id) + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + byte[] b1 = args.get(0).getRowPacketByte(); + byte[] b0 = value.ptr; + if (!args.get(0).isNull() && (nullValue || value.compare(b0, b1) > 0)) { + value.setPtr(b1); + nullValue = false; + } + return false; + } + + @Override + public String funcName() { + return "MIN"; + } + + @Override + public SQLExpr toExpression() { + Item arg0 = args.get(0); + SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName()); + aggregate.addArgument(arg0.toExpression()); + if(has_with_distinct()){ + aggregate.setOption(SQLAggregateOption.DISTINCT); + } + return aggregate; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumMin(newArgs,has_with_distinct(), false, null); + } else { + return new ItemSumMin(calArgs,has_with_distinct(), isPushDown, fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumNum.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumNum.java new file mode 100644 index 000000000..b382fae05 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumNum.java @@ -0,0 +1,63 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemSumNum extends ItemSum { + + public ItemSumNum(List args, boolean isPushDown, List fields) { + super(args, isPushDown, fields); + } + + /* 是否已经被计算过 */ + boolean is_evaluated; + + @Override + public boolean fixFields() { + decimals = 0; + maybeNull = false; + for (int i = 0; i < getArgCount(); i++) { + if (!args.get(i).fixed && args.get(i).fixFields()) + return true; + decimals = Math.max(decimals, args.get(i).decimals); + maybeNull |= args.get(i).maybeNull; + } + maxLength = floatLength(decimals); + nullValue = true; + fixLengthAndDec(); + fixed = true; + return false; + } + + @Override + public BigInteger valInt() { + return valReal().toBigInteger(); + } + + @Override + public String valStr() { + return valStringFromReal(); + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromReal(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromNumeric(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromNumeric(ltime); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumNumField.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumNumField.java new file mode 100644 index 000000000..c3afb33de --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumNumField.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigInteger; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.ItemResultField; +import io.mycat.plan.common.time.MySQLTime; + + +public abstract class ItemSumNumField extends ItemResultField { + protected ItemResult hybrid_type; + + @Override + public BigInteger valInt() { + return valReal().toBigInteger(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromNumeric(ltime, fuzzydate); /* Decimal or real */ + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromNumeric(ltime); /* Decimal or real */ + } + + @Override + public final FieldTypes fieldType() { + return hybrid_type == ItemResult.DECIMAL_RESULT ? FieldTypes.MYSQL_TYPE_NEWDECIMAL + : FieldTypes.MYSQL_TYPE_DOUBLE; + } + + @Override + public final ItemResult resultType() { + return hybrid_type; + } + + @Override + public boolean isNull() { + updateNullValue(); + return nullValue; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumOr.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumOr.java new file mode 100644 index 000000000..ecc8e0fab --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumOr.java @@ -0,0 +1,69 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumOr extends ItemSumBit { + + public ItemSumOr(List item_par, boolean isPushDown, List fields) { + super(item_par, 0, isPushDown, fields); + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AggData other = (AggData) transObj; + if (!other.isNull) + bits = bits.or(other.bits); + } else { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) + bits = bits.or(value); + } + return false; + } + + /** + * or的pushdown为or + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) + bits = bits.or(value); + return false; + } + + @Override + public String funcName() { + return "BIT_OR"; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumOr(newArgs, false, null); + } else { + return new ItemSumOr(calArgs, isPushDown, fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumStd.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumStd.java new file mode 100644 index 000000000..55668d16d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumStd.java @@ -0,0 +1,71 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigDecimal; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; + + +/** + * standard_deviation(a) = sqrt(variance(a)) + * + * + */ +public class ItemSumStd extends ItemSumVariance { + + public ItemSumStd(List args, int sample, boolean isPushDown, List fields) { + super(args, sample, isPushDown, fields); + } + + @Override + public Sumfunctype sumType() { + return Sumfunctype.STD_FUNC; + } + + @Override + public BigDecimal valReal() { + BigDecimal val = super.valReal(); + double db = Math.sqrt(val.doubleValue()); + return BigDecimal.valueOf(db); + } + + @Override + public String funcName() { + return sample == 1 ? "STDDEV_SAMP" : "STD"; + } + + @Override + public ItemResult resultType() { + return ItemResult.REAL_RESULT; + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DOUBLE; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumStd(newArgs, sample, false, null); + } else { + return new ItemSumStd(calArgs, sample, isPushDown, fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumSum.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumSum.java new file mode 100644 index 000000000..403cf311c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumSum.java @@ -0,0 +1,175 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumSum extends ItemSumNum { + + public ItemSumSum(List args, boolean distinct, boolean isPushDown, List fields) { + super(args, isPushDown, fields); + setDistinct(distinct); + } + + protected ItemResult hybrid_type; + protected BigDecimal sum; + + @Override + public void fixLengthAndDec() { + maybeNull = nullValue = true; + decimals = args.get(0).decimals; + switch (args.get(0).numericContextResultType()) { + case REAL_RESULT: + hybrid_type = ItemResult.REAL_RESULT; + sum = BigDecimal.ZERO; + break; + case INT_RESULT: + case DECIMAL_RESULT: { + /* SUM result can't be longer than length(arg) + length(MAX_ROWS) */ + int precision = args.get(0).decimalPrecision() + MySQLcom.DECIMAL_LONGLONG_DIGITS; + maxLength = precision + 2;// 一个小数点一个负号 + hybrid_type = ItemResult.DECIMAL_RESULT; + sum = BigDecimal.ZERO; + break; + } + case STRING_RESULT: + case ROW_RESULT: + default: + assert (false); + } + } + + public Sumfunctype sumType() { + return has_with_distinct() ? Sumfunctype.SUM_DISTINCT_FUNC : Sumfunctype.SUM_FUNC; + } + + @Override + public void clear() { + nullValue = true; + sum = BigDecimal.ZERO; + } + + protected static class AggData implements Serializable { + + private static final long serialVersionUID = 6951860386146676307L; + + public BigDecimal bd; + public boolean isNull; + + public AggData(BigDecimal bd, boolean isNull) { + this.bd = bd; + this.isNull = isNull; + } + + } + + @Override + public Object getTransAggObj() { + AggData data = new AggData(sum, nullValue); + return data; + } + + @Override + public int getTransSize() { + return 10; + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AggData data = (AggData) transObj; + if (hybrid_type == ItemResult.DECIMAL_RESULT) { + final BigDecimal val = data.bd; + if (!data.isNull) { + sum = sum.add(val); + nullValue = false; + } + } else { + sum = sum.add(data.bd); + if (!data.isNull) + nullValue = false; + } + } else { + if (hybrid_type == ItemResult.DECIMAL_RESULT) { + final BigDecimal val = aggr.arg_val_decimal(); + if (!aggr.arg_is_null()) { + sum = sum.add(val); + nullValue = false; + } + } else { + sum = sum.add(aggr.arg_val_real()); + if (!aggr.arg_is_null()) + nullValue = false; + } + } + return false; + } + + /** + * sum(id)的pushdown为sum(id) + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + if (hybrid_type == ItemResult.DECIMAL_RESULT) { + final BigDecimal val = aggr.arg_val_decimal(); + if (!aggr.arg_is_null()) { + sum = sum.add(val); + nullValue = false; + } + } else { + sum = sum.add(aggr.arg_val_real()); + if (!aggr.arg_is_null()) + nullValue = false; + } + return false; + } + + @Override + public BigDecimal valReal() { + if (aggr != null) { + aggr.endup(); + } + return sum; + } + + @Override + public ItemResult resultType() { + return hybrid_type; + } + + @Override + public String funcName() { + return "SUM"; + } + + @Override + public SQLExpr toExpression() { + Item arg0 = getArg(0); + SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName()); + aggregate.addArgument(arg0.toExpression()); + if(has_with_distinct()){ + aggregate.setOption(SQLAggregateOption.DISTINCT); + } + return aggregate; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumSum(newArgs, has_with_distinct(), false, null); + } else { + return new ItemSumSum(calArgs, has_with_distinct(), isPushDown, fields); + } + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumVariance.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumVariance.java new file mode 100644 index 000000000..3fc09c8cb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumVariance.java @@ -0,0 +1,252 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.ptr.DoublePtr; +import io.mycat.plan.common.ptr.LongPtr; + + +/** + * /* variance(a) = + * + * = sum (ai - avg(a))^2 / count(a) ) = sum (ai^2 - 2*ai*avg(a) + avg(a)^2) / + * count(a) = (sum(ai^2) - sum(2*ai*avg(a)) + sum(avg(a)^2))/count(a) = = + * (sum(ai^2) - 2*avg(a)*sum(a) + count(a)*avg(a)^2)/count(a) = = (sum(ai^2) - + * 2*sum(a)*sum(a)/count(a) + count(a)*sum(a)^2/count(a)^2 )/count(a) = = + * (sum(ai^2) - 2*sum(a)^2/count(a) + sum(a)^2/count(a) )/count(a) = = + * (sum(ai^2) - sum(a)^2/count(a))/count(a) + * + * But, this falls prey to catastrophic cancellation. Instead, use the + * recurrence formulas + * + * M_{1} = x_{1}, ~ M_{k} = M_{k-1} + (x_{k} - M_{k-1}) / k S_{1} = 0, ~S_{k} = + * S_{k-1} + (x_{k} - M_{k-1}) times (x_{k} - M_{k}) for 2 <= k <= n ital + * variance = S_{n} / (n-1) + * + * + * @author chenzifei + * + */ + +public class ItemSumVariance extends ItemSumNum { + public ItemResult hybrid_type; + public double recurrence_m, recurrence_s; /* Used in recurrence relation. */ + public long count = 0; + public int sample; + + // pushdown variables下发的情况下计算时所需要用到的变量 + // 下发时 v[0]:count,v[1]:sum,v[2]:variance(局部) + // 依据为 variance = (sum(ai^2) - sum(a)^2/count(a))/count(a) + private double sum = 0; + private double squareSum = 0; + + /** 为了局部聚合时使用 **/ + private boolean useTransObj = false; + // 依据为 variance = (sum(ai^2) - sum(a)^2/count(a))/count(a) + private double sumAi2 = 0; + private double sumA = 0; + + private static class AggData implements Serializable { + + private static final long serialVersionUID = -5441804522036055390L; + + public double sumAi2; + public double sumA; + public long count; + + public AggData(double sumAi2, double sumA, long count) { + this.sumAi2 = sumAi2; + this.sumA = sumA; + this.count = count; + } + + } + + public ItemSumVariance(List args, int sample, boolean isPushDown, List fields) { + super(args, isPushDown, fields); + this.sample = sample; + } + + @Override + public void fixLengthAndDec() { + maybeNull = nullValue = true; + hybrid_type = ItemResult.REAL_RESULT; + decimals = NOT_FIXED_DEC; + maxLength = floatLength(decimals); + } + + @Override + public Sumfunctype sumType() { + return Sumfunctype.VARIANCE_FUNC; + } + + @Override + public void clear() { + count = 0; + sum = squareSum = 0.0; + sumA = sumAi2 = 0.0; + useTransObj = false; + } + + @Override + public Object getTransAggObj() { + AggData data = new AggData(sumAi2, sumA, count); + return data; + } + + @Override + public int getTransSize() { + return 20; + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + useTransObj = true; + AggData other = (AggData) transObj; + sumAi2 += other.sumAi2; + sumA += other.sumA; + count += other.count; + } else { + /* + * Why use a temporary variable? We don't know if it is null until + * we evaluate it, which has the side-effect of setting null_value . + */ + double nr = args.get(0).valReal().doubleValue(); + // add for transObj + sumA += nr; + sumAi2 += nr * nr; + // end add + if (!args.get(0).nullValue) { + DoublePtr r_m = new DoublePtr(recurrence_m); + DoublePtr r_s = new DoublePtr(recurrence_s); + LongPtr countPtr = new LongPtr(count); + varianceFpRecurrenceNext(r_m, r_s, countPtr, nr); + recurrence_m = r_m.get(); + recurrence_s = r_s.get(); + count = countPtr.get(); + } + } + return false; + } + + // pushdown variables下发的情况下计算时所需要用到的变量 + // 下发时 v[0]:count,v[1]:sum,v[2]:variance(局部) + // 依据为 variance = (sum(ai^2) - sum(a)^2/count(a))/count(a) + @Override + public boolean pushDownAdd(RowDataPacket row) { + // 下发的做法,依据为 variance = (sum(ai^2) - sum(a)^2/count(a))/count(a) + long partCount = args.get(0).valInt().longValue(); + double partSum = args.get(1).valReal().doubleValue(); + double partVariane = args.get(2).valReal().doubleValue(); + count += partCount; + double partSqarSum = partVariane * partCount + partSum * partSum / partCount; + squareSum += partSqarSum; + sum += partSum; + return false; + } + + @Override + public BigDecimal valReal() { + if (!isPushDown) { + /* + * 'sample' is a 1/0 boolean value. If it is 1/true, id est this is + * a sample variance call, then we should set nullness when the + * count of the items is one or zero. If it's zero, i.e. a + * population variance, then we only set nullness when the count is + * zero. + * + * Another way to read it is that 'sample' is the numerical + * threshhold, at and below which a 'count' number of items is + * called NULL. + */ + assert ((sample == 0) || (sample == 1)); + if (count <= sample) { + nullValue = true; + return BigDecimal.ZERO; + } + + nullValue = false; + if (!useTransObj) { + double db = varianceFpRecurrenceResult(recurrence_s, count, sample != 0); + return BigDecimal.valueOf(db); + } else { + double db = (sumAi2 - sumA * sumA / count) / (count - sample); + return BigDecimal.valueOf(db); + } + } else { + double db = pushDownVal(); + return BigDecimal.valueOf(db); + } + } + + private double pushDownVal() { + if (count <= sample) { + nullValue = true; + return 0.0; + } + nullValue = false; + if (count == 1) + return 0.0; + + double s = (squareSum - sum * sum / count); + + if (sample == 1) + return s / (count - 1); + else + return s / count; + + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromReal(); + } + + @Override + public void noRowsInResult() { + } + + @Override + public String funcName() { + return sample == 1 ? "VAR_SAMP" : "VARIANCE"; + } + + @Override + public void cleanup() { + count = 0; + super.cleanup(); + } + + @Override + public ItemResult resultType() { + return ItemResult.REAL_RESULT; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumVariance(newArgs, sample, false, null); + } else { + return new ItemSumVariance(calArgs, sample, isPushDown, fields); + } + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumXor.java b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumXor.java new file mode 100644 index 000000000..b86990538 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/sumfunc/ItemSumXor.java @@ -0,0 +1,69 @@ +package io.mycat.plan.common.item.function.sumfunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; + + +public class ItemSumXor extends ItemSumBit { + + public ItemSumXor(List item_par, boolean isPushDown, List fields) { + super(item_par, 0, isPushDown, fields); + } + + @Override + public boolean add(RowDataPacket row, Object transObj) { + if (transObj != null) { + AggData other = (AggData) transObj; + if (!other.isNull) + bits = bits.xor(other.bits); + } else { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) + bits = bits.xor(value); + } + return false; + } + + /** + * xor的pushdown为xor + */ + @Override + public boolean pushDownAdd(RowDataPacket row) { + BigInteger value = args.get(0).valInt(); + if (!args.get(0).nullValue) + bits = bits.xor(value); + return false; + } + + @Override + public String funcName() { + return "BIT_XOR"; + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + if (!forCalculate) { + List newArgs = cloneStructList(args); + return new ItemSumXor(newArgs, false, null); + } else { + return new ItemSumXor(calArgs, isPushDown, fields); + } + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDateAddInterval.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDateAddInterval.java new file mode 100644 index 000000000..5eed4d174 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDateAddInterval.java @@ -0,0 +1,177 @@ +/** + * + */ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalUnit; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.ptr.StringPtr; +import io.mycat.plan.common.time.INTERVAL; +import io.mycat.plan.common.time.LLDivT; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + + +public class ItemDateAddInterval extends ItemTemporalHybridFunc { + StringPtr str_value = new StringPtr(""); + private MySqlIntervalUnit int_type; + private boolean date_sub_interval; + + public ItemDateAddInterval(Item a, Item b, MySqlIntervalUnit type, boolean neg) { + super(new ArrayList()); + args.add(a); + args.add(b); + this.int_type = type; + this.date_sub_interval = neg; + } + + @Override + public final String funcName() { + return "date_add"; + } + + @Override + public void fixLengthAndDec() { + FieldTypes arg0_field_type; + + maybeNull = true; + + /* + * The field type for the result of an Item_date function is defined as + * follows: + * + * - If first arg is a MYSQL_TYPE_DATETIME result is MYSQL_TYPE_DATETIME + * - If first arg is a MYSQL_TYPE_DATE and the interval type uses hours, + * minutes or seconds then type is MYSQL_TYPE_DATETIME. - Otherwise the + * result is MYSQL_TYPE_STRING (This is because you can't know if the + * string contains a DATE, MYSQL_TIME or DATETIME argument) + */ + arg0_field_type = args.get(0).fieldType(); + int interval_dec = 0; + if (int_type == MySqlIntervalUnit.MICROSECOND || int_type == MySqlIntervalUnit.DAY_MICROSECOND + || int_type == MySqlIntervalUnit.HOUR_MICROSECOND || int_type == MySqlIntervalUnit.MINUTE_MICROSECOND + || int_type == MySqlIntervalUnit.SECOND_MICROSECOND) + interval_dec = MyTime.DATETIME_MAX_DECIMALS; + else if (int_type == MySqlIntervalUnit.SECOND && args.get(1).decimals > 0) + interval_dec = Math.min(args.get(1).decimals, MyTime.DATETIME_MAX_DECIMALS); + + if (arg0_field_type == FieldTypes.MYSQL_TYPE_DATETIME + || arg0_field_type == FieldTypes.MYSQL_TYPE_TIMESTAMP) { + int dec = Math.max(args.get(0).datetimePrecision(), interval_dec); + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATETIME_WIDTH, dec); + cached_field_type = FieldTypes.MYSQL_TYPE_DATETIME; + } else if (arg0_field_type == FieldTypes.MYSQL_TYPE_DATE) { + if (int_type == MySqlIntervalUnit.YEAR || int_type == MySqlIntervalUnit.QUARTER + || int_type == MySqlIntervalUnit.MONTH || int_type == MySqlIntervalUnit.WEEK + || int_type == MySqlIntervalUnit.DAY || int_type == MySqlIntervalUnit.YEAR_MONTH) { + cached_field_type = FieldTypes.MYSQL_TYPE_DATE; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATE_WIDTH, 0); + } else { + cached_field_type = FieldTypes.MYSQL_TYPE_DATETIME; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATE_WIDTH, interval_dec); + } + } else if (arg0_field_type == FieldTypes.MYSQL_TYPE_TIME) { + int dec = Math.max(args.get(0).timePrecision(), interval_dec); + cached_field_type = FieldTypes.MYSQL_TYPE_TIME; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_TIME_WIDTH, dec); + } else { + cached_field_type = FieldTypes.MYSQL_TYPE_STRING; + /* Behave as a usual string function when return type is VARCHAR. */ + // fix_length_and_charset(MyTime.MAX_DATETIME_FULL_WIDTH); + } + } + + /* Here arg[1] is a Item_interval object */ + private boolean get_date_internal(MySQLTime ltime, long fuzzy_date) { + INTERVAL interval = new INTERVAL(); + + if (args.get(0).getDate(ltime, MyTime.TIME_NO_ZERO_DATE) + || MyTime.get_interval_value(args.get(1), int_type, str_value, interval)) + return (nullValue = true); + + if (date_sub_interval) + interval.neg = !interval.neg; + + /* + * Make sure we return proper time_type. It's important for val_str(). + */ + if (cached_field_type == FieldTypes.MYSQL_TYPE_DATE + && ltime.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME) + MyTime.datetime_to_date(ltime); + else if (cached_field_type == FieldTypes.MYSQL_TYPE_DATETIME + && ltime.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) + MyTime.date_to_datetime(ltime); + + if ((nullValue = MyTime.date_add_interval(ltime, int_type, interval))) + return true; + return false; + } + + private boolean get_time_internal(MySQLTime ltime) { + INTERVAL interval = new INTERVAL(); + if ((nullValue = args.get(0).getTime(ltime) + || MyTime.get_interval_value(args.get(1), int_type, str_value, interval))) + return true; + + if (date_sub_interval) + interval.neg = !interval.neg; + + long usec1 = ((((ltime.day * 24 + ltime.hour) * 60 + ltime.minute) * 60 + ltime.second) * 1000000L + + ltime.second_part) * (ltime.neg ? -1 : 1); + long usec2 = ((((interval.day * 24 + interval.hour) * 60 + interval.minute) * 60 + interval.second) * 1000000L + + interval.second_part) * (interval.neg ? -1 : 1); + long diff = usec1 + usec2; + LLDivT seconds = new LLDivT(); + seconds.quot = diff / 1000000; + seconds.rem = diff % 1000000 + * 1000; /* time.second_part= lldiv.rem / 1000 */ + if ((nullValue = (interval.year != 0 || interval.month != 0 || MyTime.sec_to_time(seconds, ltime)))) { + logger.warn("datetime function overflow!"); + return true; + } + return false; + } + + @Override + protected boolean val_datetime(MySQLTime ltime, long fuzzy_date) { + if (cached_field_type != FieldTypes.MYSQL_TYPE_TIME) + return get_date_internal(ltime, fuzzy_date | MyTime.TIME_NO_ZERO_DATE); + return get_time_internal(ltime); + } + + @Override + public SQLExpr toExpression() { + String funcName = funcName(); + if (date_sub_interval){ + funcName = "date_sub"; + } + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName); + method.addParameter(args.get(0).toExpression()); + MySqlIntervalExpr intervalExpr = new MySqlIntervalExpr(); + intervalExpr.setValue(args.get(1).toExpression()); + intervalExpr.setUnit(int_type); + method.addParameter(intervalExpr); + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemDateAddInterval(newArgs.get(0), newArgs.get(1), int_type, this.date_sub_interval); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDateFunc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDateFunc.java new file mode 100644 index 000000000..2c9d9f097 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDateFunc.java @@ -0,0 +1,68 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +/** + * Abstract class for functions returning DATE values. + * + */ +public abstract class ItemDateFunc extends ItemTemporalFunc { + + public ItemDateFunc(List args) { + super(args); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DATE; + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromDate(ltime); + } + + @Override + public String valStr() { + return valStringFromDate(); + } + + @Override + public BigInteger valInt() { + return BigInteger.valueOf(valIntFromDate()); + } + + @Override + public long valDateTemporal() { + MySQLTime ltime = new MySQLTime(); + return getDate(ltime, MyTime.TIME_FUZZY_DATE) ? 0 : (MyTime.TIME_to_longlong_date_packed(ltime)); + } + + @Override + public BigDecimal valReal() { + return new BigDecimal(valInt()); + } + + @Override + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATE_WIDTH, 0); + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromDate(); + } + + // All date functions must implement get_date() + // to avoid use of generic Item::get_date() + // which converts to string and then parses the string as DATE. + public abstract boolean getDate(MySQLTime res, long fuzzy_date); +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDatetimeFunc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDatetimeFunc.java new file mode 100644 index 000000000..6b74109f5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemDatetimeFunc.java @@ -0,0 +1,60 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +public abstract class ItemDatetimeFunc extends ItemTemporalFunc { + + public ItemDatetimeFunc(List args) { + super(args); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_DATETIME; + } + + @Override + public BigDecimal valReal() { + return valRealFromDecimal(); + } + + @Override + public String valStr() { + return valStringFromDatetime(); + } + + @Override + public BigInteger valInt() { + return BigInteger.valueOf(valIntFromDatetime()); + } + + @Override + public long valDateTemporal() { + MySQLTime ltime = new MySQLTime(); + return getDate(ltime, MyTime.TIME_FUZZY_DATE) ? 0L : MyTime.TIME_to_longlong_datetime_packed(ltime); + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromDate(); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromDatetime(ltime); + } + + // All datetime functions must implement get_date() + // to avoid use of generic Item::get_date() + // which converts to string and then parses the string as DATETIME. + public abstract boolean getDate(MySQLTime res, long fuzzy_date); + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemExtract.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemExtract.java new file mode 100644 index 000000000..8421bcc7e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemExtract.java @@ -0,0 +1,214 @@ +/** + * + */ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlExtractExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalUnit; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemExtract extends ItemIntFunc { + private MySqlIntervalUnit int_type; + private boolean date_value; + + public ItemExtract(Item a, MySqlIntervalUnit int_type) { + super(a); + this.int_type = int_type; + } + + @Override + public final String funcName() { + return "extract"; + } + + @Override + public Functype functype() { + return Functype.EXTRACT_FUNC; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; // If wrong date + switch (int_type) { + case YEAR: + maxLength = 4; + date_value = true; + break; + case YEAR_MONTH: + maxLength = 6; + date_value = true; + break; + case QUARTER: + maxLength = 2; + date_value = true; + break; + case MONTH: + maxLength = 2; + date_value = true; + break; + case WEEK: + maxLength = 2; + date_value = true; + break; + case DAY: + maxLength = 2; + date_value = true; + break; + case DAY_HOUR: + maxLength = 9; + date_value = false; + break; + case DAY_MINUTE: + maxLength = 11; + date_value = false; + break; + case DAY_SECOND: + maxLength = 13; + date_value = false; + break; + case HOUR: + maxLength = 2; + date_value = false; + break; + case HOUR_MINUTE: + maxLength = 4; + date_value = false; + break; + case HOUR_SECOND: + maxLength = 6; + date_value = false; + break; + case MINUTE: + maxLength = 2; + date_value = false; + break; + case MINUTE_SECOND: + maxLength = 4; + date_value = false; + break; + case SECOND: + maxLength = 2; + date_value = false; + break; + case MICROSECOND: + maxLength = 2; + date_value = false; + break; + case DAY_MICROSECOND: + maxLength = 20; + date_value = false; + break; + case HOUR_MICROSECOND: + maxLength = 13; + date_value = false; + break; + case MINUTE_MICROSECOND: + maxLength = 11; + date_value = false; + break; + case SECOND_MICROSECOND: + maxLength = 9; + date_value = false; + break; + } + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + int year; + int week_format; + long neg; + if (date_value) { + if (getArg0Date(ltime, MyTime.TIME_FUZZY_DATE)) + return BigInteger.ZERO; + neg = 1; + } else { + if (getArg0Time(ltime)) + return BigInteger.ZERO; + ; + neg = ltime.neg ? -1 : 1; + } + switch (int_type) { + case YEAR: + return BigInteger.valueOf(ltime.year); + case YEAR_MONTH: + return BigInteger.valueOf(ltime.year * 100L + ltime.month); + case QUARTER: + return BigInteger.valueOf((ltime.month + 2) / 3); + case MONTH: + return BigInteger.valueOf(ltime.month); + case WEEK: { + week_format = MyTime.WEEK_MONDAY_FIRST; + LongPtr lptr = new LongPtr(0); + long ret = MyTime.calc_week(ltime, MyTime.week_mode(week_format), lptr); + year = (int) lptr.get(); + return BigInteger.valueOf(ret); + + } + case DAY: + return BigInteger.valueOf(ltime.day); + case DAY_HOUR: + return BigInteger.valueOf((ltime.day * 100L + ltime.hour) * neg); + case DAY_MINUTE: + return (BigInteger.valueOf((ltime.day * 10000L + ltime.hour * 100L + ltime.minute) * neg)); + case DAY_SECOND: + return BigInteger + .valueOf((ltime.day * 1000000L + (ltime.hour * 10000L + ltime.minute * 100 + ltime.second)) * neg); + case HOUR: + return BigInteger.valueOf(ltime.hour * neg); + case HOUR_MINUTE: + return BigInteger.valueOf((ltime.hour * 100 + ltime.minute) * neg); + case HOUR_SECOND: + return BigInteger.valueOf((ltime.hour * 10000 + ltime.minute * 100 + ltime.second) * neg); + case MINUTE: + return BigInteger.valueOf(ltime.minute * neg); + case MINUTE_SECOND: + return BigInteger.valueOf((ltime.minute * 100 + ltime.second) * neg); + case SECOND: + return BigInteger.valueOf(ltime.second * neg); + case MICROSECOND: + return BigInteger.valueOf(ltime.second_part * neg); + case DAY_MICROSECOND: + return BigInteger.valueOf( + ((ltime.day * 1000000L + ltime.hour * 10000L + ltime.minute * 100 + ltime.second) * 1000000L + + ltime.second_part) * neg); + case HOUR_MICROSECOND: + return BigInteger.valueOf( + ((ltime.hour * 10000L + ltime.minute * 100 + ltime.second) * 1000000L + ltime.second_part) * neg); + case MINUTE_MICROSECOND: + return BigInteger.valueOf((((ltime.minute * 100 + ltime.second)) * 1000000L + ltime.second_part) * neg); + case SECOND_MICROSECOND: + return BigInteger.valueOf((ltime.second * 1000000L + ltime.second_part) * neg); + } + return BigInteger.ZERO; // Impossible + } + + @Override + public SQLExpr toExpression() { + MySqlExtractExpr extract = new MySqlExtractExpr(); + extract.setValue(args.get(0).toExpression()); + extract.setUnit(int_type); + return extract; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemExtract(newArgs.get(0), int_type); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncAddTime.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncAddTime.java new file mode 100644 index 000000000..707a1d4e2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncAddTime.java @@ -0,0 +1,132 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +/* + * ADDTIME(expr1,expr2) + * ADDTIME() adds expr2 to expr1 and returns the result. + * expr1 is a time or datetime expression, and expr2 is a time expression. + */ +public class ItemFuncAddTime extends ItemTemporalHybridFunc { + + boolean isDate = false; + int sign = 1; + + public ItemFuncAddTime(List args, boolean typeArg, boolean negArg) { + super(args); + this.isDate = typeArg; + this.sign = negArg ? -1 : 1; + } + + @Override + public final String funcName() { + return this.sign == 1 ? "addtime" : "subtime"; + } + + @Override + public void fixLengthAndDec() { + /* + * The field type for the result of an Item_func_add_time function is + * defined as follows: + * + * - If first arg is a MYSQL_TYPE_DATETIME or MYSQL_TYPE_TIMESTAMP + * result is MYSQL_TYPE_DATETIME - If first arg is a MYSQL_TYPE_TIME + * result is MYSQL_TYPE_TIME - Otherwise the result is MYSQL_TYPE_STRING + * + * TODO: perhaps it should also return MYSQL_TYPE_DATETIME when the + * first argument is MYSQL_TYPE_DATE. + */ + if (args.get(0).fieldType() == FieldTypes.MYSQL_TYPE_TIME && !isDate) { + cached_field_type = FieldTypes.MYSQL_TYPE_TIME; + } else if (args.get(0).isTemporalWithDateAndTime() || isDate) { + cached_field_type = FieldTypes.MYSQL_TYPE_DATETIME; + } else { + cached_field_type = FieldTypes.MYSQL_TYPE_STRING; + } + maybeNull = true; + } + + @Override + protected boolean val_datetime(MySQLTime time, long fuzzy_date) { + MySQLTime l_time1 = new MySQLTime(); + MySQLTime l_time2 = new MySQLTime(); + boolean is_time = false; + long days; + LongPtr seconds = new LongPtr(0); + LongPtr microseconds = new LongPtr(0); + int l_sign = sign; + + nullValue = false; + if (cached_field_type == FieldTypes.MYSQL_TYPE_DATETIME) // TIMESTAMP + // function + { + if (getArg0Date(l_time1, fuzzy_date) || args.get(1).getTime(l_time2) + || l_time1.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME + || l_time2.time_type != MySQLTimestampType.MYSQL_TIMESTAMP_TIME) { + nullValue = true; + return true; + } + } else // ADDTIME function + { + if (args.get(0).getTime(l_time1) || args.get(1).getTime(l_time2) + || l_time2.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME) { + nullValue = true; + return true; + } + is_time = (l_time1.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + } + if (l_time1.neg != l_time2.neg) + l_sign = -l_sign; + + time.set_zero_time(time.time_type); + + time.neg = MyTime.calc_time_diff(l_time1, l_time2, -l_sign, seconds, microseconds); + + /* + * If first argument was negative and diff between arguments is non-zero + * we need to swap sign to get proper result. + */ + if (l_time1.neg && (seconds.get() != 0 || microseconds.get() != 0)) + time.neg = time.neg ? false : true; // Swap sign of result + + if (!is_time && time.neg) { + nullValue = true; + return true; + } + + days = (long) (seconds.get() / MyTime.SECONDS_IN_24H); + + MyTime.calc_time_from_sec(time, seconds.get() % MyTime.SECONDS_IN_24H, microseconds.get()); + + if (!is_time) { + LongPtr lpyear = new LongPtr(0); + LongPtr lpmonth = new LongPtr(0); + LongPtr lpday = new LongPtr(0); + MyTime.get_date_from_daynr(days, lpyear, lpmonth, lpday); + time.year = lpyear.get(); + time.month = lpmonth.get(); + time.day = lpday.get(); + time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + if (time.day != 0) + return false; + nullValue = true; + return true; + } + time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + time.hour += days * 24; + return false; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncAddTime(realArgs, isDate, sign == -1 ? true : false); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncConvTz.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncConvTz.java new file mode 100644 index 000000000..9784d7ea1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncConvTz.java @@ -0,0 +1,44 @@ +/** + * + */ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + +/** + * timezone change,not support + * + */ +public class ItemFuncConvTz extends ItemDatetimeFunc { + + public ItemFuncConvTz(List args) { + super(args); + } + + @Override + public final String funcName() { + return "convert_tz"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unsupported function convert_tz!"); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncConvTz(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurdateLocal.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurdateLocal.java new file mode 100644 index 000000000..338608b46 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurdateLocal.java @@ -0,0 +1,30 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncCurdateLocal extends ItemDateFunc { + + public ItemFuncCurdateLocal(List args) { + super(args); + } + + @Override + public final String funcName() { + return "curdate"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + java.util.Calendar cal = java.util.Calendar.getInstance(); + ltime.year = cal.get(java.util.Calendar.YEAR); + ltime.month = cal.get(java.util.Calendar.MONTH); + ltime.day = cal.get(java.util.Calendar.DAY_OF_MONTH); + ltime.hour = ltime.minute = ltime.second = ltime.second_part = 0; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurdateUtc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurdateUtc.java new file mode 100644 index 000000000..2772f89e5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurdateUtc.java @@ -0,0 +1,30 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncCurdateUtc extends ItemDateFunc { + + public ItemFuncCurdateUtc(List args) { + super(args); + } + + @Override + public final String funcName() { + return "utc_date"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + java.util.Calendar cal = getUTCTime(); + ltime.year = cal.get(java.util.Calendar.YEAR); + ltime.month = cal.get(java.util.Calendar.MONTH); + ltime.day = cal.get(java.util.Calendar.DAY_OF_MONTH); + ltime.hour = ltime.minute = ltime.second = ltime.second_part = 0; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurtimeLocal.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurtimeLocal.java new file mode 100644 index 000000000..1f0edb851 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurtimeLocal.java @@ -0,0 +1,40 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncCurtimeLocal extends ItemTimeFunc { + + public ItemFuncCurtimeLocal(List args) { + super(args); + } + + @Override + public final String funcName() { + return "curtime"; + } + + @Override + public void fixLengthAndDec() { + /* + * We use 8 instead of MAX_TIME_WIDTH (which is 10) because: - there is + * no sign - hour is in the 2-digit range + */ + fixLengthAndDecAndCharsetDatetime(8, decimals); + } + + @Override + public boolean getTime(MySQLTime ltime) { + java.util.Calendar cal = java.util.Calendar.getInstance(); + ltime.year = ltime.month = ltime.day = 0; + ltime.hour = cal.get(java.util.Calendar.HOUR_OF_DAY); + ltime.minute = cal.get(java.util.Calendar.MINUTE); + ltime.second = cal.get(java.util.Calendar.SECOND); + ltime.second_part = cal.get(java.util.Calendar.MILLISECOND) * 1000; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurtimeUtc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurtimeUtc.java new file mode 100644 index 000000000..a94a6a792 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncCurtimeUtc.java @@ -0,0 +1,40 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncCurtimeUtc extends ItemTimeFunc { + + public ItemFuncCurtimeUtc(List args) { + super(args); + } + + @Override + public final String funcName() { + return "utc_time"; + } + + @Override + public void fixLengthAndDec() { + /* + * We use 8 instead of MAX_TIME_WIDTH (which is 10) because: - there is + * no sign - hour is in the 2-digit range + */ + fixLengthAndDecAndCharsetDatetime(8, decimals); + } + + @Override + public boolean getTime(MySQLTime ltime) { + java.util.Calendar cal = getUTCTime(); + ltime.year = ltime.month = ltime.day = 0; + ltime.hour = cal.get(java.util.Calendar.HOUR_OF_DAY); + ltime.minute = cal.get(java.util.Calendar.MINUTE); + ltime.second = cal.get(java.util.Calendar.SECOND); + ltime.second_part = cal.get(java.util.Calendar.MILLISECOND) * 1000; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDate.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDate.java new file mode 100644 index 000000000..fddda2c43 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDate.java @@ -0,0 +1,29 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncDate extends ItemDateFunc { + + public ItemFuncDate(List args) { + super(args); + } + + @Override + public final String funcName() { + return "date"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + if (nullValue = args.get(0).nullValue) { + return true; + } + nullValue = getArg0Date(ltime, fuzzy_date); + return nullValue; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDateFormat.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDateFormat.java new file mode 100644 index 000000000..2ec5668dc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDateFormat.java @@ -0,0 +1,147 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; +import io.mycat.plan.common.ptr.StringPtr; +import io.mycat.plan.common.time.DateTimeFormat; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncDateFormat extends ItemStrFunc { + private boolean isTimeFormat; + + public ItemFuncDateFormat(List args, boolean isTimeFormat) { + super(args); + this.isTimeFormat = isTimeFormat; + } + + @Override + public final String funcName() { + return "date_format"; + } + + @Override + public String valStr() { + String format; + MySQLTime l_time = new MySQLTime(); + int size; + if (!isTimeFormat) { + if (getArg0Date(l_time, MyTime.TIME_FUZZY_DATE)) + return null; + } else { + if (getArg0Time(l_time)) + return null; + l_time.year = l_time.month = l_time.day = 0; + } + if ((format = args.get(1).valStr()) == null || format.length() == 0) { + nullValue = true; + return null; + } + size = format_length(format); + if (size < MyTime.MAX_DATE_STRING_REP_LENGTH) + size = MyTime.MAX_DATE_STRING_REP_LENGTH; + DateTimeFormat date_time_format = new DateTimeFormat(); + date_time_format.format = format; + StringPtr strPtr = new StringPtr(""); + if (!MyTime.make_date_time(date_time_format, l_time, + isTimeFormat ? MySQLTimestampType.MYSQL_TIMESTAMP_TIME : MySQLTimestampType.MYSQL_TIMESTAMP_DATE, + strPtr)) { + return strPtr.get(); + } + nullValue = true; + return null; + + } + + private int format_length(final String sformat) { + char[] format = sformat.toCharArray(); + int size = 0; + int ptr = 0; + int end = format.length; + + for (; ptr != end; ptr++) { + if (format[ptr] != '%' || ptr == end - 1) + size++; + else { + switch (format[++ptr]) { + case 'M': /* month, textual */ + case 'W': /* day (of the week), textual */ + size += 64; /* large for UTF8 locale data */ + break; + case 'D': /* day (of the month), numeric plus english suffix */ + case 'Y': /* year, numeric, 4 digits */ + case 'x': /* Year, used with 'v' */ + case 'X': /* + * Year, used with 'v, where week starts with + * Monday' + */ + size += 4; + break; + case 'a': /* locale's abbreviated weekday name (Sun..Sat) */ + case 'b': /* locale's abbreviated month name (Jan.Dec) */ + size += 32; /* large for UTF8 locale data */ + break; + case 'j': /* day of year (001..366) */ + size += 3; + break; + case 'U': /* week (00..52) */ + case 'u': /* week (00..52), where week starts with Monday */ + case 'V': /* week 1..53 used with 'x' */ + case 'v': /* + * week 1..53 used with 'x', where week starts with + * Monday + */ + case 'y': /* year, numeric, 2 digits */ + case 'm': /* month, numeric */ + case 'd': /* day (of the month), numeric */ + case 'h': /* hour (01..12) */ + case 'I': /* --||-- */ + case 'i': /* minutes, numeric */ + case 'l': /* hour ( 1..12) */ + case 'p': /* locale's AM or PM */ + case 'S': /* second (00..61) */ + case 's': /* seconds, numeric */ + case 'c': /* month (0..12) */ + case 'e': /* day (0..31) */ + size += 2; + break; + case 'k': /* hour ( 0..23) */ + case 'H': /* + * hour (00..23; value > 23 OK, padding always + * 2-digit) + */ + size += 7; /* + * docs allow > 23, range depends on + * sizeof(unsigned int) + */ + break; + case 'r': /* time, 12-hour (hh:mm:ss [AP]M) */ + size += 11; + break; + case 'T': /* time, 24-hour (hh:mm:ss) */ + size += 8; + break; + case 'f': /* microseconds */ + size += 6; + break; + case 'w': /* day (of the week), numeric */ + case '%': + default: + size++; + break; + } + } + } + return size; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDateFormat(realArgs, isTimeFormat); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDatediff.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDatediff.java new file mode 100644 index 000000000..ae07b2b37 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDatediff.java @@ -0,0 +1,44 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncDatediff extends ItemIntFunc { + + public ItemFuncDatediff(List args) { + super(args); + } + + @Override + public final String funcName() { + return "datediff"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime1 = new MySQLTime(); + MySQLTime ltime2 = new MySQLTime(); + if (args.get(0).nullValue || args.get(1).nullValue || args.get(0).getDate(ltime1, MyTime.TIME_FUZZY_DATE) + || args.get(1).getDate(ltime2, MyTime.TIME_FUZZY_DATE)) { + nullValue = true; + return BigInteger.ZERO; + } + java.util.Calendar cal1 = ltime1.toCalendar(); + java.util.Calendar cal2 = ltime2.toCalendar(); + long diff = (cal1.getTimeInMillis() - cal2.getTimeInMillis()) / (24 * 3600); + return BigInteger.valueOf(diff); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDatediff(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayname.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayname.java new file mode 100644 index 000000000..37214fd58 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayname.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncDayname extends ItemStrFunc { + + public ItemFuncDayname(List args) { + super(args); + } + + @Override + public final String funcName() { + return "dayname"; + } + + @Override + public String valStr() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return null; + + long weekday = MyTime.calc_weekday(MyTime.calc_daynr(ltime.year, ltime.month, ltime.day), false); + return MyTime.day_names[(int) weekday]; + } + + @Override + public void fixLengthAndDec() { + maxLength = 9; + decimals = 0; + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDayname(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofmonth.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofmonth.java new file mode 100644 index 000000000..43f1caeb8 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofmonth.java @@ -0,0 +1,41 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncDayofmonth extends ItemIntFunc { + + public ItemFuncDayofmonth(List args) { + super(args); + } + + @Override + public final String funcName() { + return "dayofmonth"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Date(ltime, MyTime.TIME_FUZZY_DATE) ? BigInteger.ZERO : BigInteger.valueOf(ltime.day); + } + + @Override + public void fixLengthAndDec() { + maxLength = (2); /* 1..31 */ + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDayofmonth(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofweek.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofweek.java new file mode 100644 index 000000000..54fe616e7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofweek.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncDayofweek extends ItemIntFunc { + + public ItemFuncDayofweek(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "dayofweek"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_FUZZY_DATE)) { + return BigInteger.ZERO; + } else { + java.util.Calendar cal = ltime.toCalendar(); + return BigInteger.valueOf(cal.get(java.util.Calendar.DAY_OF_WEEK)); + } + } + + @Override + public void fixLengthAndDec() { + maxLength = (1); /* 1..31 */ + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDayofweek(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofyear.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofyear.java new file mode 100644 index 000000000..10dbc3d4a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncDayofyear.java @@ -0,0 +1,43 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncDayofyear extends ItemIntFunc { + + public ItemFuncDayofyear(List args) { + super(args); + } + + @Override + public final String funcName() { + return "dayofyear"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return BigInteger.ZERO; + return BigInteger.valueOf( + MyTime.calc_daynr(ltime.year, ltime.month, ltime.day) - MyTime.calc_daynr(ltime.year, 1, 1) + 1); + } + + @Override + public void fixLengthAndDec() { + maxLength = (3); + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncDayofyear(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncFromDays.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncFromDays.java new file mode 100644 index 000000000..0a919d001 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncFromDays.java @@ -0,0 +1,50 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + + +public class ItemFuncFromDays extends ItemDateFunc { + + public ItemFuncFromDays(List args) { + super(args); + } + + @Override + public final String funcName() { + return "from_days"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + long value = args.get(0).valInt().longValue(); + if ((nullValue = args.get(0).nullValue)) + return true; + ltime.set_zero_time(ltime.time_type); + LongPtr lpyear = new LongPtr(0); + LongPtr lpmonth = new LongPtr(0); + LongPtr lpday = new LongPtr(0); + MyTime.get_date_from_daynr((long) value, lpyear, lpmonth, lpday); + ltime.year = lpyear.get(); + ltime.month = lpmonth.get(); + ltime.day = lpday.get(); + + if ((nullValue = ((fuzzy_date & MyTime.TIME_NO_ZERO_DATE) != 0) + && (ltime.year == 0 || ltime.month == 0 || ltime.day == 0))) + return true; + + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + return false; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncFromDays(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncFromUnixtime.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncFromUnixtime.java new file mode 100644 index 000000000..6e6e6522c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncFromUnixtime.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.Calendar; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +public class ItemFuncFromUnixtime extends ItemDatetimeFunc { + + public ItemFuncFromUnixtime(List args) { + super(args); + } + + @Override + public final String funcName() { + return "from_unixtime"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + long milseconds = args.get(0).valInt().longValue() * 1000; + if (nullValue = args.get(0).nullValue) + return true; + java.util.Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(milseconds); + ltime.setCal(cal); + return false; + } + + @Override + public void fixLengthAndDec() { + + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncFromUnixtime(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncHour.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncHour.java new file mode 100644 index 000000000..3c62aacb8 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncHour.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFuncHour extends ItemIntFunc { + + public ItemFuncHour(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "hour"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Time(ltime) ? BigInteger.ZERO : BigInteger.valueOf(ltime.hour); + } + + @Override + public void fixLengthAndDec() { + maxLength = (2); /* 0..23 */ + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncLastDay.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncLastDay.java new file mode 100644 index 000000000..6b0116eac --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncLastDay.java @@ -0,0 +1,49 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncLastDay extends ItemDateFunc { + + public ItemFuncLastDay(List args) { + super(args); + } + + @Override + public final String funcName() { + return "last_day"; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + if ((nullValue = getArg0Date(ltime, fuzzy_date))) + return true; + + if (ltime.month == 0) { + /* + * Cannot calculate last day for zero month. Let's print a warning + * and return NULL. + */ + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + return (nullValue = true); + } + + int month_idx = (int) ltime.month - 1; + ltime.day = MyTime.days_in_month[month_idx]; + if (month_idx == 1 && MyTime.calc_days_in_year(ltime.year) == 366) + ltime.day = 29; + MyTime.datetime_to_date(ltime); + return false; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncLastDay(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMakedate.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMakedate.java new file mode 100644 index 000000000..fdffe2a86 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMakedate.java @@ -0,0 +1,70 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncMakedate extends ItemDateFunc { + + public ItemFuncMakedate(List args) { + super(args); + } + + @Override + public final String funcName() { + return "makedate"; + } + + @Override + /** + * MAKEDATE(a,b) is a date function that creates a date value from a year + * and day value. + * + * NOTES: As arguments are integers, we can't know if the year is a 2 digit + * or 4 digit year. In this case we treat all years < 100 as 2 digit years. + * Ie, this is not safe for dates between 0000-01-01 and 0099-12-31 + */ + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + long daynr = args.get(1).valInt().longValue(); + long year = args.get(0).valInt().longValue(); + long days; + + if (args.get(0).nullValue || args.get(1).nullValue || year < 0 || year > 9999 || daynr <= 0) { + nullValue = true; + return true; + } + + if (year < 100) + year = MyTime.year_2000_handling(year); + + days = MyTime.calc_daynr(year, 1, 1) + daynr - 1; + /* Day number from year 0 to 9999-12-31 */ + if (days >= 0 && days <= MyTime.MAX_DAY_NUMBER) { + nullValue = false; + LongPtr lpyear = new LongPtr(0); + LongPtr lpmonth = new LongPtr(0); + LongPtr lpday = new LongPtr(0); + MyTime.get_date_from_daynr(days, lpyear, lpmonth, lpday); + ltime.year = lpyear.get(); + ltime.month = lpmonth.get(); + ltime.day = lpday.get(); + ltime.neg = false; + ltime.hour = ltime.minute = ltime.second = ltime.second_part = 0; + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + return false; + } + nullValue = true; + return true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncMakedate(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMaketime.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMaketime.java new file mode 100644 index 000000000..b00913658 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMaketime.java @@ -0,0 +1,66 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncMaketime extends ItemTimeFunc { + + public ItemFuncMaketime(List args) { + super(args); + } + + @Override + public final String funcName() { + return "maketime"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_TIME_WIDTH, args.get(2).decimals); + } + + /** + * MAKETIME(h,m,s) is a time function that calculates a time value from the + * total number of hours, minutes, and seconds. Result: Time value + */ + @Override + public boolean getTime(MySQLTime ltime) { + long hour = args.get(0).valInt().longValue(); + long minute = args.get(1).valInt().longValue(); + BigDecimal sec = args.get(2).valDecimal(); + long scdquot = sec.longValue(); + long scdrem = (long) ((sec.doubleValue() - scdquot) * 1000000); + + if ((nullValue = (args.get(0).nullValue || args.get(1).nullValue || args.get(2).nullValue || sec == null + || minute < 0 || minute > 59 || scdquot < 0 || scdquot > 59 || scdrem < 0))) + return true; + + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + + /* Check for integer overflows */ + if (hour < 0) { + ltime.neg = true; + } + + { + ltime.hour = ((hour < 0 ? -hour : hour)); + ltime.minute = minute; + ltime.second = scdquot; + ltime.second_part = scdrem; + return false; + } + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncMaketime(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMicrosecond.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMicrosecond.java new file mode 100644 index 000000000..97a180e95 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMicrosecond.java @@ -0,0 +1,32 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFuncMicrosecond extends ItemIntFunc { + + public ItemFuncMicrosecond(List args) { + super(args); + } + + @Override + public final String funcName() { + return "microsecond"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Time(ltime) ? BigInteger.ZERO : BigInteger.valueOf(ltime.second_part); + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMinute.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMinute.java new file mode 100644 index 000000000..3e3d5b780 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMinute.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFuncMinute extends ItemIntFunc { + + public ItemFuncMinute(List args) { + super(args); + } + + @Override + public final String funcName() { + return "minute"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Time(ltime) ? BigInteger.ZERO : BigInteger.valueOf(ltime.minute); + } + + @Override + public void fixLengthAndDec() { + maxLength = (2); /* 0..59 */ + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMonth.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMonth.java new file mode 100644 index 000000000..58d376ead --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMonth.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncMonth extends ItemIntFunc { + + public ItemFuncMonth(List args) { + super(args); + } + + @Override + public final String funcName() { + return "month"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Date(ltime, MyTime.TIME_FUZZY_DATE) ? BigInteger.ZERO : BigInteger.valueOf(ltime.month); + } + + @Override + public void fixLengthAndDec() { + maxLength = (2); + maybeNull = true; + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMonthname.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMonthname.java new file mode 100644 index 000000000..cbb0785d1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncMonthname.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncMonthname extends ItemStrFunc { + + public ItemFuncMonthname(List args) { + super(args); + } + + @Override + public final String funcName() { + return "monthname"; + } + + @Override + public String valStr() { + MySQLTime ltime = new MySQLTime(); + + if ((nullValue = (getArg0Date(ltime, MyTime.TIME_FUZZY_DATE) || ltime.month == 0))) + return null; + return MyTime.month_names[(int) ltime.month - 1]; + } + + public void fixLengthAndDec() { + maxLength = 9; + decimals = 0; + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncMonthname(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncNowLocal.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncNowLocal.java new file mode 100644 index 000000000..2160ae111 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncNowLocal.java @@ -0,0 +1,38 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncNowLocal extends ItemDatetimeFunc { + + public ItemFuncNowLocal(List args) { + super(args); + } + + @Override + public final String funcName() { + return "now"; + } + + @Override + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATETIME_WIDTH, decimals); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + java.util.Calendar cal = java.util.Calendar.getInstance(); + ltime.year = cal.get(java.util.Calendar.YEAR); + ltime.month = cal.get(java.util.Calendar.MONTH); + ltime.day = cal.get(java.util.Calendar.DAY_OF_MONTH); + ltime.hour = cal.get(java.util.Calendar.HOUR_OF_DAY); + ltime.minute = cal.get(java.util.Calendar.MINUTE); + ltime.second = cal.get(java.util.Calendar.SECOND); + ltime.second_part = cal.get(java.util.Calendar.MILLISECOND) * 1000; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncNowUtc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncNowUtc.java new file mode 100644 index 000000000..9528bd672 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncNowUtc.java @@ -0,0 +1,37 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; +public class ItemFuncNowUtc extends ItemDatetimeFunc { + + public ItemFuncNowUtc(List args) { + super(args); + } + + @Override + public final String funcName() { + return "utc_timestamp"; + } + + @Override + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATETIME_WIDTH, decimals); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + java.util.Calendar cal = getUTCTime(); + ltime.year = cal.get(java.util.Calendar.YEAR); + ltime.month = cal.get(java.util.Calendar.MONTH); + ltime.day = cal.get(java.util.Calendar.DAY_OF_MONTH); + ltime.hour = cal.get(java.util.Calendar.HOUR_OF_DAY); + ltime.minute = cal.get(java.util.Calendar.MINUTE); + ltime.second = cal.get(java.util.Calendar.SECOND); + ltime.second_part = cal.get(java.util.Calendar.MILLISECOND) * 1000; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncPeriodAdd.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncPeriodAdd.java new file mode 100644 index 000000000..e9a72a954 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncPeriodAdd.java @@ -0,0 +1,41 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncPeriodAdd extends ItemIntFunc { + + public ItemFuncPeriodAdd(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "period_add"; + } + + @Override + public BigInteger valInt() { + long period = args.get(0).valInt().longValue(); + long months = args.get(1).valInt().longValue(); + + if ((nullValue = args.get(0).nullValue || args.get(1).nullValue) || period == 0L) + return BigInteger.ZERO; /* purecov: inspected */ + return BigInteger.valueOf(MyTime.convert_month_to_period(MyTime.convert_period_to_month(period) + months)); + } + + @Override + public void fixLengthAndDec() { + maxLength = 6; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncPeriodAdd(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncPeriodDiff.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncPeriodDiff.java new file mode 100644 index 000000000..142976131 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncPeriodDiff.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncPeriodDiff extends ItemIntFunc { + + public ItemFuncPeriodDiff(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "period_diff"; + } + + @Override + public BigInteger valInt() { + long period1 = args.get(0).valInt().longValue(); + long period2 = args.get(1).valInt().longValue(); + + if ((nullValue = args.get(0).nullValue || args.get(1).nullValue)) + return BigInteger.ZERO; /* purecov: inspected */ + return BigInteger.valueOf(MyTime.convert_period_to_month(period1) - MyTime.convert_period_to_month(period2)); + } + + @Override + public void fixLengthAndDec() { + maxLength = 6; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncPeriodDiff(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncQuarter.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncQuarter.java new file mode 100644 index 000000000..703d4a279 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncQuarter.java @@ -0,0 +1,36 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncQuarter extends ItemIntFunc { + + public ItemFuncQuarter(List args) { + super(args); + } + + @Override + public final String funcName() { + return "quarter"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_FUZZY_DATE)) + return BigInteger.ZERO; + return BigInteger.valueOf((ltime.month + 2) / 3); + } + + @Override + public void fixLengthAndDec() { + fixCharLength(1); /* 1..4 */ + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSecToTime.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSecToTime.java new file mode 100644 index 000000000..686c0daca --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSecToTime.java @@ -0,0 +1,52 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncSecToTime extends ItemTimeFunc { + + public ItemFuncSecToTime(List args) { + super(args); + } + + @Override + public final String funcName() { + return "sec_to_time"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_TIME_WIDTH, args.get(0).decimals); + } + + @Override + public boolean getTime(MySQLTime ltime) { + BigDecimal val = args.get(0).valDecimal(); + if (nullValue = args.get(0).nullValue) { + return true; + } + long seconds = val.longValue(); + long microseconds = (long) ((val.doubleValue() - val.longValue()) * 1000000); + if (seconds > MyTime.TIME_MAX_SECOND) { + ltime.set_max_hhmmss(); + return true; + } + ltime.hour = (seconds / 3600); + long sec = (seconds % 3600); + ltime.minute = sec / 60; + ltime.second = sec % 60; + ltime.second_part = microseconds; + return false; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncSecToTime(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSecond.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSecond.java new file mode 100644 index 000000000..6e18af1bb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSecond.java @@ -0,0 +1,33 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFuncSecond extends ItemIntFunc { + + public ItemFuncSecond(List args) { + super(args); + } + + @Override + public final String funcName(){ + return "second"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Time(ltime) ? BigInteger.ZERO : BigInteger.valueOf(ltime.second); + } + + @Override + public void fixLengthAndDec() { + fixCharLength(2); /* 0..59 */ + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncStrToDate.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncStrToDate.java new file mode 100644 index 000000000..e8d1fc2ab --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncStrToDate.java @@ -0,0 +1,157 @@ +/** + * + */ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.DateTimeFormat; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncStrToDate extends ItemTemporalHybridFunc { + private MySQLTimestampType cached_timestamp_type; + private boolean const_item; + + /** + * @param name + * @param args + */ + public ItemFuncStrToDate(List args) { + super(new ArrayList()); + const_item = false; + } + + @Override + public final String funcName() { + return "str_to_date"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + cached_field_type = FieldTypes.MYSQL_TYPE_DATETIME; + cached_timestamp_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + if ((const_item = args.get(1).basicConstItem())) { + String format = args.get(1).valStr(); + if (!args.get(1).nullValue) + fix_from_format(format); + } + } + + /** + * Set type of datetime value (DATE/TIME/...) which will be produced + * according to format string. + * + * @param format + * format string + * + * @note We don't process day format's characters('D', 'd', 'e') because day + * may be a member of all date/time types. + * + * @note Format specifiers supported by this function should be in sync with + * specifiers supported by extract_date_time() function. + */ + private void fix_from_format(String format) { + String time_part_frms = "HISThiklrs"; + String date_part_frms = "MVUXYWabcjmvuxyw"; + boolean date_part_used = false, time_part_used = false, frac_second_used = false; + int val = 0; + int end = format.length(); + char[] cs = format.toCharArray(); + + for (; val != end && val != end; val++) { + if (cs[val] == '%' && val + 1 != end) { + val++; + if (cs[val] == 'f') + frac_second_used = time_part_used = true; + else if (!time_part_used && time_part_frms.indexOf(cs[val]) >= 0) + time_part_used = true; + else if (!date_part_used && date_part_frms.indexOf(cs[val]) >= 0) + date_part_used = true; + if (date_part_used && frac_second_used) { + /* + * frac_second_used implies time_part_used, and thus we + * already have all types of date-time components and can + * end our search. + */ + cached_timestamp_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + cached_field_type = FieldTypes.MYSQL_TYPE_DATETIME; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATETIME_WIDTH, MyTime.DATETIME_MAX_DECIMALS); + return; + } + } + } + + /* We don't have all three types of date-time components */ + if (frac_second_used) /* TIME with microseconds */ + { + cached_timestamp_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + cached_field_type = FieldTypes.MYSQL_TYPE_TIME; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_TIME_FULL_WIDTH, MyTime.DATETIME_MAX_DECIMALS); + } else if (time_part_used) { + if (date_part_used) /* DATETIME, no microseconds */ + { + cached_timestamp_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + cached_field_type = FieldTypes.MYSQL_TYPE_DATETIME; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATETIME_WIDTH, 0); + } else /* TIME, no microseconds */ + { + cached_timestamp_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + cached_field_type = FieldTypes.MYSQL_TYPE_TIME; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_TIME_WIDTH, 0); + } + } else /* DATE */ + { + cached_timestamp_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + cached_field_type = FieldTypes.MYSQL_TYPE_DATE; + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATE_WIDTH, 0); + } + } + + @Override + protected boolean val_datetime(MySQLTime ltime, long fuzzy_date) { + DateTimeFormat date_time_format = new DateTimeFormat(); + String val = args.get(0).valStr(); + String format = args.get(1).valStr(); + boolean null_date = false; + if (args.get(0).nullValue || args.get(1).nullValue) + null_date = true; + if (!null_date) { + nullValue = false; + date_time_format.format = format; + if (MyTime.extract_date_time(date_time_format, val, ltime, cached_timestamp_type, "datetime") + || ((fuzzy_date & MyTime.TIME_NO_ZERO_DATE) != 0 + && (ltime.year == 0 || ltime.month == 0 || ltime.day == 0))) + null_date = true; + } + if (!null_date) { + ltime.time_type = cached_timestamp_type; + if (cached_timestamp_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME && ltime.day != 0) { + /* + * Day part for time type can be nonzero value and so we should + * add hours from day part to hour part to keep valid time + * value. + */ + ltime.hour += ltime.day * 24; + ltime.day = 0; + } + return false; + } + + null_date: if (val != null && (fuzzy_date & MyTime.TIME_NO_ZERO_DATE) != 0 /* warnings */) { + logger.warn("str_to_date value:" + val + " is wrong value for format:" + format); + } + return (nullValue = true); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncStrToDate(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSysdateLocal.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSysdateLocal.java new file mode 100644 index 000000000..1911ee3a1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncSysdateLocal.java @@ -0,0 +1,41 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +/* + * 函数执行的时间,now函数表示的是命令接收到的的时间,在这里相同处理 + */ +public class ItemFuncSysdateLocal extends ItemDatetimeFunc { + + public ItemFuncSysdateLocal(List args) { + super(args); + } + + @Override + public final String funcName() { + return "sysdate"; + } + + @Override + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATETIME_WIDTH, decimals); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzy_date) { + java.util.Calendar cal = java.util.Calendar.getInstance(); + ltime.year = cal.get(java.util.Calendar.YEAR); + ltime.month = cal.get(java.util.Calendar.MONTH); + ltime.day = cal.get(java.util.Calendar.DAY_OF_MONTH); + ltime.hour = cal.get(java.util.Calendar.HOUR_OF_DAY); + ltime.minute = cal.get(java.util.Calendar.MINUTE); + ltime.second = cal.get(java.util.Calendar.SECOND); + ltime.second_part = cal.get(java.util.Calendar.MILLISECOND) * 1000; + return false; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTime.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTime.java new file mode 100644 index 000000000..e3b7dd36d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTime.java @@ -0,0 +1,24 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFuncTime extends ItemTimeFunc { + + public ItemFuncTime(List args) { + super(args); + } + + @Override + public final String funcName() { + return "time"; + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getArg0Time(ltime); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimeToSec.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimeToSec.java new file mode 100644 index 000000000..b8fdef7fb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimeToSec.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemFuncTimeToSec extends ItemIntFunc { + + public ItemFuncTimeToSec(List args) { + super(args); + } + + @Override + public final String funcName() { + return "time_to_sec"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Time(ltime)) + return BigInteger.ZERO; + long seconds = ltime.hour * 3600L + ltime.minute * 60 + ltime.second; + return BigInteger.valueOf(ltime.neg ? -seconds : seconds); + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + fixCharLength(10); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncTimeToSec(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimediff.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimediff.java new file mode 100644 index 000000000..bd0aebcfc --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimediff.java @@ -0,0 +1,93 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncTimediff extends ItemTimeFunc { + + public ItemFuncTimediff(List args) { + super(args); + } + + @Override + public final String funcName() { + return "timediff"; + } + + @Override + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_TIME_WIDTH, 6); + maybeNull = true; + } + + /** + * TIMEDIFF(t,s) is a time function that calculates the time value between a + * start and end time. + * + * t and s: time_or_datetime_expression + * + * @param[out] l_time3 Result is stored here. + * @param[in] flags Not used in this class. + * @returns + * @retval false On succes + * @retval true On error + */ + @Override + public boolean getTime(MySQLTime ltime) { + LongPtr seconds = new LongPtr(0); + LongPtr microseconds = new LongPtr(0); + MySQLTime l_time1 = new MySQLTime(); + MySQLTime l_time2 = new MySQLTime(); + int l_sign = 1; + + nullValue = false; + + if ((args.get(0).isTemporalWithDate() && args.get(1).fieldType() == FieldTypes.MYSQL_TYPE_TIME) + || (args.get(1).isTemporalWithDate() + && args.get(0).fieldType() == FieldTypes.MYSQL_TYPE_TIME)) { + return nullValue = true; + } // Incompatible types + + if (args.get(0).isTemporalWithDate() || args.get(1).isTemporalWithDate()) { + if (args.get(0).getDate(l_time1, MyTime.TIME_FUZZY_DATE) + || args.get(1).getDate(l_time2, MyTime.TIME_FUZZY_DATE)) + return nullValue = true; + } else { + if (args.get(0).getTime(l_time1) || args.get(1).getTime(l_time2)) + return nullValue = true; + } + + if (l_time1.time_type != l_time2.time_type) { + return nullValue = true;// Incompatible types + } + if (l_time1.neg != l_time2.neg) + l_sign = -l_sign; + + MySQLTime l_time3 = new MySQLTime(); + + l_time3.neg = MyTime.calc_time_diff(l_time1, l_time2, l_sign, seconds, microseconds); + + /* + * For MYSQL_TIMESTAMP_TIME only: If first argument was negative and + * diff between arguments is non-zero we need to swap sign to get proper + * result. + */ + if (l_time1.neg && (seconds.get() != 0 || microseconds.get() != 0)) + l_time3.neg = l_time3.neg ? false : true; // Swap sign of result + + MyTime.calc_time_from_sec(l_time3, seconds.get(), microseconds.get()); + return false; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncTimediff(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimestampDiff.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimestampDiff.java new file mode 100644 index 000000000..132e7f2e3 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncTimestampDiff.java @@ -0,0 +1,155 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalUnit; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncTimestampDiff extends ItemIntFunc { + private MySqlIntervalUnit int_type; + + public ItemFuncTimestampDiff(Item a, Item b, MySqlIntervalUnit type) { + super(new ArrayList()); + args.add(a); + args.add(b); + this.int_type = type; + } + + @Override + public final String funcName() { + return "TIMESTAMPDIFF"; + } + + @Override + public void fixLengthAndDec() { + maybeNull = true; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime1 = new MySQLTime(); + MySQLTime ltime2 = new MySQLTime(); + nullValue = false; + int neg = 1; + + long months = 0; + if (args.get(0).getDate(ltime1, MyTime.TIME_NO_ZERO_DATE) + || args.get(1).getDate(ltime2, MyTime.TIME_NO_ZERO_DATE)) { + nullValue = true; + return BigInteger.ZERO; + } + LongPtr lpseconds = new LongPtr(0); + LongPtr lpmicroseconds = new LongPtr(0); + + if (MyTime.calc_time_diff(ltime2, ltime1, 1, lpseconds, lpmicroseconds)) + neg = -1; + + long seconds = lpseconds.get(), microseconds = lpmicroseconds.get(); + if (int_type == MySqlIntervalUnit.YEAR || int_type == MySqlIntervalUnit.QUARTER + || int_type == MySqlIntervalUnit.MONTH) { + long year_beg, year_end, month_beg, month_end, day_beg, day_end; + long years = 0; + long second_beg, second_end, microsecond_beg, microsecond_end; + + if (neg == -1) { + year_beg = ltime2.year; + year_end = ltime1.year; + month_beg = ltime2.month; + month_end = ltime1.month; + day_beg = ltime2.day; + day_end = ltime1.day; + second_beg = ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second; + second_end = ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second; + microsecond_beg = ltime2.second_part; + microsecond_end = ltime1.second_part; + } else { + year_beg = ltime1.year; + year_end = ltime2.year; + month_beg = ltime1.month; + month_end = ltime2.month; + day_beg = ltime1.day; + day_end = ltime2.day; + second_beg = ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second; + second_end = ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second; + microsecond_beg = ltime1.second_part; + microsecond_end = ltime2.second_part; + } + + /* calc years */ + years = year_end - year_beg; + if (month_end < month_beg || (month_end == month_beg && day_end < day_beg)) + years -= 1; + + /* calc months */ + months = 12 * years; + if (month_end < month_beg || (month_end == month_beg && day_end < day_beg)) + months += 12 - (month_beg - month_end); + else + months += (month_end - month_beg); + + if (day_end < day_beg) + months -= 1; + else if ((day_end == day_beg) + && ((second_end < second_beg) || (second_end == second_beg && microsecond_end < microsecond_beg))) + months -= 1; + } + + if (int_type == MySqlIntervalUnit.YEAR) + return BigInteger.valueOf(months / 12 * neg); + if (int_type == MySqlIntervalUnit.QUARTER) + return BigInteger.valueOf(months / 3 * neg); + if (int_type == MySqlIntervalUnit.MONTH) + return BigInteger.valueOf(months * neg); + if (int_type == MySqlIntervalUnit.WEEK) + return BigInteger.valueOf(seconds / MyTime.SECONDS_IN_24H / 7L * neg); + if (int_type == MySqlIntervalUnit.DAY) + return BigInteger.valueOf(seconds / MyTime.SECONDS_IN_24H * neg); + if (int_type == MySqlIntervalUnit.HOUR) + return BigInteger.valueOf(seconds / 3600L * neg); + if (int_type == MySqlIntervalUnit.MINUTE) + return BigInteger.valueOf(seconds / 60L * neg); + if (int_type == MySqlIntervalUnit.SECOND) + return BigInteger.valueOf(seconds * neg); + if (int_type == MySqlIntervalUnit.MICROSECOND) + /* + * In MySQL difference between any two valid datetime values in + * microseconds fits into longlong. + */ + return BigInteger.valueOf((seconds * 1000000L + microseconds) * neg); + else { + nullValue = true; + return BigInteger.ZERO; + } + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName()); + method.addParameter(new SQLIdentifierExpr(int_type.toString())); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncTimestampDiff(newArgs.get(0), newArgs.get(1), int_type); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncToDays.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncToDays.java new file mode 100644 index 000000000..9443d6036 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncToDays.java @@ -0,0 +1,42 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncToDays extends ItemIntFunc { + + public ItemFuncToDays(List args) { + super(args); + } + + @Override + public final String funcName() { + return "to_days"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return BigInteger.ZERO; + return BigInteger.valueOf(MyTime.calc_daynr(ltime.year, ltime.month, ltime.day)); + } + + @Override + public void fixLengthAndDec() { + maxLength = 6; + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncToDays(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncToSeconds.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncToSeconds.java new file mode 100644 index 000000000..6b9f1f8bb --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncToSeconds.java @@ -0,0 +1,46 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncToSeconds extends ItemIntFunc { + + public ItemFuncToSeconds(List args) { + super(args); + } + + @Override + public final String funcName() { + return "to_seconds"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + long seconds, days; + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return BigInteger.ZERO; + seconds = ltime.hour * 3600L + ltime.minute * 60 + ltime.second; + seconds = ltime.neg ? -seconds : seconds; + days = MyTime.calc_daynr(ltime.year, ltime.month, ltime.day); + return BigInteger.valueOf(seconds + days * 24L * 3600L); + } + + @Override + public void fixLengthAndDec() { + maxLength = 6; + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncToSeconds(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncUnixTimestamp.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncUnixTimestamp.java new file mode 100644 index 000000000..b337e8e8f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncUnixTimestamp.java @@ -0,0 +1,39 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.Timeval; + +public class ItemFuncUnixTimestamp extends ItemTimevalFunc { + + public ItemFuncUnixTimestamp(List args) { + super(args); + } + + @Override + public final String funcName() { + return "unix_timestamp"; + } + + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(11, getArgCount() == 0 ? 0 : args.get(0).datetimePrecision()); + } + + @Override + public boolean val_timeval(Timeval tm) { + if (getArgCount() == 0) { + tm.tv_sec = java.util.Calendar.getInstance().getTimeInMillis() / 1000; + tm.tv_usec = 0; + return false; // no args: null_value is set in constructor and is + // always 0. + } + return (nullValue = args.get(0).getTimeval(tm)); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncUnixTimestamp(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeek.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeek.java new file mode 100644 index 000000000..8f00ae78d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeek.java @@ -0,0 +1,38 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncWeek extends ItemIntFunc { + + public ItemFuncWeek(List args) { + super(args); + } + + @Override + public final String funcName() { + return "week"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return BigInteger.ZERO; + return BigInteger + .valueOf(MyTime.calc_week(ltime, MyTime.week_mode(args.get(1).valInt().intValue()), new LongPtr(0))); + } + + @Override + public void fixLengthAndDec() { + fixCharLength(2); /* 0..54 */ + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeekday.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeekday.java new file mode 100644 index 000000000..8061191ea --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeekday.java @@ -0,0 +1,44 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncWeekday extends ItemIntFunc { + + public ItemFuncWeekday(List args) { + super(args); + } + + @Override + public final String funcName() { + return "weekday"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return BigInteger.ZERO; + + return BigInteger.valueOf(MyTime.calc_weekday(MyTime.calc_daynr(ltime.year, ltime.month, ltime.day), false)); + } + + @Override + public void fixLengthAndDec() { + fixCharLength(1); + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncWeekday(realArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeekofyear.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeekofyear.java new file mode 100644 index 000000000..07d7d8d46 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncWeekofyear.java @@ -0,0 +1,36 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncWeekofyear extends ItemIntFunc { + + public ItemFuncWeekofyear(List args) { + super(args); + } + + @Override + public final String funcName() { + return "weekofyear"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + if (nullValue = getArg0Date(ltime, MyTime.TIME_FUZZY_DATE)) { + return BigInteger.ZERO; + } + return BigInteger.valueOf(ltime.month); + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncWeekofyear(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncYear.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncYear.java new file mode 100644 index 000000000..df983923c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncYear.java @@ -0,0 +1,34 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncYear extends ItemIntFunc { + + public ItemFuncYear(List args) { + super(args); + } + + @Override + public final String funcName() { + return "year"; + } + + @Override + public BigInteger valInt() { + MySQLTime ltime = new MySQLTime(); + return getArg0Date(ltime, MyTime.TIME_FUZZY_DATE) ? BigInteger.ZERO : BigInteger.valueOf(ltime.year); + } + + @Override + public void fixLengthAndDec() { + fixCharLength(4); /* 9999 */ + maybeNull = true; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncYearweek.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncYearweek.java new file mode 100644 index 000000000..9156a1941 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemFuncYearweek.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.primary.ItemIntFunc; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + +public class ItemFuncYearweek extends ItemIntFunc { + + public ItemFuncYearweek(List args) { + super(args); + } + + @Override + public final String funcName() { + return "yearweek"; + } + + @Override + public BigInteger valInt() { + LongPtr year = new LongPtr(0); + long week; + MySQLTime ltime = new MySQLTime(); + if (getArg0Date(ltime, MyTime.TIME_NO_ZERO_DATE)) + return BigInteger.ZERO; + week = MyTime.calc_week(ltime, (MyTime.week_mode(args.get(1).valInt().intValue()) | MyTime.WEEK_YEAR), year); + return BigInteger.valueOf(week + year.get() * 100); + } + + @Override + public void fixLengthAndDec() { + fixCharLength(6); /* YYYYWW */ + maybeNull = true; + } + + @Override + public ItemFunc nativeConstruct(List realArgs) { + return new ItemFuncYearweek(realArgs); + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTemporalFunc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTemporalFunc.java new file mode 100644 index 000000000..3ec547d5c --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTemporalFunc.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; + + +/** + * Abstract class for functions returning TIME, DATE, DATETIME types whose data + * type is known at constructor time. + * + * + */ +public abstract class ItemTemporalFunc extends ItemFunc { + + public ItemTemporalFunc(List args) { + super(args); + } + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + public java.util.Calendar getUTCTime() { + // 1、取得本地时间: + + java.util.Calendar cal = java.util.Calendar.getInstance(); + + // 2、取得时间偏移量: + + int zoneOffset = cal.get(java.util.Calendar.ZONE_OFFSET); + + // 3、取得夏令时差: + + int dstOffset = cal.get(java.util.Calendar.DST_OFFSET); + + // 4、从本地时间里扣除这些差量,即可以取得UTC时间: + + cal.add(java.util.Calendar.MILLISECOND, -(zoneOffset + dstOffset)); + return cal; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTemporalHybridFunc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTemporalHybridFunc.java new file mode 100644 index 000000000..c03e561ab --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTemporalHybridFunc.java @@ -0,0 +1,112 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.strfunc.ItemStrFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MySQLTimestampType; +import io.mycat.plan.common.time.MyTime; + +/** + * Abstract class for functions returning TIME, DATE, DATETIME or string values, + * whose data type depends on parameters and is set at fix_field time. + + */ +public abstract class ItemTemporalHybridFunc extends ItemStrFunc { + public ItemTemporalHybridFunc(List args) { + super(args); + } + + protected FieldTypes cached_field_type; // TIME, DATE, DATETIME or + // STRING + + /** + * Get "native" temporal value as MYSQL_TIME + * + * @param[out] ltime The value is stored here. + * @param[in] fuzzy_date Date flags. + * @retval false On success. + * @retval true On error. + */ + protected abstract boolean val_datetime(MySQLTime ltime, long fuzzy_date); + + @Override + public ItemResult resultType() { + return ItemResult.STRING_RESULT; + } + + @Override + public FieldTypes fieldType() { + return cached_field_type; + } + + @Override + public BigInteger valInt() { + return BigInteger.valueOf(valIntFromDecimal()); + } + + @Override + public BigDecimal valReal() { + return valRealFromDecimal(); + } + + @Override + public BigDecimal valDecimal() { + if (cached_field_type == FieldTypes.MYSQL_TYPE_TIME) + return valDecimalFromTime(); + else if (cached_field_type == FieldTypes.MYSQL_TYPE_DATETIME) + return valDecimalFromDate(); + else { + MySQLTime ltime = new MySQLTime(); + val_datetime(ltime, MyTime.TIME_FUZZY_DATE); + return nullValue ? BigDecimal.ZERO + : ltime.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME ? MyTime.time2my_decimal(ltime) + : MyTime.date2my_decimal(ltime); + } + } + + @Override + public String valStr() { + MySQLTime ltime = new MySQLTime(); + + if (val_datetime(ltime, MyTime.TIME_FUZZY_DATE)) + return null; + String res = MyTime.my_TIME_to_str(ltime, cached_field_type == FieldTypes.MYSQL_TYPE_STRING + ? (ltime.second_part != 0 ? MyTime.DATETIME_MAX_DECIMALS : 0) : decimals); + + if (res == null) + nullValue = true; + return res; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + MySQLTime tm = new MySQLTime(); + if (val_datetime(tm, fuzzydate)) { + assert (nullValue == true); + return true; + } + if (cached_field_type == FieldTypes.MYSQL_TYPE_TIME + || tm.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) + MyTime.time_to_datetime(tm, ltime); + else + ltime = tm; + return false; + } + + @Override + public boolean getTime(MySQLTime ltime) { + if (val_datetime(ltime, MyTime.TIME_FUZZY_DATE)) { + assert (nullValue == true); + return true; + } + if (cached_field_type == FieldTypes.MYSQL_TYPE_TIME + && ltime.time_type != MySQLTimestampType.MYSQL_TIMESTAMP_TIME) + MyTime.datetime_to_time(ltime); + return false; + } +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTimeFunc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTimeFunc.java new file mode 100644 index 000000000..5ef4d8325 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTimeFunc.java @@ -0,0 +1,70 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.MyTime; + + +/** + * Abstract class for functions returning TIME values. + * + * @author chenzifei + * + */ +public abstract class ItemTimeFunc extends ItemTemporalFunc { + + public ItemTimeFunc(List args) { + super(args); + } + + @Override + public FieldTypes fieldType() { + return FieldTypes.MYSQL_TYPE_TIME; + } + + @Override + public String valStr() { + return valStringFromTime(); + } + + @Override + public BigInteger valInt() { + return BigInteger.valueOf(valIntFromTime()); + } + + @Override + public long valDateTemporal() { + MySQLTime ltime = new MySQLTime(); + return getDate(ltime, MyTime.TIME_FUZZY_DATE) ? 0 : (MyTime.TIME_to_longlong_time_packed(ltime)); + } + + @Override + public BigDecimal valReal() { + return valRealFromDecimal(); + } + + @Override + public void fixLengthAndDec() { + fixLengthAndDecAndCharsetDatetime(MyTime.MAX_DATE_WIDTH, 0); + } + + @Override + public BigDecimal valDecimal() { + return valDecimalFromTime(); + } + + @Override + public boolean getDate(MySQLTime res, long fuzzy_date) { + return getDateFromTime(res); + } + + // All time functions must implement get_time() + // to avoid use of generic Item::get_time() + // which converts to string and then parses the string as TIME. + public abstract boolean getTime(MySQLTime ltime); +} diff --git a/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTimevalFunc.java b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTimevalFunc.java new file mode 100644 index 000000000..c73ec6910 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/timefunc/ItemTimevalFunc.java @@ -0,0 +1,72 @@ +package io.mycat.plan.common.item.function.timefunc; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemResult; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; +import io.mycat.plan.common.time.Timeval; + +/* + Abstract class for functions returning "struct timeval". + */ +public abstract class ItemTimevalFunc extends ItemFunc { + + public ItemTimevalFunc(List args) { + super(args); + } + + /** + * Return timestamp in "struct timeval" format. + * + * @param[out] tm The value is store here. + * @retval false On success + * @retval true On error + */ + public abstract boolean val_timeval(Timeval tm); + + @Override + public BigDecimal valReal() { + Timeval tm = new Timeval(); + return val_timeval(tm) ? BigDecimal.ZERO + : BigDecimal.valueOf((double) tm.tv_sec + (double) tm.tv_usec / (double) 1000000); + } + + @Override + public BigInteger valInt() { + Timeval tm = new Timeval(); + return val_timeval(tm) ? BigInteger.ZERO : BigInteger.valueOf(tm.tv_sec); + } + + @Override + public BigDecimal valDecimal() { + Timeval tm = new Timeval(); + return val_timeval(tm) ? null : tm.timeval2my_decimal(); + } + + @Override + public String valStr() { + Timeval tm = new Timeval(); + if (val_timeval(tm)) + return null; + return tm.my_timeval_to_str(); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + return getDateFromNumeric(ltime, fuzzydate); + } + + @Override + public boolean getTime(MySQLTime ltime) { + return getTimeFromNumeric(ltime); + } + + public ItemResult resultType() { + return decimals != 0 ? ItemResult.DECIMAL_RESULT : ItemResult.INT_RESULT; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/function/unknown/ItemFuncUnknown.java b/src/main/java/io/mycat/plan/common/item/function/unknown/ItemFuncUnknown.java new file mode 100644 index 000000000..54307900a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/function/unknown/ItemFuncUnknown.java @@ -0,0 +1,90 @@ +/** + * + */ +package io.mycat.plan.common.item.function.unknown; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.time.MySQLTime; + + +/** + * @author zhangyaohua + * @CreateTime 2016年5月9日 + */ +public class ItemFuncUnknown extends ItemFunc { + private final String funcName; + + /** + * @param args + */ + public ItemFuncUnknown(String funcName, List args) { + super(args); + this.funcName = funcName; + this.withUnValAble = true; + } + + @Override + public void fixLengthAndDec() { + } + + @Override + public String funcName() { + return this.funcName; + } + + @Override + public BigDecimal valReal() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", " udf func not support val()"); + } + + @Override + public BigInteger valInt() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", " udf func not support val()"); + } + + @Override + public String valStr() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", " udf func not support val()"); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", " udf func not support val()"); + } + + @Override + public boolean getTime(MySQLTime ltime) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", " udf func not support val()"); + } + + @Override + public SQLExpr toExpression() { + SQLMethodInvokeExpr method = new SQLMethodInvokeExpr(funcName); + for (Item arg : args) { + method.addParameter(arg.toExpression()); + } + return method; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + List newArgs = null; + if (!forCalculate) + newArgs = cloneStructList(args); + else + newArgs = calArgs; + return new ItemFuncUnknown(funcName, newArgs); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/num/ItemNum.java b/src/main/java/io/mycat/plan/common/item/num/ItemNum.java new file mode 100644 index 000000000..f828dbaf2 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/num/ItemNum.java @@ -0,0 +1,13 @@ +package io.mycat.plan.common.item.num; + +import io.mycat.plan.common.item.ItemBasicConstant; + +public abstract class ItemNum extends ItemBasicConstant { + + public ItemNum() { + // my_charset_numeric + charsetIndex = 8; + } + + public abstract ItemNum neg(); +} diff --git a/src/main/java/io/mycat/plan/common/item/subquery/ItemAllanySubselect.java b/src/main/java/io/mycat/plan/common/item/subquery/ItemAllanySubselect.java new file mode 100644 index 000000000..fb1f6a457 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/subquery/ItemAllanySubselect.java @@ -0,0 +1,92 @@ +/** + * ALL/ANY/SOME subselect + */ +package io.mycat.plan.common.item.subquery; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemAllanySubselect extends ItemSubselect { + private boolean isAll; + private Item leftOprand; + + /** + * @param currentDb + * @param query + */ + public ItemAllanySubselect(String currentDb, Item left, SQLSelectQuery query, boolean all) { + super(currentDb, query); + this.leftOprand = left; + this.isAll = all; + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support!"); + } + + @Override + public subSelectType substype() { + return isAll ? subSelectType.ALL_SUBS : subSelectType.ANY_SUBS; + } + + @Override + public void fixLengthAndDec() { + + } + + @Override + public BigDecimal valReal() { + // TODO Auto-generated method stub + return null; + } + + @Override + public BigInteger valInt() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String valStr() { + // TODO Auto-generated method stub + return null; + } + + @Override + public BigDecimal valDecimal() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean getTime(MySQLTime ltime) { + // TODO Auto-generated method stub + return false; + } + + @Override + public SQLExpr toExpression() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/subquery/ItemExistsSubselect.java b/src/main/java/io/mycat/plan/common/item/subquery/ItemExistsSubselect.java new file mode 100644 index 000000000..de73b893f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/subquery/ItemExistsSubselect.java @@ -0,0 +1,86 @@ +/** + * + */ +package io.mycat.plan.common.item.subquery; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemExistsSubselect extends ItemSubselect { + private boolean isNot; + + /** + * @param currentDb + * @param query + */ + public ItemExistsSubselect(String currentDb, SQLSelectQuery query, boolean isNot) { + super(currentDb, query); + this.isNot = isNot; + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support!"); + } + + @Override + public void fixLengthAndDec() { + // TODO Auto-generated method stub + + } + + @Override + public BigDecimal valReal() { + // TODO Auto-generated method stub + return null; + } + + @Override + public BigInteger valInt() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String valStr() { + // TODO Auto-generated method stub + return null; + } + + @Override + public BigDecimal valDecimal() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean getTime(MySQLTime ltime) { + // TODO Auto-generated method stub + return false; + } + + @Override + public SQLExpr toExpression() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/subquery/ItemInSubselect.java b/src/main/java/io/mycat/plan/common/item/subquery/ItemInSubselect.java new file mode 100644 index 000000000..151d5584a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/subquery/ItemInSubselect.java @@ -0,0 +1,111 @@ +/** + * + */ +package io.mycat.plan.common.item.subquery; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemInSubselect extends ItemSubselect { + private boolean isNeg; + private Item leftOprand; + + /** + * @param currentDb + * @param query + */ + public ItemInSubselect(String currentDb, Item leftOprand, SQLSelectQuery query, boolean isNeg) { + super(currentDb, query); + this.leftOprand = leftOprand; + this.isNeg = isNeg; + } + + @Override + public void fixLengthAndDec() { + + } + + public Item fixFields(NameResolutionContext context) { + super.fixFields(context); + leftOprand = leftOprand.fixFields(context); + return this; + } + + /** + * added to construct all refers in an item + * + * @param context + */ + public void fixRefer(ReferContext context) { + super.fixRefer(context); + leftOprand.fixRefer(context); + } + + @Override + public BigDecimal valReal() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support yet!"); + } + + @Override + public BigInteger valInt() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support yet!"); + } + + @Override + public String valStr() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support yet!"); + } + + @Override + public BigDecimal valDecimal() { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support yet!"); + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support yet!"); + } + + @Override + public boolean getTime(MySQLTime ltime) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support yet!"); + } + + public Item getLeftOprand() { + return leftOprand; + } + + public boolean isNeg() { + return isNeg; + } + + @Override + public SQLExpr toExpression() { + SQLExpr expr = leftOprand.toExpression(); + SQLSelect select = new SQLSelect(query); + SQLInSubQueryExpr insub = new SQLInSubQueryExpr(select); + insub.setExpr(expr); + insub.setNot(isNeg); + return insub; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected!"); + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/subquery/ItemMaxminSubselect.java b/src/main/java/io/mycat/plan/common/item/subquery/ItemMaxminSubselect.java new file mode 100644 index 000000000..c4a67bfb7 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/subquery/ItemMaxminSubselect.java @@ -0,0 +1,20 @@ +/** + * + */ +package io.mycat.plan.common.item.subquery; + +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; + +public class ItemMaxminSubselect extends ItemSinglerowSubselect { + private boolean max; + + /** + * @param currentDb + * @param query + */ + public ItemMaxminSubselect(String currentDb, SQLSelectQuery query, boolean max) { + super(currentDb, query); + this.max = max; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/subquery/ItemSinglerowSubselect.java b/src/main/java/io/mycat/plan/common/item/subquery/ItemSinglerowSubselect.java new file mode 100644 index 000000000..85cc4c4b4 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/subquery/ItemSinglerowSubselect.java @@ -0,0 +1,144 @@ +/** + * + */ +package io.mycat.plan.common.item.subquery; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; + +import io.mycat.plan.common.field.Field; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.time.MySQLTime; + +public class ItemSinglerowSubselect extends ItemSubselect { + /* 记录一行的row */ + private List row; + /* 记录row item相关的fields值 */ + private List fields; + private Item value; + private boolean no_rows; + + public ItemSinglerowSubselect(String currentDb, SQLSelectQuery query) { + super(currentDb, query); + } + + @Override + public subSelectType substype() { + return subSelectType.SINGLEROW_SUBS; + } + + @Override + public void reset() { + this.nullValue = true; + if (value != null) + value.nullValue = true; + } + + @Override + public BigDecimal valReal() { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.valReal(); + } else { + reset(); + return BigDecimal.ZERO; + } + } + + @Override + public BigInteger valInt() { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.valInt(); + } else { + reset(); + return BigInteger.ZERO; + } + } + + @Override + public String valStr() { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.valStr(); + } else { + reset(); + return null; + } + } + + @Override + public BigDecimal valDecimal() { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.valDecimal(); + } else { + reset(); + return null; + } + } + + @Override + public boolean getDate(MySQLTime ltime, long fuzzydate) { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.getDate(ltime, fuzzydate); + } else { + reset(); + return true; + } + } + + @Override + public boolean getTime(MySQLTime ltime) { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.getTime(ltime); + } else { + reset(); + return true; + } + } + + @Override + public boolean valBool() { + if (!no_rows && !execute() && !value.nullValue) { + nullValue = false; + return value.valBool(); + } else { + reset(); + return false; + } + } + + @Override + public void fixLengthAndDec() { + + } + + @Override + public SQLExpr toExpression() { + // TODO + return null; + } + + @Override + protected Item cloneStruct(boolean forCalculate, List calArgs, boolean isPushDown, List fields) { + // TODO Auto-generated method stub + return null; + } + + /*--------------------------------------getter/setter-----------------------------------*/ + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + +} diff --git a/src/main/java/io/mycat/plan/common/item/subquery/ItemSubselect.java b/src/main/java/io/mycat/plan/common/item/subquery/ItemSubselect.java new file mode 100644 index 000000000..a2a5711c5 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/item/subquery/ItemSubselect.java @@ -0,0 +1,105 @@ +/** + * + */ +package io.mycat.plan.common.item.subquery; + +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.context.NameResolutionContext; +import io.mycat.plan.common.context.ReferContext; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemResultField; +import io.mycat.plan.visitor.MySQLPlanNodeVisitor; + +public abstract class ItemSubselect extends ItemResultField { + protected SQLSelectQuery query; + private String currentDb; + private PlanNode planNode; + + public enum subSelectType { + UNKNOWN_SUBS, SINGLEROW_SUBS, EXISTS_SUBS, IN_SUBS, ALL_SUBS, ANY_SUBS + }; + + public subSelectType substype() { + return subSelectType.UNKNOWN_SUBS; + } + + public ItemSubselect(String currentDb, SQLSelectQuery query) { + this.query = query; + this.currentDb = currentDb; + init(); + } + + @Override + public ItemType type() { + return ItemType.SUBSELECT_ITEM; + } + + private void init() { + MySQLPlanNodeVisitor pv = new MySQLPlanNodeVisitor(currentDb); + pv.visit(this.query); + this.planNode = pv.getTableNode(); + this.withSubQuery = true; + } + + public void reset() { + this.nullValue = true; + } + + @Override + public final boolean isNull() { + updateNullValue(); + return nullValue; + } + + @Override + public boolean fixFields() { + throw new MySQLOutPutException(ErrorCode.ER_QUERYHANDLER, "", "not supported!"); + } + + public Item fixFields(NameResolutionContext context) { + this.planNode.setUpFields(); + return this; + } + + /** + * added to construct all refers in an item + * + * @param context + */ + public void fixRefer(ReferContext context) { + if (context.isPushDownNode()) + return; + else + context.getPlanNode().subSelects.add(this); + } + + @Override + public String funcName() { + return "subselect"; + } + + /** + * 计算子查询相关数据 + */ + public boolean execute() { + // TODO + return false; + } + + public PlanNode getPlanNode() { + return planNode; + } + + public void setPlanNode(PlanNode planNode) { + this.planNode = planNode; + } + + @Override + public String toString(){ + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not supported!"); + } +} diff --git a/src/main/java/io/mycat/plan/common/locale/MYLOCALE.java b/src/main/java/io/mycat/plan/common/locale/MYLOCALE.java new file mode 100644 index 000000000..bc72af668 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/locale/MYLOCALE.java @@ -0,0 +1,45 @@ +package io.mycat.plan.common.locale; + +import io.mycat.plan.common.typelib.TYPELIB; + +/** + * + * @author chenzifei + * + */ +public class MYLOCALE { + public int number; + public String name; + public String description; + public boolean is_ascii; + public TYPELIB month_names; + public TYPELIB ab_month_names; + public TYPELIB day_names; + public TYPELIB ab_day_names; + public int max_month_name_length; + public int max_day_name_length; + public int decimal_point; + public int thousand_sep; + public String grouping; + public MYLOCALEERRMSGS errmsgs; + + MYLOCALE(int number_par, String name_par, String descr_par, boolean is_ascii_par, TYPELIB month_names_par, + TYPELIB ab_month_names_par, TYPELIB day_names_par, TYPELIB ab_day_names_par, int max_month_name_length_par, + int max_day_name_length_par, int decimal_point_par, int thousand_sep_par, String grouping_par, + MYLOCALEERRMSGS errmsgs_par) { + this.number = (number_par); + this.name = name_par; + this.description = descr_par; + this.is_ascii = is_ascii_par; + this.month_names = month_names_par; + this.ab_month_names = ab_month_names_par; + this.day_names = day_names_par; + this.ab_day_names = ab_day_names_par; + this.max_month_name_length = max_month_name_length_par; + this.max_day_name_length = max_day_name_length_par; + this.decimal_point = decimal_point_par; + this.thousand_sep = thousand_sep_par; + this.grouping = grouping_par; + this.errmsgs = errmsgs_par; + } +}; diff --git a/src/main/java/io/mycat/plan/common/locale/MYLOCALEERRMSGS.java b/src/main/java/io/mycat/plan/common/locale/MYLOCALEERRMSGS.java new file mode 100644 index 000000000..cedc5f491 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/locale/MYLOCALEERRMSGS.java @@ -0,0 +1,13 @@ +package io.mycat.plan.common.locale; + +public class MYLOCALEERRMSGS { + + public String language; + public String errmsgs; + + public MYLOCALEERRMSGS(String language, String errmsgs) { + this.language = language; + this.errmsgs = errmsgs; + } + +} diff --git a/src/main/java/io/mycat/plan/common/locale/MYLOCALES.java b/src/main/java/io/mycat/plan/common/locale/MYLOCALES.java new file mode 100644 index 000000000..0ec71ce5a --- /dev/null +++ b/src/main/java/io/mycat/plan/common/locale/MYLOCALES.java @@ -0,0 +1,60 @@ +package io.mycat.plan.common.locale; + +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.typelib.TYPELIB; + +/** + * only locale_en_us is supported now + * + * @author chenzifei + * + */ +public class MYLOCALES { + enum err_msgs_index { + en_US, cs_CZ, da_DK, nl_NL, et_EE, fr_FR, de_DE, el_GR, hu_HU, it_IT, ja_JP, ko_KR, no_NO, nn_NO, pl_PL, pt_PT, ro_RO, ru_RU, sr_RS, sk_SK, es_ES, sv_SE, uk_UA + }; + + static MYLOCALEERRMSGS global_errmsgs[] = { new MYLOCALEERRMSGS("english", null), + new MYLOCALEERRMSGS("czech", null), new MYLOCALEERRMSGS("danish", null), + new MYLOCALEERRMSGS("dutch", null), new MYLOCALEERRMSGS("estonian", null), + new MYLOCALEERRMSGS("french", null), new MYLOCALEERRMSGS("german", null), + new MYLOCALEERRMSGS("greek", null), new MYLOCALEERRMSGS("hungarian", null), + new MYLOCALEERRMSGS("italian", null), new MYLOCALEERRMSGS("japanese", null), + new MYLOCALEERRMSGS("korean", null), new MYLOCALEERRMSGS("norwegian", null), + new MYLOCALEERRMSGS("norwegian-ny", null), new MYLOCALEERRMSGS("polish", null), + new MYLOCALEERRMSGS("portuguese", null), new MYLOCALEERRMSGS("romanian", null), + new MYLOCALEERRMSGS("russian", null), new MYLOCALEERRMSGS("serbian", null), + new MYLOCALEERRMSGS("slovak", null), new MYLOCALEERRMSGS("spanish", null), + new MYLOCALEERRMSGS("swedish", null), new MYLOCALEERRMSGS("ukrainian", null), + new MYLOCALEERRMSGS(null, null) }; + + public static final String NullS = MySQLcom.Nulls; + + /***** LOCALE BEGIN en_US: English - United States *****/ + static String my_locale_month_names_en_US[] = { "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December", NullS }; + static String my_locale_ab_month_names_en_US[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec", NullS }; + static String my_locale_day_names_en_US[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", + "Sunday", NullS }; + static String my_locale_ab_day_names_en_US[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NullS }; + static TYPELIB my_locale_typelib_month_names_en_US = new TYPELIB(my_locale_month_names_en_US.length - 1, "", + my_locale_month_names_en_US, null); + static TYPELIB my_locale_typelib_ab_month_names_en_US = new TYPELIB(my_locale_ab_month_names_en_US.length - 1, "", + my_locale_ab_month_names_en_US, null); + static TYPELIB my_locale_typelib_day_names_en_US = new TYPELIB((my_locale_day_names_en_US.length) - 1, "", + my_locale_day_names_en_US, null); + static TYPELIB my_locale_typelib_ab_day_names_en_US = new TYPELIB((my_locale_ab_day_names_en_US.length) - 1, "", + my_locale_ab_day_names_en_US, null); + public static MYLOCALE my_locale_en_US = new MYLOCALE(0, "en_US", "English - United States", true, + my_locale_typelib_month_names_en_US, my_locale_typelib_ab_month_names_en_US, + my_locale_typelib_day_names_en_US, my_locale_typelib_ab_day_names_en_US, 9, 9, + '.', /* + * decimal point en_US + */ + ',', /* thousands_sep en_US */ + new String(new byte[] { 3, 3 }), /* grouping en_US */ + global_errmsgs[err_msgs_index.en_US.ordinal()]); + /***** LOCALE END en_US *****/ + +} diff --git a/src/main/java/io/mycat/plan/common/meta/TempTable.java b/src/main/java/io/mycat/plan/common/meta/TempTable.java new file mode 100644 index 000000000..1d85d8f97 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/meta/TempTable.java @@ -0,0 +1,68 @@ +package io.mycat.plan.common.meta; + +import java.util.Date; +import java.util.List; + +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; +import io.mycat.plan.common.exception.TempTableException; +import io.mycat.plan.common.external.ResultStore; + +public class TempTable { + private String createdtime; + private List fieldPackets; + private ResultStore rowsStore; + private String charset; + + public TempTable() { + this.createdtime = new Date().toString(); + } + + public void addRow(RowDataPacket row) { + this.rowsStore.add(row); + } + + public void dataEof() { + this.rowsStore.done(); + } + + public RowDataPacket nextRow() { + if (this.rowsStore == null) + throw new TempTableException("exception happend when try to get temptable"); + return this.rowsStore.next(); + } + + public void close() { + if (rowsStore != null) + rowsStore.close(); + } + + public String getCreatedtime() { + return createdtime; + } + + public List getFieldPackets() { + return fieldPackets; + } + + public String getCharset() { + return charset; + } + + public ResultStore getRowsStore() { + return rowsStore; + } + + public void setRowsStore(ResultStore rowsStore) { + this.rowsStore = rowsStore; + } + + public void setFieldPackets(List fieldPackets) { + this.fieldPackets = fieldPackets; + } + + public void setCharset(String charset) { + this.charset = charset; + } + +} diff --git a/src/main/java/io/mycat/plan/common/ptr/BoolPtr.java b/src/main/java/io/mycat/plan/common/ptr/BoolPtr.java new file mode 100644 index 000000000..c7ac07fd1 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/BoolPtr.java @@ -0,0 +1,17 @@ +package io.mycat.plan.common.ptr; + +public class BoolPtr { + private boolean b; + + public BoolPtr(boolean b) { + this.b = b; + } + + public boolean get() { + return b; + } + + public void set(boolean b) { + this.b = b; + } +} diff --git a/src/main/java/io/mycat/plan/common/ptr/DecimalPtr.java b/src/main/java/io/mycat/plan/common/ptr/DecimalPtr.java new file mode 100644 index 000000000..33538115e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/DecimalPtr.java @@ -0,0 +1,19 @@ +package io.mycat.plan.common.ptr; + +import java.math.BigDecimal; + +public class DecimalPtr { + private BigDecimal bd; + + public DecimalPtr(BigDecimal bd) { + this.bd = bd; + } + + public BigDecimal get() { + return bd; + } + + public void set(BigDecimal bd) { + this.bd = bd; + } +} diff --git a/src/main/java/io/mycat/plan/common/ptr/DoublePtr.java b/src/main/java/io/mycat/plan/common/ptr/DoublePtr.java new file mode 100644 index 000000000..477149205 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/DoublePtr.java @@ -0,0 +1,17 @@ +package io.mycat.plan.common.ptr; + +public class DoublePtr { + private double db; + + public DoublePtr(double db) { + this.db = db; + } + + public double get() { + return db; + } + + public void set(double db) { + this.db = db; + } +} diff --git a/src/main/java/io/mycat/plan/common/ptr/GenericPtr.java b/src/main/java/io/mycat/plan/common/ptr/GenericPtr.java new file mode 100644 index 000000000..3f0b29de4 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/GenericPtr.java @@ -0,0 +1,18 @@ +package io.mycat.plan.common.ptr; + +public class GenericPtr { + private T value; + + public GenericPtr(T initValue) { + this.value = initValue; + } + + public T get() { + return value; + } + + public void set(T value) { + this.value = value; + } + +} diff --git a/src/main/java/io/mycat/plan/common/ptr/ItemResultPtr.java b/src/main/java/io/mycat/plan/common/ptr/ItemResultPtr.java new file mode 100644 index 000000000..50520a679 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/ItemResultPtr.java @@ -0,0 +1,19 @@ +package io.mycat.plan.common.ptr; + +import io.mycat.plan.common.item.Item.ItemResult; + +public class ItemResultPtr { + private ItemResult itemResult; + + public ItemResultPtr(ItemResult itemResult) { + this.itemResult = itemResult; + } + + public ItemResult get() { + return itemResult; + } + + public void set(ItemResult itemResult) { + this.itemResult = itemResult; + } +} diff --git a/src/main/java/io/mycat/plan/common/ptr/LongPtr.java b/src/main/java/io/mycat/plan/common/ptr/LongPtr.java new file mode 100644 index 000000000..e6183fbfd --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/LongPtr.java @@ -0,0 +1,25 @@ +package io.mycat.plan.common.ptr; + +public class LongPtr { + private long l; + + public LongPtr(long l) { + this.l = l; + } + + public long get() { + return l; + } + + public void set(long l) { + this.l = l; + } + + public long decre() { + return l--; + } + + public long incre() { + return l++; + } +} diff --git a/src/main/java/io/mycat/plan/common/ptr/StringPtr.java b/src/main/java/io/mycat/plan/common/ptr/StringPtr.java new file mode 100644 index 000000000..1903ee87d --- /dev/null +++ b/src/main/java/io/mycat/plan/common/ptr/StringPtr.java @@ -0,0 +1,17 @@ +package io.mycat.plan.common.ptr; + +public class StringPtr { + private String s; + + public StringPtr(String s) { + this.s = s; + } + + public String get() { + return s; + } + + public void set(String s) { + this.s = s; + } +} diff --git a/src/main/java/io/mycat/plan/common/time/DateTimeFormat.java b/src/main/java/io/mycat/plan/common/time/DateTimeFormat.java new file mode 100644 index 000000000..4d0f134f6 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/DateTimeFormat.java @@ -0,0 +1,17 @@ +package io.mycat.plan.common.time; + +public class DateTimeFormat { + public char time_separator; /* Separator between hour and minute */ + public int flag; /* For future */ + public String format; + + public DateTimeFormat() { + + } + + public DateTimeFormat(char time_separator, int flag, String format) { + this.time_separator = time_separator; + this.flag = flag; + this.format = format; + } +} diff --git a/src/main/java/io/mycat/plan/common/time/DateTimeUnit.java b/src/main/java/io/mycat/plan/common/time/DateTimeUnit.java new file mode 100644 index 000000000..89661508f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/DateTimeUnit.java @@ -0,0 +1,5 @@ +package io.mycat.plan.common.time; + +public enum DateTimeUnit { + MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR, SECOND_MICROSECOND, MINUTE_MICROSECOND, MINUTE_SECOND, HOUR_MICROSECOND, HOUR_SECOND, HOUR_MINUTE, DAY_MICROSECOND, DAY_SECOND, DAY_MINUTE, DAY_HOUR, YEAR_MONTH, +} diff --git a/src/main/java/io/mycat/plan/common/time/INTERVAL.java b/src/main/java/io/mycat/plan/common/time/INTERVAL.java new file mode 100644 index 000000000..d33607591 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/INTERVAL.java @@ -0,0 +1,12 @@ +package io.mycat.plan.common.time; + +public class INTERVAL { + public long year, month, day, hour; + public long minute, second, second_part; + public boolean neg; + + public INTERVAL() { + year = month = day = hour = minute = second = second_part = 0; + neg = false; + } +} diff --git a/src/main/java/io/mycat/plan/common/time/LLDivT.java b/src/main/java/io/mycat/plan/common/time/LLDivT.java new file mode 100644 index 000000000..352edaa56 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/LLDivT.java @@ -0,0 +1,9 @@ +/** + * + */ +package io.mycat.plan.common.time; + +public class LLDivT { + public long quot; + public long rem; +} diff --git a/src/main/java/io/mycat/plan/common/time/MySQLTime.java b/src/main/java/io/mycat/plan/common/time/MySQLTime.java new file mode 100644 index 000000000..41d07bcca --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/MySQLTime.java @@ -0,0 +1,60 @@ +package io.mycat.plan.common.time; + +import java.io.Serializable; +import java.util.Calendar; + +public class MySQLTime implements Serializable { + private static final long serialVersionUID = 8021983316690707464L; + public long year, month, day, hour, minute, second; + public long second_part; + /** < microseconds */ + public boolean neg; + public MySQLTimestampType time_type; + + public void set_zero_time(MySQLTimestampType type) { + year = month = day = hour = minute = second = second_part = 0; + neg = false; + time_type = type; + } + + public boolean isNonZeroDate() { + return year != 0 || month != 0 || day != 0; + } + + public boolean isNonZeroTime() { + return hour != 0 || minute != 0 || second != 0 || second_part != 0; + } + + public java.util.Calendar toCalendar() { + java.util.Calendar cal = java.util.Calendar.getInstance(); + cal.set((int) year, (int) month, (int) day, (int) hour, (int) minute, (int) second); + cal.set(java.util.Calendar.MILLISECOND, (int) second_part / 1000); + return cal; + } + + public void set_max_hhmmss() { + hour = MyTime.TIME_MAX_HOUR; + minute = MyTime.TIME_MAX_MINUTE; + second = MyTime.TIME_MAX_SECOND; + } + + public void setCal(java.util.Calendar cal) { + year = cal.get(Calendar.YEAR); + month = cal.get(Calendar.MONTH); + day = cal.get(Calendar.DAY_OF_MONTH); + hour = cal.get(Calendar.HOUR_OF_DAY); + minute = cal.get(Calendar.MINUTE); + second = cal.get(Calendar.SECOND); + second_part = (cal.getTimeInMillis() % 1000) * 1000; + } + + public int compareTo(MySQLTime other) { + if (other == null) + return 1; + long lt1 = MyTime.TIME_to_longlong_datetime_packed(this); + long lt2 = MyTime.TIME_to_longlong_datetime_packed(other); + long cmp = lt1 - lt2; + return cmp == 0 ? 0 : (lt1 > lt2 ? 1 : -1); + } + +} diff --git a/src/main/java/io/mycat/plan/common/time/MySQLTimeStatus.java b/src/main/java/io/mycat/plan/common/time/MySQLTimeStatus.java new file mode 100644 index 000000000..0e901af62 --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/MySQLTimeStatus.java @@ -0,0 +1,7 @@ +package io.mycat.plan.common.time; + +public class MySQLTimeStatus { + public int warnings; + public long fractional_digits; + public long nanoseconds; +} diff --git a/src/main/java/io/mycat/plan/common/time/MySQLTimestampType.java b/src/main/java/io/mycat/plan/common/time/MySQLTimestampType.java new file mode 100644 index 000000000..bccb0112f --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/MySQLTimestampType.java @@ -0,0 +1,23 @@ +package io.mycat.plan.common.time; + +public enum MySQLTimestampType { + + MYSQL_TIMESTAMP_NONE(-2), MYSQL_TIMESTAMP_ERROR(-1), MYSQL_TIMESTAMP_DATE(0), MYSQL_TIMESTAMP_DATETIME( + 1), MYSQL_TIMESTAMP_TIME(2); + private int i; + + private MySQLTimestampType(int i) { + this.i = i; + } + + public static MySQLTimestampType valueOf(int i) { + if (i < 0 || i >= values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return values()[i]; + } + + public String toString() { + return String.valueOf(i); + } +}; \ No newline at end of file diff --git a/src/main/java/io/mycat/plan/common/time/MyTime.java b/src/main/java/io/mycat/plan/common/time/MyTime.java new file mode 100644 index 000000000..3286a8f9e --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/MyTime.java @@ -0,0 +1,2916 @@ +package io.mycat.plan.common.time; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalUnit; + +import io.mycat.plan.common.Ctype; +import io.mycat.plan.common.MySQLcom; +import io.mycat.plan.common.item.FieldTypes; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.locale.MYLOCALE; +import io.mycat.plan.common.locale.MYLOCALES; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.common.ptr.LongPtr; +import io.mycat.plan.common.ptr.StringPtr; + +public class MyTime { + public static DateTimeFormat time_ampm_format = new DateTimeFormat('\0', 0, "%I:%i:%S %p"); + public static DateTimeFormat time_24hrs_format = new DateTimeFormat('\0', 0, "%H:%i:%S"); + + /* Flags to str_to_datetime and number_to_datetime */ + public final static int TIME_FUZZY_DATE = 1; + public final static int TIME_DATETIME_ONLY = 2; + public final static int TIME_NO_NSEC_ROUNDING = 4; + public final static int TIME_NO_DATE_FRAC_WARN = 8; + + public final static int DATETIME_MAX_DECIMALS = 6; + + public final static int MAX_DATE_PARTS = 8; + public final static int MAX_DATE_WIDTH = 10; + public final static int MAX_TIME_WIDTH = 10; + public final static int MAX_DATETIME_WIDTH = 19; + public final static int MAX_TIME_FULL_WIDTH = 23; /* + * -DDDDDD + * HH:MM:SS.###### + */ + public final static int MAX_DATETIME_FULL_WIDTH = 29; /* + * YYYY-MM-DD + * HH:MM:SS.###### + * AM + */ + public final static int MAX_DATE_STRING_REP_LENGTH = 30; + + public final static int TIME_MAX_HOUR = 838; + public final static int TIME_MAX_MINUTE = 59; + public final static int TIME_MAX_SECOND = 59; + + public final static int MAX_DAY_NUMBER = 3652424; + + public final static int SECONDS_IN_24H = 86400; + + public final static int TIME_MAX_VALUE = (TIME_MAX_HOUR * 10000 + TIME_MAX_MINUTE * 100 + TIME_MAX_SECOND); + + public final static long TIME_MAX_VALUE_SECONDS = (TIME_MAX_HOUR * 3600L + TIME_MAX_MINUTE * 60L + TIME_MAX_SECOND); + + /* Must be same as MODE_NO_ZERO_IN_DATE */ + public final static long TIME_NO_ZERO_IN_DATE = (65536L * 2 * 2 * 2 * 2 * 2 * 2 * 2); + /* Must be same as MODE_NO_ZERO_DATE */ + public final static long TIME_NO_ZERO_DATE = (TIME_NO_ZERO_IN_DATE * 2); + public final static long TIME_INVALID_DATES = (TIME_NO_ZERO_DATE * 2); + + /* Conversion warnings */ + public final static int MYSQL_TIME_WARN_TRUNCATED = 1; + public final static int MYSQL_TIME_WARN_OUT_OF_RANGE = 2; + public final static int MYSQL_TIME_WARN_INVALID_TIMESTAMP = 4; + public final static int MYSQL_TIME_WARN_ZERO_DATE = 8; + public final static int MYSQL_TIME_NOTE_TRUNCATED = 16; + public final static int MYSQL_TIME_WARN_ZERO_IN_DATE = 32; + + public final static int YY_PART_YEAR = 70; + + public final static long long_MAX = MySQLcom.getUnsignedLong(Integer.MAX_VALUE).longValue(); + + static char time_separator = ':'; + + /* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */ + + public static int internal_format_positions[] = { 0, 1, 2, 3, 4, 5, 6, 255 }; + + public static int days_in_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 }; + + public static final String[] month_names = { "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December" }; + + public static final String[] ab_month_names = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" }; + + public static final String[] day_names = { "Monday", "Tuesday", "Thursday", "Friday", "Saturday", "Sunday" }; + + public static final String[] ab_day_names = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + + /* Flags for calc_week() function. */ + public static final int WEEK_MONDAY_FIRST = 1; + public static final int WEEK_YEAR = 2; + public static final int WEEK_FIRST_WEEKDAY = 4; + + /*-------------------------My_time.h start------------------------------ + *-------------------------My_time.h都是time类型的转向其它类型的方法--------------- */ + + /* + * Handle 2 digit year conversions + * + * SYNOPSIS year_2000_handling() year 2 digit year + * + * RETURN Year between 1970-2069 + */ + + public static long year_2000_handling(long year) { + if ((year = year + 1900) < 1900 + YY_PART_YEAR) + year += 100; + return year; + } + + /** + * @brief Check datetime value for validity according to flags. + * @param[in] ltime Date to check. + * @param[in] not_zero_date ltime is not the zero date + * @param[in] flags flags to check (see str_to_datetime() flags in + * my_time.h) + * @param[out] was_cut set to 2 if value was invalid according to flags. + * (Feb 29 in non-leap etc.) This remains unchanged if value is + * not invalid. + * @details Here we assume that year and month is ok! If month is 0 we allow + * any date. (This only happens if we allow zero date parts in + * str_to_datetime()) Disallow dates with zero year and non-zero + * month and/or day. + * @return 0 OK 1 error + */ + + public static boolean check_date(final MySQLTime ltime, boolean not_zero_date, long flags, LongPtr was_cut) { + if (not_zero_date) { + if (((flags & TIME_NO_ZERO_IN_DATE) != 0 || (flags & TIME_FUZZY_DATE) == 0) + && (ltime.month == 0 || ltime.day == 0)) { + was_cut.set(MYSQL_TIME_WARN_ZERO_IN_DATE); + return true; + } else if (((flags & TIME_INVALID_DATES) == 0 && ltime.month != 0 + && ltime.day > days_in_month[(int) ltime.month - 1] + && (ltime.month != 2 || calc_days_in_year(ltime.year) != 366 || ltime.day != 29))) { + was_cut.set(MYSQL_TIME_WARN_OUT_OF_RANGE); + return true; + } + } else if ((flags & TIME_NO_ZERO_DATE) != 0) { + was_cut.set(MYSQL_TIME_WARN_ZERO_DATE); + return true; + } + return false; + } + + /* + * Convert a timestamp string to a MYSQL_TIME value. + * + * SYNOPSIS str_to_datetime() str String to parse length Length of string + * l_time Date is stored here flags Bitmap of following items + * TIME_FUZZY_DATE Set if we should allow partial dates TIME_DATETIME_ONLY + * Set if we only allow full datetimes. TIME_NO_ZERO_IN_DATE Don't allow + * partial dates TIME_NO_ZERO_DATE Don't allow 0000-00-00 date + * TIME_INVALID_DATES Allow 2000-02-31 status Conversion status + * + * + * DESCRIPTION At least the following formats are recogniced (based on + * number of digits) YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS + * YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS YYYYMMDDTHHMMSS where T is a the + * character T (ISO8601) Also dates where all parts are zero are allowed + * + * The second part may have an optional .###### fraction part. + * + * NOTES This function should work with a format position vector as long as + * the following things holds: - All date are kept together and all time + * parts are kept together - Date and time parts must be separated by blank + * - Second fractions must come after second part and be separated by a '.'. + * (The second fractions are optional) - AM/PM must come after second + * fractions (or after seconds if no fractions) - Year must always been + * specified. - If time is before date, then we will use datetime format + * only if the argument consist of two parts, separated by space. Otherwise + * we will assume the argument is a date. - The hour part must be specified + * in hour-minute-second order. + * + * status.warnings is set to: 0 Value OK MYSQL_TIME_WARN_TRUNCATED If value + * was cut during conversion MYSQL_TIME_WARN_OUT_OF_RANGE + * check_date(date,flags) considers date invalid + * + * l_time.time_type is set as follows: MYSQL_TIMESTAMP_NONE String wasn't a + * timestamp, like [DD [HH:[MM:[SS]]]].fraction. l_time is not changed. + * MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok) + * MYSQL_TIMESTAMP_DATETIME Full timestamp MYSQL_TIMESTAMP_ERROR Timestamp + * with wrong values. All elements in l_time is set to 0 RETURN VALUES 0 - + * Ok 1 - Error + */ + public static boolean str_to_datetime(String strori, int length, MySQLTime l_time, long flags, + MySQLTimeStatus status) { + /* Skip space at start */ + String str = strori.trim(); + long field_length, year_length = 0, digits, i, number_of_fields; + long[] date = new long[MAX_DATE_PARTS]; + int[] date_len = new int[MAX_DATE_PARTS]; + long add_hours = 0, start_loop; + long not_zero_date, allow_space; + boolean is_internal_format; + final char[] chars = str.toCharArray(); + int pos, last_field_pos = 0; + int end = str.length(); + int[] format_position; + boolean found_delimitier = false, found_space = false; + int frac_pos, frac_len; + + field_length = 0; + + my_time_status_init(status); + + if (str.isEmpty() || !Ctype.isDigit(str.charAt(0))) { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_NONE; + return true; + } + + is_internal_format = false; + /* + * This has to be changed if want to activate different timestamp + * formats + */ + format_position = internal_format_positions; + + /* + * Calculate number of digits in first part. If length= 8 or >= 14 then + * year is of format YYYY. (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS) + */ + for (pos = 0; pos != end && (Ctype.isDigit(chars[pos]) || chars[pos] == 'T'); pos++) + ; + + digits = (long) (pos - 0); + start_loop = 0; /* Start of scan loop */ + date_len[format_position[0]] = 0; /* Length of year field */ + if (pos == end || chars[pos] == '.') { + /* Found date in internal format (only numbers like YYYYMMDD) */ + year_length = (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2; + field_length = year_length; + is_internal_format = true; + format_position = internal_format_positions; + } else { + if (format_position[0] >= 3) /* If year is after HHMMDD */ + { + /* + * If year is not in first part then we have to determinate if + * we got a date field or a datetime field. We do this by + * checking if there is two numbers separated by space in the + * input. + */ + while (pos < end && !Ctype.spaceChar(chars[pos])) + pos++; + while (pos < end && !Ctype.isDigit(chars[pos])) + pos++; + if (pos == end) { + if ((flags & TIME_DATETIME_ONLY) != 0) { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_NONE; + return true; /* Can't be a full datetime */ + } + /* Date field. Set hour, minutes and seconds to 0 */ + date[0] = date[1] = date[2] = date[3] = date[4] = 0; + start_loop = 5; /* Start with first date part */ + } + } + + field_length = format_position[0] == 0 ? 4 : 2; + } + + /* + * Only allow space in the first "part" of the datetime field and: - + * after days, part seconds - before and after AM/PM (handled by code + * later) + * + * 2003-03-03 20:00:20 AM 20:00:20.000000 AM 03-03-2000 + */ + i = Math.max((long) format_position[0], (long) format_position[1]); + if (i < (long) format_position[2]) + i = (long) format_position[2]; + allow_space = ((1 << i) | (1 << format_position[6])); + allow_space &= (1 | 2 | 4 | 8 | 64); + + not_zero_date = 0; + int strindex = 0; + for (i = start_loop; i < MAX_DATE_PARTS - 1 && strindex != end && Ctype.isDigit(chars[strindex]); i++) { + final int start = strindex; + int tmp_value = chars[strindex++] - '0'; + + /* + * Internal format means no delimiters; every field has a fixed + * width. Otherwise, we scan until we find a delimiter and discard + * leading zeroes -- except for the microsecond part, where leading + * zeroes are significant, and where we never process more than six + * digits. + */ + boolean scan_until_delim = !is_internal_format && ((i != format_position[6])); + + while (strindex != end && Ctype.isDigit(chars[strindex]) && (scan_until_delim || (--field_length != 0))) { + tmp_value = tmp_value * 10 + (chars[strindex] - '0'); + strindex++; + } + date_len[(int) i] = (strindex - start); + if (tmp_value > 999999) /* Impossible date part */ + { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_NONE; + return true; + } + date[(int) i] = tmp_value; + not_zero_date |= tmp_value; + + /* Length of next field */ + field_length = format_position[(int) i + 1] == 0 ? 4 : 2; + + if ((last_field_pos = strindex) == end) { + i++; /* Register last found part */ + break; + } + /* Allow a 'T' after day to allow CCYYMMDDT type of fields */ + if (i == format_position[2] && chars[strindex] == 'T') { + strindex++; /* ISO8601: CCYYMMDDThhmmss */ + continue; + } + if (i == format_position[5]) /* Seconds */ + { + if (chars[strindex] == '.') /* Followed by part seconds */ + { + strindex++; + /* + * Shift last_field_pos, so '2001-01-01 00:00:00.' is + * treated as a valid value + */ + last_field_pos = strindex; + field_length = 6; /* 6 digits */ + } + continue; + } + while (strindex != end && (Ctype.isPunct(chars[strindex]) || Ctype.spaceChar(chars[strindex]))) + // (my_ispunct(&my_charset_latin1,*str) || + // my_isspace(&my_charset_latin1,*str))) + { + if (Ctype.spaceChar(chars[strindex])) { + if ((allow_space & (1 << i)) == 0) { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_NONE; + return true; + } + found_space = true; + } + strindex++; + found_delimitier = true; /* Should be a 'normal' date */ + } + /* Check if next position is AM/PM */ + if (i == format_position[6]) /* Seconds, time for AM/PM */ + { + i++; /* Skip AM/PM part */ + if (format_position[7] != 255) /* If using AM/PM */ + { + if (strindex + 2 <= end && (chars[strindex + 1] == 'M' || chars[strindex + 1] == 'm')) { + if (chars[strindex] == 'p' || chars[strindex] == 'P') + add_hours = 12; + else if (chars[strindex] != 'a' || chars[strindex] != 'A') + continue; /* Not AM/PM */ + strindex += 2; /* Skip AM/PM */ + /* Skip space after AM/PM */ + while (strindex != end && Ctype.spaceChar(chars[strindex])) + strindex++; + } + } + } + last_field_pos = strindex; + } + if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY) != 0) { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_NONE; + return true; /* Can't be a datetime */ + } + + strindex = last_field_pos; + + number_of_fields = i - start_loop; + while (i < MAX_DATE_PARTS) { + date_len[(int) i] = 0; + date[(int) i++] = 0; + } + + if (!is_internal_format) { + year_length = date_len[format_position[0]]; + if (year_length == 0) /* Year must be specified */ + { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_NONE; + return true; + } + + l_time.year = date[format_position[0]]; + l_time.month = date[format_position[1]]; + l_time.day = date[format_position[2]]; + l_time.hour = date[format_position[3]]; + l_time.minute = date[format_position[4]]; + l_time.second = date[format_position[5]]; + + frac_pos = format_position[6]; + frac_len = date_len[frac_pos]; + status.fractional_digits = frac_len; + if (frac_len < 6) + date[(int) frac_pos] *= MySQLcom.log_10_int[DATETIME_MAX_DECIMALS - frac_len]; + l_time.second_part = date[frac_pos]; + + if (format_position[7] != 255) { + if (l_time.hour > 12) { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + l_time.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } + l_time.hour = l_time.hour % 12 + add_hours; + } + } else { + l_time.year = date[0]; + l_time.month = date[1]; + l_time.day = date[2]; + l_time.hour = date[3]; + l_time.minute = date[4]; + l_time.second = date[5]; + if (date_len[6] < 6) + date[6] *= MySQLcom.log_10_int[DATETIME_MAX_DECIMALS - date_len[6]]; + l_time.second_part = date[6]; + status.fractional_digits = date_len[6]; + } + l_time.neg = false; + + if (year_length == 2 && not_zero_date != 0) + l_time.year += (l_time.year < YY_PART_YEAR ? 2000 : 1900); + + /* + * Set time_type before check_datetime_range(), as the latter relies on + * initialized time_type value. + */ + l_time.time_type = (number_of_fields <= 3 ? MySQLTimestampType.MYSQL_TIMESTAMP_DATE + : MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); + + if (number_of_fields < 3 || check_datetime_range(l_time)) { + /* + * Only give warning for a zero date if there is some garbage after + */ + if (not_zero_date == 0) /* If zero date */ + { + for (; strindex != end; strindex++) { + if (!Ctype.spaceChar(chars[strindex])) { + not_zero_date = 1; /* Give warning */ + break; + } + } + } + status.warnings |= not_zero_date != 0 ? MYSQL_TIME_WARN_TRUNCATED : MYSQL_TIME_WARN_ZERO_DATE; + l_time.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } + + LongPtr lptmp = new LongPtr(0); + boolean bcheckdate = check_date(l_time, not_zero_date != 0, flags, lptmp); + status.warnings = (int) lptmp.get(); + if (bcheckdate) { + l_time.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } + + /* Scan all digits left after microseconds */ + if (status.fractional_digits == 6 && strindex != end) { + if (Ctype.isDigit(chars[strindex])) { + /* + * We don't need the exact nanoseconds value. Knowing the first + * digit is enough for rounding. + */ + status.nanoseconds = 100 * (int) (chars[strindex++] - '0'); + for (; strindex != end && Ctype.isDigit(chars[strindex]); strindex++) { + } + } + } + + for (; strindex != end; strindex++) { + if (!Ctype.spaceChar(chars[strindex])) { + status.warnings = MYSQL_TIME_WARN_TRUNCATED; + break; + } + } + + return false; + } + + /* + * Convert a time string to a MYSQL_TIME struct. + * + * SYNOPSIS str_to_time() str A string in full TIMESTAMP format or [-] DAYS + * [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS, [M]MSS or [S]S There may be an + * optional [.second_part] after seconds length Length of str l_time Store + * result here status Conversion status + * + * status.warning is set to: MYSQL_TIME_WARN_TRUNCATED flag if the input + * string was cut during conversion, and/or MYSQL_TIME_WARN_OUT_OF_RANGE + * flag, if the value is out of range. + * + * NOTES Because of the extra days argument, this function can only work + * with times where the time arguments are in the above order. + * + * RETURN 0 ok 1 error + */ + public static boolean str_to_time(String str, int length, MySQLTime l_time, MySQLTimeStatus status) { + long date[] = new long[5]; + long value; + int pos = 0, end = length; + int end_of_days; + boolean found_days, found_hours; + int state; + final char[] chars = str.toCharArray(); + my_time_status_init(status); + l_time.neg = false; + for (; pos != end && Ctype.spaceChar(chars[pos]); pos++) + length--; + if (pos != end && chars[pos] == '-') { + l_time.neg = true; + pos++; + length--; + } + if (pos == end) + return true; + + /* Check first if this is a full TIMESTAMP */ + if (length >= 12) { /* Probably full timestamp */ + str_to_datetime(str.substring(pos), length, l_time, (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), status); + if (l_time.time_type.compareTo(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR) >= 0) + return l_time.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_ERROR; + my_time_status_init(status); + } + + /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ + for (value = 0; pos != end && Ctype.isDigit(chars[pos]); pos++) + value = value * 10L + (long) (chars[pos] - '0'); + + if (value > long_MAX) + return true; + + /* Skip all space after 'days' */ + end_of_days = pos; + for (; pos != end && Ctype.spaceChar(chars[pos]); pos++) + ; + + state = 0; + found_days = found_hours = false; + boolean gotofractional = false; + if ((int) (end - pos) > 1 && pos != end_of_days + && Ctype.isDigit(chars[pos])) { /* Found days part */ + date[0] = value; + state = 1; /* Assume next is hours */ + found_days = true; + } else if ((end - pos) > 1 && chars[pos] == time_separator && Ctype.isDigit(chars[pos + 1])) { + date[0] = 0; /* Assume we found hours */ + date[1] = value; + state = 2; + found_hours = true; + pos++; /* skip ':' */ + } else { + /* String given as one number; assume HHMMSS format */ + date[0] = 0; + date[1] = (value / 10000); + date[2] = (value / 100 % 100); + date[3] = (value % 100); + state = 4; + gotofractional = true; + } + if (!gotofractional) { + /* Read hours, minutes and seconds */ + for (;;) { + for (value = 0; pos != end && Ctype.isDigit(chars[pos]); pos++) + value = value * 10L + (long) (chars[pos] - '0'); + date[state++] = value; + if (state == 4 || (end - pos) < 2 || chars[pos] != time_separator || !Ctype.isDigit(chars[pos + 1])) + break; + pos++; /* Skip time_separator (':') */ + } + + if (state != 4) { /* Not HH:MM:SS */ + // TODO + /* + * Fix the date to assume that seconds was given if + * (!found_hours && !found_days) { size_t len= sizeof(long) * + * (state - 1); memmove((uchar*) (date+4) - len, (uchar*) + * (date+state) - len, len); memset(date, 0, + * sizeof(long)*(4-state)); } else memset((date+state), 0, + * sizeof(long)*(4-state)); + */ + } + } + + /* Get fractional second part */ + if ((end - pos) >= 2 && chars[pos] == '.' && Ctype.isDigit(chars[pos + 1])) { + int field_length = 5; + pos++; + value = (chars[pos] - '0'); + while (++pos != end && Ctype.isDigit(chars[pos])) { + if (field_length-- > 0) + value = value * 10 + (chars[pos] - '0'); + } + if (field_length >= 0) { + status.fractional_digits = DATETIME_MAX_DECIMALS - field_length; + if (field_length > 0) + value *= (long) MySQLcom.log_10_int[field_length]; + } else { + /* Scan digits left after microseconds */ + status.fractional_digits = 6; + status.nanoseconds = 100 * (int) (chars[pos - 1] - '0'); + for (; pos != end && Ctype.isDigit(chars[pos]); pos++) { + } + } + date[4] = value; + } else if ((end - pos) == 1 && chars[pos] == '.') { + pos++; + date[4] = 0; + } else + date[4] = 0; + + /* Check for exponent part: E | E */ + /* (may occur as result of %g formatting of time value) */ + if ((end - pos) > 1 && (chars[pos] == 'e' || chars[pos] == 'E') + && (Ctype.isDigit(chars[pos + 1]) || ((chars[pos + 1] == '-' || chars[pos + 1] == '+') + && (end - pos) > 2 && Ctype.isDigit(chars[pos + 2])))) + return true; + + if (internal_format_positions[7] != 255) { + /* Read a possible AM/PM */ + while (pos != end && Ctype.spaceChar(chars[pos])) + pos++; + if (pos + 2 <= end && (chars[pos + 1] == 'M' || chars[pos + 1] == 'm')) { + if (chars[pos] == 'p' || chars[pos] == 'P') { + str += 2; + date[1] = date[1] % 12 + 12; + } else if (chars[pos] == 'a' || chars[pos] == 'A') + str += 2; + } + } + + /* Integer overflow checks */ + if (date[0] > long_MAX || date[1] > long_MAX || date[2] > long_MAX || date[3] > long_MAX || date[4] > long_MAX) + return true; + + l_time.year = 0; /* For protocol::store_time */ + l_time.month = 0; + + l_time.day = 0; + l_time.hour = date[1] + date[0] * 24; /* Mix days and hours */ + + l_time.minute = date[2]; + l_time.second = date[3]; + l_time.second_part = date[4]; + l_time.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + + if (check_time_mmssff_range(l_time)) { + status.warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE; + return true; + } + + /* Adjust the value into supported MYSQL_TIME range */ + // adjust_time_range(l_time, &status.warnings); + + /* Check if there is garbage at end of the MYSQL_TIME specification */ + if (pos != end) { + do { + if (!Ctype.spaceChar(chars[pos])) { + status.warnings |= MYSQL_TIME_WARN_TRUNCATED; + break; + } + } while (++pos != end); + } + return false; + } + + /* + * Convert datetime value specified as number to broken-down TIME + * representation and form value of DATETIME type as side-effect. + * + * SYNOPSIS number_to_datetime() nr - datetime value as number time_res - + * pointer for structure for broken-down representation flags - flags to use + * in validating date, as in str_to_datetime() was_cut 0 Value ok 1 If value + * was cut during conversion 2 check_date(date,flags) considers date invalid + * + * DESCRIPTION Convert a datetime value of formats YYMMDD, YYYYMMDD, + * YYMMDDHHMSS, YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. + * Return value in YYYYMMDDHHMMSS format as side-effect. + * + * This function also checks if datetime value fits in DATETIME range. + * + * RETURN VALUE -1 Timestamp with wrong values anything else DATETIME as + * integer in YYYYMMDDHHMMSS format Datetime value in YYYYMMDDHHMMSS format. + * + * was_cut if return value -1: one of - MYSQL_TIME_WARN_OUT_OF_RANGE - + * MYSQL_TIME_WARN_ZERO_DATE - MYSQL_TIME_WARN_TRUNCATED otherwise 0. + */ + public static long number_to_datetime(long nr, MySQLTime time_res, long flags, LongPtr was_cut) { + was_cut.set(0); + time_res.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_DATE); + + if (nr == 0 || nr >= 10000101000000L) { + time_res.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + if (nr > 99999999999999L) /* 9999-99-99 99:99:99 */ + { + was_cut.set(MYSQL_TIME_WARN_OUT_OF_RANGE); + return -1; + } + return number_to_datetime_ok(nr, time_res, flags, was_cut); + } + if (nr < 101) { + was_cut.set(MYSQL_TIME_WARN_TRUNCATED); + return -1; + } + if (nr <= (YY_PART_YEAR - 1) * 10000L + 1231L) { + nr = (nr + 20000000L) * 1000000L; /* YYMMDD, year: 2000-2069 */ + return number_to_datetime_ok(nr, time_res, flags, was_cut); + } + if (nr < (YY_PART_YEAR) * 10000L + 101L) { + was_cut.set(MYSQL_TIME_WARN_TRUNCATED); + return -1; + } + if (nr <= 991231L) { + nr = (nr + 19000000L) * 1000000L; /* YYMMDD, year: 1970-1999 */ + return number_to_datetime_ok(nr, time_res, flags, was_cut); + } + /* + * Though officially we support DATE values from 1000-01-01 only, one + * can easily insert a value like 1-1-1. So, for consistency reasons + * such dates are allowed when TIME_FUZZY_DATE is set. + */ + if (nr < 10000101L && (flags & TIME_FUZZY_DATE) == 0) { + was_cut.set(MYSQL_TIME_WARN_TRUNCATED); + return -1; + } + if (nr <= 99991231L) { + nr = nr * 1000000L; + return number_to_datetime_ok(nr, time_res, flags, was_cut); + } + if (nr < 101000000L) { + was_cut.set(MYSQL_TIME_WARN_TRUNCATED); + return -1; + } + + time_res.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + + if (nr <= (YY_PART_YEAR - 1) * (10000000000L) + (1231235959L)) { + nr = nr + (20000000000000L); /* YYMMDDHHMMSS, 2000-2069 */ + return number_to_datetime_ok(nr, time_res, flags, was_cut); + } + if (nr < YY_PART_YEAR * (10000000000L) + 101000000L) { + was_cut.set(MYSQL_TIME_WARN_TRUNCATED); + return -1; + } + if (nr <= 991231235959L) + nr = nr + 19000000000000L; /* YYMMDDHHMMSS, 1970-1999 */ + return number_to_datetime_ok(nr, time_res, flags, was_cut); + } + + /** + * helper goto ok + * + * @param nr + * @param time_res + * @param flags + * @param was_cut + * @return + */ + private static long number_to_datetime_ok(long nr, MySQLTime time_res, long flags, LongPtr was_cut) { + long part1 = (long) (nr / (1000000L)); + long part2 = (long) (nr - (long) part1 * (1000000L)); + time_res.year = (int) (part1 / 10000L); + part1 %= 10000L; + time_res.month = (int) part1 / 100; + time_res.day = (int) part1 % 100; + time_res.hour = (int) (part2 / 10000L); + part2 %= 10000L; + time_res.minute = (int) part2 / 100; + time_res.second = (int) part2 % 100; + + if (!check_datetime_range(time_res) && !check_date(time_res, (nr != 0), flags, was_cut)) + return nr; + + /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */ + if (nr == 0 && (flags & TIME_NO_ZERO_DATE) != 0) + return -1; + was_cut.set(MYSQL_TIME_WARN_TRUNCATED); + return -1; + } + + /** + * Convert number to TIME + * + * @param nr + * Number to convert. + * @param OUT + * ltime Variable to convert to. + * @param OUT + * warnings Warning vector. + * @retval false OK + * @retval true No. is out of range + */ + private static boolean number_to_time(long nr, MySQLTime ltime, LongPtr warnings) { + if (nr > TIME_MAX_VALUE) { + /* For huge numbers try full DATETIME, like str_to_time does. */ + if (nr >= 10000000000L) /* '0001-00-00 00-00-00' */ + { + long warnings_backup = warnings.get(); + if (number_to_datetime(nr, ltime, 0, warnings) != (-1)) + return false; + warnings.set(warnings_backup); + } + set_max_time(ltime, false); + warnings.set(warnings.get() | MYSQL_TIME_WARN_OUT_OF_RANGE); + return true; + } else if (nr < -TIME_MAX_VALUE) { + set_max_time(ltime, true); + warnings.set(warnings.get() | MYSQL_TIME_WARN_OUT_OF_RANGE); + return true; + } + if ((ltime.neg = (nr < 0))) + nr = -nr; + if (nr % 100 >= 60 + || nr / 100 % 100 >= 60) /* Check hours and minutes */ + { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + warnings.set(warnings.get() | MYSQL_TIME_WARN_OUT_OF_RANGE); + return true; + } + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + ltime.year = ltime.month = ltime.day = 0; + TIME_set_hhmmss(ltime, nr); + ltime.second_part = 0; + return false; + } + + public static long TIME_to_ulonglong_datetime(final MySQLTime my_time) { + return ((long) (my_time.year * 10000L + my_time.month * 100 + my_time.day) * (1000000) + + (long) (my_time.hour * 10000L + my_time.minute * 100L + my_time.second)); + } + + public static long TIME_to_ulonglong_date(final MySQLTime my_time) { + return (long) (my_time.year * 10000L + my_time.month * 100L + my_time.day); + } + + public static long TIME_to_ulonglong_time(final MySQLTime my_time) { + return (long) (my_time.hour * 10000L + my_time.minute * 100L + my_time.second); + } + + public static long TIME_to_ulonglong(final MySQLTime my_time) { + switch (my_time.time_type) { + case MYSQL_TIMESTAMP_DATETIME: + return TIME_to_ulonglong_datetime(my_time); + case MYSQL_TIMESTAMP_DATE: + return TIME_to_ulonglong_date(my_time); + case MYSQL_TIMESTAMP_TIME: + return TIME_to_ulonglong_time(my_time); + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + return 0; + default: + assert (false); + } + return 0; + } + + public static long MY_PACKED_TIME_GET_INT_PART(long x) { + return x >> 24; + } + + public static long MY_PACKED_TIME_GET_FRAC_PART(long x) { + return ((x) % (1L << 24)); + } + + public static long MY_PACKED_TIME_MAKE(long i, long f) { + return (i << 24) + f; + } + + public static long MY_PACKED_TIME_MAKE_INT(long i) { + return i << 24; + } + + /** + * Convert year to packed numeric date representation. Packed value for YYYY + * is the same to packed value for date YYYY-00-00. + */ + public static long year_to_longlong_datetime_packed(long year) { + long ymd = ((year * 13) << 5); + return MY_PACKED_TIME_MAKE_INT(ymd << 17); + } + + /** + * Convert datetime to packed numeric datetime representation. + * + * @param ltime + * The value to convert. + * @return Packed numeric representation of ltime. + */ + public static long TIME_to_longlong_datetime_packed(final MySQLTime ltime) { + long ymd = ((ltime.year * 13 + ltime.month) << 5) | ltime.day; + long hms = (ltime.hour << 12) | (ltime.minute << 6) | ltime.second; + long tmp = MY_PACKED_TIME_MAKE(((ymd << 17) | hms), ltime.second_part); + return ltime.neg ? -tmp : tmp; + } + + /** + * Convert date to packed numeric date representation. Numeric packed date + * format is similar to numeric packed datetime representation, with zero + * hhmmss part. + * + * @param ltime + * The value to convert. + * @return Packed numeric representation of ltime. + */ + public static long TIME_to_longlong_date_packed(final MySQLTime ltime) { + long ymd = ((ltime.year * 13 + ltime.month) << 5) | ltime.day; + return MY_PACKED_TIME_MAKE_INT(ymd << 17); + } + + /** + * Convert time value to numeric packed representation. + * + * @param ltime + * The value to convert. + * @return Numeric packed representation. + */ + public static long TIME_to_longlong_time_packed(final MySQLTime ltime) { + /* If month is 0, we mix day with hours: "1 00:10:10" . "24:00:10" */ + long hms = (((ltime.month != 0 ? 0 : ltime.day * 24) + ltime.hour) << 12) | (ltime.minute << 6) | ltime.second; + long tmp = MY_PACKED_TIME_MAKE(hms, ltime.second_part); + return ltime.neg ? -tmp : tmp; + } + + /** + * Convert a temporal value to packed numeric temporal representation, + * depending on its time_type. + * + * @ltime The value to convert. + * @return Packed numeric time/date/datetime representation. + */ + public static long TIME_to_longlong_packed(final MySQLTime ltime) { + switch (ltime.time_type) { + case MYSQL_TIMESTAMP_DATE: + return TIME_to_longlong_date_packed(ltime); + case MYSQL_TIMESTAMP_DATETIME: + return TIME_to_longlong_datetime_packed(ltime); + case MYSQL_TIMESTAMP_TIME: + return TIME_to_longlong_time_packed(ltime); + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + return 0; + } + assert (false); + return 0; + } + + /** + * Convert packed numeric datetime representation to MYSQL_TIME. + * + * @param OUT + * ltime The datetime variable to convert to. + * @param tmp + * The packed numeric datetime value. + */ + public static void TIME_from_longlong_datetime_packed(MySQLTime ltime, long tmp) { + long ymd, hms; + long ymdhms, ym; + if ((ltime.neg = (tmp < 0))) + tmp = -tmp; + + ltime.second_part = MY_PACKED_TIME_GET_FRAC_PART(tmp); + ymdhms = MY_PACKED_TIME_GET_INT_PART(tmp); + + ymd = ymdhms >> 17; + ym = ymd >> 5; + hms = ymdhms % (1 << 17); + + ltime.day = ymd % (1 << 5); + ltime.month = ym % 13; + ltime.year = ym / 13; + + ltime.second = hms % (1 << 6); + ltime.minute = (hms >> 6) % (1 << 6); + ltime.hour = (hms >> 12); + + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + } + + /** + * Convert packed numeric date representation to MYSQL_TIME. + * + * @param OUT + * ltime The date variable to convert to. + * @param tmp + * The packed numeric date value. + */ + public static void TIME_from_longlong_date_packed(MySQLTime ltime, long tmp) { + TIME_from_longlong_datetime_packed(ltime, tmp); + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + } + + /** + * Convert time packed numeric representation to time. + * + * @param OUT + * ltime The MYSQL_TIME variable to set. + * @param tmp + * The packed numeric representation. + */ + public static void TIME_from_longlong_time_packed(MySQLTime ltime, long tmp) { + long hms; + if ((ltime.neg = (tmp < 0))) + tmp = -tmp; + hms = MY_PACKED_TIME_GET_INT_PART(tmp); + ltime.year = 0; + ltime.month = 0; + ltime.day = 0; + ltime.hour = (hms >> 12) % (1 << 10); /* 10 bits starting at 12th */ + ltime.minute = (hms >> 6) % (1 << 6); /* 6 bits starting at 6th */ + ltime.second = hms % (1 << 6); /* 6 bits starting at 0th */ + ltime.second_part = MY_PACKED_TIME_GET_FRAC_PART(tmp); + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + } + + /** + * Set day, month and year from a number + * + * @param ltime + * MYSQL_TIME variable + * @param yymmdd + * Number in YYYYMMDD format + */ + public static void TIME_set_yymmdd(MySQLTime ltime, long yymmdd) { + ltime.day = (int) (yymmdd % 100); + ltime.month = (int) (yymmdd / 100) % 100; + ltime.year = (int) (yymmdd / 10000); + } + + /** + * Set hour, minute and secondr from a number + * + * @param ltime + * MYSQL_TIME variable + * @param hhmmss + * Number in HHMMSS format + */ + public static void TIME_set_hhmmss(MySQLTime ltime, long hhmmss) { + ltime.second = (int) (hhmmss % 100); + ltime.minute = (int) (hhmmss / 100) % 100; + ltime.hour = (int) (hhmmss / 10000); + } + + /* + * Calculate nr of day since year 0 in new date-system (from 1615) + * + * SYNOPSIS calc_daynr() year Year (exact 4 digit year, no year conversions) + * month Month day Day + * + * NOTES: 0000-00-00 is a valid date, and will return 0 + * + * RETURN Days since 0000-00-00 + */ + + public static long calc_daynr(long year, long month, long day) { + long delsum; + int temp; + int y = (int) year; /* may be < 0 temporarily */ + + if (y == 0 && month == 0) + return 0; /* Skip errors */ + /* Cast to int to be able to handle month == 0 */ + delsum = (long) (365 * y + 31 * ((int) month - 1) + (int) day); + if (month <= 2) + y--; + else + delsum -= (long) ((int) month * 4 + 23) / 10; + temp = (int) ((y / 100 + 1) * 3) / 4; + assert (delsum + (int) y / 4 - temp >= 0); + return (delsum + (int) y / 4 - temp); + } /* calc_daynr */ + + /* Calc days in one year. works with 0 <= year <= 99 */ + public static int calc_days_in_year(int year) { + return ((year & 3) == 0 && (year % 100 != 0 || (year % 400 == 0 && year != 0)) ? 366 : 365); + } + + public static long week_mode(int mode) { + int week_format = (mode & 7); + if ((week_format & WEEK_MONDAY_FIRST) == 0) + week_format ^= WEEK_FIRST_WEEKDAY; + return week_format; + } + + public static String my_time_to_str(MySQLTime l_time, long dec) { + String stime = String.format("%s%02d:%02d:%02d", (l_time.neg ? "-" : ""), l_time.hour, l_time.minute, + l_time.second); + if (dec != 0) { + // 目前无法显示小数点后的6位 + // String stmp = String.format("%06d", l_time.second_part); + // stime += "." + stmp.substring(0, (int) dec); + } + return stime; + } + + public static String my_date_to_str(MySQLTime MYSQL_TIME) { + return String.format("%04d-%02d-%02d", MYSQL_TIME.year, MYSQL_TIME.month, MYSQL_TIME.day); + } + + /** + * Print a datetime value with an optional fractional part. + * + * @l_time The MYSQL_TIME value to print. + * @to OUT The string pointer to print at. + * @return The length of the result string. + */ + public static String my_datetime_to_str(final MySQLTime l_time, long dec) { + StringPtr ptrtmp = new StringPtr(""); + TIME_to_datetime_str(ptrtmp, l_time); + String res = ptrtmp.get(); + if (dec != 0) { + // 目前无法显示小数点后的位 + // String stmp = String.format("%06d", l_time.second_part); + // res += "." + stmp.substring(0, (int) dec); + } + return res; + } + + /* + * Convert struct DATE/TIME/DATETIME value to string using built-in MySQL + * time conversion formats. + * + * SYNOPSIS my_TIME_to_string() + * + * NOTE The string must have at least MAX_DATE_STRING_REP_LENGTH bytes + * reserved. + */ + public static String my_TIME_to_str(final MySQLTime l_time, int dec) { + switch (l_time.time_type) { + case MYSQL_TIMESTAMP_DATETIME: + return my_datetime_to_str(l_time, dec); + case MYSQL_TIMESTAMP_DATE: + return my_date_to_str(l_time); + case MYSQL_TIMESTAMP_TIME: + return my_time_to_str(l_time, dec); + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + return null; + default: + return null; + } + } + + /*-------------------------My_time.h end------------------------------*/ + + /*-------------------------Sql_time.h(其它类型向MySqlTime转换)start----------------------------*/ + + /* Rounding functions */ + public static long[] msec_round_add = new long[] { 500000000, 50000000, 5000000, 500000, 50000, 5000, 0 }; + + /** + * Convert decimal value to datetime value with a warning. + * + * @param decimal + * The value to convert from. + * @param[out] ltime The variable to convert to. + * @param flags + * Conversion flags. + * @return False on success, true on error. + */ + public static boolean my_decimal_to_datetime_with_warn(BigDecimal decimal, MySQLTime ltime, long flags) { + LongPtr warnings = new LongPtr(0); + String sbd = decimal.toString(); + String[] sbds = sbd.split("\\."); + long int_part = Long.valueOf(sbds[0]); + long second_part = 0; + if (sbds.length == 2) + second_part = Long.valueOf(sbds[1]); + if (number_to_datetime(int_part, ltime, flags, warnings) == -1) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } else if (ltime.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) { + /** + * Generate a warning in case of DATE with fractional part: + * 20011231.1234 . '2001-12-31' unless the caller does not want the + * warning: for example, CAST does. + */ + if (second_part != 0 && (flags & TIME_NO_DATE_FRAC_WARN) == 0) { + warnings.set(warnings.get() | MYSQL_TIME_WARN_TRUNCATED); + } + } else if ((flags & TIME_NO_NSEC_ROUNDING) == 0) { + ltime.second_part = second_part; + } + return false; + } + + /** + * Round time value to the given precision. + * + * @param IN + * /OUT ltime The value to round. + * @param dec + * Precision. + * @return False on success, true on error. + */ + public static boolean my_time_round(MySQLTime ltime, int dec) { + /* Add half away from zero */ + boolean rc = time_add_nanoseconds_with_round(ltime, msec_round_add[dec]); + /* Truncate non-significant digits */ + my_time_trunc(ltime, dec); + return rc; + } + + /** + * Round datetime value to the given precision. + * + * @param IN + * /OUT ltime The value to round. + * @param dec + * Precision. + * @return False on success, true on error. + */ + public static boolean my_datetime_round(MySQLTime ltime, int dec) { + assert (dec <= DATETIME_MAX_DECIMALS); + /* Add half away from zero */ + boolean rc = datetime_add_nanoseconds_with_round(ltime, msec_round_add[dec]); + /* Truncate non-significant digits */ + my_time_trunc(ltime, dec); + return rc; + } + + public static boolean date_add_interval(MySQLTime ltime, MySqlIntervalUnit int_type, INTERVAL interval) { + long period, sign; + + ltime.neg = false; + + sign = (interval.neg ? -1 : 1); + + switch (int_type) { + case SECOND: + case SECOND_MICROSECOND: + case MICROSECOND: + case MINUTE: + case HOUR: + case MINUTE_MICROSECOND: + case MINUTE_SECOND: + case HOUR_MICROSECOND: + case HOUR_SECOND: + case HOUR_MINUTE: + case DAY_MICROSECOND: + case DAY_SECOND: + case DAY_MINUTE: + case DAY_HOUR: { + long sec, days, daynr, microseconds, extra_sec; + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; // Return + // full + // date + microseconds = ltime.second_part + sign * interval.second_part; + extra_sec = microseconds / 1000000L; + microseconds = microseconds % 1000000L; + + sec = ((ltime.day - 1) * 3600 * 24L + ltime.hour * 3600 + ltime.minute * 60 + ltime.second + + sign * (long) (interval.day * 3600 * 24L + interval.hour * 3600 + interval.minute * (60) + + interval.second)) + + extra_sec; + if (microseconds < 0) { + microseconds += (1000000L); + sec--; + } + days = sec / (3600 * (24)); + sec -= days * 3600 * (24); + if (sec < 0) { + days--; + sec += 3600 * 24; + } + ltime.second_part = microseconds; + ltime.second = (sec % 60); + ltime.minute = (sec / 60 % 60); + ltime.hour = (sec / 3600); + daynr = calc_daynr(ltime.year, ltime.month, 1) + days; + /* Day number from year 0 to 9999-12-31 */ + if (daynr > MAX_DAY_NUMBER) + return true; + LongPtr ptrYear = new LongPtr(ltime.year); + LongPtr ptrMonth = new LongPtr(ltime.month); + LongPtr ptrDay = new LongPtr(ltime.day); + get_date_from_daynr((long) daynr, ptrYear, ptrMonth, ptrDay); + ltime.year = ptrYear.get(); + ltime.month = ptrMonth.get(); + ltime.day = ptrDay.get(); + break; + } + case DAY: + case WEEK: + period = (calc_daynr(ltime.year, ltime.month, ltime.day) + sign * (long) interval.day); + /* Daynumber from year 0 to 9999-12-31 */ + if (period > MAX_DAY_NUMBER) + return true; + LongPtr ptrYear = new LongPtr(ltime.year); + LongPtr ptrMonth = new LongPtr(ltime.month); + LongPtr ptrDay = new LongPtr(ltime.day); + get_date_from_daynr((long) period, ptrYear, ptrMonth, ptrDay); + ltime.year = ptrYear.get(); + ltime.month = ptrMonth.get(); + ltime.day = ptrDay.get(); + break; + case YEAR: + ltime.year += sign * (long) interval.year; + if (ltime.year >= 10000) + return true; + if (ltime.month == 2 && ltime.day == 29 && calc_days_in_year(ltime.year) != 366) + ltime.day = 28; // Was leap-year + break; + case YEAR_MONTH: + case QUARTER: + case MONTH: + period = (ltime.year * 12 + sign * (long) interval.year * 12 + ltime.month - 1 + + sign * (long) interval.month); + if (period >= 120000L) + return true; + ltime.year = (period / 12); + ltime.month = (period % 12L) + 1; + /* Adjust day if the new month doesn't have enough days */ + if (ltime.day > days_in_month[(int) ltime.month - 1]) { + ltime.day = days_in_month[(int) ltime.month - 1]; + if (ltime.month == 2 && calc_days_in_year(ltime.year) == 366) + ltime.day++; // Leap-year + } + break; + default: + return true; + } + + return false; // Ok + + } + + /** + * Convert double value to datetime value with a warning. + * + * @param nr + * The value to convert from. + * @param[out] ltime The variable to convert to. + * @param flags + * Conversion flags. + * @return False on success, true on error. + */ + public static boolean my_double_to_datetime_with_warn(double db, MySQLTime ltime, long flags) { + LongPtr warnings = new LongPtr(0); + String sbd = String.valueOf(db); + String[] sbds = sbd.split("\\."); + long int_part = Long.valueOf(sbds[0]); + long second_part = 0; + if (sbds.length == 2) + second_part = Long.valueOf(sbds[1]); + if (number_to_datetime(int_part, ltime, flags, warnings) == -1) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } else if (ltime.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) { + /** + * Generate a warning in case of DATE with fractional part: + * 20011231.1234 . '2001-12-31' unless the caller does not want the + * warning: for example, CAST does. + */ + if (second_part != 0 && (flags & TIME_NO_DATE_FRAC_WARN) == 0) { + warnings.set(warnings.get() | MYSQL_TIME_WARN_TRUNCATED); + } + } else if ((flags & TIME_NO_NSEC_ROUNDING) == 0) { + ltime.second_part = second_part; + } + return false; + } + + /** + * Convert longlong value to datetime value with a warning. + * + * @param nr + * The value to convert from. + * @param[out] ltime The variable to convert to. + * @return False on success, true on error. + */ + public static boolean my_longlong_to_datetime_with_warn(long nr, MySQLTime ltime, long flags) { + LongPtr warning = new LongPtr(0); + return number_to_datetime(nr, ltime, flags, warning) == -1; + } + + public static boolean str_to_datetime_with_warn(String str, MySQLTime ltime, long flags) { + return str_to_datetime(str, str.length(), ltime, flags, new MySQLTimeStatus()); + } + + public static boolean my_decimal_to_time_with_warn(BigDecimal decimal, MySQLTime ltime) { + LongPtr warnings = new LongPtr(0); + String sbd = decimal.toString(); + String[] sbds = sbd.split("\\."); + long int_part = Long.valueOf(sbds[0]); + long second_part = 0; + if (sbds.length == 2) + second_part = Long.valueOf(sbds[1]); + if (number_to_time(int_part, ltime, warnings)) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } + ltime.second_part = second_part; + return false; + } + + public static boolean my_double_to_time_with_warn(double db, MySQLTime ltime) { + LongPtr warnings = new LongPtr(0); + String sbd = String.valueOf(db); + String[] sbds = sbd.split("\\."); + long int_part = Long.valueOf(sbds[0]); + long second_part = 0; + if (sbds.length == 2) + second_part = Long.valueOf(sbds[1]); + if (number_to_time(int_part, ltime, warnings)) { + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + return true; + } + ltime.second_part = second_part; + return false; + } + + public static boolean my_longlong_to_time_with_warn(long nr, MySQLTime ltime) { + LongPtr warning = new LongPtr(0); + return number_to_time(nr, ltime, warning); + } + + public static boolean str_to_time_with_warn(String str, MySQLTime ltime) { + return str_to_time(str, str.length(), ltime, new MySQLTimeStatus()); + } + + /** + * Convert time to datetime. + * + * The time value is added to the current datetime value. + * + * @param IN + * ltime Time value to convert from. + * @param OUT + * ltime2 Datetime value to convert to. + */ + public static void time_to_datetime(final MySQLTime ltime, MySQLTime ltime2) { + java.util.Calendar cal1 = ltime.toCalendar(); + java.util.Calendar cal2 = java.util.Calendar.getInstance(); + cal2.clear(); + cal2.setTimeInMillis(cal1.getTimeInMillis()); + ltime2.year = cal2.get(java.util.Calendar.YEAR); + ltime2.month = cal2.get(java.util.Calendar.MONTH); + ltime.day = cal2.get(java.util.Calendar.DAY_OF_MONTH); + ltime2.hour = cal2.get(java.util.Calendar.HOUR_OF_DAY); + ltime2.minute = cal2.get(java.util.Calendar.MINUTE); + ltime2.second = cal2.get(java.util.Calendar.SECOND); + ltime2.second_part = cal2.get(java.util.Calendar.MILLISECOND) * 1000; + } + + public static void datetime_to_time(MySQLTime ltime) { + ltime.year = ltime.month = ltime.day = 0; + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + } + + public static void datetime_to_date(MySQLTime ltime) { + ltime.hour = ltime.minute = ltime.second = ltime.second_part = 0; + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATE; + } + + public static void date_to_datetime(MySQLTime ltime) { + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME; + } + + public static long TIME_to_ulonglong_datetime_round(final MySQLTime ltime) { + // Catch simple cases + if (ltime.second_part < 500000) + return TIME_to_ulonglong_datetime(ltime); + if (ltime.second < 59) + return TIME_to_ulonglong_datetime(ltime) + 1; + return TIME_to_ulonglong_datetime(ltime);// TIME_microseconds_round(ltime); + } + + public static long TIME_to_ulonglong_time_round(final MySQLTime ltime) { + if (ltime.second_part < 500000) + return TIME_to_ulonglong_time(ltime); + if (ltime.second < 59) + return TIME_to_ulonglong_time(ltime) + 1; + // Corner case e.g. 'hh:mm:59.5'. Proceed with slower method. + return TIME_to_ulonglong_time(ltime); + } + + public static double TIME_to_double_datetime(final MySQLTime ltime) { + return (double) TIME_to_ulonglong_datetime(ltime) + TIME_microseconds(ltime); + } + + public static double TIME_to_double_time(final MySQLTime ltime) { + return (double) TIME_to_ulonglong_time(ltime) + TIME_microseconds(ltime); + } + + public static double TIME_to_double(final MySQLTime ltime) { + return (double) TIME_to_ulonglong(ltime) + TIME_microseconds(ltime); + } + + /** + * Convert a datetime from broken-down MYSQL_TIME representation to + * corresponding TIMESTAMP value. + * + * @param thd + * - current thread + * @param t + * - datetime in broken-down representation, + * @param in_dst_time_gap + * - pointer to bool which is set to true if t represents value + * which doesn't exists (falls into the spring time-gap) or to + * false otherwise. + * @return + * @retval Number seconds in UTC since start of Unix Epoch corresponding to + * t. + * @retval 0 - t contains datetime value which is out of TIMESTAMP range. + */ + public static long TIME_to_timestamp(final MySQLTime t) { + long timestamp = t.toCalendar().getTimeInMillis() / 1000; + + return timestamp; + } + + public static long TIME_to_longlong_packed(final MySQLTime ltime, FieldTypes type) { + switch (type) { + case MYSQL_TYPE_TIME: + return TIME_to_longlong_time_packed(ltime); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return TIME_to_longlong_datetime_packed(ltime); + case MYSQL_TYPE_DATE: + return TIME_to_longlong_date_packed(ltime); + default: + return TIME_to_longlong_packed(ltime); + } + } + + public static void TIME_from_longlong_packed(MySQLTime ltime, FieldTypes type, long packed_value) { + switch (type) { + case MYSQL_TYPE_TIME: + TIME_from_longlong_time_packed(ltime, packed_value); + break; + case MYSQL_TYPE_DATE: + TIME_from_longlong_date_packed(ltime, packed_value); + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + TIME_from_longlong_datetime_packed(ltime, packed_value); + break; + default: + assert (false); + ltime.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR); + break; + } + } + + /** + * Unpack packed numeric temporal value to date/time value and then convert + * to decimal representation. + * + * @param OUT + * dec The variable to write to. + * @param type + * MySQL field type. + * @param packed_value + * Packed numeric temporal representation. + * @return A decimal value in on of the following formats, depending on + * type: YYYYMMDD, hhmmss.ffffff or YYMMDDhhmmss.ffffff. + */ + public static BigDecimal my_decimal_from_datetime_packed(FieldTypes type, long packed_value) { + MySQLTime ltime = new MySQLTime(); + switch (type) { + case MYSQL_TYPE_TIME: + TIME_from_longlong_time_packed(ltime, packed_value); + return time2my_decimal(ltime); + case MYSQL_TYPE_DATE: + TIME_from_longlong_date_packed(ltime, packed_value); + return ulonglong2decimal(TIME_to_ulonglong_date(ltime)); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + TIME_from_longlong_datetime_packed(ltime, packed_value); + return date2my_decimal(ltime); + default: + assert (false); + return ulonglong2decimal(0); + } + } + + public static long longlong_from_datetime_packed(FieldTypes type, long packed_value) { + MySQLTime ltime = new MySQLTime(); + switch (type) { + case MYSQL_TYPE_TIME: + TIME_from_longlong_time_packed(ltime, packed_value); + return TIME_to_ulonglong_time(ltime); + case MYSQL_TYPE_DATE: + TIME_from_longlong_date_packed(ltime, packed_value); + return TIME_to_ulonglong_date(ltime); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + TIME_from_longlong_datetime_packed(ltime, packed_value); + return TIME_to_ulonglong_datetime(ltime); + default: + return 0; + } + } + + public static double double_from_datetime_packed(FieldTypes type, long packed_value) { + long result = longlong_from_datetime_packed(type, packed_value); + return result + ((double) MY_PACKED_TIME_GET_FRAC_PART(packed_value)) / 1000000; + } + + /** + * Convert time value to my_decimal in format hhmmss.ffffff + * + * @param ltime + * Date value to convert from. + * @param dec + * Decimal value to convert to. + */ + public static BigDecimal time2my_decimal(final MySQLTime ltime) { + String stmp = String.format("%02d%02d%02d.%06d", ltime.hour, ltime.minute, ltime.second, ltime.second_part); + return new BigDecimal(stmp); + } + + /** + * Convert datetime value to my_decimal in format YYYYMMDDhhmmss.ffffff + * + * @param ltime + * Date value to convert from. + * @param dec + * Decimal value to convert to. + */ + public static BigDecimal date2my_decimal(final MySQLTime ltime) { + String stmp = String.format("%04d%02d%02d%02d%02d%02d.%06d", ltime.year, ltime.month, ltime.day, ltime.hour, + ltime.minute, ltime.second, ltime.second_part); + return new BigDecimal(stmp); + } + + /* Functions to handle periods */ + public static long convert_period_to_month(long period) { + long a, b; + if (period == 0) + return 0L; + if ((a = period / 100) < YY_PART_YEAR) + a += 2000; + else if (a < 100) + a += 1900; + b = period % 100; + return a * 12 + b - 1; + } + + public static long convert_month_to_period(long month) { + long year; + if (month == 0L) + return 0L; + if ((year = month / 12) < 100) { + year += (year < YY_PART_YEAR) ? 2000 : 1900; + } + return year * 100 + month % 12 + 1; + } + + /* Calc weekday from daynr */ + /* Returns 0 for monday, 1 for tuesday .... */ + + public static int calc_weekday(long daynr, boolean sunday_first_day_of_week) { + return ((int) ((daynr + 5L + (sunday_first_day_of_week ? 1L : 0L)) % 7)); + } + + /* + * The bits in week_format has the following meaning: WEEK_MONDAY_FIRST (0) + * If not set Sunday is first day of week If set Monday is first day of week + * WEEK_YEAR (1) If not set Week is in range 0-53 + * + * Week 0 is returned for the the last week of the previous year (for a date + * at start of january) In this case one can get 53 for the first week of + * next year. This flag ensures that the week is relevant for the given + * year. Note that this flag is only releveant if WEEK_JANUARY is not set. + * + * If set Week is in range 1-53. + * + * In this case one may get week 53 for a date in January (when the week is + * that last week of previous year) and week 1 for a date in December. + * + * WEEK_FIRST_WEEKDAY (2) If not set Weeks are numbered according to ISO + * 8601:1988 If set The week that contains the first 'first-day-of-week' is + * week 1. + * + * ISO 8601:1988 means that if the week containing January 1 has four or + * more days in the new year, then it is week 1; Otherwise it is the last + * week of the previous year, and the next week is week 1. + */ + + public static long calc_week(MySQLTime l_time, long week_behaviour, LongPtr year) { + long days; + long daynr = calc_daynr(l_time.year, l_time.month, l_time.day); + long first_daynr = calc_daynr(l_time.year, 1, 1); + boolean monday_first = (week_behaviour & WEEK_MONDAY_FIRST) != 0; + boolean week_year = (week_behaviour & WEEK_YEAR) != 0; + boolean first_weekday = (week_behaviour & WEEK_FIRST_WEEKDAY) != 0; + + long weekday = calc_weekday(first_daynr, !monday_first); + year.set(l_time.year); + + if (l_time.month == 1 && l_time.day <= 7 - weekday) { + if (!week_year && ((first_weekday && weekday != 0) || (!first_weekday && weekday >= 4))) + return 0; + week_year = true; + year.decre(); + first_daynr -= (days = calc_days_in_year(year.get())); + weekday = (weekday + 53 * 7 - days) % 7; + } + + if ((first_weekday && weekday != 0) || (!first_weekday && weekday >= 4)) + days = daynr - (first_daynr + (7 - weekday)); + else + days = daynr - (first_daynr - weekday); + + if (week_year && days >= 52 * 7) { + weekday = (weekday + calc_days_in_year(year.get())) % 7; + if ((!first_weekday && weekday < 4) || (first_weekday && weekday == 0)) { + year.incre(); + return 1; + } + } + return days / 7 + 1; + } + + /** + * Convert a datetime MYSQL_TIME representation to corresponding + * "struct timeval" value. + * + * Things like '0000-01-01', '2000-00-01', '2000-01-00' (i.e. incomplete + * date) return error. + * + * Things like '0000-00-00 10:30:30' or '0000-00-00 00:00:00.123456' (i.e. + * empty date with non-empty time) return error. + * + * Zero datetime '0000-00-00 00:00:00.000000' is allowed and is mapper to + * {tv_sec=0, tv_usec=0}. + * + * Note: In case of error, tm value is not initialized. + * + * Note: "warnings" is not initialized to zero, so new warnings are added to + * the old ones. Caller must make sure to initialize "warnings". + * + * @param[in] thd current thd + * @param[in] ltime datetime value + * @param[out] tm timeval value + * @param[out] warnings pointer to warnings vector + * @return + * @retval false on success + * @retval true on error + */ + public static boolean datetime_to_timeval(final MySQLTime ltime, Timeval tm) { + return check_date(ltime, ltime.isNonZeroDate(), TIME_NO_ZERO_IN_DATE, new LongPtr(0)) + || datetime_with_no_zero_in_date_to_timeval(ltime, tm); + } + + /* Change a daynr to year, month and day */ + /* Daynr 0 is returned as date 00.00.00 */ + + public static void get_date_from_daynr(long daynr, LongPtr ret_year, LongPtr ret_month, LongPtr ret_day) { + int year, temp, leap_day, day_of_year, days_in_year; + int month_pos; + if (daynr <= 365L || daynr >= 3652500) { /* Fix if wrong daynr */ + ret_year.set(0); + ret_month.set(0); + ret_day.set(0); + } else { + year = (int) (daynr * 100 / 36525L); + temp = (((year - 1) / 100 + 1) * 3) / 4; + day_of_year = (int) (daynr - (long) year * 365L) - (year - 1) / 4 + temp; + while (day_of_year > (days_in_year = calc_days_in_year(year))) { + day_of_year -= days_in_year; + (year)++; + } + leap_day = 0; + if (days_in_year == 366) { + if (day_of_year > 31 + 28) { + day_of_year--; + if (day_of_year == 31 + 28) + leap_day = 1; /* Handle leapyears leapday */ + } + } + ret_month.set(1); + ; + for (month_pos = 0; day_of_year > days_in_month[month_pos]; day_of_year -= days_in_month[month_pos++], ret_month + .incre()) + ; + ret_year.set(year); + ret_day.set(day_of_year + leap_day); + } + } + + /*-----------------------helper method---------------------------*/ + private static boolean check_time_mmssff_range(final MySQLTime ltime) { + return ltime.minute >= 60 || ltime.second >= 60 || ltime.second_part > 999999; + } + + private static void set_max_time(MySQLTime tm, boolean neg) { + tm.set_zero_time(MySQLTimestampType.MYSQL_TIMESTAMP_TIME); + set_max_hhmmss(tm); + tm.neg = neg; + } + + /** + * Set hour, minute and second of a MYSQL_TIME variable to maximum time + * value. Unlike set_max_time(), does not touch the other structure members. + */ + private static void set_max_hhmmss(MySQLTime tm) { + tm.hour = TIME_MAX_HOUR; + tm.minute = TIME_MAX_MINUTE; + tm.second = TIME_MAX_SECOND; + } + + private static double TIME_microseconds(final MySQLTime ltime) { + return (double) ltime.second_part / 1000000; + } + + private static BigDecimal ulonglong2decimal(long from) { + BigInteger bi = MySQLcom.getUnsignedLong(from); + return new BigDecimal(bi); + } + + private static int TIME_to_datetime_str(StringPtr ptr, final MySQLTime ltime) { + char[] res = new char[19]; + int index = 0; + long temp, temp2; + /* Year */ + temp = ltime.year / 100; + res[index++] = (char) ('0' + temp / 10); + res[index++] = (char) ('0' + temp % 10); + temp = ltime.year % 100; + res[index++] = (char) ('0' + temp / 10); + res[index++] = (char) ('0' + temp % 10); + res[index++] = '-'; + /* Month */ + temp = ltime.month; + temp2 = temp / 10; + temp = temp - temp2 * 10; + res[index++] = (char) ('0' + (char) (temp2)); + res[index++] = (char) ('0' + (char) (temp)); + res[index++] = '-'; + /* Day */ + temp = ltime.day; + temp2 = temp / 10; + temp = temp - temp2 * 10; + res[index++] = (char) ('0' + (char) (temp2)); + res[index++] = (char) ('0' + (char) (temp)); + res[index++] = ' '; + /* Hour */ + temp = ltime.hour; + temp2 = temp / 10; + temp = temp - temp2 * 10; + res[index++] = (char) ('0' + (char) (temp2)); + res[index++] = (char) ('0' + (char) (temp)); + res[index++] = ':'; + /* Minute */ + temp = ltime.minute; + temp2 = temp / 10; + temp = temp - temp2 * 10; + res[index++] = (char) ('0' + (char) (temp2)); + res[index++] = (char) ('0' + (char) (temp)); + res[index++] = ':'; + /* Second */ + temp = ltime.second; + temp2 = temp / 10; + temp = temp - temp2 * 10; + res[index++] = (char) ('0' + (char) (temp2)); + res[index++] = (char) ('0' + (char) (temp)); + ptr.set(new String(res)); + return 19; + } + + /** + * Check datetime, date, or normalized time (i.e. time without days) range. + * + * @param ltime + * Datetime value. + * @returns + * @retval FALSE on success + * @retval TRUE on error + */ + private static boolean check_datetime_range(MySQLTime ltime) { + /* + * In case of MYSQL_TIMESTAMP_TIME hour value can be up to + * TIME_MAX_HOUR. In case of MYSQL_TIMESTAMP_DATETIME it cannot be + * bigger than 23. + */ + return ltime.year > 9999 || ltime.month > 12 || ltime.day > 31 || ltime.minute > 59 || ltime.second > 59 + || ltime.second_part > 999999 + || (ltime.hour > (ltime.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME ? TIME_MAX_HOUR + : 23)); + } + + /* Calc days in one year. works with 0 <= year <= 99 */ + + public static long calc_days_in_year(long year) { + return ((year & 3) == 0 && (year % 100 != 0 || (year % 400 == 0 && year != 0)) ? 366 : 365); + } + + private static void my_time_status_init(MySQLTimeStatus status) { + status.warnings = 0; + status.fractional_digits = status.nanoseconds = 0; + } + + /** + * Convert a datetime MYSQL_TIME representation to corresponding + * "struct timeval" value. + * + * ltime must previously be checked for TIME_NO_ZERO_IN_DATE. Things like + * '0000-01-01', '2000-00-01', '2000-01-00' are not allowed and asserted. + * + * Things like '0000-00-00 10:30:30' or '0000-00-00 00:00:00.123456' (i.e. + * empty date with non-empty time) return error. + * + * Zero datetime '0000-00-00 00:00:00.000000' is allowed and is mapper to + * {tv_sec=0, tv_usec=0}. + * + * Note: In case of error, tm value is not initialized. + * + * Note: "warnings" is not initialized to zero, so new warnings are added to + * the old ones. Caller must make sure to initialize "warnings". + * + * @param[in] thd current thd + * @param[in] ltime datetime value + * @param[out] tm timeval value + * @param[out] warnings pointer to warnings vector + * @return + * @retval false on success + * @retval true on error + */ + private static boolean datetime_with_no_zero_in_date_to_timeval(final MySQLTime ltime, Timeval tm) { + if (ltime.month == 0) /* Zero date */ + { + assert (ltime.year == 0 && ltime.day == 0); + if (ltime.isNonZeroTime()) { + /* + * Return error for zero date with non-zero time, e.g.: + * '0000-00-00 10:20:30' or '0000-00-00 00:00:00.123456' + */ + return true; + } + tm.tv_sec = tm.tv_usec = 0; // '0000-00-00 00:00:00.000000' + return false; + } + + tm.tv_sec = TIME_to_timestamp(ltime); + tm.tv_usec = ltime.second_part; + return false; + } + + private static boolean time_add_nanoseconds_with_round(MySQLTime ltime, long nanoseconds) { + /* We expect correct input data */ + assert (nanoseconds < 1000000000); + assert (!check_time_mmssff_range(ltime)); + + if (nanoseconds < 500) + return false; + + ltime.second_part += (nanoseconds + 500) / 1000; + if (ltime.second_part < 1000000) + return false; + + ltime.second_part %= 1000000; + if (ltime.second < 59) { + ltime.second++; + return false; + } + + ltime.second = 0; + if (ltime.minute < 59) { + ltime.minute++; + return false; + } + ltime.minute = 0; + ltime.hour++; + + ret: + /* + * We can get '838:59:59.000001' at this point, which is bigger than the + * maximum possible value '838:59:59.000000'. Checking only "hour > 838" + * is not enough. Do full adjust_time_range(). + */ + // adjust_time_range(ltime, warnings); + return false; + } + + /** + * Add nanoseconds to a datetime value with rounding. + * + * @param IN + * /OUT ltime MYSQL_TIME variable to add to. + * @param nanosecons + * Nanosecons value. + * @param IN + * /OUT warnings Warning flag vector. + * @retval False on success, true on error. + */ + private static boolean datetime_add_nanoseconds_with_round(MySQLTime ltime, long nanoseconds) { + assert (nanoseconds < 1000000000); + if (nanoseconds < 500) + return false; + + ltime.second_part += (nanoseconds + 500) / 1000; + if (ltime.second_part < 1000000) + return false; + + ltime.second_part %= 1000000; + INTERVAL interval = new INTERVAL(); + interval.second = 1; + + if (date_add_interval(ltime, MySqlIntervalUnit.SECOND, interval)) { + return true; + } + return false; + } + + private static void my_time_trunc(MySQLTime ltime, int decimals) { + ltime.second_part -= my_time_fraction_remainder(ltime.second_part, decimals); + } + + private static long my_time_fraction_remainder(long nr, int decimals) { + return nr % (long) MySQLcom.log_10_int[DATETIME_MAX_DECIMALS - decimals]; + } + + /* + * Calculate difference between two datetime values as seconds + + * microseconds. + * + * SYNOPSIS calc_time_diff() l_time1 - TIME/DATE/DATETIME value l_time2 - + * TIME/DATE/DATETIME value seconds_out - Out parameter where difference + * between l_time1 and l_time2 in seconds is stored. microseconds_out- Out + * parameter where microsecond part of difference between l_time1 and + * l_time2 is stored. + * + * NOTE This function calculates difference between l_time1 and l_time2 + * absolute values. So one should set l_sign and correct result if he want + * to take signs into account (i.e. for MYSQL_TIME values). + * + * RETURN VALUES Returns sign of difference. 1 means negative result 0 means + * positive result + */ + public static boolean calc_time_diff(MySQLTime l_time1, MySQLTime l_time2, int l_sign, LongPtr seconds_out, + LongPtr microseconds_out) { + long days; + boolean neg; + long microseconds; + + /* + * We suppose that if first argument is MYSQL_TIMESTAMP_TIME the second + * argument should be TIMESTAMP_TIME also. We should check it before + * calc_time_diff call. + */ + if (l_time1.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) // Time + // value + days = (long) l_time1.day - l_sign * (long) l_time2.day; + else { + days = calc_daynr(l_time1.year, l_time1.month, l_time1.day); + if (l_time2.time_type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) + days -= l_sign * (long) l_time2.day; + else + days -= l_sign * calc_daynr(l_time2.year, l_time2.month, l_time2.day); + } + + microseconds = ((long) days * SECONDS_IN_24H + + (long) (l_time1.hour * 3600L + l_time1.minute * 60L + l_time1.second) + - l_sign * (long) (l_time2.hour * 3600L + l_time2.minute * 60L + l_time2.second)) * (1000000) + + (long) l_time1.second_part - l_sign * (long) l_time2.second_part; + + neg = false; + if (microseconds < 0) { + microseconds = -microseconds; + neg = true; + } + seconds_out.set(microseconds / 1000000L); + microseconds_out.set((long) (microseconds % 1000000L)); + return neg; + + // boolean ret = false; + // try { + // Calendar cal1 = l_time1.toCalendar(); + // Calendar cal2 = l_time2.toCalendar(); + // long milsecond1 = cal1.getTimeInMillis(); + // long milsecond2 = cal2.getTimeInMillis(); + // if (milsecond1 < milsecond2) + // ret = true; + // long abs = Math.abs(milsecond1 - milsecond2); + // seconds_out.set(abs / 1000); + // microseconds_out.set(abs % 1000 * 1000); + // return ret; + // } catch (Exception e) { + // return ret; + // } + } + + public static void calc_time_from_sec(MySQLTime to, long seconds, long microseconds) { + long t_seconds; + // to.neg is not cleared, it may already be set to a useful value + to.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + to.year = 0; + to.month = 0; + to.day = 0; + to.hour = (long) (seconds / 3600L); + t_seconds = (long) (seconds % 3600L); + to.minute = t_seconds / 60L; + to.second = t_seconds % 60L; + to.second_part = microseconds; + } + + /** + * Convert a string to a interval value. + * + * To make code easy, allow interval objects without separators. + */ + + public static boolean get_interval_value(Item arg, MySqlIntervalUnit unit, StringPtr str_value, + INTERVAL interval) { + long[] array = new long[5]; + long value = 0; +// int int_type = unit.ordinal(); + + if (unit == MySqlIntervalUnit.SECOND && arg.decimals != 0) { + BigDecimal decimal_value = arg.valDecimal(); + if (decimal_value == null) + return false; + + boolean neg = decimal_value.compareTo(BigDecimal.ZERO) < 0; + if (!neg) { + interval.neg = false; + interval.second = decimal_value.longValue(); + interval.second_part = (long) ((decimal_value.doubleValue() - interval.second) * 1000000); + } else { + interval.neg = true; + interval.second = -decimal_value.longValue(); + interval.second_part = (long) ((-decimal_value.doubleValue() - interval.second) * 1000000); + } + return false; + } else if (unit == MySqlIntervalUnit.YEAR || unit == MySqlIntervalUnit.QUARTER + || unit == MySqlIntervalUnit.MONTH || unit == MySqlIntervalUnit.WEEK || unit == MySqlIntervalUnit.DAY + || unit == MySqlIntervalUnit.HOUR || unit == MySqlIntervalUnit.MINUTE + || unit == MySqlIntervalUnit.SECOND || unit == MySqlIntervalUnit.MICROSECOND) { + value = arg.valInt().longValue(); + if (arg.nullValue) + return true; + if (value < 0) { + interval.neg = true; + value = -value; + } + } + + BoolPtr negPtr = new BoolPtr(interval.neg); + switch (unit) { + case YEAR: + interval.year = value; + break; + case QUARTER: + interval.month = (value * 3); + break; + case MONTH: + interval.month = value; + break; + case WEEK: + interval.day = (value * 7); + break; + case DAY: + interval.day = value; + break; + case HOUR: + interval.hour = value; + break; + case MINUTE: + interval.minute = value; + break; + case SECOND: + interval.second = value; + break; + case MICROSECOND: + interval.second_part = value; + break; + case YEAR_MONTH: // Allow YEAR-MONTH YYYYYMM + if (get_interval_info(arg, str_value, negPtr, 2, array, false)) + return true; + interval.year = array[0]; + interval.month = array[1]; + break; + case DAY_HOUR: + if (get_interval_info(arg, str_value, negPtr, 2, array, false)) + return true; + interval.day = array[0]; + interval.hour = array[1]; + break; + case DAY_MINUTE: + if (get_interval_info(arg, str_value, negPtr, 3, array, false)) + return true; + interval.day = array[0]; + interval.hour = array[1]; + interval.minute = array[2]; + break; + case DAY_SECOND: + if (get_interval_info(arg, str_value, negPtr, 4, array, false)) + return true; + interval.day = array[0]; + interval.hour = array[1]; + interval.minute = array[2]; + interval.second = array[3]; + break; + case HOUR_MINUTE: + if (get_interval_info(arg, str_value, negPtr, 2, array, false)) + return true; + interval.hour = array[0]; + interval.minute = array[1]; + break; + case HOUR_SECOND: + if (get_interval_info(arg, str_value, negPtr, 3, array, false)) + return true; + interval.hour = array[0]; + interval.minute = array[1]; + interval.second = array[2]; + break; + case MINUTE_SECOND: + if (get_interval_info(arg, str_value, negPtr, 2, array, false)) + return true; + interval.minute = array[0]; + interval.second = array[1]; + break; + case DAY_MICROSECOND: + if (get_interval_info(arg, str_value, negPtr, 5, array, true)) + return true; + interval.day = array[0]; + interval.hour = array[1]; + interval.minute = array[2]; + interval.second = array[3]; + interval.second_part = array[4]; + break; + case HOUR_MICROSECOND: + if (get_interval_info(arg, str_value, negPtr, 4, array, true)) + return true; + interval.hour = array[0]; + interval.minute = array[1]; + interval.second = array[2]; + interval.second_part = array[3]; + break; + case MINUTE_MICROSECOND: + if (get_interval_info(arg, str_value, negPtr, 3, array, true)) + return true; + interval.minute = array[0]; + interval.second = array[1]; + interval.second_part = array[2]; + break; + case SECOND_MICROSECOND: + if (get_interval_info(arg, str_value, negPtr, 2, array, true)) + return true; + interval.second = array[0]; + interval.second_part = array[1]; + break; + } + interval.neg = negPtr.get(); + return false; + } + + /** + * @details Get a array of positive numbers from a string object. Each + * number is separated by 1 non digit character Return error if + * there is too many numbers. If there is too few numbers, assume + * that the numbers are left out from the high end. This allows one + * to give: DAY_TO_SECOND as "D MM:HH:SS", "MM:HH:SS" "HH:SS" or as + * seconds. + * + * @param args + * item expression which we convert to an ASCII string + * @param str_value + * string buffer + * @param negPtr + * set to true if interval is prefixed by '-' + * @param count: + * count of elements in result array + * @param values: + * array of results + * @param transform_msec: + * if value is true we suppose that the last part of string value + * is microseconds and we should transform value to six digit + * value. For example, '1.1' . '1.100000' + */ + public static boolean get_interval_info(Item args, StringPtr str_value, BoolPtr negPtr, int count, long[] values, + boolean transform_msec) { + String res = args.valStr(); + str_value.set(res); + if (res == null) + return true; + + char[] cs = res.toCharArray(); + int str = 0; + int end = cs.length; + + if (str < end && cs[str] == '-') { + negPtr.set(true); + str++; + } + + while (str < end && !Ctype.isDigit(cs[str])) + str++; + + long msec_length = 0; + for (int i = 0; i < count; i++) { + long value; + int start = str; + for (value = 0; str != end && Ctype.isDigit(cs[str]); str++) + value = value * 10 + (cs[str] - '0'); + msec_length = 6 - (str - start); + values[i] = value; + while (str != end && !Ctype.isDigit(cs[str])) + str++; + if (str == end && i != count - 1) { + i++; + /* Change values[0...i-1] . values[0...count-1] */ + // FIXME + break; + } + } + + if (transform_msec && msec_length > 0) + values[count - 1] *= (long) MySQLcom.pow10((int) msec_length); + + return (str != end); + } + + public static boolean sec_to_time(LLDivT seconds, MySQLTime ltime) { + boolean warning = false; + + ltime.time_type = MySQLTimestampType.MYSQL_TIMESTAMP_TIME; + + if (seconds.quot < 0 || seconds.rem < 0) { + ltime.neg = true; + seconds.quot = -seconds.quot; + seconds.rem = -seconds.rem; + } + + if (seconds.quot > TIME_MAX_VALUE_SECONDS) { + set_max_hhmmss(ltime); + return true; + } + + ltime.hour = (seconds.quot / 3600); + int sec = (int) (seconds.quot % 3600); + ltime.minute = sec / 60; + ltime.second = sec % 60; + warning = time_add_nanoseconds_with_round(ltime, seconds.rem); + return warning; + } + + /** + * Extract datetime value to MYSQL_TIME struct from string value according + * to format string. + * + * @param format + * date/time format specification + * @param val + * String to decode + * @param l_time + * Store result here + * @param cached_timestamp_type + * It uses to get an appropriate warning in the case when the + * value is truncated. + * + * @note Possibility to parse strings matching to patterns equivalent to + * compound specifiers is mainly intended for use from inside of this + * function in order to understand %T and %r conversion specifiers, so + * number of conversion specifiers that can be used in such + * sub-patterns is limited. Also most of checks are skipped in this + * case. + * + * @note If one adds new format specifiers to this function he should also + * consider adding them to Item_func_str_to_date::fix_from_format(). + * + * @return 0 ok + * @return 1 error + */ + public static boolean extract_date_time(DateTimeFormat format, String val_str, MySQLTime l_time, + MySQLTimestampType cached_timestamp_type, String date_time_type) { + int weekday = 0, yearday = 0, daypart = 0; + int week_number = -1; + BoolPtr error = new BoolPtr(false); + int strict_week_number_year = -1; + int frac_part; + boolean usa_time = false; + boolean sunday_first_n_first_week_non_iso = false; + boolean strict_week_number = false; + boolean strict_week_number_year_type = false; + int val = 0; + int val_end = val_str.length(); + char[] valcs = val_str.toCharArray(); + int ptr = 0; + int end = format.format.length(); + char[] ptrcs = format.format.toCharArray(); + + for (; ptr != end && val != val_end; ptr++) { + + if (ptrcs[ptr] == '%' && ptr + 1 != end) { + int val_len; + int tmp; + + error.set(false); + + val_len = val_end - val; + switch (ptrcs[++ptr]) { + /* Year */ + case 'Y': + tmp = val + Math.min(4, val_len); + l_time.year = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + if (tmp - val <= 2) + l_time.year = MyTime.year_2000_handling(l_time.year); + val = tmp; + break; + case 'y': + tmp = val + Math.min(2, val_len); + l_time.year = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + l_time.year = MyTime.year_2000_handling(l_time.year); + break; + + /* Month */ + case 'm': + case 'c': + tmp = val + Math.min(2, val_len); + l_time.month = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + case 'M': + if ((l_time.month = MySQLcom.check_word(month_names, valcs, val, val_end)) <= 0) + err: { + // logger.warn + return true; + } + break; + case 'b': + if ((l_time.month = MySQLcom.check_word(ab_month_names, valcs, val, val_end)) <= 0) + err: { + // logger.warn + return true; + } + break; + /* Day */ + case 'd': + case 'e': + tmp = val + Math.min(2, val_len); + l_time.day = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + case 'D': + tmp = val + Math.min(2, val_len); + l_time.day = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + /* Skip 'st, 'nd, 'th .. */ + val = tmp + Math.min((int) (val_end - tmp), 2); + break; + + /* Hour */ + case 'h': + case 'I': + case 'l': + usa_time = true; + /* fall through */ + case 'k': + case 'H': + tmp = val + Math.min(2, val_len); + l_time.hour = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + + /* Minute */ + case 'i': + tmp = val + Math.min(2, val_len); + l_time.minute = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + + /* Second */ + case 's': + case 'S': + tmp = val + Math.min(2, val_len); + l_time.second = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + + /* Second part */ + case 'f': + tmp = val_end; + if (tmp - val > 6) + tmp = val + 6; + l_time.second_part = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + frac_part = 6 - (int) (tmp - val); + if (frac_part > 0) + l_time.second_part *= MySQLcom.log_10_int[frac_part]; + val = tmp; + break; + + /* AM / PM */ + case 'p': + if (val_len < 2 || !usa_time) + err: { + // logger.warn + return true; + } + if (new String(valcs, val, 2).compareTo("PM") == 0) + daypart = 12; + else if (new String(valcs, val, 2).compareTo("AM") == 0) { + err: { + // logger.warn + return true; + } + } + val += 2; + break; + + /* Exotic things */ + case 'W': + if ((weekday = MySQLcom.check_word(day_names, valcs, val, val_end)) <= 0) { + err: { + // logger.warn + return true; + } + } + break; + case 'a': + if ((weekday = MySQLcom.check_word(ab_day_names, valcs, val, val_end)) <= 0) { + err: { + // logger.warn + return true; + } + } + break; + case 'w': + tmp = val + 1; + if ((weekday = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue()) < 0 || weekday >= 7) { + err: { + // logger.warn + return true; + } + } + /* We should use the same 1 - 7 scale for %w as for %W */ + if (weekday == 0) + weekday = 7; + val = tmp; + break; + case 'j': + tmp = val + Math.min(val_len, 3); + yearday = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + + /* Week numbers */ + case 'V': + case 'U': + case 'v': + case 'u': + sunday_first_n_first_week_non_iso = (ptrcs[ptr] == 'U' || ptrcs[ptr] == 'V'); + strict_week_number = (ptrcs[ptr] == 'V' || ptrcs[ptr] == 'v'); + tmp = val + Math.min(val_len, 2); + if ((week_number = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue()) < 0 + || (strict_week_number && week_number == 0) || week_number > 53) { + err: { + // logger.warn + return true; + } + } + val = tmp; + break; + + /* Year used with 'strict' %V and %v week numbers */ + case 'X': + case 'x': + strict_week_number_year_type = (ptrcs[ptr] == 'X'); + tmp = val + Math.min(4, val_len); + strict_week_number_year = MySQLcom.my_strtoll10(valcs, val, tmp, error).intValue(); + val = tmp; + break; + + /* Time in AM/PM notation */ + case 'r': + /* + * We can't just set error here, as we don't want to + * generate two warnings in case of errors + */ + if (extract_date_time(time_ampm_format, new String(valcs, val, val_end - val), l_time, + cached_timestamp_type, "time")) + return true; + break; + + /* Time in 24-hour notation */ + case 'T': + if (extract_date_time(time_24hrs_format, new String(valcs, val, val_end - val), l_time, + cached_timestamp_type, "time")) + return true; + break; + + /* Conversion specifiers that match classes of characters */ + case '.': + while (val < val_end && Ctype.isPunct(valcs[val])) + val++; + break; + case '@': + while (val < val_end && Ctype.my_isalpha(valcs[val])) + val++; + break; + case '#': + while (val < val_end && Ctype.isDigit(valcs[val])) + val++; + break; + default: + err: { + // logger.warn + return true; + } + } + if (error.get()) // Error from MySql_com.my_strtoll10 + err: { + // logger.warn + return true; + } + } else if (!Ctype.spaceChar(ptrcs[ptr])) { + if (valcs[val] != ptrcs[ptr]) + err: { + // logger.warn + return true; + } + val++; + } + } + if (usa_time) { + if (l_time.hour > 12 || l_time.hour < 1) + err: { + // logger.warn + return true; + } + l_time.hour = l_time.hour % 12 + daypart; + } + + if (yearday > 0) { + long days; + days = calc_daynr(l_time.year, 1L, 1L) + yearday - 1; + if (days <= 0 || days > MAX_DAY_NUMBER) + err: { + // logger.warn + return true; + } + LongPtr yPtr = new LongPtr(l_time.year); + LongPtr mPtr = new LongPtr(l_time.month); + LongPtr dPtr = new LongPtr(l_time.day); + get_date_from_daynr(days, yPtr, mPtr, dPtr); + l_time.year = yPtr.get(); + l_time.month = mPtr.get(); + l_time.day = dPtr.get(); + } + + if (week_number >= 0 && weekday != 0) { + int days; + long weekday_b; + + /* + * %V,%v require %X,%x resprectively, %U,%u should be used with %Y + * and not %X or %x + */ + if ((strict_week_number && (strict_week_number_year < 0 + || strict_week_number_year_type != sunday_first_n_first_week_non_iso)) + || (!strict_week_number && strict_week_number_year >= 0)) + err: { + // logger.warn + return true; + } + + /* Number of days since year 0 till 1st Jan of this year */ + days = (int) calc_daynr((strict_week_number ? strict_week_number_year : l_time.year), 1, 1); + /* Which day of week is 1st Jan of this year */ + weekday_b = calc_weekday(days, sunday_first_n_first_week_non_iso); + + /* + * Below we are going to sum: 1) number of days since year 0 till + * 1st day of 1st week of this year 2) number of days between 1st + * week and our week 3) and position of our day in the week + */ + if (sunday_first_n_first_week_non_iso) { + days += ((weekday_b == 0) ? 0 : 7) - weekday_b + (week_number - 1) * 7 + weekday % 7; + } else { + days += ((weekday_b <= 3) ? 0 : 7) - weekday_b + (week_number - 1) * 7 + (weekday - 1); + } + + if (days <= 0 || days > MAX_DAY_NUMBER) + err: { + // logger.warn + return true; + } + LongPtr yPtr = new LongPtr(l_time.year); + LongPtr mPtr = new LongPtr(l_time.month); + LongPtr dPtr = new LongPtr(l_time.day); + get_date_from_daynr(days, yPtr, mPtr, dPtr); + l_time.year = yPtr.get(); + l_time.month = mPtr.get(); + l_time.day = dPtr.get(); + } + + if (l_time.month > 12 || l_time.day > 31 || l_time.hour > 23 || l_time.minute > 59 || l_time.second > 59) + err: { + // logger.warn + return true; + } + + if (val != val_end) { + do { + if (!Ctype.spaceChar(valcs[val])) { + // TS-TODO: extract_date_time is not UCS2 safe + // + break; + } + } while (++val != val_end); + } + return false; + + } + + /** + * Create a formated date/time value in a string. + * + * @return + */ + public static boolean make_date_time(DateTimeFormat format, MySQLTime l_time, MySQLTimestampType type, + StringPtr strPtr) { + int hours_i; + int weekday; + int ptr = 0, end; + char[] formatChars = format.format.toCharArray(); + StringBuilder str = new StringBuilder(); + MYLOCALE locale = MYLOCALES.my_locale_en_US; + + if (l_time.neg) + str.append('-'); + + end = format.format.length(); + for (; ptr != end; ptr++) { + if (formatChars[ptr] != '%' || ptr + 1 == end) + str.append(formatChars[ptr]); + else { + switch (formatChars[++ptr]) { + case 'M': + if (l_time.month == 0) { + strPtr.set(str.toString()); + return true; + } + str.append(locale.month_names.type_names[(int) (l_time.month - 1)]); + + break; + case 'b': + if (l_time.month == 0) { + strPtr.set(str.toString()); + return true; + } + str.append(locale.ab_month_names.type_names[(int) (l_time.month - 1)]); + break; + case 'W': + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME + || (l_time.month == 0 && l_time.year == 0)) { + strPtr.set(str.toString()); + return true; + } + weekday = MyTime.calc_weekday(MyTime.calc_daynr(l_time.year, l_time.month, l_time.day), false); + str.append(locale.day_names.type_names[weekday]); + break; + case 'a': + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME + || (l_time.month == 0 && l_time.year == 0)) { + strPtr.set(str.toString()); + return true; + } + weekday = MyTime.calc_weekday(MyTime.calc_daynr(l_time.year, l_time.month, l_time.day), false); + str.append(locale.ab_day_names.type_names[weekday]); + break; + case 'D': + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) { + strPtr.set(str.toString()); + return true; + } + str.append(String.format("%01d", l_time.day)); + if (l_time.day >= 10 && l_time.day <= 19) + str.append("th"); + else { + int tmp = (int) (l_time.day % 10); + switch (tmp) { + case 1: + str.append("st"); + break; + case 2: + str.append("nd"); + break; + case 3: + str.append("rd"); + break; + default: + str.append("th"); + break; + } + } + break; + case 'Y': + str.append(String.format("%04d", l_time.year)); + break; + case 'y': + str.append(String.format("%02d", l_time.year)); + break; + case 'm': + str.append(String.format("%02d", l_time.month)); + break; + case 'c': + str.append(String.format("%01d", l_time.month)); + break; + case 'd': + str.append(String.format("%02d", l_time.day)); + break; + case 'e': + str.append(String.format("%01d", l_time.day)); + break; + case 'f': + str.append(String.format("%06d", l_time.second_part)); + break; + case 'H': + str.append(String.format("%02d", l_time.hour)); + break; + case 'h': + case 'I': + hours_i = (int) ((l_time.hour % 24 + 11) % 12 + 1); + str.append(String.format("%02d", hours_i)); + break; + case 'i': /* minutes */ + str.append(String.format("%02d", l_time.minute)); + break; + case 'j': + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) { + strPtr.set(str.toString()); + return true; + } + str.append(String.format("%03d", MyTime.calc_daynr(l_time.year, l_time.month, l_time.day) + - MyTime.calc_daynr(l_time.year, 1, 1) + 1)); + break; + case 'k': + str.append(String.format("%01d", l_time.hour)); + break; + case 'l': + hours_i = (int) ((l_time.hour % 24 + 11) % 12 + 1); + str.append(String.format("%01d", l_time.hour)); + break; + case 'p': + hours_i = (int) (l_time.hour % 24); + str.append(hours_i < 12 ? "AM" : "PM"); + break; + case 'r': + String tmpFmt = ((l_time.hour % 24) < 12) ? "%02d:%02d:%02d AM" : "%02d:%02d:%02d PM"; + str.append(String.format(tmpFmt, (l_time.hour + 11) % 12 + 1, l_time.minute, l_time.second)); + break; + case 'S': + case 's': + str.append(String.format("%02d", l_time.second)); + break; + case 'T': + str.append(String.format("%02d:%02d:%02d", l_time.hour, l_time.minute, l_time.second)); + break; + case 'U': + case 'u': { + LongPtr year = new LongPtr(0); + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) { + strPtr.set(str.toString()); + return true; + } + str.append(String.format("%02d", MyTime.calc_week(l_time, + formatChars[ptr] == 'U' ? MyTime.WEEK_FIRST_WEEKDAY : MyTime.WEEK_MONDAY_FIRST, year))); + } + break; + case 'v': + case 'V': { + LongPtr year = new LongPtr(0); + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) { + strPtr.set(str.toString()); + return true; + } + str.append(String.format("%02d", + MyTime.calc_week(l_time, + formatChars[ptr] == 'V' ? (MyTime.WEEK_YEAR | MyTime.WEEK_FIRST_WEEKDAY) + : (MyTime.WEEK_YEAR | MyTime.WEEK_MONDAY_FIRST), + year))); + } + break; + case 'x': + case 'X': { + LongPtr year = new LongPtr(0); + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) { + strPtr.set(str.toString()); + return true; + } + MyTime.calc_week(l_time, formatChars[ptr] == 'X' ? MyTime.WEEK_YEAR | MyTime.WEEK_FIRST_WEEKDAY + : MyTime.WEEK_YEAR | MyTime.WEEK_MONDAY_FIRST, year); + str.append(String.format("%04d", year.get())); + } + break; + case 'w': + if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME + || (l_time.month == 0 && l_time.year == 0)) { + strPtr.set(str.toString()); + return true; + } + weekday = MyTime.calc_weekday(MyTime.calc_daynr(l_time.year, l_time.month, l_time.day), true); + str.append(String.format("%01d", weekday)); + break; + + default: + str.append(format.format.substring(ptr)); + break; + } + } + } + strPtr.set(str.toString()); + return false; + } +} diff --git a/src/main/java/io/mycat/plan/common/time/Timeval.java b/src/main/java/io/mycat/plan/common/time/Timeval.java new file mode 100644 index 000000000..497f942ff --- /dev/null +++ b/src/main/java/io/mycat/plan/common/time/Timeval.java @@ -0,0 +1,43 @@ +package io.mycat.plan.common.time; + +import java.math.BigDecimal; + +/* + * Structure returned by gettimeofday(2) system call, + * and used in other calls. + */ +public class Timeval { + public long tv_sec; /* seconds */ + public long tv_usec; /* and microseconds */ + + public Timeval() { + tv_sec = tv_usec = 0; + } + + /** + * see My_decimal.cc /** Convert timeval value to my_decimal. my_decimal + * *timeval2my_decimal(const struct timeval *tm, my_decimal *dec) + * + * @return + */ + public BigDecimal timeval2my_decimal() { + BigDecimal intpart = BigDecimal.valueOf(tv_sec); + BigDecimal frac = BigDecimal.valueOf(tv_usec / 1000000.0); + return intpart.add(frac); + } + + /** + * Print a timestamp with an oprional fractional part: XXXXX[.YYYYY] + * + * @param tm + * The timestamp value to print. + * @param OUT + * to The string pointer to print at. + * @param dec + * Precision, in the range 0..6. + * @return The length of the result string. + */ + public String my_timeval_to_str() { + return tv_sec + "." + tv_usec; + } +}; diff --git a/src/main/java/io/mycat/plan/common/typelib/TYPELIB.java b/src/main/java/io/mycat/plan/common/typelib/TYPELIB.java new file mode 100644 index 000000000..129c888ed --- /dev/null +++ b/src/main/java/io/mycat/plan/common/typelib/TYPELIB.java @@ -0,0 +1,15 @@ +package io.mycat.plan.common.typelib; + +public class TYPELIB { + public int count; /* How many types */ + public String name; /* Name of typelib */ + public String[] type_names; + public Integer type_lengths; + + public TYPELIB(int count_par, String name_par, String[] type_names_par, Integer type_lengths_par) { + this.count = count_par; + this.name = name_par; + this.type_names = type_names_par; + this.type_lengths = type_lengths_par; + } +} diff --git a/src/main/java/io/mycat/plan/node/JoinNode.java b/src/main/java/io/mycat/plan/node/JoinNode.java new file mode 100644 index 000000000..ed7016524 --- /dev/null +++ b/src/main/java/io/mycat/plan/node/JoinNode.java @@ -0,0 +1,422 @@ +package io.mycat.plan.node; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.config.model.ERTable; +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.util.FilterUtils; +import io.mycat.plan.util.PlanUtil; +import io.mycat.plan.util.ToStringUtil; + + +public class JoinNode extends PlanNode { + + public PlanNodeType type() { + return PlanNodeType.JOIN; + } + + public enum Strategy { + SORTMERGE, NESTLOOP + } + + /** + * 是否是not in + */ + private boolean isNotIn = false; + + public boolean isNotIn() { + return isNotIn; + } + + public void setNotIn(boolean isNotIn) { + this.isNotIn = isNotIn; + } + + // sort-merge-join时左节点的排序属性 + private List leftJoinOnOrders = new ArrayList(); + private boolean isLeftOrderMatch = false; + // sort-merge-join时右节点的排序属性 + private List rightJoinOnOrders = new ArrayList(); + private boolean isRightOrderMatch = false; + + /** + *
+	 * leftOuterJoin:
+	 *      leftOuter=true && rightOuter=false
+	 * rightOuterJoin:
+	 *      leftOuter=false && rightOuter=true
+	 * innerJoin:
+	 *      leftOuter=false && rightOuter=false 
+	 * outerJoin:
+	 *      leftOuter=true && rightOuter=true
+	 * 
+ */ + private boolean leftOuter; + private boolean rightOuter; + private boolean needOptimizeJoinOrder; + private List joinFilter; + private Item otherJoinOnFilter; + + /** + * 虚拟化一个节点可以被ER关联的key代表 + */ + protected List ERkeys = new ArrayList(); + + protected Strategy strategy = Strategy.SORTMERGE; + + public JoinNode() { + this.leftOuter = false; + this.rightOuter = false; + this.needOptimizeJoinOrder = true; + this.joinFilter = new ArrayList(); + } + + public JoinNode(PlanNode left, PlanNode right) { + this(); + addChild(left); + addChild(right); + } + + @Override + public void setUpFields() { + super.setUpFields(); + buildJoinFilters(); + buildOtherJoinOn(); + buildJoinKeys(false); + } + + private void buildJoinFilters() { + nameContext.setFindInSelect(false); + nameContext.setSelectFirst(false); + for (int index = 0; index < joinFilter.size(); index++) { + Item bf = joinFilter.get(index); + bf = setUpItem(bf); + joinFilter.set(index, (ItemFuncEqual) bf); + } + } + + private void buildOtherJoinOn() { + nameContext.setFindInSelect(false); + nameContext.setSelectFirst(false); + if (otherJoinOnFilter != null) + otherJoinOnFilter = setUpItem(otherJoinOnFilter); + } + + /** + * setupJoinfilters + * + * @param clearName + * if true:clear filter's itemname,else keep + */ + private void buildJoinKeys(boolean clearName) { + List otherJoinOnFilters = new ArrayList(getJoinFilter().size()); + for (int index = 0; index < joinFilter.size(); index++) { + ItemFuncEqual bf = joinFilter.get(index); + if (clearName) + bf.setItemName(null); + boolean isJoinKey = PlanUtil.isJoinKey(bf, this); + if (!isJoinKey) { + otherJoinOnFilters.add(bf); + otherJoinOnFilter = FilterUtils.and(otherJoinOnFilter, bf); + } + } + getJoinFilter().removeAll(otherJoinOnFilters); + } + + public List getLeftKeys() { + List leftKeys = new ArrayList(this.getJoinFilter().size()); + for (ItemFuncEqual f : this.getJoinFilter()) { + leftKeys.add(f.arguments().get(0)); + } + return leftKeys; + } + + public List getRightKeys() { + List rightKeys = new ArrayList(this.getJoinFilter().size()); + for (ItemFuncEqual f : this.getJoinFilter()) { + rightKeys.add(f.arguments().get(1)); + } + return rightKeys; + } + + public JoinNode addJoinKeys(Item leftKey, Item rightKey) { + this.joinFilter.add(FilterUtils.equal(leftKey, rightKey)); + return this; + } + + public void addJoinFilter(ItemFuncEqual filter) { + this.joinFilter.add(filter); + } + + public PlanNode getLeftNode() { + if (children.isEmpty()) + return null; + return children.get(0); + + } + + public PlanNode getRightNode() { + if (children.size() < 2) + return null; + return children.get(1); + } + + public void setLeftNode(PlanNode left) { + if (children.isEmpty()) { + addChild(left); + } else { + children.set(0, left); + left.setParent(this); + } + } + + public void setRightNode(PlanNode right) { + if (this.getChildren().isEmpty()) { + addChild(null); + } + if (this.getChildren().size() == 1) { + addChild(right); + } else { + children.set(1, right); + right.setParent(this); + } + + } + + public String getPureName() { + return null; + } + + /** + * 交换左右节点 + */ + public void exchangeLeftAndRight() { + + PlanNode tmp = this.getLeftNode(); + this.setLeftNode(this.getRightNode()); + this.setRightNode(tmp); + + boolean tmpouter = this.leftOuter; + this.leftOuter = this.rightOuter; + this.rightOuter = tmpouter; + + this.buildJoinKeys(true); + + } + + public List getJoinFilter() { + return this.joinFilter; + } + + public void setJoinFilter(List joinFilter) { + this.joinFilter = joinFilter; + } + + public JoinNode setLeftOuterJoin() { + this.leftOuter = true; + this.rightOuter = false; + return this; + } + + public JoinNode setRightOuterJoin() { + this.rightOuter = true; + this.leftOuter = false; + return this; + } + + public JoinNode setInnerJoin() { + this.leftOuter = false; + this.rightOuter = false; + return this; + } + + /** + * 或者称为full join + */ + public JoinNode setOuterJoin() { + this.leftOuter = true; + this.rightOuter = true; + return this; + } + + public boolean getLeftOuter() { + return this.leftOuter; + } + + public boolean getRightOuter() { + return this.rightOuter; + } + + public boolean isLeftOuterJoin() { + return (this.getLeftOuter()) && (!this.getRightOuter()); + } + + public boolean isRightOuterJoin() { + return (!this.getLeftOuter()) && (this.getRightOuter()); + } + + public boolean isInnerJoin() { + return (!this.getLeftOuter()) && (!this.getRightOuter()); + } + + public boolean isOuterJoin() { + return (this.getLeftOuter()) && (this.getRightOuter()); + } + + public boolean isNeedOptimizeJoinOrder() { + return this.needOptimizeJoinOrder; + } + + public void setNeedOptimizeJoinOrder(boolean needOptimizeJoinOrder) { + this.needOptimizeJoinOrder = needOptimizeJoinOrder; + } + + public JoinNode setLeftRightJoin(boolean leftOuter, boolean rightOuter) { + this.leftOuter = leftOuter; + this.rightOuter = rightOuter; + return this; + } + + public List getLeftJoinOnOrders() { + return leftJoinOnOrders; + } + + public List getRightJoinOnOrders() { + return rightJoinOnOrders; + } + + @Override + public JoinNode copy() { + JoinNode newJoinNode = new JoinNode(); + this.copySelfTo(newJoinNode); + newJoinNode.setJoinFilter(new ArrayList()); + for (Item bf : joinFilter) { + newJoinNode.addJoinFilter((ItemFuncEqual) bf.cloneStruct()); + } + newJoinNode.setLeftNode((PlanNode) this.getLeftNode().copy()); + newJoinNode.setRightNode((PlanNode) this.getRightNode().copy()); + newJoinNode.setNeedOptimizeJoinOrder(this.isNeedOptimizeJoinOrder()); + newJoinNode.leftOuter = this.leftOuter; + newJoinNode.rightOuter = this.rightOuter; + newJoinNode.isNotIn = this.isNotIn; + newJoinNode.otherJoinOnFilter = this.otherJoinOnFilter == null ? null : this.otherJoinOnFilter.cloneItem(); + return newJoinNode; + } + + public List getERkeys() { + return this.ERkeys; + } + + @Override + public int getHeight() { + int maxChildHeight = 0; + for (PlanNode child : children) { + int childHeight = child.getHeight(); + if (childHeight > maxChildHeight) + maxChildHeight = childHeight; + } + return maxChildHeight + 1; + } + + /** + * @return the isLeftOrderMatch + */ + public boolean isLeftOrderMatch() { + return isLeftOrderMatch; + } + + /** + * @param isLeftOrderMatch + * the isLeftOrderMatch to set + */ + public void setLeftOrderMatch(boolean isLeftOrderMatch) { + this.isLeftOrderMatch = isLeftOrderMatch; + } + + /** + * @return the isRightOrderMatch + */ + public boolean isRightOrderMatch() { + return isRightOrderMatch; + } + + /** + * @param isRightOrderMatch + * the isRightOrderMatch to set + */ + public void setRightOrderMatch(boolean isRightOrderMatch) { + this.isRightOrderMatch = isRightOrderMatch; + } + + /** + * @return the strategy + */ + public Strategy getStrategy() { + return strategy; + } + + /** + * @param strategy + * the strategy to set + */ + public void setStrategy(Strategy strategy) { + this.strategy = strategy; + } + + public Item getOtherJoinOnFilter() { + return otherJoinOnFilter; + } + + public void setOtherJoinOnFilter(Item otherJoinOnFilter) { + this.otherJoinOnFilter = otherJoinOnFilter; + } + + @Override + public String toString(int level) { + StringBuilder sb = new StringBuilder(); + String tabTittle = ToStringUtil.getTab(level); + String tabContent = ToStringUtil.getTab(level + 1); + if (this.getAlias() != null) { + ToStringUtil.appendln(sb, tabTittle + "Join" + " as " + this.getAlias()); + } else { + ToStringUtil.appendln(sb, tabTittle + "Join"); + } + ToStringUtil.appendln(sb, tabContent + "joinStrategy: " + this.getStrategy()); + if (this.isInnerJoin()) { + ToStringUtil.appendln(sb, tabContent + "type: " + "inner join"); + } else if (this.isRightOuterJoin()) { + ToStringUtil.appendln(sb, tabContent + "type: " + "right outter join"); + } else if (this.isLeftOuterJoin()) { + ToStringUtil.appendln(sb, tabContent + "type: " + "left outter join"); + } else if (this.isOuterJoin()) { + ToStringUtil.appendln(sb, tabContent + "type: " + "outter join"); + } + ToStringUtil.appendln(sb, tabContent + "joinFilter: " + ToStringUtil.itemListString(this.getJoinFilter())); + ToStringUtil.appendln(sb, tabContent + "otherJoinOnFilter: " + ToStringUtil.itemString(this.otherJoinOnFilter)); + ToStringUtil.appendln(sb, tabContent + "leftJoinOnOrder: " + ToStringUtil.orderListString(leftJoinOnOrders)); + ToStringUtil.appendln(sb, tabContent + "rightJoinOnOrder: " + ToStringUtil.orderListString(rightJoinOnOrders)); + ToStringUtil.appendln(sb, tabContent + "isDistinct: " + isDistinct()); + ToStringUtil.appendln(sb, tabContent + "columns: " + ToStringUtil.itemListString(columnsSelected)); + ToStringUtil.appendln(sb, tabContent + "where: " + ToStringUtil.itemString(whereFilter)); + ToStringUtil.appendln(sb, tabContent + "having: " + ToStringUtil.itemString(havingFilter)); + ToStringUtil.appendln(sb, tabContent + "groupBy: " + ToStringUtil.orderListString(groups)); + ToStringUtil.appendln(sb, tabContent + "orderBy: " + ToStringUtil.orderListString(orderBys)); + if (this.getLimitFrom() >= 0L && this.getLimitTo() > 0L) { + ToStringUtil.appendln(sb, tabContent + "limitFrom: " + this.getLimitFrom()); + ToStringUtil.appendln(sb, tabContent + "limitTo: " + this.getLimitTo()); + } + ToStringUtil.appendln(sb, tabContent + "sql: " + this.getSql()); + + ToStringUtil.appendln(sb, tabContent + "left:"); + sb.append(this.getLeftNode().toString(level + 2)); + ToStringUtil.appendln(sb, tabContent + "right:"); + sb.append(this.getRightNode().toString(level + 2)); + + return sb.toString(); + } + +} diff --git a/src/main/java/io/mycat/plan/node/MergeNode.java b/src/main/java/io/mycat/plan/node/MergeNode.java new file mode 100644 index 000000000..9a85ed74d --- /dev/null +++ b/src/main/java/io/mycat/plan/node/MergeNode.java @@ -0,0 +1,155 @@ +package io.mycat.plan.node; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.NamedField; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.util.ToStringUtil; + +/** + * @author zhangyaohua + * @createTime 2014-1-21 + */ +public class MergeNode extends PlanNode { + + private List comeInFields; + + public PlanNodeType type() { + return PlanNodeType.MERGE; + } + + private boolean union = false; + + public MergeNode() { + } + + public MergeNode merge(PlanNode o) { + this.addChild(o); + return this; + } + + // 记录union字段名称以及对应index的map + public Map getColIndexs() { + Map colIndexs = new HashMap(); + for (int index = 0; index < getColumnsSelected().size(); index++) { + String name = getColumnsSelected().get(index).getItemName(); + colIndexs.put(name, index); + } + return colIndexs; + } + + public String getPureName() { + return null; + } + + public boolean isUnion() { + return union; + } + + public void setUnion(boolean union) { + this.union = union; + } + + public List getComeInFields() { + // modify:允许为了让union的order + // by直接下发,在SelectPush后允许union的select列和child的select列不同, + // uionhandler的列以firstchild为准,union sql的结果仍然为node.getcolumnSelected + if (comeInFields == null) + return getColumnsSelected(); + else + return comeInFields; + } + + @Override + protected void setUpInnerFields() { + innerFields.clear(); + boolean isFirst = true; + int columnSize = 0; + for (PlanNode child : children) { + child.setUpFields(); + int childSelects = child.getColumnsSelected().size(); + if (isFirst) { + columnSize = childSelects; + isFirst = false; + } else { + if (columnSize != childSelects) { + throw new MySQLOutPutException(ErrorCode.ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, "21000", + "The used SELECT statements have a different number of columns"); + } + } + } + } + + @Override + protected void setUpSelects() { + columnsSelected.clear(); + PlanNode firstNode = getChild(); + outerFields.clear(); + for (NamedField coutField : firstNode.getOuterFields().keySet()) { + ItemField column = new ItemField(null, null, coutField.name); + NamedField tmpField = new NamedField(null, coutField.name, this); + if (outerFields.containsKey(tmpField) && getParent() != null) + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "duplicate field"); + outerFields.put(tmpField, column); + getColumnsSelected().add(column); + } + } + + @Override + public MergeNode copy() { + MergeNode newMergeNode = new MergeNode(); + this.copySelfTo(newMergeNode); + newMergeNode.setUnion(union); + for (PlanNode child : children) { + newMergeNode.addChild((PlanNode) child.copy()); + } + return newMergeNode; + } + + @Override + public int getHeight() { + int maxChildHeight = 0; + for (PlanNode child : children) { + int childHeight = child.getHeight(); + if (childHeight > maxChildHeight) + maxChildHeight = childHeight; + } + return maxChildHeight + 1; + } + + public void setComeInFields(List comeInFields) { + this.comeInFields = comeInFields; + } + + @Override + public String toString(int level) { + StringBuilder sb = new StringBuilder(); + String tabTittle = ToStringUtil.getTab(level); + String tabContent = ToStringUtil.getTab(level + 1); + if (this.getAlias() != null) { + ToStringUtil.appendln(sb, tabTittle + (this.isUnion() ? "Union" : "Union all") + " as " + this.getAlias()); + } else { + ToStringUtil.appendln(sb, tabTittle + (this.isUnion() ? "Union" : "Union all")); + } + ToStringUtil.appendln(sb, tabContent + "columns: " + ToStringUtil.itemListString(columnsSelected)); + ToStringUtil.appendln(sb, tabContent + "where: " + ToStringUtil.itemString(whereFilter)); + ToStringUtil.appendln(sb, tabContent + "orderBy: " + ToStringUtil.orderListString(orderBys)); + if (this.getLimitFrom() >= 0L && this.getLimitTo() > 0L) { + ToStringUtil.appendln(sb, tabContent + "limitFrom: " + this.getLimitFrom()); + ToStringUtil.appendln(sb, tabContent + "limitTo: " + this.getLimitTo()); + } + + for (PlanNode node : children) { + sb.append(node.toString(level + 2)); + } + + return sb.toString(); + } + +} diff --git a/src/main/java/io/mycat/plan/node/NoNameNode.java b/src/main/java/io/mycat/plan/node/NoNameNode.java new file mode 100644 index 000000000..12310c45b --- /dev/null +++ b/src/main/java/io/mycat/plan/node/NoNameNode.java @@ -0,0 +1,79 @@ +package io.mycat.plan.node; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.util.ToStringUtil; + +/** + * 匿名表,比如select 1,only exists selecteditems + * + * @author chenzifei + * + */ + +public class NoNameNode extends PlanNode { + + private final static String NONAME = ""; + private final String catalog; + + public PlanNodeType type() { + return PlanNodeType.NONAME; + } + + /** + * @param areaSchema + * @param tableName + */ + public NoNameNode(String catalog, String sql) { + this.catalog = catalog; + this.sql = sql; + } + + /** + * @return the tableName + */ + public String getTableName() { + return NONAME; + } + + @Override + public NoNameNode copy() { + NoNameNode noNameNode = new NoNameNode(catalog, sql); + this.copySelfTo(noNameNode); + return noNameNode; + } + + @Override + public String getPureName() { + return this.getTableName(); + } + + @Override + public List getReferedTableNodes() { + return new ArrayList(); + } + + public String getCatalog() { + return this.catalog; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public String toString(int level) { + StringBuilder sb = new StringBuilder(); + String tabTittle = ToStringUtil.getTab(level); + + if (this.getAlias() != null) { + ToStringUtil.appendln(sb, tabTittle + "Query from[ " + this.getSql() + " ] as " + this.getAlias()); + } else { + ToStringUtil.appendln(sb, tabTittle + "Query from[ " + this.getSql() + " ]"); + } + return sb.toString(); + } +} diff --git a/src/main/java/io/mycat/plan/node/QueryNode.java b/src/main/java/io/mycat/plan/node/QueryNode.java new file mode 100644 index 000000000..5eaf8b635 --- /dev/null +++ b/src/main/java/io/mycat/plan/node/QueryNode.java @@ -0,0 +1,81 @@ +package io.mycat.plan.node; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.util.ToStringUtil; + +/* + * query a logic table + */ +public class QueryNode extends PlanNode { + + public PlanNodeType type() { + return PlanNodeType.QUERY; + } + + public QueryNode(PlanNode child) { + this(child, null); + } + + public QueryNode(PlanNode child, Item filter) { + this.whereFilter = filter; + this.setChild(child); + if (child != null) { + child.setSubQuery(true);// 默认设置为subQuery + } + } + + public void setChild(PlanNode child) { + if (child == null) { + return; + } + children.clear(); + addChild(child); + } + + @Override + public String getPureName() { + return this.getChild().getAlias(); + } + + @Override + public QueryNode copy() { + QueryNode newTableNode = new QueryNode((PlanNode) this.getChild().copy()); + this.copySelfTo(newTableNode); + return newTableNode; + } + + @Override + public int getHeight() { + return getChild().getHeight() + 1; + } + + @Override + public String toString(int level) { + StringBuilder sb = new StringBuilder(); + String tabTittle = ToStringUtil.getTab(level); + String tabContent = ToStringUtil.getTab(level + 1); + if (this.getAlias() != null) { + ToStringUtil.appendln(sb, tabTittle + "SubQuery" + " as " + this.getAlias()); + } else { + ToStringUtil.appendln(sb, tabTittle + "SubQuery"); + } + ToStringUtil.appendln(sb, tabContent + "isDistinct: " + isDistinct()); + ToStringUtil.appendln(sb, tabContent + "columns: " + ToStringUtil.itemListString(columnsSelected)); + ToStringUtil.appendln(sb, tabContent + "where: " + ToStringUtil.itemString(whereFilter)); + ToStringUtil.appendln(sb, tabContent + "having: " + ToStringUtil.itemString(havingFilter)); + ToStringUtil.appendln(sb, tabContent + "groupBy: " + ToStringUtil.orderListString(groups)); + ToStringUtil.appendln(sb, tabContent + "orderBy: " + ToStringUtil.orderListString(orderBys)); + if (this.getLimitFrom() >= 0L && this.getLimitTo() > 0L) { + ToStringUtil.appendln(sb, tabContent + "limitFrom: " + this.getLimitFrom()); + ToStringUtil.appendln(sb, tabContent + "limitTo: " + this.getLimitTo()); + } + ToStringUtil.appendln(sb, tabContent + "sql: " + this.getSql()); + if (this.getChild() != null) { + ToStringUtil.appendln(sb, tabContent + "from:"); + sb.append(this.getChild().toString(level + 2)); + } + return sb.toString(); + } + +} diff --git a/src/main/java/io/mycat/plan/node/TableNode.java b/src/main/java/io/mycat/plan/node/TableNode.java new file mode 100644 index 000000000..ca7d37d2d --- /dev/null +++ b/src/main/java/io/mycat/plan/node/TableNode.java @@ -0,0 +1,174 @@ +package io.mycat.plan.node; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLHint; + +import io.mycat.MycatServer; +import io.mycat.config.model.SchemaConfig; +import io.mycat.config.model.TableConfig; +import io.mycat.config.model.TableConfig.TableTypeEnum; +import io.mycat.meta.protocol.MyCatMeta.ColumnMeta; +import io.mycat.meta.protocol.MyCatMeta.TableMeta; +import io.mycat.plan.NamedField; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.util.ToStringUtil; + +public class TableNode extends PlanNode { + + public PlanNodeType type() { + return PlanNodeType.TABLE; + } + + private String schema; + private String tableName; + private TableMeta tableMeta; + private TableConfig tableConfig; + private List hintList; + + /** + * @param areaSchema + * @param tableName + */ + public TableNode(String catalog, String tableName) { + if (catalog == null || tableName == null) + throw new RuntimeException("Table db or name is null error!"); + this.schema = catalog; + SchemaConfig schemaConfig = MycatServer.getInstance().getConfig().getSchemas().get(catalog); + if(schemaConfig == null){ + throw new RuntimeException("schema "+catalog+" is not exists!"); + } + this.referedTableNodes.add(this); + this.tableName = tableName; + String tableKey = tableName; + if(schemaConfig.getLowerCase()==1){ + tableKey = tableKey.toUpperCase(); + } + this.tableMeta = MycatServer.getInstance().getTmManager().getSyncTableMeta(catalog, tableKey); + this.tableConfig = schemaConfig.getTables().get(tableKey); + this.isGlobaled = this.tableConfig != null && (this.tableConfig.getTableType()==TableTypeEnum.TYPE_GLOBAL_TABLE); + if (!this.isGlobaled) + this.unGlobalTableCount = 1; + } + + /** + * @return the tableName + */ + public String getTableName() { + return tableName; + } + + /** + * @param tableName + * the tableName to set + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + @Override + protected void setUpInnerFields() { + innerFields.clear(); + String tmpTable = subAlias == null ? tableName : subAlias; + for (ColumnMeta cm : tableMeta.getColumnsList()) { + NamedField tmpField = new NamedField(tmpTable, cm.getName(), this); + innerFields.put(tmpField, tmpField); + } + } + + @Override + protected void dealStarColumn() { + List newSels = new ArrayList(); + for (Item sel : columnsSelected) { + if (!sel.isWild()) + newSels.add(sel); + else { + for (NamedField innerField : innerFields.keySet()) { + ItemField col = new ItemField(null, sel.getTableName(), innerField.name); + newSels.add(col); + } + } + } + columnsSelected = newSels; + } + + public TableNode copy() { + TableNode newTableNode = new TableNode(schema, tableName); + this.copySelfTo(newTableNode); + newTableNode.setHintList(this.hintList); + return newTableNode; + } + + @Override + public String getPureName() { + return this.getTableName(); + } + + public String getSchema() { + return this.schema; + } + +// public String getFullName() { +// return String.format("`%s`.`%s`", schema, tableName); +// } + + @Override + public int getHeight() { + return 1; + } + + public TableConfig getTableConfig() { + return this.tableConfig; + } + +// public boolean isPartitioned() { +// return isPartitioned; +// } + +// private boolean isPartition(TableConfig tableConfig) { +// if (tableConfig != null) { +// TableRuleConfig tbRuleConfig = tableConfig.getRule(); +// if (tbRuleConfig != null) { +// return tbRuleConfig.getClusteredRule().getTbRule() != null; +// } +// } +// return false; +// } + + @Override + public String toString(int level) { + StringBuilder sb = new StringBuilder(); + String tabTittle = ToStringUtil.getTab(level); + String tabContent = ToStringUtil.getTab(level + 1); + if (this.getAlias() != null) { + ToStringUtil.appendln(sb, tabTittle + "Query from " + this.getTableName() + "<" + this.getSubAlias() + ">" + " as " + + this.getAlias()); + } else { + ToStringUtil.appendln(sb, tabTittle + "Query from " + this.getTableName() + "<" + this.getSubAlias() + ">"); + } + ToStringUtil.appendln(sb, tabContent + "isDistinct: " + isDistinct()); + ToStringUtil.appendln(sb, tabContent + "columns: " + ToStringUtil.itemListString(columnsSelected)); + ToStringUtil.appendln(sb, tabContent + "where: " + ToStringUtil.itemString(whereFilter)); + ToStringUtil.appendln(sb, tabContent + "having: " + ToStringUtil.itemString(havingFilter)); + ToStringUtil.appendln(sb, tabContent + "groupBy: " + ToStringUtil.orderListString(groups)); + ToStringUtil.appendln(sb, tabContent + "orderBy: " + ToStringUtil.orderListString(orderBys)); + if (this.getLimitFrom() >= 0L && this.getLimitTo() > 0L) { + ToStringUtil.appendln(sb, tabContent + "limitFrom: " + this.getLimitFrom()); + ToStringUtil.appendln(sb, tabContent + "limitTo: " + this.getLimitTo()); + } + ToStringUtil.appendln(sb, tabContent + "sql: " + this.getSql()); + return sb.toString(); + } + + public List getHintList() { + return hintList; + } + + public void setHintList(List hintList) { + this.hintList = hintList; + } + +} diff --git a/src/main/java/io/mycat/plan/node/view/ViewNode.java b/src/main/java/io/mycat/plan/node/view/ViewNode.java new file mode 100644 index 000000000..5f2072b27 --- /dev/null +++ b/src/main/java/io/mycat/plan/node/view/ViewNode.java @@ -0,0 +1,94 @@ +package io.mycat.plan.node.view; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.meta.protocol.MyCatMeta.ColumnMeta; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.TableNode; +import io.mycat.plan.util.ToStringUtil; + +/** + * present a view + * + * @author chenzifei + * + */ +public class ViewNode extends QueryNode { + public PlanNodeType type() { + return PlanNodeType.VIEW; + } + + private String catalog; + private String viewname; + private String createSql; + + public ViewNode(String catalog, String viewName, PlanNode selNode, String createSql) { + super(selNode); + this.catalog = catalog; + this.viewname = viewName; + this.createSql = createSql; + this.getChild().setAlias(viewname); + } + + public QueryNode toQueryNode() { + QueryNode newNode = new QueryNode((PlanNode) this.getChild().copy()); + this.copySelfTo(newNode); + return newNode; + } + + public String getCatalog() { + return catalog; + } + + /** + * 根据编译好的viewnode获取当前view的列 + * + * @return + */ + public List getColumns() { + List cols = new ArrayList(); + for (Item sel : getColumnsSelected()) { + String cn = sel.getAlias() == null ? sel.getItemName() : sel.getAlias(); + ColumnMeta.Builder cmBuilder = ColumnMeta.newBuilder(); + cmBuilder.setName(cn); + cols.add(cmBuilder.build()); + } + return cols; + } + + /** + * 获得该view应用到的table名 + * + * @return + */ + public List getReferedTables() { + List tls = new ArrayList(); + for (TableNode tn : this.getReferedTableNodes()) { + tls.add(tn.getTableName()); + } + return tls; + } + + public String getCreateSql() { + return createSql; + } + + @Override + public ViewNode copy() { + PlanNode selCopy = (PlanNode) this.getChild().copy(); + return new ViewNode(catalog, viewname, selCopy, createSql); + } + + @Override + public String toString(int level) { + StringBuilder sb = new StringBuilder(); + String tabTittle = ToStringUtil.getTab(level); + ToStringUtil.appendln(sb, tabTittle + "View " + this.viewname + " AS: "); + ToStringUtil.appendln(sb, this.getChild().toString(level + 1)); + return sb.toString(); + } + +} diff --git a/src/main/java/io/mycat/plan/node/view/ViewUtil.java b/src/main/java/io/mycat/plan/node/view/ViewUtil.java new file mode 100644 index 000000000..ff5d25ffb --- /dev/null +++ b/src/main/java/io/mycat/plan/node/view/ViewUtil.java @@ -0,0 +1,32 @@ +package io.mycat.plan.node.view; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.util.PlanUtil; + +/** + * Some function from mysql source code + * + * @author chenzifei + * + */ +public class ViewUtil { + + /** + * Check whether the merging algorithm can be used on this VIEW see + * LEX::can_be_merged() in sql_lex.cc + * + * @param viewSelNode + * view的真实selectnode + * @return FALSE - only temporary table algorithm can be used TRUE - merge + * algorithm can be used + */ + public static boolean canBeMerged(PlanNode viewSelNode) { + if (viewSelNode.type() == PlanNodeType.NONAME) + return true; + boolean selectsAllowMerge = viewSelNode.type() != PlanNodeType.MERGE; + // TODO as the same as LEX::can_be_merged(); + boolean existAggr = PlanUtil.existAggr(viewSelNode); + return selectsAllowMerge && viewSelNode.getReferedTableNodes().size() >= 1 && !existAggr; + } +} diff --git a/src/main/java/io/mycat/plan/optimizer/FilterJoinColumnPusher.java b/src/main/java/io/mycat/plan/optimizer/FilterJoinColumnPusher.java new file mode 100644 index 000000000..a0f31cc78 --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/FilterJoinColumnPusher.java @@ -0,0 +1,156 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.TableNode; +import io.mycat.plan.util.FilterUtils; +import io.mycat.plan.util.PlanUtil; +import io.mycat.route.parser.util.Pair; + +/** + * 只下推有ER关系可能的filter + * + * @author chenzifei + * + */ +public class FilterJoinColumnPusher { + + public static PlanNode optimize(PlanNode qtn) { + qtn = pushFilter(qtn, new ArrayList()); + return qtn; + } + + private static PlanNode pushFilter(PlanNode qtn, List DNFNodeToPush) { + // 如果是叶节点,接收filter做为where条件,否则继续合并当前where条件,然后下推 + if (qtn.getChildren().isEmpty()) { + Item node = FilterUtils.and(DNFNodeToPush); + if (node != null) { + qtn.query(FilterUtils.and(qtn.getWhereFilter(), node)); + } + return qtn; + } + + Item filterInWhere = qtn.getWhereFilter(); + if (filterInWhere != null) { + List splits = FilterUtils.splitFilter(filterInWhere); + List nonJoinFilter = new ArrayList(); + for (Item filter : splits) { + if (isPossibleERJoinColumnFilter(qtn, filter) == false) { + nonJoinFilter.add(filter); + } else { + DNFNodeToPush.add((ItemFuncEqual) filter); + } + } + qtn.query(FilterUtils.and(nonJoinFilter));// 清空where条件 + } + + // 无法完成下推的filters + List DNFNodeToCurrent = new LinkedList(); + switch (qtn.type()) { + case QUERY: + refreshPdFilters(qtn, DNFNodeToPush); + PlanNode child = pushFilter(qtn.getChild(), DNFNodeToPush); + ((QueryNode) qtn).setChild(child); + break; + case JOIN: + JoinNode jn = (JoinNode) qtn; + List DNFNodetoPushToLeft = new LinkedList(); + List DNFNodetoPushToRight = new LinkedList(); + PlanUtil.findJoinKeysAndRemoveIt(DNFNodeToPush, jn); + for (Item filter : DNFNodeToPush) { + if (PlanUtil.canPush(filter, jn.getLeftNode(), jn)) { + DNFNodetoPushToLeft.add(filter); + } else if (PlanUtil.canPush(filter, jn.getRightNode(), jn)) { + DNFNodetoPushToRight.add(filter); + } else { + DNFNodeToCurrent.add(filter); + } + } + // 针对不能下推的,合并到当前的where + Item node = FilterUtils.and(DNFNodeToCurrent); + if (node != null) { + qtn.query(FilterUtils.and(qtn.getWhereFilter(), node)); + } + if (jn.isInnerJoin()) { + refreshPdFilters(jn, DNFNodetoPushToLeft); + refreshPdFilters(jn, DNFNodetoPushToRight); + pushFilter(jn.getLeftNode(), DNFNodetoPushToLeft); + pushFilter(((JoinNode) qtn).getRightNode(), DNFNodetoPushToRight); + } else if (jn.isLeftOuterJoin()) { + refreshPdFilters(jn, DNFNodetoPushToLeft); + pushFilter(jn.getLeftNode(), DNFNodetoPushToLeft); + if (!DNFNodeToPush.isEmpty()) { + jn.query(FilterUtils.and(DNFNodetoPushToRight)); // 在父节点完成filter,不能下推 + } + } else if (jn.isRightOuterJoin()) { + refreshPdFilters(jn, DNFNodetoPushToRight); + pushFilter(((JoinNode) qtn).getRightNode(), DNFNodetoPushToRight); + if (!DNFNodeToPush.isEmpty()) { + jn.query(FilterUtils.and(DNFNodetoPushToLeft));// 在父节点完成filter,不能下推 + } + } else { + if (!DNFNodeToPush.isEmpty()) { + jn.query(FilterUtils.and(DNFNodeToPush)); + } + } + break; + case MERGE: + List children = qtn.getChildren(); + for (int index = 0; index < children.size(); index++) { + pushFilter(children.get(index), new ArrayList()); + } + break; + default: + break; + } + return qtn; + } + + /** + * 是否是可能得ER关系Filter: 1.Filter必须是=关系 2.Filter必须是Column = Column + * 3.Filter的key和value必须来自于不同的两张表 ex:a.id=b.id true a.id=b.id+1 false + * + * @param filter + * @return + */ + private static boolean isPossibleERJoinColumnFilter(PlanNode node, Item ifilter) { + if (!(ifilter instanceof ItemFuncEqual)) + return false; + ItemFuncEqual filter = (ItemFuncEqual) ifilter; + Item column = filter.arguments().get(0); + Item value = filter.arguments().get(1); + if (column != null && column instanceof ItemField && value != null && value instanceof ItemField) { + Pair foundColumn = PlanUtil.findColumnInTableLeaf((ItemField) column, node); + Pair foundValue = PlanUtil.findColumnInTableLeaf((ItemField) value, node); + if (foundColumn != null && foundValue != null) { + String columnTable = foundColumn.getValue().getTableName(); + String valueTable = foundValue.getValue().getTableName(); + // 不是同一张表才可以 + return !StringUtils.equals(columnTable, valueTable); + } else { + return false; + } + } else { + return false; + } + } + + private static void refreshPdFilters(PlanNode qtn, List filters) { + for (int index = 0; index < filters.size(); index++) { + Item toPsFilter = filters.get(index); + Item pdFilter = PlanUtil.pushDownItem(qtn, toPsFilter); + filters.set(index, pdFilter); + } + } + +} diff --git a/src/main/java/io/mycat/plan/optimizer/FilterPreProcessor.java b/src/main/java/io/mycat/plan/optimizer/FilterPreProcessor.java new file mode 100644 index 000000000..40262c986 --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/FilterPreProcessor.java @@ -0,0 +1,233 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.function.ItemFunc.Functype; +import io.mycat.plan.common.item.function.operator.ItemBoolFunc2; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncGe; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncGt; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIn; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncLe; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncLt; +import io.mycat.plan.common.item.function.operator.logic.ItemCond; +import io.mycat.plan.common.item.function.operator.logic.ItemCondAnd; +import io.mycat.plan.common.item.function.operator.logic.ItemCondOr; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.util.FilterUtils; +import io.mycat.plan.util.PlanUtil; + +/** + * http://dev.mysql.com/doc/refman/5.7/en/where-optimizations.html + * + * @author zhangyaohua + * @CreateTime Mar 16, 2016 + */ +public class FilterPreProcessor { + + public static PlanNode optimize(PlanNode qtn) { + mergeHavingFilter(qtn); + qtn = preProcess(qtn); + return qtn; + } + + /** + * 将having中可合并的条件合并到where + * + * @param qtn + */ + private static void mergeHavingFilter(PlanNode qtn) { + if (qtn.getHavingFilter() != null) { + List subFilters = FilterUtils.splitFilter(qtn.getHavingFilter()); + List canMergeSubs = new ArrayList(); + for (Item subFilter : subFilters) { + if (!subFilter.withSumFunc) { + canMergeSubs.add(subFilter); + } + } + subFilters.removeAll(canMergeSubs); + qtn.having(FilterUtils.and(subFilters)); + qtn.setWhereFilter(FilterUtils.and(qtn.getWhereFilter(), FilterUtils.and(canMergeSubs))); + } + for (PlanNode child : qtn.getChildren()) + mergeHavingFilter(child); + } + + private static PlanNode preProcess(PlanNode qtn) { + qtn.having(processFilter(qtn.getHavingFilter())); + qtn.query(processFilter(qtn.getWhereFilter())); + if (qtn instanceof JoinNode) { + JoinNode jn = (JoinNode) qtn; + for (int i = 0; i < ((JoinNode) qtn).getJoinFilter().size(); i++) { + processFilter(jn.getJoinFilter().get(i)); + } + jn.setOtherJoinOnFilter(processFilter(jn.getOtherJoinOnFilter())); + } + for (PlanNode child : qtn.getChildren()) { + preProcess((PlanNode) child); + } + return qtn; + } + + private static Item processFilter(Item root) { + if (root == null) { + return null; + } + + root = shortestFilter(root); + root = processOneFilter(root); // 做一下转换处理 + root = convertOrToIn(root); + return root; + } + + /** + * 将0=1/1=1/true的恒等式进行优化 + */ + private static Item shortestFilter(Item root) { + if (root == null) + return root; + if (root.canValued()) { + boolean value = root.valBool(); + if (value) + return new ItemInt(1); + else + return new ItemInt(0); + } else if (root.type().equals(ItemType.COND_ITEM)) { + ItemCond cond = (ItemCond) root; + for (int index = 0; index < cond.getArgCount(); index++) { + Item shortedsub = shortestFilter(cond.arguments().get(index)); + cond.arguments().set(index, shortedsub); + } + boolean isAnd = cond.functype().equals(Functype.COND_AND_FUNC); + List newSubFilters = new ArrayList(); + for (Item sub : cond.arguments()) { + if (sub == null) + continue; + if (sub.canValued()) { + boolean value = sub.valBool(); + if (value == true && !isAnd) + return new ItemInt(1); + if (value == false && isAnd) + return new ItemInt(0); + } else { + newSubFilters.add(sub); + } + } + if (isAnd) + return FilterUtils.and(newSubFilters); + else + return FilterUtils.or(newSubFilters); + } else { + return root; + } + } + + /** + * 尽量将const op column统一改为column op const这种 + * + * @param root + * @return + */ + private static Item processOneFilter(Item root) { + if (root == null) { + return null; + } + Item newRoot = root; + if (root instanceof ItemBoolFunc2) { + Item a = root.arguments().get(0); + Item b = root.arguments().get(1); + if (a.basicConstItem() && !b.basicConstItem()) { + if (root instanceof ItemFuncGe) { + newRoot = new ItemFuncLe(b, a); + } else if (root instanceof ItemFuncGt) { + newRoot = new ItemFuncLt(b, a); + } else if (root instanceof ItemFuncLt) { + newRoot = new ItemFuncGt(b, a); + } else if (root instanceof ItemFuncLe) { + newRoot = new ItemFuncGe(b, a); + } else { + root.arguments().set(1, a); + root.arguments().set(0, b); + root.setItemName(null); + } + newRoot.getReferTables().addAll(root.getReferTables()); + } + } else if (root instanceof ItemCond) { + ItemCond condfun = (ItemCond) root; + List newArgs = new ArrayList(); + for (Item arg : condfun.arguments()) { + Item newArg = processOneFilter(arg); + if (newArg != null) + newArgs.add(newArg); + } + if (condfun.functype().equals(Functype.COND_AND_FUNC)) + newRoot = FilterUtils.and(newArgs); + else + newRoot = FilterUtils.or(newArgs); + } + return newRoot; + } + + /** + * 将单个的Logicalfilter【or】尽可能的转换成in + * + * @param filter + */ + private static Item convertOrToIn(Item filter) { + if (filter == null) + return null; + if (filter.type().equals(ItemType.COND_ITEM)) { + if (filter instanceof ItemCondAnd) { + ItemCondAnd andFilter = (ItemCondAnd) filter; + for (int index = 0; index < andFilter.getArgCount(); index++) { + andFilter.arguments().set(index, convertOrToIn(andFilter.arguments().get(index))); + } + andFilter.setItemName(null); + PlanUtil.refreshReferTables(andFilter); + return andFilter; + } else { + // or + ItemCondOr orFilter = (ItemCondOr) filter; + HashMap> inMap = new HashMap>(); + List newSubFilterList = new ArrayList(); + for (int index = 0; index < orFilter.getArgCount(); index++) { + Item subFilter = orFilter.arguments().get(index); + if (subFilter == null) + continue; + if (subFilter instanceof ItemFuncEqual) { + Item a = ((ItemFuncEqual) subFilter).arguments().get(0); + Item b = ((ItemFuncEqual) subFilter).arguments().get(1); + if (!a.canValued() && b.canValued()) { + if (!inMap.containsKey(a)) + inMap.put(a, new HashSet()); + inMap.get(a).add(b); + } + } else { + Item subNew = convertOrToIn(subFilter); + newSubFilterList.add(subNew); + } + } + for (Item inKey : inMap.keySet()) { + Set inValues = inMap.get(inKey); + List args = new ArrayList(); + args.add(inKey); + args.addAll(inValues); + ItemFuncIn inItem = new ItemFuncIn(args, false); + PlanUtil.refreshReferTables(inItem); + newSubFilterList.add(inItem); + } + return FilterUtils.or(newSubFilterList); + } + } + return filter; + } + +} \ No newline at end of file diff --git a/src/main/java/io/mycat/plan/optimizer/FilterPusher.java b/src/main/java/io/mycat/plan/optimizer/FilterPusher.java new file mode 100644 index 000000000..2be779e7f --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/FilterPusher.java @@ -0,0 +1,346 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.util.FilterUtils; +import io.mycat.plan.util.PlanUtil; + +/** + * 将filter进行下推 + * + *
+ * a. 如果条件中包含||条件则暂不优化,下推时会导致语义不正确 b. 如果条件中的column/value包含function,也不做下推
+ * (比较麻烦,需要递归处理函数中的字段信息,同时检查是否符合下推条件,先简答处理) c.
+ * 如果条件中的column/value中的字段来自于子节点的函数查询,也不做下推
+ * 
+ * 几种场景: 1. where条件尽可能提前到叶子节点,同时提取出joinFilter 处理类型: JoinNode/QueryNode
+ * 注意点:JoinNode如果是outter节点,则不能继续下推
+ * 
+ * 如: tabl1.join(table2).query(
+ * "table1.id>5 && table2.id<10 && table1.name = table2.name") 优化成:
+ * table1.query("table1.id>5").join(table2.query("table2.id<10").on(
+ * "table1.name = table2.name")
+ * 
+ * 如: table1.join(table2).query("table1.id = table2.id")
+ * 优化成:table1.join(table2).on("table1.id = table2.id")
+ * 
+ * 2. join中的非字段列条件,比如column = 1的常量关系,提前到叶子节点 处理类型:JoinNode 注意点:
+ * 
+ * 如: tabl1.join(table2).on("table1.id>5&&table2.id<10") 优化成:
+ * table1.query("table1.id>5").join(table2.query("table2.id<10")) t但如果条件中包含
+ * 
+ * 3. join filter中的字段进行条件推导到左/右的叶子节点上,在第1和第2步优化中同时处理 处理类型:JoinNode
+ * 
+ * 如: table.join(table2).on(
+ * "table1.id = table2.id and table1.id>5 && table2.id<10") 优化成:table1.query(
+ * "table1.id>5 && table1.id<10").join(table2.query(
+ * "table2.id>5 && table2.id<10"))
+ */
+public class FilterPusher {
+
+	/**
+	 * 详细优化见类描述 {@linkplain FilterPusher}
+	 */
+	public static PlanNode optimize(PlanNode qtn) {
+		mergeJoinOnFilter(qtn);
+		qtn = pushJoinOnFilter(qtn);
+		qtn = pushFilter(qtn, new ArrayList());
+		return qtn;
+	}
+
+	/**
+	 * 将inner join中可合并的otheron条件合并到where
+	 * 
+	 * @param qtn
+	 * @return
+	 */
+	private static void mergeJoinOnFilter(PlanNode qtn) {
+		if (PlanUtil.isGlobalOrER(qtn))
+			return;
+		if (qtn.type().equals(PlanNodeType.JOIN) && ((JoinNode) qtn).isInnerJoin()) {
+			JoinNode jn = (JoinNode) qtn;
+			Item otherJoinOn = jn.getOtherJoinOnFilter();
+			jn.setOtherJoinOnFilter(null);
+			jn.query(FilterUtils.and(otherJoinOn, jn.getWhereFilter()));
+		}
+		for (PlanNode child : qtn.getChildren()) {
+			mergeJoinOnFilter(child);
+		}
+	}
+
+	private static PlanNode pushFilter(PlanNode qtn, List DNFNodeToPush) {
+		List subHavingList = new ArrayList();
+		for (Item filter : DNFNodeToPush) {
+			if (filter.withSumFunc) {
+				subHavingList.add(filter);
+			}
+		}
+		if (!subHavingList.isEmpty()) {
+			qtn.having(FilterUtils.and(qtn.getHavingFilter(), FilterUtils.and(subHavingList)));
+			DNFNodeToPush.removeAll(subHavingList);
+		}
+
+		// 如果是根节点,接收filter做为where条件,否则继续合并当前where条件,然后下推
+		if (qtn.getChildren().isEmpty() || PlanUtil.isGlobalOrER(qtn)) {
+			Item node = FilterUtils.and(DNFNodeToPush);
+			if (node != null) {
+				qtn.query(FilterUtils.and(qtn.getWhereFilter(), node));
+			}
+			return qtn;
+		}
+
+		Item filterInWhere = qtn.getWhereFilter();
+		if (filterInWhere != null) {
+			List splits = FilterUtils.splitFilter(filterInWhere);
+			qtn.query(null);
+			DNFNodeToPush.addAll(splits);
+		}
+
+		if (qtn.type() == PlanNodeType.QUERY) {
+			refreshPdFilters(qtn, DNFNodeToPush);
+			PlanNode child = pushFilter(qtn.getChild(), DNFNodeToPush);
+			((QueryNode) qtn).setChild(child);
+		} else if (qtn.type() == PlanNodeType.JOIN) {
+			JoinNode jn = (JoinNode) qtn;
+			List DNFNodetoPushToLeft = new LinkedList();
+			List DNFNodetoPushToRight = new LinkedList();
+			// @bug1531 filters copyed by on condition
+			List leftCopyedPushFilters = new LinkedList();
+			List rightCopyedPushFilters = new LinkedList();
+			List DNFNodeToCurrent = new LinkedList();
+
+			PlanUtil.findJoinKeysAndRemoveIt(DNFNodeToPush, jn);
+			for (Item filter : DNFNodeToPush) {
+				// ex. 1 = -1
+				if (filter.getReferTables().size() == 0) {
+					DNFNodetoPushToLeft.add(filter);
+					DNFNodetoPushToRight.add(filter);
+					continue;
+				}
+				if (PlanUtil.canPush(filter, jn.getLeftNode(), jn)) {
+					DNFNodetoPushToLeft.add(filter);
+				} else if (PlanUtil.canPush(filter, jn.getRightNode(), jn)) {
+					DNFNodetoPushToRight.add(filter);
+				} else {
+					DNFNodeToCurrent.add(filter);
+				}
+			}
+			// modify by czf where的所有的条件应该都可以被copy到where条件中下推
+			if (jn.isInnerJoin() || jn.isLeftOuterJoin() || jn.isRightOuterJoin()) {
+				// 将左条件的表达式,推导到join filter的右条件上
+				rightCopyedPushFilters
+						.addAll(copyFilterToJoinOnColumns(DNFNodetoPushToLeft, jn.getLeftKeys(), jn.getRightKeys()));
+
+				// 将右条件的表达式,推导到join filter的左条件上
+				leftCopyedPushFilters
+						.addAll(copyFilterToJoinOnColumns(DNFNodetoPushToRight, jn.getRightKeys(), jn.getLeftKeys()));
+			}
+
+			// 针对不能下推的,合并到当前的where
+			Item node = FilterUtils.and(DNFNodeToCurrent);
+			if (node != null) {
+				qtn.query(FilterUtils.and(qtn.getWhereFilter(), node));
+			}
+
+			if (jn.isInnerJoin() || jn.isLeftOuterJoin() || jn.isRightOuterJoin()) {
+				if (jn.isLeftOuterJoin()) {
+					// left join,把right join下推之后,还得把right join的条件留下来
+					jn.query(FilterUtils.and(qtn.getWhereFilter(), FilterUtils.and(DNFNodetoPushToRight)));
+				}
+				if (jn.isRightOuterJoin()) {
+					// right join,把right join下推之后,还得把left join的条件留下来
+					jn.query(FilterUtils.and(qtn.getWhereFilter(), FilterUtils.and(DNFNodetoPushToLeft)));
+				}
+				// 合并起来
+				DNFNodetoPushToRight.addAll(rightCopyedPushFilters);
+				DNFNodetoPushToLeft.addAll(leftCopyedPushFilters);
+				refreshPdFilters(jn, DNFNodetoPushToLeft);
+				refreshPdFilters(jn, DNFNodetoPushToRight);
+				jn.setLeftNode(pushFilter(jn.getLeftNode(), DNFNodetoPushToLeft));
+				jn.setRightNode(pushFilter(((JoinNode) qtn).getRightNode(), DNFNodetoPushToRight));
+			} else {
+				if (DNFNodeToPush != null && !DNFNodeToPush.isEmpty()) {
+					jn.query(FilterUtils.and(qtn.getWhereFilter(), FilterUtils.and(DNFNodeToPush)));
+				}
+			}
+			return jn;
+		} else if (qtn.type() == PlanNodeType.MERGE) {
+			// union语句的where条件可以下推,但是要替换成相应的child节点的过滤条件
+
+			Item node = FilterUtils.and(DNFNodeToPush);
+			if (node != null) {
+				qtn.query(FilterUtils.and(qtn.getWhereFilter(), node));
+			}
+			Item mergeWhere = qtn.getWhereFilter();
+			// 加速优化,将merge的条件挨个下推
+			qtn.query(null);
+			List pushFilters = PlanUtil.getPushItemsToUnionChild((MergeNode) qtn, mergeWhere,
+					((MergeNode) qtn).getColIndexs());
+			List childs = qtn.getChildren();
+			for (int index = 0; index < childs.size(); index++) {
+				PlanNode child = childs.get(index);
+				if (pushFilters != null) {
+					Item pushFilter = pushFilters.get(index);
+					child.query(FilterUtils.and(child.getWhereFilter(), pushFilter));
+				}
+				FilterPusher.optimize(child);
+			}
+			return qtn;
+		}
+
+		return qtn;
+	}
+
+	/**
+	 * inner join的other join on在FilterPre时会被优化成where,只有left join有这个可能性 Left
+	 * join时, select * from t1 left jion t2 on t1.id=t2.id and t1.id = 10 and
+	 * t2.name = 'aaa' 可以将t2.id=10和t2.name='aaa'进行下推
+	 * 
+	 */
+	private static PlanNode pushJoinOnFilter(PlanNode qtn) {
+		if (PlanUtil.isGlobalOrER(qtn))
+			return qtn;
+		if (qtn.type().equals(PlanNodeType.JOIN)) {
+			JoinNode jn = (JoinNode) qtn;
+			Item otherJoinOn = jn.getOtherJoinOnFilter();
+			if (jn.isLeftOuterJoin() && otherJoinOn != null) {
+				List pushToRightNode = new ArrayList();
+				List splitedFilters = FilterUtils.splitFilter(otherJoinOn);
+				for (Item filter : splitedFilters) {
+					if (filter.getReferTables().isEmpty())
+						pushToRightNode.add(filter);
+					else if (PlanUtil.canPush(filter, jn.getRightNode(), jn))
+						pushToRightNode.add(filter);
+					else if (PlanUtil.canPush(filter, jn.getLeftNode(), jn)) {
+						Item copyedFilter = copyFilterToJoinOnColumns(filter, jn.getRightKeys(), jn.getLeftKeys());
+						if (copyedFilter != null)
+							pushToRightNode.add(copyedFilter);
+					} else
+						continue;
+				}
+				if (!pushToRightNode.isEmpty()) {
+					splitedFilters.removeAll(pushToRightNode);
+					Item newOtherJoinOn = FilterUtils.and(splitedFilters);
+					jn.setOtherJoinOnFilter(newOtherJoinOn);
+					refreshPdFilters(jn, pushToRightNode);
+					List subHavingList = new ArrayList();
+					List subWhereList = new ArrayList();
+					for (Item filter : pushToRightNode) {
+						if (filter.withSumFunc) {
+							subHavingList.add(filter);
+						} else {
+							subWhereList.add(filter);
+						}
+					}
+					Item subHaving = FilterUtils.and(subHavingList);
+					Item subWhere = FilterUtils.and(subWhereList);
+					jn.getRightNode().having(FilterUtils.and(jn.getRightNode().getHavingFilter(), subHaving));
+					jn.getRightNode().setWhereFilter(FilterUtils.and(jn.getRightNode().getWhereFilter(), subWhere));
+				}
+			}
+		}
+		for (PlanNode child : qtn.getChildren())
+			pushJoinOnFilter(child);
+		return qtn;
+	}
+
+	/**
+	 * 将连接列上的约束复制到目标节点内
+	 * 
+	 * @param DNF
+	 *            要复制的DNF filter
+	 * @param other
+	 *            要复制的目标节点
+	 * @param qnColumns
+	 *            源节点的join字段
+	 * @param otherColumns
+	 *            目标节点的join字段
+	 * @throws QueryException
+	 */
+	private static List copyFilterToJoinOnColumns(List DNF, List qnColumns, List otherColumns) {
+		List newIFilterToPush = new LinkedList();
+		for (Item filter : DNF) {
+			Item newFilter = copyFilterToJoinOnColumns(filter, qnColumns, otherColumns);
+			if (newFilter != null)
+				newIFilterToPush.add(newFilter);
+		}
+		return newIFilterToPush;
+	}
+
+	private static Item copyFilterToJoinOnColumns(Item filter, List qnColumns, List otherColumns) {
+		if (filter.type().equals(ItemType.FUNC_ITEM) || filter.type().equals(ItemType.COND_ITEM)) {
+			ItemFunc newFilter = replaceFunctionArg((ItemFunc) filter, qnColumns, otherColumns);
+			if (newFilter != null)
+				return newFilter;
+		}
+		return null;
+	}
+
+	/**
+	 * 将连接列上的约束复制到目标节点内
+	 * 
+	 * @param DNF
+	 *            要复制的DNF filter
+	 * @param other
+	 *            要复制的目标节点
+	 * @param qnColumns
+	 *            源节点的join字段
+	 * @param otherColumns
+	 *            目标节点的join字段
+	 * @throws QueryException
+	 */
+	private static void refreshPdFilters(PlanNode qtn, List filters) {
+		for (int index = 0; index < filters.size(); index++) {
+			Item toPsFilter = filters.get(index);
+			Item pdFilter = PlanUtil.pushDownItem(qtn, toPsFilter);
+			filters.set(index, pdFilter);
+		}
+	}
+
+	/**
+	 * 将function中的涉及到c1的参数替换成c2
+	 * 
+	 * @param f
+	 * @param sels1
+	 * @param sels2
+	 * @return 如果f中还存在非sels1的selectable,返回null
+	 */
+	public static ItemFunc replaceFunctionArg(ItemFunc f, List sels1, List sels2) {
+		ItemFunc ret = (ItemFunc) f.cloneStruct();
+		for (int index = 0; index < ret.getArgCount(); index++) {
+			Item arg = ret.arguments().get(index);
+			if (arg instanceof ItemFunc) {
+				ItemFunc newfArg = replaceFunctionArg((ItemFunc) arg, sels1, sels2);
+				if (newfArg == null)
+					return null;
+				else
+					ret.arguments().set(index, newfArg);
+			} else if (arg instanceof ItemField) {
+				int tmpIndex = sels1.indexOf(arg);
+				if (tmpIndex < 0) {
+					return null;
+				} else {
+					Item newArg = sels2.get(tmpIndex);
+					ret.arguments().set(index, newArg.cloneStruct());
+				}
+			} else {
+				// do nothing;
+			}
+		}
+		ret.setPushDownName(null);
+		PlanUtil.refreshReferTables(ret);
+		return ret;
+	}
+
+}
diff --git a/src/main/java/io/mycat/plan/optimizer/JoinPreProcessor.java b/src/main/java/io/mycat/plan/optimizer/JoinPreProcessor.java
new file mode 100644
index 000000000..5ff1ffaae
--- /dev/null
+++ b/src/main/java/io/mycat/plan/optimizer/JoinPreProcessor.java
@@ -0,0 +1,34 @@
+package io.mycat.plan.optimizer;
+
+import io.mycat.plan.PlanNode;
+import io.mycat.plan.node.JoinNode;
+
+public class JoinPreProcessor {
+
+	public static PlanNode optimize(PlanNode qtn) {
+		qtn = findAndChangeRightJoinToLeftJoin(qtn);
+		return qtn;
+	}
+
+	/**
+	 * 会遍历所有节点将right join的左右节点进行调换,转换成left join.
+	 * 
+	 * 
+	 * 比如 A right join B on A.id = B.id
+	 * 转化为 B left join B on A.id = B.id
+	 * 
+ */ + private static PlanNode findAndChangeRightJoinToLeftJoin(PlanNode qtn) { + for (PlanNode child : qtn.getChildren()) { + findAndChangeRightJoinToLeftJoin((PlanNode) child); + } + + if (qtn instanceof JoinNode && ((JoinNode) qtn).isRightOuterJoin()) { + JoinNode jn = (JoinNode) qtn; + jn.exchangeLeftAndRight(); + } + + return qtn; + } + +} diff --git a/src/main/java/io/mycat/plan/optimizer/LimitPusher.java b/src/main/java/io/mycat/plan/optimizer/LimitPusher.java new file mode 100644 index 000000000..2aaf0a817 --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/LimitPusher.java @@ -0,0 +1,47 @@ +package io.mycat.plan.optimizer; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.node.QueryNode; + +public class LimitPusher { + + public static PlanNode optimize(PlanNode qtn) { + qtn = findChild(qtn); + return qtn; + } + + private static PlanNode findChild(PlanNode qtn) { + if (qtn instanceof MergeNode) { + // limit优化 + // union直接把limit下发到各个子节点 + // union all下发各个子节点并且加上distinct + MergeNode node = (MergeNode) qtn; + long limitFrom = node.getLimitFrom(); + long limitTo = node.getLimitTo(); + if (limitFrom != -1 && limitTo != -1) { + for (PlanNode child : node.getChildren()) { + pushLimit(child, limitFrom, limitTo, node.isUnion()); + } + } + + } else if ((qtn instanceof JoinNode) || (qtn instanceof QueryNode)) { + for (PlanNode child : qtn.getChildren()) { + findChild(child); + } + } + return qtn; + } + + private static void pushLimit(PlanNode node, long limitFrom, long limitTo, boolean isUnion) { + if (isUnion) { + node.setDistinct(true); + } + if (node.getLimitFrom() == -1 && node.getLimitTo() == -1) { + node.setLimitFrom(0); + node.setLimitTo(limitFrom + limitTo); + } + } + +} diff --git a/src/main/java/io/mycat/plan/optimizer/MyOptimizer.java b/src/main/java/io/mycat/plan/optimizer/MyOptimizer.java new file mode 100644 index 000000000..9d06ea938 --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/MyOptimizer.java @@ -0,0 +1,55 @@ +package io.mycat.plan.optimizer; + +import org.apache.log4j.Logger; + +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.util.PlanUtil; + +public class MyOptimizer { + //TODO:YHQ CHECK LOGIC + public static PlanNode optimize(String schema, PlanNode node) { + + try { + // 预先处理子查询 + node = SubQueryPreProcessor.optimize(node); + if (node.isExsitView() || PlanUtil.existShardTable(node)) { + + // 子查询优化 + node = SubQueryProcessor.optimize(node); + + node = JoinPreProcessor.optimize(node); + + // 预处理filter,比如过滤永假式/永真式 + node = FilterPreProcessor.optimize(node); + //TODO 疑似错误 +// // // 将约束条件推向叶节点 +// node = FilterJoinColumnPusher.optimize(node); + + //TODO +// node = JoinERProcessor.optimize(node); +// +// node = GlobalTableProcessor.optimize(node); + + node = FilterPusher.optimize(node); + + + node = OrderByPusher.optimize(node); + + node = LimitPusher.optimize(node); + + node = SelectedProcessor.optimize(node); + + //TODO +// boolean useJoinStrategy = ProxyServer.getInstance().getConfig().getSystem().isUseJoinStrategy(); +// if (useJoinStrategy){ +// node = JoinStrategyProcessor.optimize(node); +// } + } + return node; + } catch (MySQLOutPutException e) { + Logger.getLogger(MyOptimizer.class).error(node.toString(), e); + throw e; + } + } +} diff --git a/src/main/java/io/mycat/plan/optimizer/OrderByPusher.java b/src/main/java/io/mycat/plan/optimizer/OrderByPusher.java new file mode 100644 index 000000000..24825085f --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/OrderByPusher.java @@ -0,0 +1,264 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; + +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.util.PlanUtil; + +/** + * 将merge/join中的order by条件下推,包括隐式的order by条件,比如将groupBy转化为orderBy + * + * @author chenzifei 2015-07-10 + */ +public class OrderByPusher { + + /** + * 详细优化见类描述 + */ + public static PlanNode optimize(PlanNode qtn) { + qtn = preOrderByPusher(qtn); + qtn = pushOrderBy(qtn); + return qtn; + } + + private static PlanNode preOrderByPusher(PlanNode qtn) { + if (PlanUtil.isGlobalOrER(qtn)) { + return qtn; + } + for (PlanNode child : qtn.getChildren()) { + preOrderByPusher(child); + } + buildImplicitOrderBys(qtn); + return qtn; + } + + private static PlanNode pushOrderBy(PlanNode qtn) { + if (PlanUtil.isGlobalOrER(qtn)) + return qtn; + if (qtn.type() == PlanNodeType.MERGE) { + // note:目前不做mergenode的处理 + for (PlanNode child : qtn.getChildren()) { + pushOrderBy(child); + } + return qtn; + } else if (qtn.type() == PlanNodeType.JOIN) { + JoinNode join = (JoinNode) qtn; + + // sort merge join中的order by,需要推到左/右节点 + List implicitOrders = getOrderBysGroupFirst(join); + boolean canMatch = getJoinColumnOrders(join.getJoinFilter(), join.getLeftJoinOnOrders(), + join.getRightJoinOnOrders(), implicitOrders); + boolean leftOrderPushSuc = false; + boolean rightOrderPushSuc = false; + if (canMatch) { + // match的时候,先推join列,在推全部 + leftOrderPushSuc = tryPushOrderToChild(join, join.getLeftJoinOnOrders(), join.getLeftNode()); + if (leftOrderPushSuc) { + tryPushOrderToChild(join, implicitOrders, join.getLeftNode()); + } + rightOrderPushSuc = tryPushOrderToChild(join, join.getRightJoinOnOrders(), join.getRightNode()); + if (rightOrderPushSuc) { + tryPushOrderToChild(join, implicitOrders, join.getRightNode()); + } + } else { + leftOrderPushSuc = tryPushOrderToChild(join, join.getLeftJoinOnOrders(), join.getLeftNode()); + rightOrderPushSuc = tryPushOrderToChild(join, join.getRightJoinOnOrders(), join.getRightNode()); + } + join.setLeftOrderMatch(leftOrderPushSuc); + join.setRightOrderMatch(rightOrderPushSuc); + } else if (qtn.type() == PlanNodeType.QUERY) { + // 可以将order推到子查询 + QueryNode query = (QueryNode) qtn; + tryPushOrderToChild(query, getOrderBysGroupFirst(query), query.getChild()); + } + + for (PlanNode child : ((PlanNode) qtn).getChildren()) { + if (child instanceof PlanNode) { + pushOrderBy((PlanNode) child); + } + } + + return qtn; + } + + /** + * 生成joinOnFilters,如果能够调整joinOn的orderBy的顺序并且和implicitOrders顺序吻合,返回true + * + * @param joinOnCondition + * in + * @param leftOnOrders + * out + * @param rightOnOrders + * out + * @param implicitOrders + * in + * @return + */ + private static boolean getJoinColumnOrders(List joinOnFilters, List leftOnOrders, + List rightOnOrders, List implicitOrders) { + List leftOnSels = new ArrayList(); + List rightOnSels = new ArrayList(); + for (ItemFuncEqual bf : joinOnFilters) { + leftOnSels.add(bf.arguments().get(0)); + rightOnSels.add(bf.arguments().get(1)); + } + // 是否可以通过调整on的顺序使得on的orderbys和implicitorders相同 + boolean canMatch = false; + if (implicitOrders.size() < leftOnSels.size()) + canMatch = false; + else { + Map foundOnIndexs = new LinkedHashMap(); + for (Order orderby : implicitOrders) { + Item orderSel = orderby.getItem(); + int index = -1; + if ((index = leftOnSels.indexOf(orderSel)) >= 0) { + foundOnIndexs.put(index, orderby.getSortOrder()); + } else if ((index = rightOnSels.indexOf(orderSel)) >= 0) { + foundOnIndexs.put(index, orderby.getSortOrder()); + } else { + // 既不属于leftOn,也不属于rightOn,结束 + break; + } + } + if (foundOnIndexs.size() == leftOnSels.size()) { + canMatch = true; + for (Integer foundOnIndex : foundOnIndexs.keySet()) { + SQLOrderingSpecification sortOrder = foundOnIndexs.get(foundOnIndex); + Item leftOn = leftOnSels.get(foundOnIndex); + Item rightOn = rightOnSels.get(foundOnIndex); + // add lefton order + Order leftOnOrder = new Order(leftOn, sortOrder); + leftOnOrders.add(leftOnOrder); + // add righton order + Order rightOnOrder = new Order(rightOn, sortOrder); + rightOnOrders.add(rightOnOrder); + } + return canMatch; + } + } + // can not match + for (int index = 0; index < leftOnSels.size(); index++) { + SQLOrderingSpecification sortOrder = SQLOrderingSpecification.ASC; + Item leftOn = leftOnSels.get(index); + Item rightOn = rightOnSels.get(index); + // add lefton order + Order leftOnOrder = new Order(leftOn, sortOrder); + leftOnOrders.add(leftOnOrder); + // add righton order + Order rightOnOrder = new Order(rightOn, sortOrder); + rightOnOrders.add(rightOnOrder); + } + return canMatch; + } + + /** + * 尝试将orders下推到child中去 + * + * @param pOrders + * @param child + * @return 最终失败,child的orders和pOrders不吻合 + */ + private static boolean tryPushOrderToChild(PlanNode parent, List pOrders, PlanNode child) { + for (Order pOrder : pOrders) { + if (!PlanUtil.canPush(pOrder.getItem(), child, parent)) + return false; + } + List pushedOrders = PlanUtil.getPushDownOrders(parent, pOrders); + List childImplicitOrders = child.getOrderBys(); + boolean childOrderContains = PlanUtil.orderContains(childImplicitOrders, pushedOrders); + if (child.getLimitTo() == -1) { + // 如果child的orders多余parent的orders,保留child的,否则,以parent的为准 + if (!childOrderContains) + child.setOrderBys(pushedOrders); + return true; + } else { + // 存在limit时,不能下推order by + return childOrderContains; + } + } + + /** + * 生成该node的最终数据的orderby. 例如不存在orderby属性,但是存在groupby属性时,手动添加上orderby属性 + */ + private static void buildImplicitOrderBys(PlanNode node) { + // 如果用户没指定order by,则显示index的order by + List newOrderBys = new ArrayList(); + if (!node.getOrderBys().isEmpty()) { + if (!node.getGroupBys().isEmpty()) { + // 首先以order by的顺序,查找group by中对应的字段 + for (Order orderBy : node.getOrderBys()) { + if (findOrderByByColumn(node.getGroupBys(), orderBy.getItem()) != null) { + newOrderBys.add(orderBy.copy()); + } else { + if (newOrderBys.size() == node.getGroupBys().size()) { + // 说明出现order by包含了整个group by + node.setGroupBys(newOrderBys);// 将group by重置一下顺序 + } else { + return; + } + } + } + for (Order groupBy : node.getGroupBys()) { + if (findOrderByByColumn(newOrderBys, groupBy.getItem()) == null) { + // 添加下order by中没有的字段 + newOrderBys.add(groupBy.copy()); + } + } + node.setGroupBys(newOrderBys);// 将group by重置一下顺序 + node.setOrderBys(newOrderBys); + } + } else { + // 没有orderby,复制group by + if (!node.getGroupBys().isEmpty()) { + for (Order orderBy : node.getGroupBys()) { + newOrderBys.add(orderBy.copy()); + } + } + node.setOrderBys(newOrderBys); + } + } + + /** + * 尝试查找一个同名的排序字段 + */ + private static Order findOrderByByColumn(List orderbys, Item column) { + for (Order order : orderbys) { + if (order.getItem().equals(column)) { + return order; + } + } + + return null; + } + + /** + * 以groupby为优先的order by list + * + * @param preOrderPushedNode + * @return + */ + private static List getOrderBysGroupFirst(PlanNode node) { + List groupBys = node.getGroupBys(); + List orderBys = node.getOrderBys(); + if (groupBys.isEmpty()) { + return orderBys; + } else if (orderBys.isEmpty()) { + return groupBys; + } else if (PlanUtil.orderContains(orderBys, groupBys)) { + return orderBys; + } else + return groupBys; + } + +} diff --git a/src/main/java/io/mycat/plan/optimizer/SelectedProcessor.java b/src/main/java/io/mycat/plan/optimizer/SelectedProcessor.java new file mode 100644 index 000000000..82171b0f4 --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/SelectedProcessor.java @@ -0,0 +1,220 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.util.PlanUtil; + +/** + * 尽量使得select项在传输过程中最少 + * + * @author chenzifei + * + */ +public class SelectedProcessor { + public static PlanNode optimize(PlanNode qtn) { + qtn = pushSelected(qtn, new HashSet()); + return qtn; + } + + /** + * 将topushColumns列下推到当前节点上 父节点的selectedrefered为A.id,A.name,B.id,B.name, + * name子节点A只要提供父节点的selectcolumn为 A.id,A.name即可 + * + * @param qtn + * @param toPushColumns + * @return + */ + private static PlanNode pushSelected(PlanNode qtn, Collection toPushColumns) { + boolean isPushDownNode = false; + if (PlanUtil.isGlobalOrER(qtn)) { + // 这边应该循环遍历它的child然后每个进行buildColumnRefers,先不处理了 + List selList = qtn.getColumnsSelected(); + for (Item pdSel : toPushColumns) { + if (!selList.contains(pdSel)) { + selList.add(pdSel); + } + } + isPushDownNode = true; + qtn.setUpRefers(isPushDownNode); + return qtn; + } + isPushDownNode = (qtn.type() == PlanNodeType.TABLE || qtn.type() == PlanNodeType.NONAME); + if (qtn.type() == PlanNodeType.MERGE) { + return mergePushSelected((MergeNode) qtn, toPushColumns); + } else { + if (toPushColumns.isEmpty()) { + qtn.setUpRefers(isPushDownNode); + } else if (qtn.isDistinct()) { + List selList = qtn.getColumnsSelected(); + for (Item pdSel : toPushColumns) { + if (!selList.contains(pdSel)) { + selList.add(pdSel); + } + } + qtn.setUpRefers(isPushDownNode); + } else { + List selList = qtn.getColumnsSelected(); + selList.clear(); + boolean existSum = false; + for (Item toPush : toPushColumns) { + selList.add(toPush); + existSum |= toPush.type().equals(ItemType.SUM_FUNC_ITEM); + } + // @bug select sum(id) from (select id,sum(id) from t1) t + // 如果直接将id下推,少去了sum(id)则出错 + if (!existSum && qtn.sumFuncs.size() > 0) { + selList.add(qtn.sumFuncs.iterator().next()); + } + qtn.setUpRefers(isPushDownNode); + } + switch (qtn.type()) { + case NONAME: + return qtn; + case TABLE: + return qtn; + default: + for (PlanNode child : qtn.getChildren()) { + List referList = qtn.getColumnsReferedMap().get(child); + if (referList.isEmpty()) { + referList.add(new ItemInt(1)); + } + Collection pdRefers = getPushDownSel(qtn, child, referList); + pushSelected(child, pdRefers); + } + return qtn; + } + } + } + + private static Collection getPushDownSel(PlanNode parent, PlanNode child, List selList) { + // oldselectable->newselectbable + HashMap oldNewMap = new HashMap(); + HashMap oldKeyKeyMap = new HashMap(); + for (Item sel : selList) { + Item pdSel = oldNewMap.get(sel); + if (pdSel == null) { + pdSel = PlanUtil.pushDownItem(parent, sel); + oldNewMap.put(sel, pdSel); + oldKeyKeyMap.put(sel, sel); + } else { + Item sameKey = oldKeyKeyMap.get(sel); + sel.setPushDownName(sameKey.getPushDownName()); + } + } + return oldNewMap.values(); + } + + // union的push须区别于普通的push,toPushColumn.isEmpty时需保持merge的select属性不能被修改 + private static PlanNode mergePushSelected(MergeNode merge, Collection toPushColumns) { + if (toPushColumns.isEmpty() && merge.getOrderBys().isEmpty()) { + for (PlanNode child : merge.getChildren()) { + pushSelected(child, new HashSet()); + } + return merge; + } + boolean canOverload = mergeNodeChildsCheck(merge) && !toPushColumns.isEmpty(); + final Map colIndexs = merge.getColIndexs(); + List mergeSelects = null; + if (toPushColumns.isEmpty()) { + // 不修改merge的select属性 + mergeSelects = new ArrayList(); + merge.setComeInFields(mergeSelects); + mergeSelects.addAll(merge.getColumnsSelected()); + } else { + mergeSelects = merge.getColumnsSelected(); + } + if (canOverload) { + mergeSelects.clear(); + mergeSelects.addAll(toPushColumns); + } else { + for (Item toPush : toPushColumns) { + if (!mergeSelects.contains(toPush)) { + mergeSelects.add(toPush); + } + } + } + // 把order by添加进来 + for (Order orderby : merge.getOrderBys()) { + Item orderSel = orderby.getItem(); + mergePushOrderBy(orderSel, mergeSelects); + } + // 将mergeselect中的内容下推到child中去 + List> allChildPushs = new ArrayList>(toPushColumns.size()); + for (Item toPush : mergeSelects) { + // union的order by必须从selects中直接查找 + if (toPush.getPushDownName() == null && !toPush.type().equals(ItemType.FIELD_ITEM)) + toPush.setPushDownName(toPush.getItemName()); + List childPushs = PlanUtil.getPushItemsToUnionChild(merge, toPush, colIndexs); + allChildPushs.add(childPushs); + } + // 保证每个child的下推个数都是一致的 + for (int index = 0; index < merge.getChildren().size(); index++) { + List colSels = merge.getChildren().get(index).getColumnsSelected(); + colSels.clear(); + for (List childPushs : allChildPushs) { + colSels.add(childPushs.get(index)); + } + pushSelected(merge.getChildren().get(index), new HashSet()); + } + return merge; + } + + /** + * 检查merge下的subchild是否存在distinct或者聚合函数 + * + * @param merge + * @return + */ + private static boolean mergeNodeChildsCheck(MergeNode merge) { + for (PlanNode child : merge.getChildren()) { + boolean cdis = child.isDistinct(); + boolean bsum = child.sumFuncs.size() > 0; + if (cdis || bsum) + return false; + } + return true; + } + + private static void mergePushOrderBy(Item orderSel, List mergeSelects) { + if (orderSel instanceof ItemField) { + if (!mergeSelects.contains(orderSel)) + mergeSelects.add(orderSel); + } else if (orderSel instanceof ItemFunc) { + ItemFunc func = (ItemFunc) orderSel; + if (func.withSumFunc) { + for (int index = 0; index < func.getArgCount(); index++) { + Item arg = func.arguments().get(index); + mergePushOrderBy(arg, mergeSelects); + } + } else { + if (!mergeSelects.contains(func)) { + mergeSelects.add(func); + } + // union的order by必须从selects中直接查找 + func.setPushDownName(func.getItemName()); + } + } else if (orderSel instanceof ItemSum) { + ItemSum func = (ItemSum) orderSel; + for (int index = 0; index < func.getArgCount(); index++) { + Item arg = func.arguments().get(index); + mergePushOrderBy(arg, mergeSelects); + } + } + } + +} diff --git a/src/main/java/io/mycat/plan/optimizer/SubQueryPreProcessor.java b/src/main/java/io/mycat/plan/optimizer/SubQueryPreProcessor.java new file mode 100644 index 000000000..e2630e455 --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/SubQueryPreProcessor.java @@ -0,0 +1,194 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.common.item.function.operator.logic.ItemCondAnd; +import io.mycat.plan.common.item.function.operator.logic.ItemCondOr; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.plan.common.item.subquery.ItemInSubselect; +import io.mycat.plan.common.item.subquery.ItemSubselect; +import io.mycat.plan.node.JoinNode; + +public class SubQueryPreProcessor { + private final static String AUTONAME = "autosubgenrated0"; + private final static String AUTOALIAS = "autoalias_"; + + public static PlanNode optimize(PlanNode qtn) { + qtn = findComparisonsSubQueryToJoinNode(qtn); + return qtn; + } + + /** + * 处理下 = in的子查询转化为join + * http://dev.mysql.com/doc/refman/5.0/en/comparisons-using-subqueries.html + */ + private static PlanNode findComparisonsSubQueryToJoinNode(PlanNode qtn) { + for (int i = 0; i < qtn.getChildren().size(); i++) { + PlanNode child = qtn.getChildren().get(i); + qtn.getChildren().set(i, findComparisonsSubQueryToJoinNode((PlanNode) child)); + } + + SubQueryAndFilter find = new SubQueryAndFilter(); + find.query = qtn; + find.filter = null; + Item where = qtn.getWhereFilter(); + SubQueryAndFilter result = buildSubQuery(find, where); + if (result != find) { + // 如果出现filter,代表where条件中没有组合条件,只有单自查询的条件,直接替换即可 + result.query.query(result.filter); + qtn.query(null); + // 修改了result.filter哦归属,需要重新build + result.query.setUpFields(); + return result.query; + } else { + return qtn; // 没变化 + } + } + + private static class SubQueryAndFilter { + + PlanNode query; // 子查询可能会改变query节点为join node + Item filter; // 子查询带上来的filter + } + + private static SubQueryAndFilter buildSubQuery(SubQueryAndFilter qtn, Item filter) { + if (filter == null) + return qtn; + if (!filter.withSubQuery) { + qtn.filter = filter; + } else if (filter instanceof ItemCondOr) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support 'or' when condition subquery"); + } else if (filter instanceof ItemCondAnd) { + ItemCondAnd andFilter = (ItemCondAnd) filter; + for (int index = 0; index < andFilter.getArgCount(); index++) { + SubQueryAndFilter result = buildSubQuery(qtn, andFilter.arguments().get(index)); + if (result != qtn) { + if (result.filter == null) { + result.filter = new ItemInt(1); + } + andFilter.arguments().set(index, result.filter); + qtn = result; + } + } + qtn.filter = andFilter; + } else { + Item leftColumn; + PlanNode query; + boolean isNotIn = false; + if (filter instanceof ItemFuncEqual) { + ItemFuncEqual eqFilter = (ItemFuncEqual) filter; + Item arg0 = eqFilter.arguments().get(0); + Item arg1 = eqFilter.arguments().get(1); + boolean arg0IsSubQuery = arg0.type().equals(ItemType.SUBSELECT_ITEM); + boolean arg1IsSubQuery = arg1.type().equals(ItemType.SUBSELECT_ITEM); + if (arg0IsSubQuery && arg1IsSubQuery) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "left and right both condition subquery,not supported..."); + } + leftColumn = arg0IsSubQuery ? arg1 : arg0; + query = arg0IsSubQuery ? ((ItemSubselect) arg0).getPlanNode() : ((ItemSubselect) arg1).getPlanNode(); + } else if (filter instanceof ItemInSubselect) { + ItemInSubselect inSub = (ItemInSubselect) filter; + leftColumn = inSub.getLeftOprand(); + query = inSub.getPlanNode(); + isNotIn = inSub.isNeg(); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support subquery of:" + filter.type()); + } + if (StringUtils.isEmpty(query.getAlias())) + query.alias(AUTOALIAS + query.getPureName()); + if (query.getColumnsSelected().size() != 1) + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "only support subquery of one column"); + query.setSubQuery(true).setDistinct(true); + List newSelects = qtn.query.getColumnsSelected(); + SubQueryAndFilter result = new SubQueryAndFilter(); + Item rightColumn = query.getColumnsSelected().get(0); + qtn.query.setColumnsSelected(new ArrayList()); + String rightJoinName = rightColumn.getAlias(); + // add 聚合函数类型的支持 + if (StringUtils.isEmpty(rightJoinName)) { + if (rightColumn instanceof ItemField) { + rightJoinName = rightColumn.getItemName(); + } else { + rightColumn.setAlias(AUTONAME); + rightJoinName = AUTONAME; + } + } + + ItemField rightJoinColumn = new ItemField(null, query.getAlias(), rightJoinName); + // left column的table名称需要改变 + result.query = new JoinNode(qtn.query, query).addJoinKeys(leftColumn, rightJoinColumn); + // 保留原sql至新的join节点 + result.query.setSql(qtn.query.getSql()); + qtn.query.setSql(null); + result.query.select(newSelects); + if (!qtn.query.getOrderBys().isEmpty()) { + List orderBys = new ArrayList(); + orderBys.addAll(qtn.query.getOrderBys()); + result.query.setOrderBys(orderBys); + qtn.query.getOrderBys().clear(); + } + if (!qtn.query.getGroupBys().isEmpty()) { + List groupBys = new ArrayList(); + groupBys.addAll(qtn.query.getGroupBys()); + result.query.setGroupBys(groupBys); + qtn.query.getGroupBys().clear(); + } + if (qtn.query.getLimitFrom() != -1) { + result.query.setLimitFrom(qtn.query.getLimitFrom()); + qtn.query.setLimitFrom(-1); + } + if (qtn.query.getLimitTo() != -1) { + result.query.setLimitTo(qtn.query.getLimitTo()); + qtn.query.setLimitTo(-1); + } + if (isNotIn) { + ((JoinNode) result.query).setLeftOuterJoin().setNotIn(true); + } else { + result.filter = null; + } + if (qtn.query.getAlias() == null && qtn.query.getSubAlias() == null) { + result.query.setAlias(qtn.query.getPureName()); + } else { + String queryAlias = qtn.query.getAlias(); + if(queryAlias == null) + queryAlias = qtn.query.getSubAlias(); + result.query.setAlias(queryAlias); + refreshItemTable(leftColumn, queryAlias); + } + result.query.setUpFields(); + return result; + } + return qtn; + } + + private static void refreshItemTable(Item item, String newTable) { + if (item instanceof ItemField) { + ((ItemField) item).tableName = newTable; + } else if (item instanceof ItemFunc) { + ItemFunc func = (ItemFunc) item; + for (int index = 0; index < func.getArgCount(); index++) { + refreshItemTable(func.arguments().get(index), newTable); + } + } else if (item instanceof ItemSum) { + ItemSum func = (ItemSum) item; + for (int index = 0; index < func.getArgCount(); index++) { + refreshItemTable(func.arguments().get(index), newTable); + } + } + } + +} diff --git a/src/main/java/io/mycat/plan/optimizer/SubQueryProcessor.java b/src/main/java/io/mycat/plan/optimizer/SubQueryProcessor.java new file mode 100644 index 000000000..c365a623c --- /dev/null +++ b/src/main/java/io/mycat/plan/optimizer/SubQueryProcessor.java @@ -0,0 +1,173 @@ +package io.mycat.plan.optimizer; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.ptr.BoolPtr; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.view.ViewUtil; +import io.mycat.plan.util.FilterUtils; +import io.mycat.plan.util.PlanUtil; + +/** + * 将View进行处理,将虚拟的merge node变成其它的三种类型的node + * + * @author chenzifei + * + */ +public class SubQueryProcessor { + + public static PlanNode optimize(PlanNode qtn) { + BoolPtr merged = new BoolPtr(false); + qtn = tryTransformerQuery(qtn, merged); + if (merged.get()) + qtn.setUpFields(); + return qtn; + } + + /** + * 尝试找出qtn中的querynode,并将他们转换为对应的三种node + * + * @param qtn + * @return + */ + private static PlanNode tryTransformerQuery(PlanNode qtn, BoolPtr boolptr) { + boolean childMerged = false; + for (int index = 0; index < qtn.getChildren().size(); index++) { + PlanNode child = qtn.getChildren().get(index); + BoolPtr cbptr = new BoolPtr(false); + PlanNode newChild = tryTransformerQuery(child, cbptr); + if (cbptr.get()) + childMerged = true; + qtn.getChildren().set(index, newChild); + } + if (childMerged) + qtn.setUpFields(); + if (qtn.type() == PlanNodeType.QUERY) { + qtn = transformerQuery((QueryNode) qtn, boolptr); + } + return qtn; + } + + /** + * 转换一个querynode + * + * @param query + * @return + */ + private static PlanNode transformerQuery(QueryNode query, BoolPtr boolptr) { + boolean canBeMerged = ViewUtil.canBeMerged(query.getChild()); + if (canBeMerged) { + // 需要将viewnode的属性merge到view的child,从而实现优化 + PlanNode newNode = mergeNode(query, query.getChild()); + boolptr.set(true); + return newNode; + } else { + return query; + } + } + + /** + * 将parent的属性merge到child,并返回新的child前提是child是canBeMerged + * + * @param parent + * @param child + * @return + */ + private static PlanNode mergeNode(PlanNode parent, PlanNode child) { + List newSels = mergeSelect(parent, child); + mergeWhere(parent, child); + mergeGroupBy(parent, child); + mergeHaving(parent, child); + mergeOrderBy(parent, child); + mergeLimit(parent, child); + child.setColumnsSelected(newSels); + if (!StringUtils.isEmpty(parent.getAlias())) + child.setAlias(parent.getAlias()); + else if(!StringUtils.isEmpty(parent.getSubAlias())) + child.setAlias(parent.getSubAlias()); + child.setSubQuery(parent.isSubQuery()); + child.setParent(parent.getParent()); + return child; + } + + /** + * view v_t1 as select id+1 idd from t1 tt1 order by idd select view + * sql:select idd + 1 from v_t1 ==> select (id+1) + 1 from t1 tt1 order by + * id+1 + * + * @notice 交给Mysqlvisitor执行 + * @return 返回child应该保留的新的select信息 + */ + + private static List mergeSelect(PlanNode parent, PlanNode child) { + List pSels = parent.getColumnsSelected(); + List cNewSels = new ArrayList(); + for (Item pSel : pSels) { + Item pSel0 = PlanUtil.pushDownItem(parent, pSel, true); + String selName = pSel.getAlias(); + if (StringUtils.isEmpty(selName)) { + selName = pSel.getItemName(); + // 下推时,父节点是函数,且函数无别名,mysql不允许select func() as func()这种 + if (pSel.type() == ItemType.FUNC_ITEM || pSel.type() == ItemType.COND_ITEM + || pSel.type() == ItemType.SUM_FUNC_ITEM) + selName = Item.FNAF + selName; + } + pSel0.setAlias(selName); + cNewSels.add(pSel0); + } + return cNewSels; + } + + private static void mergeWhere(PlanNode parent, PlanNode child) { + Item pWhere = parent.getWhereFilter(); + Item pWhere0 = PlanUtil.pushDownItem(parent, pWhere, true); + Item mWhere = FilterUtils.and(pWhere0, child.getWhereFilter()); + child.setWhereFilter(mWhere); + } + + private static void mergeGroupBy(PlanNode parent, PlanNode child) { + List pGroups = parent.getGroupBys(); + List cGroups = new ArrayList(); + for (Order pGroup : pGroups) { + Item col = pGroup.getItem(); + Item col0 = PlanUtil.pushDownItem(parent, col); + Order pGroup0 = new Order(col0, pGroup.getSortOrder()); + cGroups.add(pGroup0); + } + child.setGroupBys(cGroups); + } + + private static void mergeHaving(PlanNode parent, PlanNode child) { + Item pHaving = parent.getHavingFilter(); + Item pHaving0 = PlanUtil.pushDownItem(parent, pHaving, true); + Item mHaving = FilterUtils.and(pHaving0, child.getHavingFilter()); + child.having(mHaving); + } + + private static void mergeOrderBy(PlanNode parent, PlanNode child) { + List pOrders = parent.getOrderBys(); + List cOrders = new ArrayList(); + for (Order pOrder : pOrders) { + Item col = pOrder.getItem(); + Item col0 = PlanUtil.pushDownItem(parent, col, true); + Order pOrder0 = new Order(col0, pOrder.getSortOrder()); + cOrders.add(pOrder0); + } + if (cOrders.size() > 0) + child.setOrderBys(cOrders); + } + + private static void mergeLimit(PlanNode parent, PlanNode child) { + child.setLimitFrom(parent.getLimitFrom()); + child.setLimitTo(parent.getLimitTo()); + } + +} diff --git a/src/main/java/io/mycat/plan/util/ExpressionUtil.java b/src/main/java/io/mycat/plan/util/ExpressionUtil.java new file mode 100644 index 000000000..9290d2850 --- /dev/null +++ b/src/main/java/io/mycat/plan/util/ExpressionUtil.java @@ -0,0 +1,141 @@ +package io.mycat.plan.util; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; + +public final class ExpressionUtil { + + /** + * convert Expression to DNF Expression + * + * @param expr + * @return + */ + public static SQLExpr toDNF(SQLExpr expr) { + if (expr == null) + return null; + + while (!isDNF(expr)) { + SQLBinaryOpExpr binOpExpr= (SQLBinaryOpExpr)expr; + if (binOpExpr.getOperator()==SQLBinaryOperator.BooleanOr) { + expr = expandOrExpression(binOpExpr); + } else if (binOpExpr.getOperator()==SQLBinaryOperator.BooleanAnd) { + expr = expandAndExpression(binOpExpr); + } + } + return expr; + } + + + /** + * (A OR B) AND C OR B OR (E OR F) AND G = ((A AND C ) OR (A AND B)) OR B OR + * ((E AND G) OR (F AND G)) + * + * @param expr + * @return + */ + private static SQLBinaryOpExpr expandOrExpression(SQLBinaryOpExpr expr) { + SQLExpr left = expr.getLeft(); + SQLExpr right = expr.getRight(); + SQLExpr leftOp = toDNF(left); + SQLExpr rightOp = toDNF(right); + return new SQLBinaryOpExpr(leftOp, SQLBinaryOperator.BooleanOr, rightOp); + } + + /** + * (A or B) and C and (D or E) = ((A and C and D) or (B and C and D)) or ((A + * and C and E) or (B and C and E)) + * + * @param expr + * @return + */ + private static SQLBinaryOpExpr expandAndExpression(SQLBinaryOpExpr expr) { + SQLExpr left = expr.getLeft(); + SQLExpr right = expr.getRight(); + if (!isLogicalExpression(left) && !isLogicalExpression(right)) { + return expr; + } + + SQLBinaryOpExpr leftOp = (SQLBinaryOpExpr)toDNF(left); + SQLBinaryOpExpr rightOp = (SQLBinaryOpExpr)toDNF(right); + SQLBinaryOpExpr dnfExpr = null; + if(leftOp.getOperator()==SQLBinaryOperator.BooleanOr){ + SQLExpr subLeft = leftOp.getLeft(); + SQLBinaryOpExpr newLeft = new SQLBinaryOpExpr(subLeft, SQLBinaryOperator.BooleanAnd, rightOp); + SQLExpr subright = leftOp.getRight(); + SQLBinaryOpExpr newRight = new SQLBinaryOpExpr(subright, SQLBinaryOperator.BooleanAnd, rightOp); + SQLBinaryOpExpr logicOr = new SQLBinaryOpExpr(newLeft, SQLBinaryOperator.BooleanOr, newRight); + dnfExpr = expandOrExpression(logicOr); + }else if(rightOp.getOperator()==SQLBinaryOperator.BooleanOr){ + SQLExpr subLeft = rightOp.getLeft(); + SQLBinaryOpExpr newLeft = new SQLBinaryOpExpr(leftOp, SQLBinaryOperator.BooleanAnd, subLeft); + SQLExpr subright = rightOp.getRight(); + SQLBinaryOpExpr newRight = new SQLBinaryOpExpr(leftOp, SQLBinaryOperator.BooleanAnd, subright); + SQLBinaryOpExpr logicOr = new SQLBinaryOpExpr(newLeft, SQLBinaryOperator.BooleanOr, newRight); + dnfExpr = expandOrExpression(logicOr); + }else{ + SQLBinaryOpExpr logicAnd = new SQLBinaryOpExpr(leftOp,SQLBinaryOperator.BooleanAnd,rightOp); + dnfExpr = logicAnd; + } + return dnfExpr; + } + + /** + * 非严格DNF检查,允许出现 (A and B) and C + * + * @param expr + * @return + */ + public static boolean isDNF(SQLExpr expr) { + if (!isLogicalExpression(expr)) { + return true; + } + SQLBinaryOpExpr binOpExpr= (SQLBinaryOpExpr)expr; + SQLExpr left = binOpExpr.getLeft(); + SQLExpr right = binOpExpr.getRight(); + if (binOpExpr.getOperator() == SQLBinaryOperator.BooleanAnd) { + boolean isAllNonLogicExpr = true; + if(left instanceof SQLBinaryOpExpr){ + SQLBinaryOpExpr leftBinaryOp = (SQLBinaryOpExpr) left; + if (leftBinaryOp.getOperator() == SQLBinaryOperator.BooleanOr) { + return false; + } + if (isLogicalExpression(leftBinaryOp)) { + isAllNonLogicExpr = false; + } + } + if(right instanceof SQLBinaryOpExpr ){ + SQLBinaryOpExpr rightBinaryOp = (SQLBinaryOpExpr) right; + if (rightBinaryOp.getOperator() == SQLBinaryOperator.BooleanOr) { + return false; + } + if (isLogicalExpression(rightBinaryOp)) { + isAllNonLogicExpr = false; + } + } + if (isAllNonLogicExpr) { + return true; + } + } + + if (!isDNF(left)) { + return false; + } + if (!isDNF(right)) { + return false; + } + return true; + } + + private static boolean isLogicalExpression(SQLExpr expr) {//XOR? + if(!(expr instanceof SQLBinaryOpExpr)){ + return false; + } + SQLBinaryOpExpr binOpExpr= (SQLBinaryOpExpr)expr; + if (binOpExpr.getOperator() == SQLBinaryOperator.BooleanAnd || binOpExpr.getOperator() == SQLBinaryOperator.BooleanOr) { + return true; + } + return false; + } +} diff --git a/src/main/java/io/mycat/plan/util/FilterUtils.java b/src/main/java/io/mycat/plan/util/FilterUtils.java new file mode 100644 index 000000000..11c61bbbb --- /dev/null +++ b/src/main/java/io/mycat/plan/util/FilterUtils.java @@ -0,0 +1,125 @@ +package io.mycat.plan.util; + +import java.util.ArrayList; +import java.util.List; + +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.function.ItemFunc.Functype; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.common.item.function.operator.logic.ItemCond; +import io.mycat.plan.common.item.function.operator.logic.ItemCondAnd; +import io.mycat.plan.common.item.function.operator.logic.ItemCondOr; + +public class FilterUtils { + /** + * 将filter尽可能拆分 一个无法拆分的filter:booleanfiler,orfilter + * + * @param filter + * @return + */ + public static List splitFilter(Item filter) { + if (filter == null) + throw new RuntimeException("check filter before split"); + List filterList = new ArrayList(); + if (filter.type() == ItemType.COND_ITEM) { + ItemCond cond = (ItemCond) filter; + if (cond.functype() == Functype.COND_AND_FUNC) { + for (int index = 0; index < cond.getArgCount(); index++) { + Item subFilter = cond.arguments().get(index); + if (subFilter == null) + continue; + List subSplits = splitFilter(subFilter); + filterList.addAll(subSplits); + } + } else { + filterList.add(cond); + } + } else { + filterList.add(filter); + } + return filterList; + } + + public static ItemCond createLogicalFilterNoName(boolean and, List subFilters) { + ItemCond cond = null; + if (and) + cond = new ItemCondAnd(subFilters); + else + cond = new ItemCondOr(subFilters); + PlanUtil.refreshReferTables(cond); + return cond; + } + + public static Item and(List filters) { + if (filters == null || filters.isEmpty()) + return null; + List subFilters = new ArrayList(); + for (Item filter : filters) { + if (filter == null) + continue; + if (filter.type() == ItemType.COND_ITEM && ((ItemCond) filter).functype() == Functype.COND_AND_FUNC) { + subFilters.addAll(((ItemCond) filter).arguments()); + } else + subFilters.add(filter); + } + if (subFilters.isEmpty()) + return null; + else if (subFilters.size() == 1) + return subFilters.get(0); + else { + return createLogicalFilterNoName(true, subFilters); + } + } + + /** + * 创建and条件 + */ + public static Item and(Item root, Item o) { + List list = new ArrayList(); + list.add(root); + list.add(o); + return and(list); + } + + public static Item or(List filters) { + if (filters == null) + return null; + List subFilters = new ArrayList(); + for (Item filter : filters) { + if (filter == null) + continue; + if (filter.type() == ItemType.COND_ITEM && ((ItemCond) filter).functype() == Functype.COND_OR_FUNC) { + subFilters.addAll(((ItemCond) filter).arguments()); + } else + subFilters.add(filter); + } + if (subFilters.isEmpty()) + return new ItemInt(0); + else if (subFilters.size() == 1) + return subFilters.get(0); + else { + return createLogicalFilterNoName(false, subFilters); + } + } + + /** + * 创建or条件 + */ + public static Item or(Item root, Item o) { + List list = new ArrayList(); + list.add(root); + list.add(o); + return or(list); + } + + /** + * 创建equal filter + */ + public static ItemFuncEqual equal(Item column, Item value) { + ItemFuncEqual f = new ItemFuncEqual(column, value); + PlanUtil.refreshReferTables(f); + return f; + } +} diff --git a/src/main/java/io/mycat/plan/util/PlanUtil.java b/src/main/java/io/mycat/plan/util/PlanUtil.java new file mode 100644 index 000000000..42dc3c5f9 --- /dev/null +++ b/src/main/java/io/mycat/plan/util/PlanUtil.java @@ -0,0 +1,379 @@ +package io.mycat.plan.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.mycat.config.ErrorCode; +import io.mycat.config.model.TableConfig.TableTypeEnum; +import io.mycat.plan.NamedField; +import io.mycat.plan.Order; +import io.mycat.plan.PlanNode; +import io.mycat.plan.PlanNode.PlanNodeType; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.Item.ItemType; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.function.ItemFunc; +import io.mycat.plan.common.item.function.ItemFunc.Functype; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.common.item.function.sumfunc.ItemSum; +import io.mycat.plan.common.item.function.sumfunc.ItemSum.Sumfunctype; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.node.TableNode; +import io.mycat.route.parser.util.Pair; + +public class PlanUtil { + public static boolean existAggr(PlanNode node) { + if (node.getReferedTableNodes().size() == 0) + return false; + else { + if (node.sumFuncs.size() > 0 || node.getGroupBys().size() > 0 || node.getLimitTo() != -1 + || node.isDistinct()) + return true; + } + return false; + } + + /** + * 查找obj在node树当中真正属于的tablenode的column 如果obj不是Column类型返回null + * <查找的column必须是table的上级的column> + * + * @param obj + * @param node + * @return 找到的那个tablenode以及column属性 + */ + public static Pair findColumnInTableLeaf(ItemField column, PlanNode node) { + // union的不可下查 + if (node.type() == PlanNodeType.MERGE) + return null; + NamedField tmpField = new NamedField(column.getTableName(), column.getItemName(), null); + NamedField coutField = node.getInnerFields().get(tmpField); + if (coutField == null) + return null; + else if (node.type() == PlanNodeType.TABLE) { + return new Pair((TableNode) node, column); + } else { + PlanNode referNode = column.getReferTables().iterator().next(); + Item cSel = referNode.getOuterFields().get(coutField); + if (cSel instanceof ItemField) { + return findColumnInTableLeaf((ItemField) cSel, referNode); + } else { + return null; + } + } + } + + /** + * 刷新function的refertable + * + * @param f + */ + public static void refreshReferTables(Item f) { + if (!(f instanceof ItemFunc || f instanceof ItemSum)) + return; + HashSet rtables = f.getReferTables(); + rtables.clear(); + for (int index = 0; index < f.getArgCount(); index++) { + Item arg = f.arguments().get(index); + rtables.addAll(arg.getReferTables()); + // refresh exist sel is null + f.withIsNull = f.withIsNull || arg.withIsNull; + // refresh exist agg + f.withSumFunc = f.withSumFunc || arg.withSumFunc; + f.withSubQuery = f.withSubQuery || arg.withSubQuery; + f.withUnValAble = f.withUnValAble || arg.withUnValAble; + } + return; + } + + /** + * 判断sel是否可以下推到child中 + * + * @param sel + * @param child + * @return + */ + public static boolean canPush(Item sel, PlanNode child, PlanNode parent) { + if (sel == null) + return false; + if (sel.withSumFunc) + return false; + HashSet referTables = sel.getReferTables(); + if (referTables.size() == 0) { + return true; + } else if (referTables.size() == 1) { + PlanNode referTable = referTables.iterator().next(); + if (referTable == child) { + // left join的right 节点的is null不下发 + if (sel.withIsNull && parent.type() == PlanNodeType.JOIN && ((JoinNode) parent).isLeftOuterJoin() + && ((JoinNode) parent).getRightNode() == child) + return false; + else + return true; + } + } + return false; + } + + /** 是否属于可直接下推的函数 **/ + public static boolean isDirectPushDownFunction(Item func) { + if (func.withSumFunc) + return false; + else { + // 如果函数涉及的所有的参数仅有一个table,则可以下推 + if (func.getReferTables().size() <= 1) + return true; + else + return false; + } + } + + public static boolean isUnPushDownSum(ItemSum sumFunc) { + if (sumFunc.sumType() == Sumfunctype.GROUP_CONCAT_FUNC) + return true; + if (sumFunc.has_with_distinct()) + return true; + if (sumFunc.sumType() == Sumfunctype.UDF_SUM_FUNC) + return true; + return false; + } + + public static Item pushDownItem(PlanNode node, Item sel) { + return pushDownItem(node, sel, false); + } + + // 将node中的sel进行下推 + public static Item pushDownItem(PlanNode node, Item sel, boolean subQueryOpt) { + if (sel == null) + return null; + if (sel.basicConstItem()) + return sel; + if (sel.getReferTables().size() > 1) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "can not pushdown sel when refer table's > 1!"); + } + if (sel instanceof ItemField) { + return pushDownCol(node, (ItemField) sel); + } else if (sel instanceof ItemFunc || sel instanceof ItemSum) { + Item func = sel.cloneStruct(); + if (sel.getReferTables().isEmpty()) { + func.setPushDownName(null); + sel.setPushDownName(func.getItemName()); + return func; + } + if (!subQueryOpt && (func.withSumFunc)) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "can not pushdown sum func!"); + } + for (int index = 0; index < func.getArgCount(); index++) { + Item arg = func.arguments().get(index); + Item newArg = pushDownItem(node, arg, subQueryOpt); + func.arguments().set(index, newArg); + } + refreshReferTables(func); + func.setPushDownName(null); + sel.setPushDownName(func.getItemName()); + return func; + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not supported!"); + } + } + + /** + * 判断bf是否可以做为node的joinkey,并且调整bf的左右位置 + * + * @param bf + * @param node + * @return + */ + public static boolean isJoinKey(ItemFuncEqual bf, JoinNode node) { + // 只有等于的才可能是joinkey + boolean isJoinKey = false; + Item selCol = bf.arguments().get(0); + Item selVal = bf.arguments().get(1); + Set colTns = selCol.getReferTables(); + Set valTns = selVal.getReferTables(); + if (colTns.size() == 1 && valTns.size() == 1) { + // a.id=b.id is join key,else not + PlanNode colTn = colTns.iterator().next(); + PlanNode valTn = valTns.iterator().next(); + if (colTn == node.getLeftNode() && valTn == node.getRightNode()) { + isJoinKey = true; + } else if (colTn == node.getRightNode() && valTn == node.getLeftNode()) { + isJoinKey = true; + bf.arguments().set(0, selVal); + bf.arguments().set(1, selCol); + } + } + + return isJoinKey; + } + + /** + * 将原本的Join的where条件中的a.id=b.id构建为join条件,并从where条件中移除 + */ + public static void findJoinKeysAndRemoveIt(List DNFNode, JoinNode join) { + List joinFilters = new LinkedList(); + for (Item subItem : DNFNode) { // 一定是简单条件 + if (subItem.type().equals(ItemType.FUNC_ITEM) || subItem.type().equals(ItemType.COND_ITEM)) { + ItemFunc sub = (ItemFunc) subItem; + if (!(sub.functype().equals(Functype.EQ_FUNC))) + continue; + boolean isJoinKey = isJoinKey((ItemFuncEqual) sub, join); + if (isJoinKey) { + joinFilters.add(sub); + join.addJoinFilter((ItemFuncEqual) sub); + } + } + } + DNFNode.removeAll(joinFilters); + } + + public static boolean isERNode(PlanNode node) { + if (node instanceof JoinNode) { + JoinNode join = (JoinNode) node; + if (join.getERkeys() != null && join.getERkeys().size() > 0) { + return true; + } + } + return false; + } + + public static boolean isGlobalOrER(PlanNode node) { + if (node.isGlobaled()) + return true; + else { + return isERNode(node); + } + } + + /** + * 将merge节点上的函数下推到下面的child节点上 目前仅下推filter以及自定义函数 + * + * @param mn + * @param toPush + * @return 返回所有child对应的pushdown sel + */ + public static List getPushItemsToUnionChild(MergeNode mn, Item toPush, Map colIndexs) { + if (toPush == null) + return null; + List pusheds = new ArrayList(); + for (int i = 0; i < mn.getChildren().size(); i++) { + Item pushed = pushItemToUnionChild(toPush, colIndexs, mn.getChildren().get(i).getColumnsSelected()); + pusheds.add(pushed); + } + return pusheds; + } + + private static Item pushItemToUnionChild(Item toPush, Map colIndexs, List childSelects) { + if (toPush instanceof ItemField) { + return pushColToUnionChild((ItemField) toPush, colIndexs, childSelects); + } else if (toPush instanceof ItemFunc) { + return pushFunctionToUnionChild((ItemFunc) toPush, colIndexs, childSelects); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "unexpected!"); + } + } + + private static Item pushColToUnionChild(ItemField toPush, Map colIndexs, List childSelects) { + String name = toPush.getItemName(); + int colIndex = colIndexs.get(name); + return childSelects.get(colIndex); + } + + /** + * merge的function的每一个arg一定属于child + * + * @param toPush + * @param colIndexs + * @param childSelects + * @return + */ + private static ItemFunc pushFunctionToUnionChild(ItemFunc toPush, Map colIndexs, + List childSelects) { + ItemFunc func = (ItemFunc) toPush.cloneStruct(); + for (int index = 0; index < toPush.getArgCount(); index++) { + Item arg = toPush.arguments().get(index); + Item pushedArg = pushItemToUnionChild(arg, colIndexs, childSelects); + func.arguments().set(index, pushedArg); + } + func.setPushDownName(null); + refreshReferTables(func); + return func; + } + + // ----------------help method------------ + + private static Item pushDownCol(PlanNode node, ItemField col) { + NamedField tmpField = new NamedField(col.getTableName(), col.getItemName(), null); + NamedField coutField = node.getInnerFields().get(tmpField); + return coutField.planNode.getOuterFields().get(coutField); + } + + /** + * 生成node中的orders的pushorders + * + * @param node + * @param orders + * @return + */ + public static List getPushDownOrders(PlanNode node, List orders) { + List pushOrders = new ArrayList(); + for (Order order : orders) { + Item newSel = pushDownItem(node, order.getItem()); + Order newOrder = new Order(newSel, order.getSortOrder()); + pushOrders.add(newOrder); + } + return pushOrders; + } + + /** + * 当order1 包含order2的时候,返回true,要求order2的列在order1中要从第一列开始按照顺序来进行 + * + * @param orders1 + * @param orders2 + * @return + */ + public static boolean orderContains(List orders1, List orders2) { + if (orders1.size() < orders2.size()) + return false; + else { + for (int index = 0; index < orders2.size(); index++) { + Order order2 = orders2.get(index); + Order order1 = orders1.get(index); + if (!order2.equals(order1)) { + return false; + } + } + return true; + } + } + + /** + * node不存在表名或者node全部为global表时 + * + * @param node + * @return + */ + public static boolean existShardTable(PlanNode node) { + for (TableNode tn : node.getReferedTableNodes()) { + if (tn.getTableConfig() != null && tn.getTableConfig().getTableType()!=TableTypeEnum.TYPE_GLOBAL_TABLE) + return true; + } + return false; + } + +// public static boolean existPartiTable(PlanNode tn) { +// boolean existNumParti = false; +// for (TableNode table : tn.getReferedTableNodes()) { +// if (table.isPartitioned()) { +// existNumParti = true; +// break; +// } +// } +// return existNumParti; +// } +} diff --git a/src/main/java/io/mycat/plan/util/ToStringUtil.java b/src/main/java/io/mycat/plan/util/ToStringUtil.java new file mode 100644 index 000000000..42f619d17 --- /dev/null +++ b/src/main/java/io/mycat/plan/util/ToStringUtil.java @@ -0,0 +1,88 @@ +package io.mycat.plan.util; + +import java.util.List; + +import io.mycat.plan.Order; +import io.mycat.plan.common.item.Item; + +public class ToStringUtil { + /** + * add tab like <[ ]> + * + * @param count + * @return + */ + public static String getTab(int count) { + StringBuffer tab = new StringBuffer(); + for (int i = 0; i < count; i++) + tab.append(" "); + return tab.toString(); + } + + /** + * 拼接换行符 + */ + public static void appendln(StringBuilder sb, String v) { + sb.append(v).append("\n"); + } + + /** + * 获取itemlist的string表示 + * + * @param itemList + * @return + */ + public static String itemString(Item item) { + if (item == null) + return "null"; + return item.toString(); + } + + /** + * 获取itemlist的string表示 + * + * @param itemList + * @return + */ + public static String itemListString(List itemList) { + if (itemList == null) + return "null"; + if (itemList.isEmpty()) + return " "; + boolean isFirst = true; + StringBuilder sb = new StringBuilder(); + for (Object item : itemList) { + if (isFirst) { + isFirst = false; + } else { + sb.append(", "); + } + sb.append(item); + } + return sb.toString(); + } + + public static String orderString(Order order) { + if (order == null) + return "null"; + return order.getItem() + " " + order.getSortOrder(); + } + + public static String orderListString(List orderList) { + if (orderList == null) + return "null"; + if (orderList.isEmpty()) + return " "; + boolean isFirst = true; + StringBuilder sb = new StringBuilder(); + for (Order order : orderList) { + if (isFirst) { + isFirst = false; + } else { + sb.append(", "); + } + sb.append(orderString(order)); + } + return sb.toString(); + } +} diff --git a/src/main/java/io/mycat/plan/visitor/MySQLItemVisitor.java b/src/main/java/io/mycat/plan/visitor/MySQLItemVisitor.java new file mode 100644 index 000000000..66c2afdd5 --- /dev/null +++ b/src/main/java/io/mycat/plan/visitor/MySQLItemVisitor.java @@ -0,0 +1,660 @@ +package io.mycat.plan.visitor; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.ast.SQLDataType; +import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAggregateOption; +import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; +import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLBooleanExpr; +import com.alibaba.druid.sql.ast.expr.SQLCaseExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; +import com.alibaba.druid.sql.ast.expr.SQLCharExpr; +import com.alibaba.druid.sql.ast.expr.SQLExistsExpr; +import com.alibaba.druid.sql.ast.expr.SQLHexExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLInListExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLNCharExpr; +import com.alibaba.druid.sql.ast.expr.SQLNotExpr; +import com.alibaba.druid.sql.ast.expr.SQLNullExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumberExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLUnaryExpr; +import com.alibaba.druid.sql.ast.statement.SQLCharacterDataType; +import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlExtractExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalUnit; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.Order; +import io.mycat.plan.common.CastTarget; +import io.mycat.plan.common.CastType; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.ItemFloat; +import io.mycat.plan.common.item.ItemInt; +import io.mycat.plan.common.item.ItemNull; +import io.mycat.plan.common.item.ItemString; +import io.mycat.plan.common.item.function.ItemCreate; +import io.mycat.plan.common.item.function.ItemFuncKeyWord; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncBitAnd; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncBitInversion; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncBitOr; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncBitXor; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncLeftShift; +import io.mycat.plan.common.item.function.bitfunc.ItemFuncRightShift; +import io.mycat.plan.common.item.function.castfunc.ItemCharTypecast; +import io.mycat.plan.common.item.function.castfunc.ItemFuncConvCharset; +import io.mycat.plan.common.item.function.castfunc.ItemNCharTypecast; +import io.mycat.plan.common.item.function.mathsfunc.operator.ItemFuncDiv; +import io.mycat.plan.common.item.function.mathsfunc.operator.ItemFuncMinus; +import io.mycat.plan.common.item.function.mathsfunc.operator.ItemFuncMod; +import io.mycat.plan.common.item.function.mathsfunc.operator.ItemFuncMul; +import io.mycat.plan.common.item.function.mathsfunc.operator.ItemFuncNeg; +import io.mycat.plan.common.item.function.mathsfunc.operator.ItemFuncPlus; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncBetweenAnd; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncGe; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncGt; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIn; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIsfalse; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIsnotfalse; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIsnotnull; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIsnottrue; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIsnull; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncIstrue; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncLe; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncLike; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncLt; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncNe; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncRegex; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncStrictEqual; +import io.mycat.plan.common.item.function.operator.controlfunc.ItemFuncCase; +import io.mycat.plan.common.item.function.operator.logic.ItemCondAnd; +import io.mycat.plan.common.item.function.operator.logic.ItemCondOr; +import io.mycat.plan.common.item.function.operator.logic.ItemFuncNot; +import io.mycat.plan.common.item.function.operator.logic.ItemFuncXor; +import io.mycat.plan.common.item.function.strfunc.ItemFuncChar; +import io.mycat.plan.common.item.function.strfunc.ItemFuncTrim; +import io.mycat.plan.common.item.function.strfunc.ItemFuncTrim.TRIM_TYPE_ENUM; +import io.mycat.plan.common.item.function.sumfunc.ItemFuncGroupConcat; +import io.mycat.plan.common.item.function.sumfunc.ItemSumAvg; +import io.mycat.plan.common.item.function.sumfunc.ItemSumCount; +import io.mycat.plan.common.item.function.sumfunc.ItemSumMax; +import io.mycat.plan.common.item.function.sumfunc.ItemSumMin; +import io.mycat.plan.common.item.function.sumfunc.ItemSumSum; +import io.mycat.plan.common.item.function.sumfunc.ItemSumVariance; +import io.mycat.plan.common.item.function.timefunc.ItemDateAddInterval; +import io.mycat.plan.common.item.function.timefunc.ItemExtract; +import io.mycat.plan.common.item.function.timefunc.ItemFuncTimestampDiff; +import io.mycat.plan.common.item.function.unknown.ItemFuncUnknown; +import io.mycat.plan.common.item.subquery.ItemInSubselect; +import io.mycat.plan.common.item.subquery.ItemSinglerowSubselect; + +public class MySQLItemVisitor extends MySqlASTVisitorAdapter { + private String currentDb; + + public MySQLItemVisitor(String currentDb) { + this.currentDb = currentDb; + } + private Item item; + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + @Override + public void endVisit(SQLBetweenExpr x){ + item = new ItemFuncBetweenAnd(getItem(x.getTestExpr()), getItem(x.getBeginExpr()), getItem(x.getEndExpr()), x.isNot()); + initName(x); + } + + + @Override + public void endVisit(SQLBooleanExpr x) { + if(x.getValue()){ + item = new ItemInt(1); + }else{ + item = new ItemInt(0); + } + initName(x); + } + + @Override + public void endVisit(SQLBinaryOpExpr x){ + Item itemLeft = getItem(x.getLeft()); + Item itemRight = getItem(x.getRight()); + switch (x.getOperator()) { + case Is: + // is null, or is unknown + if (itemRight instanceof ItemNull || itemRight instanceof ItemString) { + item = new ItemFuncIsnull(itemLeft); + } else if (itemRight instanceof ItemInt) { + ItemInt itemBool = (ItemInt) itemRight; + if (itemBool.valInt().longValue() == 1) {// is true + item = new ItemFuncIstrue(itemLeft); + } else { + item = new ItemFuncIsfalse(itemLeft); + } + } else{ + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support type:" + x.getRight()); + } + break; + case IsNot: + // is not null, or is notunknown + if (itemRight instanceof ItemNull || itemRight instanceof ItemString) { + item = new ItemFuncIsnotnull(itemLeft); + } else if (itemRight instanceof ItemInt) { + ItemInt itemBool = (ItemInt) itemRight; + if (itemBool.valInt().longValue() == 1) {// is true + item = new ItemFuncIsnottrue(itemLeft); + } else { + item = new ItemFuncIsnotfalse(itemLeft); + } + } else{ + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support type:" + x.getRight()); + } + break; + case Escape: + if (itemLeft instanceof ItemFuncLike) { + // A LIKE B ESCAPE C ,A is "itemLeft" + SQLBinaryOpExpr like = (SQLBinaryOpExpr) (x.getLeft()); + Item itemLikeLeft = getItem(like.getLeft()); + Item itemLikeRight = getItem(x.getRight()); + boolean isNot = (like.getOperator() == SQLBinaryOperator.NotLike); + item = new ItemFuncLike(itemLikeLeft, itemLikeRight, itemRight, isNot); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "not supported kind expression:" + x.getOperator()); + } + break; + case NotLike: + item = new ItemFuncLike(itemLeft, itemLeft, null, true); + break; + case Like: + item = new ItemFuncLike(itemLeft, itemLeft, null, false); + break; + case Equality: + item = new ItemFuncEqual(itemLeft, itemRight); + break; + case Add: + item = new ItemFuncPlus(itemLeft, itemRight); + break; + case Divide: + item = new ItemFuncDiv(itemLeft, itemRight); + break; + case Mod: + case Modulus: + item = new ItemFuncMod(itemLeft, itemRight); + break; + case Multiply: + item = new ItemFuncMul(itemLeft, itemRight); + break; + case Subtract: + item = new ItemFuncMinus(itemLeft, itemRight); + break; + case PG_And: + case BooleanAnd: + List argsAnd = new ArrayList(); + argsAnd.add(itemLeft); + argsAnd.add(itemRight); + item = new ItemCondAnd(argsAnd); + break; + case Concat: + case BooleanOr: + List argsOr = new ArrayList(); + argsOr.add(itemLeft); + argsOr.add(itemRight); + item = new ItemCondOr(argsOr); + break; + case BooleanXor: + item = new ItemFuncXor(itemLeft, itemRight); + break; + case BitwiseAnd: + item = new ItemFuncBitAnd(itemLeft, itemRight); + break; + case BitwiseOr: + item = new ItemFuncBitOr(itemLeft, itemRight); + break; + case BitwiseXor: + item = new ItemFuncBitXor(itemLeft, itemRight); + break; + case LeftShift: + item = new ItemFuncLeftShift(itemLeft, itemRight); + break; + case RightShift: + item = new ItemFuncRightShift(itemLeft, itemRight); + break; + case GreaterThan: + item = new ItemFuncGt(itemLeft, itemRight); + break; + case GreaterThanOrEqual: + item = new ItemFuncGe(itemLeft, itemRight); + break; + case NotEqual: + case LessThanOrGreater: + item = new ItemFuncNe(itemLeft, itemRight); + break; + case LessThan: + item = new ItemFuncLt(itemLeft, itemRight); + break; + case LessThanOrEqual: + item = new ItemFuncLe(itemLeft, itemRight); + break; + case LessThanOrEqualOrGreaterThan: + item = new ItemFuncStrictEqual(itemLeft, itemRight); + break; + case RegExp: + item = new ItemFuncRegex(itemLeft, itemRight); + break; + case NotRegExp: + item = new ItemFuncRegex(itemLeft, itemRight); + item = new ItemFuncNot(item); + break; + case Assignment: + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not support assignment"); + default: + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not supported kind expression:" + x.getOperator()); + } + initName(x); + } + + @Override + public void endVisit(SQLUnaryExpr x) { + Item a = getItem(x.getExpr()); + switch(x.getOperator()){ + case Negative: + item = new ItemFuncNeg(a); + break; + case Not: + case NOT: + item = new ItemFuncNot(a); + break; + case Compl: + item = new ItemFuncBitInversion(a); + break; + case Plus: + item = a; + break; + default: + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", + "not supported kind expression:" + x.getOperator()); + } + initName(x); + } + @Override + public void endVisit(SQLInListExpr x){ + boolean isNeg = x.isNot(); + Item left = getItem(x.getExpr()); + List args = visitExprList(x.getTargetList()); + if(args.get(0) instanceof ItemSinglerowSubselect){ + SQLSelectStatement query = (SQLSelectStatement) (x.getTargetList().get(0)); + item = new ItemInSubselect(currentDb, left, query.getSelect().getQuery(), isNeg); + }else{ + args.add(left); + args.addAll(visitExprList(x.getTargetList())); + item = new ItemFuncIn(args, isNeg); + } + initName(x); + } + + @Override + public void endVisit(MySqlExtractExpr x){ + item = new ItemExtract(getItem(x.getValue()), x.getUnit()); + initName(x); + } + @Override + public void endVisit(MySqlIntervalExpr x){ + //Just as placeholder + item = new ItemString(x.toString()); + item.setItemName(x.toString()); + } + @Override + public void endVisit(SQLNotExpr x){ + item = new ItemFuncNot(getItem(x.getExpr())); + initName(x); + } + @Override + public void endVisit(SQLAllColumnExpr x){ + item = new ItemField(null, null, "*"); + initName(x); + } + @Override + public void endVisit(SQLCaseExpr x) { + SQLExpr comparee = x.getValueExpr(); + SQLExpr elseExpr = x.getElseExpr(); + List whenlists= x.getItems(); + ArrayList args = new ArrayList(); + int ncases, firstExprNum = -1, elseExprNum = -1; + for (SQLCaseExpr.Item when : whenlists) { + args.add(getItem(when.getConditionExpr())); + args.add(getItem(when.getValueExpr())); + } + ncases = args.size(); + // add comparee + if (comparee != null) { + firstExprNum = args.size(); + args.add(getItem(comparee)); + } + // add else exp + if (elseExpr != null) { + elseExprNum = args.size(); + args.add(getItem(elseExpr)); + } + item = new ItemFuncCase(args, ncases, firstExprNum, elseExprNum); + } + @Override + public void endVisit(SQLCastExpr x) { + Item a = getItem(x.getExpr()); + SQLDataType datetype = x.getDataType(); + + if(datetype instanceof SQLCharacterDataType){ + SQLCharacterDataType charType = (SQLCharacterDataType) datetype; + String upType = charType.getName().toUpperCase(); + List args = changeExprListToInt(charType.getArguments()); + String charSetName = charType.getCharSetName(); + if(upType.equals("CHAR")){ + int len = -1; + if (args.size() > 0) { + len = args.get(0); + } + item = new ItemCharTypecast(a, len, charSetName); + } + else if(charSetName == null){ + int len = -1; + if (args.size() > 0) { + len = args.get(0); + } + item = new ItemNCharTypecast(a, len); + }else{ + throw new MySQLOutPutException(ErrorCode.ER_PARSE_ERROR, "", + "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'character set "+charSetName+")'"); + } + } + else{ + CastType castType = getCastType((SQLDataTypeImpl)datetype); + item = ItemCreate.getInstance().create_func_cast(a, castType); + } + initName(x); + } + @Override + public void endVisit(SQLCharExpr x) { + item = new ItemString(x.getText()); + initName(x); + } + @Override + public void endVisit(SQLIdentifierExpr x) { + item = new ItemField(null, null, x.getSimpleName()); + initName(x); + } + @Override + public void endVisit(SQLNullExpr x){ + item = new ItemNull(); + initName(x); + } + @Override + public void endVisit(SQLIntegerExpr x) { + Number number = x.getNumber(); + item = new ItemInt(number.longValue()); + initName(x); + } + @Override + public void endVisit(SQLNCharExpr x) { + item = new ItemString(x.getText()); + initName(x); + } + @Override + public void endVisit(SQLNumberExpr x) { + Number number = x.getNumber(); + if (number instanceof BigDecimal) { + item = new ItemFloat((BigDecimal) number); + } else { + item = new ItemInt(number.longValue()); + } + initName(x); + } + @Override + public void endVisit(SQLPropertyExpr x) { + SQLIdentifierExpr owner= (SQLIdentifierExpr) x.getOwner(); + item = new ItemField(null, owner.getSimpleName(), x.getSimpleName()); + } + + @Override + public void endVisit(SQLAggregateExpr x) { + List args = visitExprList(x.getArguments()); + String funcName = x.getMethodName().toUpperCase(); + SQLAggregateOption option = x.getOption(); + boolean isDistinct = option==null?false:true; + switch(funcName){ + case "MAX": + item = new ItemSumMax(args,isDistinct, false, null); + break; + case "MIN": + item = new ItemSumMin(args,isDistinct, false, null); + break; + case "SUM": + item = new ItemSumSum(args,isDistinct, false, null); + break; + case "AVG": + item = new ItemSumAvg(args,isDistinct, false, null); + break; + case "GROUP_CONCAT": + SQLOrderBy orderExpr = (SQLOrderBy) x.getAttribute(ItemFuncKeyWord.ORDER_BY); + List orderList = null; + if (orderExpr != null) { + orderList = new ArrayList(); + for (SQLSelectOrderByItem orderItem : orderExpr.getItems()) { + Order order = new Order(getItem(orderItem.getExpr()), orderItem.getType()); + orderList.add(order); + } + } + SQLCharExpr charExpr = (SQLCharExpr) x.getAttribute(ItemFuncKeyWord.SEPARATOR); + String separator = null; + if (charExpr != null) { + separator = charExpr.getText(); + } + item = new ItemFuncGroupConcat(args, isDistinct, orderList, separator, false, null); + break; + case "COUNT": + item = new ItemSumCount(args,isDistinct, false, null); + break; + } + + initName(x); + } + @Override + public void endVisit(SQLMethodInvokeExpr x) { + List args = visitExprList(x.getParameters()); + String funcName = x.getMethodName().toUpperCase(); + Map attributes = x.getAttributes(); + switch(funcName){ + case "TRIM": + if (attributes == null) { + item = new ItemFuncTrim(args.get(0), TRIM_TYPE_ENUM.DEFAULT); + } else { + TRIM_TYPE_ENUM trimType = TRIM_TYPE_ENUM.DEFAULT; + String type = (String) attributes.get(ItemFuncKeyWord.TRIM_TYPE); + if (type != null) { + trimType = TRIM_TYPE_ENUM.valueOf(type); + } + if (attributes.get(ItemFuncKeyWord.FROM) == null) { + item = new ItemFuncTrim(args.get(0), trimType); + } else { + SQLCharExpr from = (SQLCharExpr) attributes.get(ItemFuncKeyWord.FROM); + item = new ItemFuncTrim(args.get(0), getItem(from), trimType); + } + } + break; + case "CONVERT": + if(args.size()>=2){ + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not supported CONVERT(expr, type) ,please use CAST(expr AS type)" ); + } + if(attributes ==null || attributes.get(ItemFuncKeyWord.USING)==null){ + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "CONVERT(... USING ...) is standard SQL syntax" ); + } + item = new ItemFuncConvCharset(args.get(0), (String) attributes.get(ItemFuncKeyWord.USING)); + break; + case "CHAR": + if (attributes == null || attributes.get(ItemFuncKeyWord.USING) == null) { + item = new ItemFuncChar(args); + } else { + item = new ItemFuncChar(args, (String) attributes.get(ItemFuncKeyWord.USING)); + } + case "ADDDATE": + if(x.getParameters().get(1) instanceof SQLIntegerExpr){ + item = new ItemDateAddInterval(args.get(0), args.get(1), MySqlIntervalUnit.DAY, false); + break; + } + case "DATE_ADD": + MySqlIntervalExpr intervalExpr = (MySqlIntervalExpr)(x.getParameters().get(1)); + item = new ItemDateAddInterval(args.get(0), getItem(intervalExpr.getValue()), getIntervalUint(x.getParameters().get(1)), false); + break; + case "SUBDATE": + if(x.getParameters().get(1) instanceof SQLIntegerExpr){ + item = new ItemDateAddInterval(args.get(0), args.get(1), MySqlIntervalUnit.DAY, true); + break; + } + case "DATE_SUB": + MySqlIntervalExpr valueExpr = (MySqlIntervalExpr)(x.getParameters().get(1)); + item = new ItemDateAddInterval(args.get(0), getItem(valueExpr.getValue()), getIntervalUint(x.getParameters().get(1)), true); + break; + case "TIMESTAMPADD": + SQLIdentifierExpr addUnit = (SQLIdentifierExpr)x.getParameters().get(0); + item = new ItemDateAddInterval(args.get(2), args.get(1), MySqlIntervalUnit.valueOf(addUnit.getSimpleName()), false); + break; + case "TIMESTAMPDIFF": + SQLIdentifierExpr diffUnit = (SQLIdentifierExpr)x.getParameters().get(0); + item = new ItemFuncTimestampDiff(args.get(1), args.get(2), MySqlIntervalUnit.valueOf(diffUnit.getSimpleName())); + break; + case "VAR_SAMP": + item = new ItemSumVariance(args, 1, false, null); + break; + case "VAR_POP": + case "VARIANCE": + item = new ItemSumVariance(args, 0, false, null); + break; + default: + if (ItemCreate.getInstance().isNativeFunc(funcName)) { + item = ItemCreate.getInstance().createNativeFunc(funcName, args); + } else { + // unKnownFunction + item = new ItemFuncUnknown(funcName, args); + } + } + initName(x); + } + @Override + public void endVisit(SQLExistsExpr x) { + // TODO + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not supported exists!"); + } + + //TODO: CHECK + @Override + public void endVisit(SQLHexExpr x) { + item = new ItemString(x.toString()); + initName(x); + } + + @Override + public void endVisit(SQLSelectStatement node) { + SQLSelectQuery sqlSelect = node.getSelect().getQuery(); + item = new ItemSinglerowSubselect(currentDb, sqlSelect); + } + + private CastType getCastType(SQLDataTypeImpl dataTypeImpl) { + CastType castType = new CastType(); + String upType = dataTypeImpl.getName().toUpperCase(); + List args = changeExprListToInt(dataTypeImpl.getArguments()); + if (upType.equals("BINARY")) { + castType.target = CastTarget.ITEM_CAST_BINARY; + if (args.size() > 0) { + castType.length = args.get(0); + } + } else if (upType.equals("DATE")) { + castType.target = CastTarget.ITEM_CAST_DATE; + } else if (upType.equals("DATETIME")) { + castType.target = CastTarget.ITEM_CAST_DATETIME; + if (args.size() > 0) { + castType.length = args.get(0); + } + } else if (upType.equals("DECIMAL")) { + castType.target = CastTarget.ITEM_CAST_DECIMAL; + if (args.size() > 0) { + castType.length = args.get(0); + } + if (args.size() > 1) { + castType.dec = args.get(1); + } + } else if (upType.equals("NCHAR")) { + castType.target = CastTarget.ITEM_CAST_NCHAR; + if (args.size() > 0) { + castType.length = args.get(0); + } + } else if (upType.equals("SIGNED")) { + castType.target = CastTarget.ITEM_CAST_SIGNED_INT; + } else if (upType.equals("UNSIGNED")) { + castType.target = CastTarget.ITEM_CAST_UNSIGNED_INT; + } else if (upType.equals("TIME")) { + castType.target = CastTarget.ITEM_CAST_TIME; + if (args.size() > 0) { + castType.length = args.get(0); + } + } else { + // not support SIGNED INT /UNSIGNED INT/JSON + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "not supported cast as:" + upType); + } + return castType; + } + private List changeExprListToInt(List exprList) { + List args = new ArrayList(); + for (SQLExpr expr : exprList) { + Number num = ((SQLNumericLiteralExpr)expr).getNumber(); + args.add(num.intValue()); + } + return args; + } + private List visitExprList(List exprList) { + List args = new ArrayList(); + for (SQLExpr expr : exprList) { + args.add(getItem(expr)); + } + return args; + } + private Item getItem(SQLExpr expr){ + MySQLItemVisitor fv = new MySQLItemVisitor(currentDb); + expr.accept(fv); + return fv.getItem(); + } + private MySqlIntervalUnit getIntervalUint(SQLExpr expr){ + return ((MySqlIntervalExpr)expr).getUnit(); + } + private void initName(SQLExpr expr) { + StringBuilder sb = new StringBuilder(); + MySqlOutputVisitor ov = new MySqlOutputVisitor(sb); + expr.accept(ov); + item.setItemName(sb.toString()); + } +} diff --git a/src/main/java/io/mycat/plan/visitor/MySQLPlanNodeVisitor.java b/src/main/java/io/mycat/plan/visitor/MySQLPlanNodeVisitor.java new file mode 100644 index 000000000..cbbc12da6 --- /dev/null +++ b/src/main/java/io/mycat/plan/visitor/MySQLPlanNodeVisitor.java @@ -0,0 +1,348 @@ +package io.mycat.plan.visitor; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUnionOperator; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOrderingExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUnionQuery; + +import io.mycat.config.ErrorCode; +import io.mycat.plan.PlanNode; +import io.mycat.plan.common.exception.MySQLOutPutException; +import io.mycat.plan.common.item.Item; +import io.mycat.plan.common.item.ItemField; +import io.mycat.plan.common.item.function.operator.cmpfunc.ItemFuncEqual; +import io.mycat.plan.common.item.function.operator.logic.ItemCondAnd; +import io.mycat.plan.node.JoinNode; +import io.mycat.plan.node.MergeNode; +import io.mycat.plan.node.NoNameNode; +import io.mycat.plan.node.QueryNode; +import io.mycat.plan.node.TableNode; +import io.mycat.plan.util.FilterUtils; + +public class MySQLPlanNodeVisitor { + private PlanNode tableNode; + private String currentDb; + + public MySQLPlanNodeVisitor(String currentDb) { + this.currentDb = currentDb; + } + + public PlanNode getTableNode() { + return tableNode; + } + + public boolean visit(SQLSelectStatement node) { + SQLSelectQuery sqlSelect = node.getSelect().getQuery(); + MySQLPlanNodeVisitor mtv = new MySQLPlanNodeVisitor(this.currentDb); + mtv.visit(sqlSelect); + this.tableNode = mtv.getTableNode(); + return true; + } + public void visit(SQLSelectQuery node) { + if(node instanceof MySqlSelectQueryBlock){ + visit((MySqlSelectQueryBlock)node); + }else if(node instanceof MySqlUnionQuery){ + visit((MySqlUnionQuery)node); + } + } + public boolean visit(MySqlUnionQuery sqlSelectQuery) { + SQLSelectQuery left = sqlSelectQuery.getLeft(); + MySQLPlanNodeVisitor mtvleft = new MySQLPlanNodeVisitor(this.currentDb); + mtvleft.visit(left); + + SQLSelectQuery right = sqlSelectQuery.getRight(); + MySQLPlanNodeVisitor mtvright = new MySQLPlanNodeVisitor(this.currentDb); + mtvright.visit(right); + + SQLOrderBy orderBy = sqlSelectQuery.getOrderBy(); + Limit limit = sqlSelectQuery.getLimit(); + MergeNode mergeNode = new MergeNode(); + if (sqlSelectQuery.getOperator() != SQLUnionOperator.UNION) { + mergeNode.setUnion(true); + } + mergeNode.addChild(mtvleft.getTableNode()); + mergeNode.addChild(mtvright.getTableNode()); + this.tableNode = mergeNode; + if (orderBy != null) { + handleOrderBy(orderBy); + } + if (limit != null) { + handleLimit(limit); + } + return true; + } + + public boolean visit(MySqlSelectQueryBlock sqlSelectQuery) { + SQLTableSource from = sqlSelectQuery.getFrom(); + if (from != null) { + visit(from); + } else { + this.tableNode = new NoNameNode(currentDb, sqlSelectQuery.toString()); + } + + if (tableNode != null && (sqlSelectQuery.getDistionOption() != 0)) + this.tableNode.setDistinct(true); + + List items = sqlSelectQuery.getSelectList(); + if (items != null) { + List selectItems = handleSelectItems(items); + if (selectItems != null) { + this.tableNode.select(selectItems); + } + } + + SQLExpr whereExpr = sqlSelectQuery.getWhere(); + if (whereExpr != null) { + handleWhereCondition(whereExpr); + } + + SQLOrderBy orderBy = sqlSelectQuery.getOrderBy(); + if (orderBy != null) { + handleOrderBy(orderBy); + } + + SQLSelectGroupByClause groupBy = sqlSelectQuery.getGroupBy(); + if (groupBy != null) { + handleGroupBy(groupBy); + } + + Limit limit = sqlSelectQuery.getLimit(); + if (limit != null) { + handleLimit(limit); + } + return true; + } + + + public boolean visit(SQLExprTableSource tableSource) { + TableNode table = null; + SQLExpr expr = tableSource.getExpr(); + if (expr instanceof SQLPropertyExpr) { + SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr; + table = new TableNode(propertyExpr.getOwner().toString(), propertyExpr.getName()); + } else if (expr instanceof SQLIdentifierExpr) { + SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) expr; + table = new TableNode(this.currentDb, identifierExpr.getName()); + } + if (tableSource.getAlias() != null) { + table.setSubAlias(tableSource.getAlias()); + } + table.setHintList(tableSource.getHints()); + this.tableNode = table; + return true; + } + + public boolean visit(SQLJoinTableSource joinTables) { + SQLTableSource left = joinTables.getLeft(); + MySQLPlanNodeVisitor mtvLeft = new MySQLPlanNodeVisitor(this.currentDb); + mtvLeft.visit(left); + + SQLTableSource right = joinTables.getRight(); + MySQLPlanNodeVisitor mtvRight = new MySQLPlanNodeVisitor(this.currentDb); + mtvRight.visit(right); + JoinNode joinNode = new JoinNode(mtvLeft.getTableNode(), mtvRight.getTableNode()); + switch (joinTables.getJoinType()) { + case JOIN: + case CROSS_JOIN: + case INNER_JOIN: + joinNode.setInnerJoin(); + break; + case LEFT_OUTER_JOIN: + joinNode.setLeftOuterJoin(); + break; + case RIGHT_OUTER_JOIN: + joinNode.setRightOuterJoin(); + break; + case NATURAL_JOIN:// col? + break; + case STRAIGHT_JOIN: + break; + default: + break; + } + + SQLExpr cond = joinTables.getCondition(); + if (cond != null) { + MySQLItemVisitor ev = new MySQLItemVisitor(this.currentDb); + cond.accept(ev); + Item ifilter = ev.getItem(); + addJoinOnColumns(ifilter, joinNode); + } else if (joinTables.getUsing() != null && joinTables.getUsing().size() != 0) { + String lName = joinNode.getLeftNode().getCombinedName(); + String rName = joinNode.getRightNode().getCombinedName(); + List filterList = this.getUsingFilter(joinTables.getUsing(), lName, rName); + for (ItemFuncEqual filter : filterList){ + addJoinOnColumns(filter, joinNode); + } + } + this.tableNode = joinNode; + return true; + } + public boolean visit(SQLSubqueryTableSource subQueryTables) { + SQLSelect sqlSelect = subQueryTables.getSelect(); + visit(sqlSelect.getQuery()); + return true; + } + public boolean visit(SQLSelect node) { + MySQLPlanNodeVisitor mtv = new MySQLPlanNodeVisitor(this.currentDb); + mtv.visit(node); + this.tableNode = mtv.getTableNode(); + return true; + } + + public void visit(SQLTableSource tables) { + if (tables instanceof SQLExprTableSource) { + SQLExprTableSource table = (SQLExprTableSource) tables; + MySQLPlanNodeVisitor mtv = new MySQLPlanNodeVisitor(this.currentDb); + mtv.visit(table); + this.tableNode = mtv.getTableNode(); + } else if (tables instanceof SQLJoinTableSource) { + SQLJoinTableSource joinTables = (SQLJoinTableSource) tables; + MySQLPlanNodeVisitor mtv = new MySQLPlanNodeVisitor(this.currentDb); + mtv.visit(joinTables); + this.tableNode = mtv.getTableNode(); + } else if (tables instanceof SQLSubqueryTableSource) { + SQLSubqueryTableSource subQueryTables = (SQLSubqueryTableSource) tables; + MySQLPlanNodeVisitor mtv = new MySQLPlanNodeVisitor(this.currentDb); + mtv.visit(subQueryTables); + if (this.tableNode == null) { + this.tableNode = mtv.getTableNode(); + // 如果是第一个table,并且是唯一的一个,才做queryNode,因为如果多于两个可以通过joinNode来代替 + if (this.tableNode.isSubQuery() + && (this.tableNode instanceof TableNode || this.tableNode instanceof NoNameNode)) { + this.tableNode = new QueryNode(this.tableNode); + } + } else { + this.tableNode = new JoinNode(this.tableNode, mtv.getTableNode()); + } + } + } + + private List handleSelectItems(List items) { + List selectItems = new ArrayList(); + for (SQLSelectItem item : items) { + SQLExpr expr = item.getExpr(); + if (expr instanceof SQLQueryExpr) + throw new RuntimeException("query statement as column not supported!"); + MySQLItemVisitor ev = new MySQLItemVisitor(currentDb); + expr.accept(ev); + Item selItem = ev.getItem(); + + selItem.setAlias(item.getAlias()); + selectItems.add(selItem); + } + return selectItems; + } + + private void handleWhereCondition(SQLExpr whereExpr) { + MySQLItemVisitor mev = new MySQLItemVisitor(this.currentDb); + whereExpr.accept(mev); + if (this.tableNode != null) { + Item whereFiler = mev.getItem(); + tableNode.query(whereFiler); + // this.tableNode.setWhereFilter(tableNode.getWhereFilter()); + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "from expression is null,check the sql!"); + } + } + + private void handleOrderBy(SQLOrderBy orderBy) { + for (SQLSelectOrderByItem p : orderBy.getItems()) { + SQLExpr expr = p.getExpr(); + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + expr.accept(v); + this.tableNode = tableNode.orderBy(v.getItem(), p.getType()); + } + } + + private void handleGroupBy(SQLSelectGroupByClause groupBy) { + for (SQLExpr p : groupBy.getItems()) { + if (p instanceof MySqlOrderingExpr) { + MySqlOrderingExpr groupitem = (MySqlOrderingExpr) p; + SQLExpr q = groupitem.getExpr(); + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + q.accept(v); + this.tableNode = tableNode.groupBy(v.getItem(), groupitem.getType()); + } else { + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + p.accept(v); + this.tableNode = tableNode.groupBy(v.getItem(), SQLOrderingSpecification.ASC); + } + } + + if (groupBy.isWithRollUp()) { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "with rollup is not supported yet!"); + } + if (groupBy.getHaving() != null) { + handleHavingCondition(groupBy.getHaving()); + } + } + + private void handleHavingCondition(SQLExpr havingExpr) { + MySQLItemVisitor mev = new MySQLItemVisitor(currentDb); + havingExpr.accept(mev); + Item havingFilter = mev.getItem(); + if (this.tableNode == null) { + throw new IllegalArgumentException("from expression is null,check the sql!"); + } + this.tableNode = this.tableNode.having(havingFilter); + + } + + private void handleLimit(Limit limit) { + tableNode.setLimitFrom(((Number) limit.getOffset()).longValue()); + tableNode.setLimitTo(((Number) limit.getRowCount()).longValue()); + } + + private void addJoinOnColumns(Item ifilter, JoinNode joinNode) { + if (ifilter instanceof ItemFuncEqual) { + joinNode.addJoinFilter((ItemFuncEqual) ifilter); + } else if (ifilter instanceof ItemCondAnd) { + ItemCondAnd ilfand = (ItemCondAnd) ifilter; + List subFilter = ilfand.arguments(); + if (subFilter != null) { + for (Item arg : subFilter) { + Item orgOtherJoin = joinNode.getOtherJoinOnFilter(); + addJoinOnColumns(arg, joinNode); + joinNode.setOtherJoinOnFilter(FilterUtils.and(orgOtherJoin, joinNode.getOtherJoinOnFilter())); + } + } else { + throw new MySQLOutPutException(ErrorCode.ER_OPTIMIZER, "", "and has no other columns , " + ifilter); + } + + } else { + joinNode.setOtherJoinOnFilter(ifilter); + } + } + + private List getUsingFilter(List using, String leftJoinNode, String rightJoinNode) { + List filterList = new ArrayList(); + for (SQLExpr us : using) { + ItemField column1 = new ItemField(null, leftJoinNode, us.toString()); + ItemField column2 = new ItemField(null, rightJoinNode, us.toString()); + ItemFuncEqual arg = new ItemFuncEqual(column1, column2); + filterList.add(arg); + } + return filterList; + } +} diff --git a/src/main/java/io/mycat/route/RouteResultset.java b/src/main/java/io/mycat/route/RouteResultset.java index c67455442..c455764b0 100644 --- a/src/main/java/io/mycat/route/RouteResultset.java +++ b/src/main/java/io/mycat/route/RouteResultset.java @@ -26,7 +26,6 @@ package io.mycat.route; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import com.alibaba.druid.sql.ast.SQLStatement; @@ -43,10 +42,9 @@ public final class RouteResultset implements Serializable { private String statement; // 原始语句 private final int sqlType; private RouteResultsetNode[] nodes; // 路由结果节点 - private Set subTables; private SQLStatement sqlStatement; - + private boolean needOptimizer; private int limitStart; private boolean cacheAble; // used to store table's ID->datanodes cache @@ -61,11 +59,14 @@ public final class RouteResultset implements Serializable { // 是否为全局表,只有在insert、update、delete、ddl里会判断并修改。默认不是全局表,用于修正全局表修改数据的反馈。 private boolean globalTableFlag = false; - //是否完成了路由 - private boolean isFinishedRoute = false; + // 是否完成了路由 + private boolean isFinishedRoute = false; + + // 是否完成了执行 + private boolean isFinishedExecute = false; //是否自动提交,此属性主要用于记录ServerConnection上的autocommit状态 - private boolean autocommit = true; + private boolean autocommit = true; private boolean isLoadData=false; @@ -83,6 +84,14 @@ public final class RouteResultset implements Serializable { return session; } + public boolean isNeedOptimizer() { + return needOptimizer; + } + + public void setNeedOptimizer(boolean needOptimizer) { + this.needOptimizer = needOptimizer; + } + public Boolean getRunOnSlave() { return runOnSlave; } @@ -112,6 +121,13 @@ public final class RouteResultset implements Serializable { this.isLoadData = isLoadData; } + public boolean isFinishedExecute() { + return isFinishedExecute; + } + + public void setFinishedExecute(boolean isFinishedExecute) { + this.isFinishedExecute = isFinishedExecute; + } public boolean isFinishedRoute() { return isFinishedRoute; } @@ -235,24 +251,28 @@ public final class RouteResultset implements Serializable { public void setOrderByCols(LinkedHashMap orderByCols) { if (orderByCols != null && !orderByCols.isEmpty()) { createSQLMergeIfNull().setOrderByCols(orderByCols); +// this.setNeedOptimizer(true); } } public void setHasAggrColumn(boolean hasAggrColumn) { if (hasAggrColumn) { createSQLMergeIfNull().setHasAggrColumn(true); +// this.setNeedOptimizer(true); } } public void setGroupByCols(String[] groupByCols) { if (groupByCols != null && groupByCols.length > 0) { createSQLMergeIfNull().setGroupByCols(groupByCols); +// this.setNeedOptimizer(true); } } public void setMergeCols(Map mergeCols) { if (mergeCols != null && !mergeCols.isEmpty()) { createSQLMergeIfNull().setMergeCols(mergeCols); +// this.setNeedOptimizer(true); } } @@ -344,10 +364,7 @@ public final class RouteResultset implements Serializable { return (sqlMerge != null) ? sqlMerge.getHavingCols() : null; } - public void setSubTables(Set subTables) { - this.subTables = subTables; - } - + public void setHavings(HavingCols havings) { if (havings != null) { createSQLMergeIfNull().setHavingCols(havings); @@ -370,16 +387,6 @@ public final class RouteResultset implements Serializable { this.sqlStatement = sqlStatement; } - public Set getSubTables() { - return this.subTables; - } - - public boolean isDistTable(){ - if(this.getSubTables()!=null && !this.getSubTables().isEmpty() ){ - return true; - } - return false; - } @Override public String toString() { diff --git a/src/main/java/io/mycat/route/RouteResultsetNode.java b/src/main/java/io/mycat/route/RouteResultsetNode.java index 8b6ec196b..c8c8a8ee0 100644 --- a/src/main/java/io/mycat/route/RouteResultsetNode.java +++ b/src/main/java/io/mycat/route/RouteResultsetNode.java @@ -25,6 +25,7 @@ package io.mycat.route; import java.io.Serializable; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import io.mycat.server.parser.ServerParse; import io.mycat.sqlengine.mpp.LoadData; @@ -54,8 +55,7 @@ public final class RouteResultsetNode implements Serializable , Comparable nodeSet = new TreeSet(); - for(RouteCalculateUnit unit: druidParser.getCtx().getRouteCalculateUnits()) { - RouteResultset rrsTmp = RouterUtil.tryRouteForTables(schema, druidParser.getCtx(), unit, rrs, isSelect(statement), cachePool); - if(rrsTmp != null) { - for(RouteResultsetNode node :rrsTmp.getNodes()) { - nodeSet.add(node); - } - } - } - - RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSet.size()]; - int i = 0; - for (RouteResultsetNode aNodeSet : nodeSet) { - nodes[i] = aNodeSet; - i++; - } - rrs.setNodes(nodes); - - //分表 - /** - * subTables="t_order$1-2,t_order3" - *目前分表 1.6 开始支持dataNode 在分表条件下只能配置一个,分表条件下不支持join。 - */ - if(rrs.isDistTable()){ - return this.routeDisTable(statement,rrs); - } - - return rrs; } - - private SQLExprTableSource getDisTable(SQLTableSource tableSource,RouteResultsetNode node) throws SQLSyntaxErrorException{ - if(node.getSubTableName()==null){ - String msg = " sub table not exists for " + node.getName() + " on " + tableSource; - LOGGER.error("DruidMycatRouteStrategyError " + msg); - throw new SQLSyntaxErrorException(msg); - } - - SQLIdentifierExpr sqlIdentifierExpr = new SQLIdentifierExpr(); - sqlIdentifierExpr.setParent(tableSource.getParent()); - sqlIdentifierExpr.setName(node.getSubTableName()); - SQLExprTableSource from2 = new SQLExprTableSource(sqlIdentifierExpr); - return from2; - } - - private RouteResultset routeDisTable(SQLStatement statement, RouteResultset rrs) throws SQLSyntaxErrorException{ - SQLTableSource tableSource = null; - if(statement instanceof SQLInsertStatement) { - SQLInsertStatement insertStatement = (SQLInsertStatement) statement; - tableSource = insertStatement.getTableSource(); - for (RouteResultsetNode node : rrs.getNodes()) { - SQLExprTableSource from2 = getDisTable(tableSource, node); - insertStatement.setTableSource(from2); - node.setStatement(insertStatement.toString()); - } - } - if(statement instanceof SQLDeleteStatement) { - SQLDeleteStatement deleteStatement = (SQLDeleteStatement) statement; - tableSource = deleteStatement.getTableSource(); - for (RouteResultsetNode node : rrs.getNodes()) { - SQLExprTableSource from2 = getDisTable(tableSource, node); - deleteStatement.setTableSource(from2); - node.setStatement(deleteStatement.toString()); - } - } - if(statement instanceof SQLUpdateStatement) { - SQLUpdateStatement updateStatement = (SQLUpdateStatement) statement; - tableSource = updateStatement.getTableSource(); - for (RouteResultsetNode node : rrs.getNodes()) { - SQLExprTableSource from2 = getDisTable(tableSource, node); - updateStatement.setTableSource(from2); - node.setStatement(updateStatement.toString()); - } - } - - return rrs; - } - - /** - * SELECT 语句 - */ - private boolean isSelect(SQLStatement statement) { - if(statement instanceof SQLSelectStatement) { - return true; - } - return false; - } /** * 检验不支持的SQLStatement类型 :不支持的类型直接抛SQLSyntaxErrorException异常 @@ -346,11 +228,6 @@ public class DruidMycatRouteStrategy extends AbstractRouteStrategy { switch(sqlType){ case ServerParse.SHOW:// if origSQL is like show tables return analyseShowSQL(schema, rrs, stmt); - case ServerParse.SELECT://if origSQL is like select @@ - if(stmt.contains("@@")){ - return analyseDoubleAtSgin(schema, rrs, stmt); - } - break; case ServerParse.DESCRIBE:// if origSQL is meta SQL, such as describe table int ind = stmt.indexOf(' '); stmt = stmt.trim(); @@ -409,24 +286,4 @@ public class DruidMycatRouteStrategy extends AbstractRouteStrategy { RouterUtil.routeForTableMeta(rrs, schema, tableName, stmt); return rrs; } - - /** - * 根据执行语句判断数据路由 - * - * @param schema 数据库名 - * @param rrs 数据路由集合 - * @param stmt 执行sql - * @return RouteResultset 数据路由集合 - * @throws SQLSyntaxErrorException - * @author mycat - */ - private RouteResultset analyseDoubleAtSgin(SchemaConfig schema, - RouteResultset rrs, String stmt) throws SQLSyntaxErrorException { - String upStmt = stmt.toUpperCase(); - int atSginInd = upStmt.indexOf(" @@"); - if (atSginInd > 0) { - return RouterUtil.routeToMultiNode(false, rrs, schema.getMetaDataNodes(), stmt); - } - return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), stmt); - } } diff --git a/src/main/java/io/mycat/route/parser/druid/DruidParser.java b/src/main/java/io/mycat/route/parser/druid/DruidParser.java index 8158e1313..42b3f371e 100644 --- a/src/main/java/io/mycat/route/parser/druid/DruidParser.java +++ b/src/main/java/io/mycat/route/parser/druid/DruidParser.java @@ -22,21 +22,14 @@ public interface DruidParser { * @param schema * @param stmt */ - public void parser(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, String originSql,LayerCachePool cachePool,MycatSchemaStatVisitor schemaStatVisitor) throws SQLNonTransientException; + public SchemaConfig parser(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, String originSql,LayerCachePool cachePool,MycatSchemaStatVisitor schemaStatVisitor) throws SQLNonTransientException; - /** - * statement方式解析 - * 子类可覆盖(如果visitorParse解析得不到表名、字段等信息的,就通过覆盖该方法来解析) - * 子类覆盖该方法一般是将SQLStatement转型后再解析(如转型为MySqlInsertStatement) - */ - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException; - /** * 子类可覆盖(如果该方法解析得不到表名、字段等信息的,就覆盖该方法,覆盖成空方法,然后通过statementPparse去解析) * 通过visitor解析:有些类型的Statement通过visitor解析得不到表名、 * @param stmt */ - public void visitorParse(RouteResultset rrs, SQLStatement stmt,MycatSchemaStatVisitor visitor) throws SQLNonTransientException; + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt,MycatSchemaStatVisitor visitor) throws SQLNonTransientException; /** * 改写sql:加limit,加group by、加order by如有些没有加limit的可以通过该方法增加 diff --git a/src/main/java/io/mycat/route/parser/druid/DruidParserFactory.java b/src/main/java/io/mycat/route/parser/druid/DruidParserFactory.java index 718508ab6..097407531 100644 --- a/src/main/java/io/mycat/route/parser/druid/DruidParserFactory.java +++ b/src/main/java/io/mycat/route/parser/druid/DruidParserFactory.java @@ -15,9 +15,7 @@ import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlLockTableStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement; -import com.alibaba.druid.sql.visitor.SchemaStatVisitor; -import io.mycat.config.model.SchemaConfig; import io.mycat.route.parser.druid.impl.DefaultDruidParser; import io.mycat.route.parser.druid.impl.DruidDeleteParser; import io.mycat.route.parser.druid.impl.DruidInsertParser; @@ -38,7 +36,7 @@ import io.mycat.route.parser.druid.impl.ddl.DruidTruncateTableParser; */ public class DruidParserFactory { - public static DruidParser create(SchemaConfig schema, SQLStatement statement, SchemaStatVisitor visitor) + public static DruidParser create(SQLStatement statement) throws SQLNonTransientException { DruidParser parser = null; if (statement instanceof SQLSelectStatement) { diff --git a/src/main/java/io/mycat/route/parser/druid/DruidShardingParseInfo.java b/src/main/java/io/mycat/route/parser/druid/DruidShardingParseInfo.java index 203164278..2a5e36984 100644 --- a/src/main/java/io/mycat/route/parser/druid/DruidShardingParseInfo.java +++ b/src/main/java/io/mycat/route/parser/druid/DruidShardingParseInfo.java @@ -1,17 +1,9 @@ package io.mycat.route.parser.druid; import java.util.ArrayList; -import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; - -import com.alibaba.druid.sql.visitor.SchemaStatVisitor; - -import io.mycat.sqlengine.mpp.ColumnRoutePair; -import io.mycat.sqlengine.mpp.RangeValue; /** * druid parser result @@ -19,10 +11,6 @@ import io.mycat.sqlengine.mpp.RangeValue; * */ public class DruidShardingParseInfo { - /** - * 一个sql中可能有多个WhereUnit(如子查询中的where可能导致多个) - */ - private List whereUnits = new ArrayList(); private List routeCalculateUnits = new ArrayList(); @@ -41,8 +29,6 @@ public class DruidShardingParseInfo { */ private Map tableAliasMap = new LinkedHashMap(); - private SchemaStatVisitor visitor; - public Map getTableAliasMap() { return tableAliasMap; } @@ -90,14 +76,5 @@ public class DruidShardingParseInfo { } } - public void setVisitor(SchemaStatVisitor visitor) { - - this.visitor = visitor; - } - - public SchemaStatVisitor getVisitor(){ - - return this.visitor; - } } diff --git a/src/main/java/io/mycat/route/parser/druid/MycatSchemaStatVisitor.java b/src/main/java/io/mycat/route/parser/druid/MycatSchemaStatVisitor.java index 3619a02d3..1dcdff25a 100644 --- a/src/main/java/io/mycat/route/parser/druid/MycatSchemaStatVisitor.java +++ b/src/main/java/io/mycat/route/parser/druid/MycatSchemaStatVisitor.java @@ -57,8 +57,6 @@ public class MycatSchemaStatVisitor extends MySqlSchemaStatVisitor { @Override public boolean visit(SQLSelectStatement x) { setAliasMap(); -// getAliasMap().put("DUAL", null); - return true; } diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DefaultDruidParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DefaultDruidParser.java index a62784a86..720d13041 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/DefaultDruidParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/DefaultDruidParser.java @@ -38,44 +38,24 @@ public class DefaultDruidParser implements DruidParser { */ protected DruidShardingParseInfo ctx; - private Map tableAliasMap = new HashMap(); - - private List conditions = new ArrayList(); - - public Map getTableAliasMap() { - return tableAliasMap; - } - - public List getConditions() { - return conditions; - } - /** * 使用MycatSchemaStatVisitor解析,得到tables、tableAliasMap、conditions等 * @param schema * @param stmt */ - public void parser(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, String originSql,LayerCachePool cachePool,MycatSchemaStatVisitor schemaStatVisitor) throws SQLNonTransientException { + public SchemaConfig parser(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, String originSql,LayerCachePool cachePool,MycatSchemaStatVisitor schemaStatVisitor) throws SQLNonTransientException { ctx = new DruidShardingParseInfo(); //设置为原始sql,如果有需要改写sql的,可以通过修改SQLStatement中的属性,然后调用SQLStatement.toString()得到改写的sql ctx.setSql(originSql); //通过visitor解析 - visitorParse(rrs,stmt,schemaStatVisitor); - //通过Statement解析 - statementParse(schema, rrs, stmt); + schema = visitorParse(schema, rrs,stmt,schemaStatVisitor); //改写sql:如insert语句主键自增长的可以 changeSql(schema, rrs, stmt,cachePool); + return schema; } - /** - * 子类可覆盖(如果visitorParse解析得不到表名、字段等信息的,就通过覆盖该方法来解析) - * 子类覆盖该方法一般是将SQLStatement转型后再解析(如转型为MySqlInsertStatement) - */ - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException { - - } + /** * 改写sql:如insert是 @@ -92,7 +72,7 @@ public class DefaultDruidParser implements DruidParser { * @param stmt */ @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { stmt.accept(visitor); List> mergedConditionList = new ArrayList>(); @@ -103,7 +83,7 @@ public class DefaultDruidParser implements DruidParser { } else {// 不包含OR语句 mergedConditionList.add(visitor.getConditions()); } - + Map tableAliasMap = new HashMap(); if (visitor.getAliasMap() != null) { for (Map.Entry entry : visitor.getAliasMap().entrySet()) { String key = entry.getKey(); @@ -116,14 +96,18 @@ public class DefaultDruidParser implements DruidParser { } // 表名前面带database的,去掉 if (key != null) { + boolean needAddTable = false; + if (key.equalsIgnoreCase(value)) { + needAddTable = true; + } int pos = key.indexOf("."); if (pos > 0) { key = key.substring(pos + 1); } - if (key.equalsIgnoreCase(value)) { - ctx.addTable(key.toUpperCase()); + if(needAddTable){ + ctx.addTable(key); } - tableAliasMap.put(key.toUpperCase(), value); + tableAliasMap.put(key, value); } // else { // tableAliasMap.put(key, value); @@ -133,7 +117,7 @@ public class DefaultDruidParser implements DruidParser { ctx.setTableAliasMap(tableAliasMap); } ctx.setRouteCalculateUnits(this.buildRouteCalculateUnits(visitor, mergedConditionList)); - ctx.setVisitor(visitor); + return schema; } private List buildRouteCalculateUnits(SchemaStatVisitor visitor, List> conditionList) { @@ -148,14 +132,14 @@ public class DefaultDruidParser implements DruidParser { } if(checkConditionValues(values)) { String columnName = StringUtil.removeBackquote(condition.getColumn().getName().toUpperCase()); - String tableName = StringUtil.removeBackquote(condition.getColumn().getTable().toUpperCase()); + String tableName = StringUtil.removeBackquote(condition.getColumn().getTable()); if(visitor.getAliasMap() != null && visitor.getAliasMap().get(tableName) != null && !visitor.getAliasMap().get(tableName).equals(tableName)) { tableName = visitor.getAliasMap().get(tableName); } - if(visitor.getAliasMap() != null && visitor.getAliasMap().get(StringUtil.removeBackquote(condition.getColumn().getTable().toUpperCase())) == null) {//子查询的别名条件忽略掉,不参数路由计算,否则后面找不到表 + if(visitor.getAliasMap() != null && visitor.getAliasMap().get(StringUtil.removeBackquote(condition.getColumn().getTable())) == null) {//子查询的别名条件忽略掉,不参数路由计算,否则后面找不到表 continue; } @@ -164,9 +148,9 @@ public class DefaultDruidParser implements DruidParser { //只处理between ,in和=3中操作符 if(operator.equals("between")) { RangeValue rv = new RangeValue(values.get(0), values.get(1), RangeValue.EE); - routeCalculateUnit.addShardingExpr(tableName.toUpperCase(), columnName, rv); + routeCalculateUnit.addShardingExpr(tableName, columnName, rv); } else if(operator.equals("=") || operator.toLowerCase().equals("in")){ //只处理=号和in操作符,其他忽略 - routeCalculateUnit.addShardingExpr(tableName.toUpperCase(), columnName, values.toArray()); + routeCalculateUnit.addShardingExpr(tableName, columnName, values.toArray()); } } } diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DruidBaseSelectParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DruidBaseSelectParser.java new file mode 100644 index 000000000..5dd6459e7 --- /dev/null +++ b/src/main/java/io/mycat/route/parser/druid/impl/DruidBaseSelectParser.java @@ -0,0 +1,501 @@ +package io.mycat.route.parser.druid.impl; + +import java.sql.SQLNonTransientException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLName; +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLTextLiteralExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOrderingExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUnionQuery; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; + +import io.mycat.MycatServer; +import io.mycat.cache.LayerCachePool; +import io.mycat.config.MycatPrivileges; +import io.mycat.config.MycatPrivileges.Checktype; +import io.mycat.config.model.SchemaConfig; +import io.mycat.route.RouteResultset; +import io.mycat.route.RouteResultsetNode; +import io.mycat.route.parser.druid.MycatSchemaStatVisitor; +import io.mycat.route.parser.druid.RouteCalculateUnit; +import io.mycat.route.util.RouterUtil; +import io.mycat.server.handler.MysqlInformationSchemaHandler; +import io.mycat.server.handler.MysqlProcHandler; +import io.mycat.server.response.InformationSchemaProfiling; +import io.mycat.server.util.SchemaUtil; +import io.mycat.server.util.SchemaUtil.SchemaInfo; +import io.mycat.sqlengine.mpp.HavingCols; +import io.mycat.sqlengine.mpp.MergeCol; +import io.mycat.sqlengine.mpp.OrderCol; +import io.mycat.util.ObjectUtil; +import io.mycat.util.StringUtil; + +public class DruidBaseSelectParser extends DefaultDruidParser { + + protected boolean isNeedParseOrderAgg = true; + + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, + MycatSchemaStatVisitor visitor) throws SQLNonTransientException { + SQLSelectStatement selectStmt = (SQLSelectStatement) stmt; + SQLSelectQuery sqlSelectQuery = selectStmt.getSelect().getQuery(); + if (sqlSelectQuery instanceof MySqlSelectQueryBlock) { + MySqlSelectQueryBlock mysqlSelectQuery = (MySqlSelectQueryBlock) selectStmt.getSelect().getQuery(); + SQLTableSource mysqlFrom = mysqlSelectQuery.getFrom(); + if (mysqlFrom == null) { + String db = SchemaUtil.getRandomDb(); + if (db == null) { + String msg = "No schema is configured, make sure your config is right, sql:" + stmt; + throw new SQLNonTransientException(msg); + } + schema = MycatServer.getInstance().getConfig().getSchemas().get(db); + rrs = RouterUtil.routeToMultiNode(false, rrs, schema.getMetaDataNodes(), rrs.getStatement()); + rrs.setFinishedRoute(true); + return schema; + } + if (mysqlFrom instanceof SQLSubqueryTableSource || mysqlFrom instanceof SQLJoinTableSource + || mysqlFrom instanceof SQLUnionQueryTableSource) { + rrs.setSqlStatement(stmt); + rrs.setNeedOptimizer(true); + rrs.setFinishedRoute(true); + return schema; + } + String schemaName = schema == null ? null : schema.getName(); + SQLExprTableSource fromSource = (SQLExprTableSource) mysqlFrom; + SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, fromSource); + if (schemaInfo == null) { + String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; + throw new SQLNonTransientException(msg); + } + // 兼容PhpAdmin's, 支持对MySQL元数据的模拟返回 + if (SchemaUtil.INFORMATION_SCHEMA.equalsIgnoreCase(schemaInfo.schema)) { + MysqlInformationSchemaHandler.handle(schemaInfo, rrs.getSession().getSource()); + rrs.setFinishedExecute(true); + return schema; + } + + if (SchemaUtil.MYSQL_SCHEMA.equalsIgnoreCase(schemaInfo.schema) + && SchemaUtil.TABLE_PROC.equalsIgnoreCase(schemaInfo.table)) { + // 兼容MySQLWorkbench + MysqlProcHandler.handle(rrs.getStatement(), rrs.getSession().getSource()); + rrs.setFinishedExecute(true); + return schema; + } + // fix navicat SELECT STATE AS `State`, ROUND(SUM(DURATION),7) AS + // `Duration`, CONCAT(ROUND(SUM(DURATION)/*100,3), '%') AS + // `Percentage` FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID= + // GROUP BY STATE ORDER BY SEQ + if (SchemaUtil.INFORMATION_SCHEMA.equalsIgnoreCase(schemaInfo.schema) + && SchemaUtil.TABLE_PROFILING.equalsIgnoreCase(schemaInfo.table) + && rrs.getStatement().toUpperCase().contains("CONCAT(ROUND(SUM(DURATION)/*100,3)")) { + InformationSchemaProfiling.response(rrs.getSession().getSource()); + rrs.setFinishedExecute(true); + return schema; + } + if (schemaInfo.schemaConfig == null) { + String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; + throw new SQLNonTransientException(msg); + } + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); + if (!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.SELECT)) { + String msg = "The statement DML privilege check is not passed, sql:" + stmt; + throw new SQLNonTransientException(msg); + } + ctx.setSql(rrs.getStatement()); + if (fromSource.getExpr() instanceof SQLPropertyExpr) { + fromSource.setExpr(new SQLIdentifierExpr(schemaInfo.table)); + } + schema = schemaInfo.schemaConfig; + super.visitorParse(schema, rrs, stmt, visitor); + parseOrderAggGroupMysql(schema, stmt, rrs, mysqlSelectQuery); + // 更改canRunInReadDB属性 + if ((mysqlSelectQuery.isForUpdate() || mysqlSelectQuery.isLockInShareMode()) + && rrs.isAutocommit() == false) { + rrs.setCanRunInReadDB(false); + } + } else if (sqlSelectQuery instanceof MySqlUnionQuery) { + rrs.setSqlStatement(stmt); + rrs.setNeedOptimizer(true); + rrs.setFinishedRoute(true); + } + return schema; + } + + protected void parseOrderAggGroupMysql(SchemaConfig schema, SQLStatement stmt, RouteResultset rrs, + MySqlSelectQueryBlock mysqlSelectQuery) { + MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); + stmt.accept(visitor); + if (!isNeedParseOrderAgg) { + return; + } + Map aliaColumns = parseAggGroupCommon(schema, stmt, rrs, mysqlSelectQuery); + + // setOrderByCols + if (mysqlSelectQuery.getOrderBy() != null) { + List orderByItems = mysqlSelectQuery.getOrderBy().getItems(); + rrs.setOrderByCols(buildOrderByCols(orderByItems, aliaColumns)); + } + isNeedParseOrderAgg = false; + } + + protected Map parseAggGroupCommon(SchemaConfig schema, SQLStatement stmt, RouteResultset rrs, + SQLSelectQueryBlock mysqlSelectQuery) { + Map aliaColumns = new HashMap(); + Map aggrColumns = new HashMap(); + // Added by winbill, 20160314, for having clause, Begin ==> + List havingColsName = new ArrayList(); + // Added by winbill, 20160314, for having clause, End <== + List selectList = mysqlSelectQuery.getSelectList(); + boolean isNeedChangeSql = false; + int size = selectList.size(); + boolean isDistinct = mysqlSelectQuery.getDistionOption() == 2; + for (int i = 0; i < size; i++) { + SQLSelectItem item = selectList.get(i); + if (item.getExpr() instanceof SQLAggregateExpr) { + SQLAggregateExpr expr = (SQLAggregateExpr) item.getExpr(); + String method = expr.getMethodName(); + boolean isHasArgument = !expr.getArguments().isEmpty(); + if (isHasArgument) { + String aggrColName = method + "(" + expr.getArguments().get(0) + ")"; // Added + // by + // winbill, + // 20160314, + // for + // having + // clause + havingColsName.add(aggrColName); // Added by winbill, + // 20160314, for having + // clause + } + // 只处理有别名的情况,无别名添加别名,否则某些数据库会得不到正确结果处理 + int mergeType = MergeCol.getMergeType(method); + if (MergeCol.MERGE_AVG == mergeType && isRoutMultiNode(schema, rrs)) { // 跨分片avg需要特殊处理,直接avg结果是不对的 + String colName = item.getAlias() != null ? item.getAlias() : method + i; + SQLSelectItem sum = new SQLSelectItem(); + String sumColName = colName + "SUM"; + sum.setAlias(sumColName); + SQLAggregateExpr sumExp = new SQLAggregateExpr("SUM"); + ObjectUtil.copyProperties(expr, sumExp); + sumExp.getArguments().addAll(expr.getArguments()); + sumExp.setMethodName("SUM"); + sum.setExpr(sumExp); + selectList.set(i, sum); + aggrColumns.put(sumColName, MergeCol.MERGE_SUM); + havingColsName.add(sumColName); // Added by winbill, + // 20160314, for having + // clause + havingColsName.add(item.getAlias() != null ? item.getAlias() : ""); // Added + // by + // winbill, + // 20160314, + // two + // aliases + // for + // AVG + + SQLSelectItem count = new SQLSelectItem(); + String countColName = colName + "COUNT"; + count.setAlias(countColName); + SQLAggregateExpr countExp = new SQLAggregateExpr("COUNT"); + ObjectUtil.copyProperties(expr, countExp); + countExp.getArguments().addAll(expr.getArguments()); + countExp.setMethodName("COUNT"); + count.setExpr(countExp); + selectList.add(count); + aggrColumns.put(countColName, MergeCol.MERGE_COUNT); + + isNeedChangeSql = true; + aggrColumns.put(colName, mergeType); + rrs.setHasAggrColumn(true); + } else if (MergeCol.MERGE_UNSUPPORT != mergeType) { + if (item.getAlias() != null && item.getAlias().length() > 0) { + aggrColumns.put(item.getAlias(), mergeType); + } else { // 如果不加,jdbc方式时取不到正确结果 ;修改添加别名 + item.setAlias(method + i); + aggrColumns.put(method + i, mergeType); + isNeedChangeSql = true; + } + rrs.setHasAggrColumn(true); + havingColsName.add(item.getAlias()); // Added by winbill, + // 20160314, for + // having clause + havingColsName.add(""); // Added by winbill, 20160314, one + // alias for non-AVG + } + } else { + if (!(item.getExpr() instanceof SQLAllColumnExpr)) { + String alia = item.getAlias(); + String field = getFieldName(item); + if (alia == null) { + alia = field; + } + aliaColumns.put(field, alia); + } + } + + } + if (aggrColumns.size() > 0) { + rrs.setMergeCols(aggrColumns); + } + + // 通过优化转换成group by来实现 + if (isDistinct) { + mysqlSelectQuery.setDistionOption(0); + SQLSelectGroupByClause groupBy = new SQLSelectGroupByClause(); + for (String fieldName : aliaColumns.keySet()) { + groupBy.addItem(new SQLIdentifierExpr(fieldName)); + } + mysqlSelectQuery.setGroupBy(groupBy); + isNeedChangeSql = true; + } + + // setGroupByCols + if (mysqlSelectQuery.getGroupBy() != null) { + List groupByItems = mysqlSelectQuery.getGroupBy().getItems(); + String[] groupByCols = buildGroupByCols(groupByItems, aliaColumns); + rrs.setGroupByCols(groupByCols); + rrs.setHavings(buildGroupByHaving(mysqlSelectQuery.getGroupBy().getHaving())); + rrs.setHasAggrColumn(true); + rrs.setHavingColsName(havingColsName.toArray()); // Added by + // winbill, + // 20160314, for + // having clause + } + + if (isNeedChangeSql) { + String sql = stmt.toString(); + rrs.changeNodeSqlAfterAddLimit(schema, sql, 0, -1); + getCtx().setSql(sql); + } + return aliaColumns; + } + + private HavingCols buildGroupByHaving(SQLExpr having) { + if (having == null) { + return null; + } + + SQLBinaryOpExpr expr = ((SQLBinaryOpExpr) having); + SQLExpr left = expr.getLeft(); + SQLBinaryOperator operator = expr.getOperator(); + SQLExpr right = expr.getRight(); + + String leftValue = null; + ; + if (left instanceof SQLAggregateExpr) { + leftValue = ((SQLAggregateExpr) left).getMethodName() + "(" + + ((SQLAggregateExpr) left).getArguments().get(0) + ")"; + } else if (left instanceof SQLIdentifierExpr) { + leftValue = ((SQLIdentifierExpr) left).getName(); + } + + String rightValue = null; + if (right instanceof SQLNumericLiteralExpr) { + rightValue = right.toString(); + } else if (right instanceof SQLTextLiteralExpr) { + rightValue = StringUtil.removeBackquote(right.toString()); + } + + return new HavingCols(leftValue, rightValue, operator.getName()); + } + + protected boolean isRoutMultiNode(SchemaConfig schema, RouteResultset rrs) { + if (rrs.getNodes() != null && rrs.getNodes().length > 1) { + return true; + } + LayerCachePool tableId2DataNodeCache = (LayerCachePool) MycatServer.getInstance().getCacheService() + .getCachePool("TableID2DataNodeCache"); + try { + tryRoute(schema, rrs, tableId2DataNodeCache); + if (rrs.getNodes() != null && rrs.getNodes().length > 1) { + return true; + } + } catch (SQLNonTransientException e) { + throw new RuntimeException(e); + } + return false; + } + + private String getFieldName(SQLSelectItem item) { + if ((item.getExpr() instanceof SQLPropertyExpr) || (item.getExpr() instanceof SQLMethodInvokeExpr) + || (item.getExpr() instanceof SQLIdentifierExpr) || item.getExpr() instanceof SQLBinaryOpExpr) { + return item.getExpr().toString();// 字段别名 + } else { + return item.toString(); + } + } + + protected void tryRoute(SchemaConfig schema, RouteResultset rrs, LayerCachePool cachePool) + throws SQLNonTransientException { + if (rrs.isFinishedRoute()) { + return;// 避免重复路由 + } + + // 无表的select语句直接路由带任一节点 + if ((ctx.getTables() == null || ctx.getTables().size() == 0) + && (ctx.getTableAliasMap() == null || ctx.getTableAliasMap().isEmpty())) { + rrs = RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), ctx.getSql()); + rrs.setFinishedRoute(true); + return; + } + // RouterUtil.tryRouteForTables(schema, ctx, rrs, true, cachePool); + SortedSet nodeSet = new TreeSet(); + boolean isAllGlobalTable = RouterUtil.isAllGlobalTable(ctx, schema); + for (RouteCalculateUnit unit : ctx.getRouteCalculateUnits()) { + RouteResultset rrsTmp = RouterUtil.tryRouteForTables(schema, ctx, unit, rrs, true, cachePool); + if (rrsTmp != null && rrsTmp.getNodes() != null) { + for (RouteResultsetNode node : rrsTmp.getNodes()) { + nodeSet.add(node); + } + } + if (isAllGlobalTable) {// 都是全局表时只计算一遍路由 + break; + } + } + + if (nodeSet.size() == 0) { + + Collection stringCollection = ctx.getTableAliasMap().values(); + for (String table : stringCollection) { + if (table != null && table.toLowerCase().contains("information_schema.")) { + rrs = RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), ctx.getSql()); + rrs.setFinishedRoute(true); + return; + } + } + String msg = " find no Route:" + ctx.getSql(); + LOGGER.warn(msg); + throw new SQLNonTransientException(msg); + } + + RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSet.size()]; + int i = 0; + for (Iterator iterator = nodeSet.iterator(); iterator.hasNext();) { + nodes[i] = (RouteResultsetNode) iterator.next(); + i++; + + } + + rrs.setNodes(nodes); + rrs.setFinishedRoute(true); + } + + protected String getAliaColumn(Map aliaColumns, String column) { + String alia = aliaColumns.get(column); + if (alia == null) { + if (column.indexOf(".") < 0) { + String col = "." + column; + String col2 = ".`" + column + "`"; + // 展开aliaColumns,将之类的键值对展开成 + for (Map.Entry entry : aliaColumns.entrySet()) { + if (entry.getKey().endsWith(col) || entry.getKey().endsWith(col2)) { + if (entry.getValue() != null && entry.getValue().indexOf(".") > 0) { + return column; + } + return entry.getValue(); + } + } + } + + return column; + } else { + return alia; + } + } + + private String[] buildGroupByCols(List groupByItems, Map aliaColumns) { + String[] groupByCols = new String[groupByItems.size()]; + for (int i = 0; i < groupByItems.size(); i++) { + SQLExpr sqlExpr = groupByItems.get(i); + String column = null; + if (sqlExpr instanceof SQLIdentifierExpr) { + column = ((SQLIdentifierExpr) sqlExpr).getName(); + } else if (sqlExpr instanceof SQLMethodInvokeExpr) { + column = ((SQLMethodInvokeExpr) sqlExpr).toString(); + } else if (sqlExpr instanceof MySqlOrderingExpr) { + // todo czn + SQLExpr expr = ((MySqlOrderingExpr) sqlExpr).getExpr(); + + if (expr instanceof SQLName) { + column = StringUtil.removeBackquote(((SQLName) expr).getSimpleName());// 不要转大写 + // 2015-2-10 + // sohudo + // StringUtil.removeBackquote(expr.getSimpleName().toUpperCase()); + } else { + column = StringUtil.removeBackquote(expr.toString()); + } + } else if (sqlExpr instanceof SQLPropertyExpr) { + /** + * 针对子查询别名,例如select id from (select h.id from hotnews h union + * select h.title from hotnews h ) as t1 group by t1.id; + */ + column = sqlExpr.toString(); + } + if (column == null) { + column = sqlExpr.toString(); + } + int dotIndex = column.indexOf("."); + int bracketIndex = column.indexOf("("); + // 通过判断含有括号来决定是否为函数列 + if (dotIndex != -1 && bracketIndex == -1) { + // 此步骤得到的column必须是不带.的,有别名的用别名,无别名的用字段名 + column = column.substring(dotIndex + 1); + } + groupByCols[i] = getAliaColumn(aliaColumns, column);// column; + } + return groupByCols; + } + + protected LinkedHashMap buildOrderByCols(List orderByItems, + Map aliaColumns) { + LinkedHashMap map = new LinkedHashMap(); + for (int i = 0; i < orderByItems.size(); i++) { + SQLOrderingSpecification type = orderByItems.get(i).getType(); + // orderColumn只记录字段名称,因为返回的结果集是不带表名的。 + SQLExpr expr = orderByItems.get(i).getExpr(); + String col; + if (expr instanceof SQLName) { + col = ((SQLName) expr).getSimpleName(); + } else { + col = expr.toString(); + } + if (type == null) { + type = SQLOrderingSpecification.ASC; + } + col = getAliaColumn(aliaColumns, col);// 此步骤得到的col必须是不带.的,有别名的用别名,无别名的用字段名 + map.put(col, + type == SQLOrderingSpecification.ASC ? OrderCol.COL_ORDER_TYPE_ASC : OrderCol.COL_ORDER_TYPE_DESC); + } + return map; + } +} diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DruidDeleteParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DruidDeleteParser.java index 87c03a39e..327ce3bb5 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/DruidDeleteParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/DruidDeleteParser.java @@ -12,6 +12,7 @@ import io.mycat.config.MycatPrivileges; import io.mycat.config.MycatPrivileges.Checktype; import io.mycat.config.model.SchemaConfig; import io.mycat.route.RouteResultset; +import io.mycat.route.parser.druid.MycatSchemaStatVisitor; import io.mycat.route.util.RouterUtil; import io.mycat.server.util.SchemaUtil; import io.mycat.server.util.SchemaUtil.SchemaInfo; @@ -24,7 +25,7 @@ import io.mycat.server.util.SchemaUtil.SchemaInfo; */ public class DruidDeleteParser extends DefaultDruidParser { @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { String schemaName = schema == null ? null : schema.getName(); MySqlDeleteStatement delete = (MySqlDeleteStatement) stmt; @@ -39,13 +40,14 @@ public class DruidDeleteParser extends DefaultDruidParser { String msg = "deleting from multiple tables is not supported, sql:" + stmt; throw new SQLNonTransientException(msg); } else { + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); if(!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.DELETE)){ String msg = "The statement DML privilege check is not passed, sql:" + stmt; throw new SQLNonTransientException(msg); } + super.visitorParse(schema, rrs, stmt, visitor); RouterUtil.routeForTableMeta(rrs, schemaInfo.schemaConfig, schemaInfo.table, rrs.getStatement()); rrs.setFinishedRoute(true); - return; } } else { SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, (SQLExprTableSource) tableSource); @@ -53,6 +55,7 @@ public class DruidDeleteParser extends DefaultDruidParser { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; throw new SQLNonTransientException(msg); } + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); if(!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.DELETE)){ String msg = "The statement DML privilege check is not passed, sql:" + stmt; throw new SQLNonTransientException(msg); @@ -60,6 +63,8 @@ public class DruidDeleteParser extends DefaultDruidParser { schema = schemaInfo.schemaConfig; ctx.addTable(schemaInfo.table); ctx.setSql(RouterUtil.getFixedSql(RouterUtil.removeSchema(ctx.getSql(),schemaInfo.schema))); + super.visitorParse(schema, rrs, stmt, visitor); } + return schema; } } diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DruidInsertParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DruidInsertParser.java index 0b3f2d8d2..8ac8458a1 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/DruidInsertParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/DruidInsertParser.java @@ -1,10 +1,12 @@ package io.mycat.route.parser.druid.impl; import java.sql.SQLNonTransientException; +import java.sql.SQLSyntaxErrorException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -30,7 +32,9 @@ import io.mycat.route.RouteResultsetNode; import io.mycat.route.function.AbstractPartitionAlgorithm; import io.mycat.route.parser.druid.MycatSchemaStatVisitor; import io.mycat.route.util.RouterUtil; +import io.mycat.server.ServerConnection; import io.mycat.server.interceptor.impl.GlobalTableUtil; +import io.mycat.server.parser.ServerParse; import io.mycat.server.util.SchemaUtil; import io.mycat.server.util.SchemaUtil.SchemaInfo; import io.mycat.sqlengine.mpp.ColumnRoutePair; @@ -38,32 +42,34 @@ import io.mycat.util.StringUtil; public class DruidInsertParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { - - } - - /** - * 考虑因素:isChildTable、批量、是否分片 - */ - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { MySqlInsertStatement insert = (MySqlInsertStatement) stmt; String schemaName = schema == null ? null : schema.getName(); SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, insert.getTableSource()); if (schemaInfo == null) { - String msg = "No MyCAT Database selected Or Define"; + String msg = "No MyCAT Database is selected Or defined"; throw new SQLNonTransientException(msg); } + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); if(!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.INSERT)){ String msg = "The statement DML privilege check is not passed, sql:" + stmt; throw new SQLNonTransientException(msg); } - if (parserNoSharding(schemaName, schemaInfo, rrs, insert)) { - return; - } schema = schemaInfo.schemaConfig; String tableName = schemaInfo.table; + + if (RouterUtil.processWithMycatSeq(schema, ServerParse.INSERT, rrs.getStatement(), rrs.getSession().getSource())) { + rrs.setFinishedExecute(true); + return schema; + } + if (processInsert(schema, tableName, rrs.getStatement(), rrs.getSession().getSource())) { + rrs.setFinishedExecute(true); + return schema; + } + if (parserNoSharding(schemaName, schemaInfo, rrs, insert)) { + return schema; + } insert.setTableSource(new SQLIdentifierExpr(tableName)); ctx.addTable(tableName); // 整个schema都不分库或者该表不拆分 @@ -77,12 +83,12 @@ public class DruidInsertParser extends DefaultDruidParser { rrs.setStatement(sql); RouterUtil.routeToMultiNode(false, rrs, tc.getDataNodes(), sql, tc.isGlobalTable()); rrs.setFinishedRoute(true); - return; + return schema; } // childTable的insert直接在解析过程中完成路由 if (tc.isChildTable()) { parserChildTable(schemaInfo, rrs, insert); - return; + return schema; } String partitionColumn = tc.getPartitionColumn(); if (partitionColumn != null) { @@ -93,8 +99,160 @@ public class DruidInsertParser extends DefaultDruidParser { parserSingleInsert(schemaInfo, rrs, partitionColumn, insert); } } + return schema; } + private boolean processInsert(SchemaConfig schema, String tableName, String origSQL, ServerConnection sc) + throws SQLNonTransientException { + TableConfig tableConfig = schema.getTables().get(tableName); + boolean processedInsert = false; + // 判断是有自增字段 + if (null != tableConfig && tableConfig.isAutoIncrement()) { + String primaryKey = tableConfig.getPrimaryKey(); + processedInsert = processInsert(sc, schema, origSQL, tableName, primaryKey); + } + return processedInsert; + } + + private boolean processInsert(ServerConnection sc,SchemaConfig schema, + String origSQL,String tableName,String primaryKey) throws SQLNonTransientException { + + int firstLeftBracketIndex = origSQL.indexOf("("); + int firstRightBracketIndex = origSQL.indexOf(")"); + String upperSql = origSQL.toUpperCase(); + int valuesIndex = upperSql.indexOf("VALUES"); + int selectIndex = upperSql.indexOf("SELECT"); + int fromIndex = upperSql.indexOf("FROM"); + //屏蔽insert into table1 select * from table2语句 + if(firstLeftBracketIndex < 0) { + String msg = "invalid sql:" + origSQL; + LOGGER.warn(msg); + throw new SQLNonTransientException(msg); + } + //屏蔽批量插入 + if(selectIndex > 0 &&fromIndex>0&&selectIndex>firstRightBracketIndex&&valuesIndex<0) { + String msg = "multi insert not provided" ; + LOGGER.warn(msg); + throw new SQLNonTransientException(msg); + } + //插入语句必须提供列结构,因为MyCat默认对于表结构无感知 + if(valuesIndex + "VALUES".length() <= firstLeftBracketIndex) { + throw new SQLSyntaxErrorException("insert must provide ColumnList"); + } + //如果主键不在插入语句的fields中,则需要进一步处理 + boolean processedInsert=!isPKInFields(origSQL,primaryKey,firstLeftBracketIndex,firstRightBracketIndex); + if(processedInsert){ + List insertSQLs = handleBatchInsert(origSQL, valuesIndex); + for(String insertSQL:insertSQLs) { + processInsert(sc, schema, insertSQL, tableName, primaryKey, firstLeftBracketIndex + 1, insertSQL.indexOf('(', firstRightBracketIndex) + 1); + } + } + return processedInsert; + } + + private boolean isPKInFields(String origSQL, String primaryKey, int firstLeftBracketIndex, + int firstRightBracketIndex) { + + if (primaryKey == null) { + throw new RuntimeException("please make sure the primaryKey's config is not null in schemal.xml"); + } + boolean isPrimaryKeyInFields = false; + String upperSQL = origSQL.substring(firstLeftBracketIndex, firstRightBracketIndex + 1).toUpperCase(); + for (int pkOffset = 0, primaryKeyLength = primaryKey.length(), pkStart = 0;;) { + pkStart = upperSQL.indexOf(primaryKey, pkOffset); + if (pkStart >= 0 && pkStart < firstRightBracketIndex) { + char pkSide = upperSQL.charAt(pkStart - 1); + if (pkSide <= ' ' || pkSide == '`' || pkSide == ',' || pkSide == '(') { + pkSide = upperSQL.charAt(pkStart + primaryKey.length()); + isPrimaryKeyInFields = pkSide <= ' ' || pkSide == '`' || pkSide == ',' || pkSide == ')'; + } + if (isPrimaryKeyInFields) { + break; + } + pkOffset = pkStart + primaryKeyLength; + } else { + break; + } + } + return isPrimaryKeyInFields; + } + private List handleBatchInsert(String origSQL, int valuesIndex){ + List handledSQLs = new LinkedList<>(); + String prefix = origSQL.substring(0,valuesIndex + "VALUES".length()); + String values = origSQL.substring(valuesIndex + "VALUES".length()); + int flag = 0; + StringBuilder currentValue = new StringBuilder(); + currentValue.append(prefix); + for (int i = 0; i < values.length(); i++) { + char j = values.charAt(i); + if(j=='(' && flag == 0){ + flag = 1; + currentValue.append(j); + }else if(j=='\"' && flag == 1){ + flag = 2; + currentValue.append(j); + } else if(j=='\'' && flag == 1){ + flag = 2; + currentValue.append(j); + } else if(j=='\\' && flag == 2){ + flag = 3; + currentValue.append(j); + } else if (flag == 3){ + flag = 2; + currentValue.append(j); + }else if(j=='\"' && flag == 2){ + flag = 1; + currentValue.append(j); + } else if(j=='\'' && flag == 2){ + flag = 1; + currentValue.append(j); + } else if (j==')' && flag == 1){ + flag = 0; + currentValue.append(j); + handledSQLs.add(currentValue.toString()); + currentValue = new StringBuilder(); + currentValue.append(prefix); + } else if(j == ',' && flag == 0){ + continue; + } else { + currentValue.append(j); + } + } + return handledSQLs; + } + + + private void processInsert(ServerConnection sc, SchemaConfig schema, String origSQL, + String tableName, String primaryKey, int afterFirstLeftBracketIndex, int afterLastLeftBracketIndex) { + /** + * 对于主键不在插入语句的fields中的SQL,需要改写。比如hotnews主键为id,插入语句为: + * insert into hotnews(title) values('aaa'); + * 需要改写成: + * insert into hotnews(id, title) values(next value for MYCATSEQ_hotnews,'aaa'); + */ + int primaryKeyLength = primaryKey.length(); + int insertSegOffset = afterFirstLeftBracketIndex; + String mycatSeqPrefix = "next value for MYCATSEQ_"; + int mycatSeqPrefixLength = mycatSeqPrefix.length(); + int tableNameLength = tableName.length(); + + char[] newSQLBuf = new char[origSQL.length() + primaryKeyLength + mycatSeqPrefixLength + tableNameLength + 2]; + origSQL.getChars(0, afterFirstLeftBracketIndex, newSQLBuf, 0); + primaryKey.getChars(0, primaryKeyLength, newSQLBuf, insertSegOffset); + insertSegOffset += primaryKeyLength; + newSQLBuf[insertSegOffset] = ','; + insertSegOffset++; + origSQL.getChars(afterFirstLeftBracketIndex, afterLastLeftBracketIndex, newSQLBuf, insertSegOffset); + insertSegOffset += afterLastLeftBracketIndex - afterFirstLeftBracketIndex; + mycatSeqPrefix.getChars(0, mycatSeqPrefixLength, newSQLBuf, insertSegOffset); + insertSegOffset += mycatSeqPrefixLength; + tableName.getChars(0, tableNameLength, newSQLBuf, insertSegOffset); + insertSegOffset += tableNameLength; + newSQLBuf[insertSegOffset] = ','; + insertSegOffset++; + origSQL.getChars(afterLastLeftBracketIndex, origSQL.length(), newSQLBuf, insertSegOffset); + RouterUtil.processSQL(sc, schema, new String(newSQLBuf), ServerParse.INSERT); + } private boolean parserNoSharding(String schemaName, SchemaInfo schemaInfo, RouteResultset rrs, MySqlInsertStatement insert) { if (RouterUtil.isNoSharding(schemaInfo.schemaConfig, schemaInfo.table)) { if (insert.getQuery() != null) { diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DruidLockTableParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DruidLockTableParser.java index c08935a5c..bc1015bb0 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/DruidLockTableParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/DruidLockTableParser.java @@ -11,7 +11,6 @@ import io.mycat.config.model.SchemaConfig; import io.mycat.config.model.TableConfig; import io.mycat.route.RouteResultset; import io.mycat.route.RouteResultsetNode; -import io.mycat.route.parser.druid.DruidParser; import io.mycat.route.parser.druid.MycatSchemaStatVisitor; import io.mycat.server.parser.ServerParse; import io.mycat.util.SplitUtil; @@ -20,11 +19,38 @@ import io.mycat.util.SplitUtil; * lock tables [table] [write|read]语句解析器 * @author songdabin */ -public class DruidLockTableParser extends DefaultDruidParser implements DruidParser { +public class DruidLockTableParser extends DefaultDruidParser{ @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { - MySqlLockTableStatement lockTableStat = (MySqlLockTableStatement)stmt; + // 对于lock tables table1 write, table2 + // read类型的多表锁语句,DruidParser只能解析出table1, + // 由于多表锁在分布式场景处理逻辑繁琐,且应用场景较少,因此在此处对这种锁表语句进行拦截。 + // 多表锁的语句在语义上会有",",这里以此为判断依据 + String sql = rrs.getStatement(); + sql = sql.replaceAll("\n", " ").replaceAll("\t", " "); + String[] stmts = SplitUtil.split(sql, ',', true); + // 如果命令中存在",",则按多表锁的语句来处理 + if (stmts.length > 1) { + String tmpStmt = null; + String tmpWords[] = null; + for (int i = 1; i < stmts.length; i++) { + tmpStmt = stmts[i]; + tmpWords = SplitUtil.split(tmpStmt, ' ', true); + if (tmpWords.length == 2 + && ("READ".equalsIgnoreCase(tmpWords[1]) || "WRITE".equalsIgnoreCase(tmpWords[1]))) { + // 如果符合多表锁的语法,则继续,并在最后提示不能多表锁! + continue; + } else { + // 如果不符合多表锁的语法,则提示语法错误和不能多表锁! + throw new SQLNonTransientException( + "You have an error in your SQL syntax, don't support lock multi tables!"); + } + } + LOGGER.error("can't lock multi-table"); + throw new SQLNonTransientException("can't lock multi-table"); + } + MySqlLockTableStatement lockTableStat = (MySqlLockTableStatement) stmt; String table = lockTableStat.getTableSource().toString().toUpperCase(); TableConfig tableConfig = schema.getTables().get(table); if (tableConfig == null) { @@ -40,39 +66,12 @@ public class DruidLockTableParser extends DefaultDruidParser implements DruidPar } List dataNodes = tableConfig.getDataNodes(); RouteResultsetNode[] nodes = new RouteResultsetNode[dataNodes.size()]; - for (int i = 0; i < dataNodes.size(); i ++) { + for (int i = 0; i < dataNodes.size(); i++) { nodes[i] = new RouteResultsetNode(dataNodes.get(i), ServerParse.LOCK, stmt.toString()); } rrs.setNodes(nodes); rrs.setFinishedRoute(true); + return schema; } - @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) - throws SQLNonTransientException { - // 对于lock tables table1 write, table2 read类型的多表锁语句,DruidParser只能解析出table1, - // 由于多表锁在分布式场景处理逻辑繁琐,且应用场景较少,因此在此处对这种锁表语句进行拦截。 - // 多表锁的语句在语义上会有",",这里以此为判断依据 - String sql = rrs.getStatement(); - sql = sql.replaceAll("\n", " ").replaceAll("\t", " "); - String[] stmts = SplitUtil.split(sql, ',', true); - // 如果命令中存在",",则按多表锁的语句来处理 - if (stmts.length > 1) { - String tmpStmt = null; - String tmpWords[] = null; - for (int i = 1; i < stmts.length; i ++) { - tmpStmt = stmts[i]; - tmpWords = SplitUtil.split(tmpStmt, ' ', true); - if (tmpWords.length==2 && ("READ".equalsIgnoreCase(tmpWords[1]) || "WRITE".equalsIgnoreCase(tmpWords[1]))) { - // 如果符合多表锁的语法,则继续,并在最后提示不能多表锁! - continue; - } else { - // 如果不符合多表锁的语法,则提示语法错误和不能多表锁! - throw new SQLNonTransientException("You have an error in your SQL syntax, don't support lock multi tables!"); - } - } - LOGGER.error("can't lock multi-table"); - throw new SQLNonTransientException("can't lock multi-table"); - } - } } diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DruidSelectParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DruidSelectParser.java index eaaeba294..371774d33 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/DruidSelectParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/DruidSelectParser.java @@ -1,296 +1,43 @@ package io.mycat.route.parser.druid.impl; import java.sql.SQLNonTransientException; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import com.alibaba.druid.sql.ast.SQLExpr; -import com.alibaba.druid.sql.ast.SQLName; -import com.alibaba.druid.sql.ast.SQLOrderingSpecification; import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; -import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; -import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; -import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; -import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; -import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; -import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr; -import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; -import com.alibaba.druid.sql.ast.expr.SQLTextLiteralExpr; -import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; -import com.alibaba.druid.sql.ast.statement.SQLSelectItem; -import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; -import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; -import com.alibaba.druid.sql.ast.statement.SQLTableSource; -import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOrderingExpr; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUnionQuery; -import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; -import io.mycat.MycatServer; import io.mycat.cache.LayerCachePool; import io.mycat.config.ErrorCode; import io.mycat.config.model.SchemaConfig; import io.mycat.config.model.TableConfig; import io.mycat.route.RouteResultset; -import io.mycat.route.RouteResultsetNode; import io.mycat.route.parser.druid.RouteCalculateUnit; -import io.mycat.route.util.RouterUtil; import io.mycat.sqlengine.mpp.ColumnRoutePair; -import io.mycat.sqlengine.mpp.HavingCols; -import io.mycat.sqlengine.mpp.MergeCol; -import io.mycat.sqlengine.mpp.OrderCol; -import io.mycat.util.ObjectUtil; -import io.mycat.util.StringUtil; -public class DruidSelectParser extends DefaultDruidParser { +public class DruidSelectParser extends DruidBaseSelectParser { - protected boolean isNeedParseOrderAgg=true; - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) { - SQLSelectStatement selectStmt = (SQLSelectStatement) stmt; - SQLSelectQuery sqlSelectQuery = selectStmt.getSelect().getQuery(); - if (sqlSelectQuery instanceof MySqlSelectQueryBlock) { - MySqlSelectQueryBlock mysqlSelectQuery = (MySqlSelectQueryBlock) selectStmt.getSelect().getQuery(); - parseOrderAggGroupMysql(schema, stmt, rrs, mysqlSelectQuery); - // 更改canRunInReadDB属性 - if ((mysqlSelectQuery.isForUpdate() || mysqlSelectQuery.isLockInShareMode()) - && rrs.isAutocommit() == false) { - rrs.setCanRunInReadDB(false); - } - } else if (sqlSelectQuery instanceof MySqlUnionQuery) { - // MySqlUnionQuery unionQuery = (MySqlUnionQuery)sqlSelectQuery; - // MySqlSelectQueryBlock left = - // (MySqlSelectQueryBlock)unionQuery.getLeft(); - // MySqlSelectQueryBlock right = - // (MySqlSelectQueryBlock)unionQuery.getLeft(); - // System.out.println(); - } - } - protected void parseOrderAggGroupMysql(SchemaConfig schema, SQLStatement stmt, RouteResultset rrs, MySqlSelectQueryBlock mysqlSelectQuery) - { - MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); - stmt.accept(visitor); -// rrs.setGroupByCols((String[])visitor.getGroupByColumns().toArray()); - if(!isNeedParseOrderAgg) - { - return; - } - Map aliaColumns = parseAggGroupCommon(schema, stmt, rrs, mysqlSelectQuery); - - //setOrderByCols - if(mysqlSelectQuery.getOrderBy() != null) { - List orderByItems = mysqlSelectQuery.getOrderBy().getItems(); - rrs.setOrderByCols(buildOrderByCols(orderByItems,aliaColumns)); - } - isNeedParseOrderAgg=false; - } - protected Map parseAggGroupCommon(SchemaConfig schema, SQLStatement stmt, RouteResultset rrs, SQLSelectQueryBlock mysqlSelectQuery) - { - Map aliaColumns = new HashMap(); - Map aggrColumns = new HashMap(); - // Added by winbill, 20160314, for having clause, Begin ==> - List havingColsName = new ArrayList(); - // Added by winbill, 20160314, for having clause, End <== - List selectList = mysqlSelectQuery.getSelectList(); - boolean isNeedChangeSql=false; - int size = selectList.size(); - boolean isDistinct=mysqlSelectQuery.getDistionOption()==2; - for (int i = 0; i < size; i++) - { - SQLSelectItem item = selectList.get(i); - - if (item.getExpr() instanceof SQLAggregateExpr) - { - SQLAggregateExpr expr = (SQLAggregateExpr) item.getExpr(); - String method = expr.getMethodName(); - boolean isHasArgument=!expr.getArguments().isEmpty(); - if(isHasArgument) - { - String aggrColName = method + "(" + expr.getArguments().get(0) + ")"; // Added by winbill, 20160314, for having clause - havingColsName.add(aggrColName); // Added by winbill, 20160314, for having clause - } - //只处理有别名的情况,无别名添加别名,否则某些数据库会得不到正确结果处理 - int mergeType = MergeCol.getMergeType(method); - if (MergeCol.MERGE_AVG == mergeType&&isRoutMultiNode(schema,rrs)) - { //跨分片avg需要特殊处理,直接avg结果是不对的 - String colName = item.getAlias() != null ? item.getAlias() : method + i; - SQLSelectItem sum =new SQLSelectItem(); - String sumColName = colName + "SUM"; - sum.setAlias(sumColName); - SQLAggregateExpr sumExp =new SQLAggregateExpr("SUM"); - ObjectUtil.copyProperties(expr,sumExp); - sumExp.getArguments().addAll(expr.getArguments()); - sumExp.setMethodName("SUM"); - sum.setExpr(sumExp); - selectList.set(i, sum); - aggrColumns.put(sumColName, MergeCol.MERGE_SUM); - havingColsName.add(sumColName); // Added by winbill, 20160314, for having clause - havingColsName.add(item.getAlias() != null ? item.getAlias() : ""); // Added by winbill, 20160314, two aliases for AVG - - SQLSelectItem count =new SQLSelectItem(); - String countColName = colName + "COUNT"; - count.setAlias(countColName); - SQLAggregateExpr countExp = new SQLAggregateExpr("COUNT"); - ObjectUtil.copyProperties(expr,countExp); - countExp.getArguments().addAll(expr.getArguments()); - countExp.setMethodName("COUNT"); - count.setExpr(countExp); - selectList.add(count); - aggrColumns.put(countColName, MergeCol.MERGE_COUNT); - - isNeedChangeSql=true; - aggrColumns.put(colName, mergeType); - rrs.setHasAggrColumn(true); - } else - if (MergeCol.MERGE_UNSUPPORT != mergeType) - { - if (item.getAlias() != null && item.getAlias().length() > 0) - { - aggrColumns.put(item.getAlias(), mergeType); - } else - { //如果不加,jdbc方式时取不到正确结果 ;修改添加别名 - item.setAlias(method + i); - aggrColumns.put(method + i, mergeType); - isNeedChangeSql=true; - } - rrs.setHasAggrColumn(true); - havingColsName.add(item.getAlias()); // Added by winbill, 20160314, for having clause - havingColsName.add(""); // Added by winbill, 20160314, one alias for non-AVG - } - } else - { - if (!(item.getExpr() instanceof SQLAllColumnExpr)) - { - String alia = item.getAlias(); - String field = getFieldName(item); - if (alia == null) - { - alia = field; - } - aliaColumns.put(field, alia); - } - } - - } - if(aggrColumns.size() > 0) { - rrs.setMergeCols(aggrColumns); - } - - //通过优化转换成group by来实现 - if(isDistinct) - { - mysqlSelectQuery.setDistionOption(0); - SQLSelectGroupByClause groupBy=new SQLSelectGroupByClause(); - for (String fieldName : aliaColumns.keySet()) - { - groupBy.addItem(new SQLIdentifierExpr(fieldName)); - } - mysqlSelectQuery.setGroupBy(groupBy); - isNeedChangeSql=true; - } + + - //setGroupByCols - if(mysqlSelectQuery.getGroupBy() != null) { - List groupByItems = mysqlSelectQuery.getGroupBy().getItems(); - String[] groupByCols = buildGroupByCols(groupByItems,aliaColumns); - rrs.setGroupByCols(groupByCols); - rrs.setHavings(buildGroupByHaving(mysqlSelectQuery.getGroupBy().getHaving())); - rrs.setHasAggrColumn(true); - rrs.setHavingColsName(havingColsName.toArray()); // Added by winbill, 20160314, for having clause - } - - - if (isNeedChangeSql) - { - String sql = stmt.toString(); - rrs.changeNodeSqlAfterAddLimit(schema,sql,0,-1 ); - getCtx().setSql(sql); - } - return aliaColumns; - } - - private HavingCols buildGroupByHaving(SQLExpr having){ - if (having == null) { - return null; - } - - SQLBinaryOpExpr expr = ((SQLBinaryOpExpr) having); - SQLExpr left = expr.getLeft(); - SQLBinaryOperator operator = expr.getOperator(); - SQLExpr right = expr.getRight(); - - String leftValue = null;; - if (left instanceof SQLAggregateExpr) { - leftValue = ((SQLAggregateExpr) left).getMethodName() + "(" - + ((SQLAggregateExpr) left).getArguments().get(0) + ")"; - } else if (left instanceof SQLIdentifierExpr) { - leftValue = ((SQLIdentifierExpr) left).getName(); - } - - String rightValue = null; - if (right instanceof SQLNumericLiteralExpr) { - rightValue = right.toString(); - }else if(right instanceof SQLTextLiteralExpr){ - rightValue = StringUtil.removeBackquote(right.toString()); - } - - return new HavingCols(leftValue,rightValue,operator.getName()); - } - - private boolean isRoutMultiNode(SchemaConfig schema, RouteResultset rrs) - { - if(rrs.getNodes()!=null&&rrs.getNodes().length>1) - { - return true; - } - LayerCachePool tableId2DataNodeCache = (LayerCachePool) MycatServer.getInstance().getCacheService().getCachePool("TableID2DataNodeCache"); - try - { - tryRoute(schema, rrs, tableId2DataNodeCache); - if(rrs.getNodes()!=null&&rrs.getNodes().length>1) - { - return true; - } - } catch (SQLNonTransientException e) - { - throw new RuntimeException(e); - } - return false; - } - - private String getFieldName(SQLSelectItem item){ - if ((item.getExpr() instanceof SQLPropertyExpr)||(item.getExpr() instanceof SQLMethodInvokeExpr) - || (item.getExpr() instanceof SQLIdentifierExpr) || item.getExpr() instanceof SQLBinaryOpExpr) { - return item.getExpr().toString();//字段别名 - } - else { - return item.toString(); - } - } + /** * 改写sql:需要加limit的加上 */ @Override public void changeSql(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, LayerCachePool cachePool) throws SQLNonTransientException { - + if (rrs.isFinishedExecute() || rrs.isNeedOptimizer()) { + return; + } tryRoute(schema, rrs, cachePool); rrs.copyLimitToNodes(); SQLSelectStatement selectStmt = (SQLSelectStatement) stmt; @@ -361,19 +108,6 @@ public class DruidSelectParser extends DefaultDruidParser { } - if (rrs.isDistTable()) { - SQLTableSource from = mysqlSelectQuery.getFrom(); - - for (RouteResultsetNode node : rrs.getNodes()) { - SQLIdentifierExpr sqlIdentifierExpr = new SQLIdentifierExpr(); - sqlIdentifierExpr.setParent(from); - sqlIdentifierExpr.setName(node.getSubTableName()); - SQLExprTableSource from2 = new SQLExprTableSource(sqlIdentifierExpr); - mysqlSelectQuery.setFrom(from2); - node.setStatement(stmt.toString()); - } - } - rrs.setCacheAble(isNeedCache(schema, rrs, mysqlSelectQuery, allConditions)); } @@ -394,61 +128,6 @@ public class DruidSelectParser extends DefaultDruidParser { return map; } - private void tryRoute(SchemaConfig schema, RouteResultset rrs, LayerCachePool cachePool) throws SQLNonTransientException { - if(rrs.isFinishedRoute()) - { - return;//避免重复路由 - } - - //无表的select语句直接路由带任一节点 - if((ctx.getTables() == null || ctx.getTables().size() == 0)&&(ctx.getTableAliasMap()==null||ctx.getTableAliasMap().isEmpty())) { - rrs = RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), ctx.getSql()); - rrs.setFinishedRoute(true); - return; - } -// RouterUtil.tryRouteForTables(schema, ctx, rrs, true, cachePool); - SortedSet nodeSet = new TreeSet(); - boolean isAllGlobalTable = RouterUtil.isAllGlobalTable(ctx, schema); - for (RouteCalculateUnit unit : ctx.getRouteCalculateUnits()) { - RouteResultset rrsTmp = RouterUtil.tryRouteForTables(schema, ctx, unit, rrs, true, cachePool); - if (rrsTmp != null&&rrsTmp.getNodes()!=null) { - for (RouteResultsetNode node : rrsTmp.getNodes()) { - nodeSet.add(node); - } - } - if(isAllGlobalTable) {//都是全局表时只计算一遍路由 - break; - } - } - - if(nodeSet.size() == 0) { - - Collection stringCollection= ctx.getTableAliasMap().values() ; - for (String table : stringCollection) - { - if(table!=null&&table.toLowerCase().contains("information_schema.")) - { - rrs = RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), ctx.getSql()); - rrs.setFinishedRoute(true); - return; - } - } - String msg = " find no Route:" + ctx.getSql(); - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - - RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSet.size()]; - int i = 0; - for (Iterator iterator = nodeSet.iterator(); iterator.hasNext();) { - nodes[i] = (RouteResultsetNode) iterator.next(); - i++; - - } - - rrs.setNodes(nodes); - rrs.setFinishedRoute(true); - } protected String getSql(RouteResultset rrs, SQLStatement stmt, boolean isNeedAddLimit) { if ((isNeedChangeLimit(rrs) || isNeedAddLimit)) { @@ -552,92 +231,5 @@ public class DruidSelectParser extends DefaultDruidParser { } } - private String getAliaColumn(Map aliaColumns,String column ){ - String alia=aliaColumns.get(column); - if (alia==null){ - if(column.indexOf(".") < 0) { - String col = "." + column; - String col2 = ".`" + column+"`"; - //展开aliaColumns,将之类的键值对展开成 - for(Map.Entry entry : aliaColumns.entrySet()) { - if(entry.getKey().endsWith(col)||entry.getKey().endsWith(col2)) { - if(entry.getValue() != null && entry.getValue().indexOf(".") > 0) { - return column; - } - return entry.getValue(); - } - } - } - - return column; - } - else { - return alia; - } - } - private String[] buildGroupByCols(List groupByItems,Map aliaColumns) { - String[] groupByCols = new String[groupByItems.size()]; - for(int i= 0; i < groupByItems.size(); i++) { - SQLExpr sqlExpr = groupByItems.get(i); - String column = null; - if(sqlExpr instanceof SQLIdentifierExpr ) - { - column=((SQLIdentifierExpr) sqlExpr).getName(); - } else if(sqlExpr instanceof SQLMethodInvokeExpr){ - column = ((SQLMethodInvokeExpr) sqlExpr).toString(); - } else if(sqlExpr instanceof MySqlOrderingExpr){ - //todo czn - SQLExpr expr = ((MySqlOrderingExpr) sqlExpr).getExpr(); - - if (expr instanceof SQLName) - { - column = StringUtil.removeBackquote(((SQLName) expr).getSimpleName());//不要转大写 2015-2-10 sohudo StringUtil.removeBackquote(expr.getSimpleName().toUpperCase()); - } else - { - column = StringUtil.removeBackquote(expr.toString()); - } - } else if(sqlExpr instanceof SQLPropertyExpr){ - /** - * 针对子查询别名,例如select id from (select h.id from hotnews h union select h.title from hotnews h ) as t1 group by t1.id; - */ - column = sqlExpr.toString(); - } - if(column == null){ - column = sqlExpr.toString(); - } - int dotIndex=column.indexOf(".") ; - int bracketIndex=column.indexOf("(") ; - //通过判断含有括号来决定是否为函数列 - if(dotIndex!=-1&&bracketIndex==-1) - { - //此步骤得到的column必须是不带.的,有别名的用别名,无别名的用字段名 - column=column.substring(dotIndex+1) ; - } - groupByCols[i] = getAliaColumn(aliaColumns,column);//column; - } - return groupByCols; } - - protected LinkedHashMap buildOrderByCols(List orderByItems,Map aliaColumns) { - LinkedHashMap map = new LinkedHashMap(); - for(int i= 0; i < orderByItems.size(); i++) { - SQLOrderingSpecification type = orderByItems.get(i).getType(); - //orderColumn只记录字段名称,因为返回的结果集是不带表名的。 - SQLExpr expr = orderByItems.get(i).getExpr(); - String col; - if (expr instanceof SQLName) { - col = ((SQLName)expr).getSimpleName(); - } - else { - col =expr.toString(); - } - if(type == null) { - type = SQLOrderingSpecification.ASC; - } - col=getAliaColumn(aliaColumns,col);//此步骤得到的col必须是不带.的,有别名的用别名,无别名的用字段名 - map.put(col, type == SQLOrderingSpecification.ASC ? OrderCol.COL_ORDER_TYPE_ASC : OrderCol.COL_ORDER_TYPE_DESC); - } - return map; - } -} diff --git a/src/main/java/io/mycat/route/parser/druid/impl/DruidUpdateParser.java b/src/main/java/io/mycat/route/parser/druid/impl/DruidUpdateParser.java index a5a21d7d3..9cac72086 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/DruidUpdateParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/DruidUpdateParser.java @@ -30,6 +30,7 @@ import io.mycat.config.model.SchemaConfig; import io.mycat.config.model.TableConfig; import io.mycat.meta.protocol.MyCatMeta.TableMeta; import io.mycat.route.RouteResultset; +import io.mycat.route.parser.druid.MycatSchemaStatVisitor; import io.mycat.route.util.RouterUtil; import io.mycat.server.interceptor.impl.GlobalTableUtil; import io.mycat.server.util.SchemaUtil; @@ -41,24 +42,27 @@ import io.mycat.util.StringUtil; * */ public class DruidUpdateParser extends DefaultDruidParser { - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException { + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + throws SQLNonTransientException { MySqlUpdateStatement update = (MySqlUpdateStatement) stmt; - String schemaName = schema == null ? null : schema.getName(); SQLTableSource tableSource = update.getTableSource(); + String schemaName = schema == null ? null : schema.getName(); if (tableSource instanceof SQLJoinTableSource) { SchemaInfo schemaInfo = SchemaUtil.isNoSharding(schemaName, (SQLJoinTableSource) tableSource, stmt); if (schemaInfo == null) { String msg = "updating multiple tables is not supported, sql:" + stmt; throw new SQLNonTransientException(msg); } else { + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); if(!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.UPDATE)){ String msg = "The statement DML privilege check is not passed, sql:" + stmt; throw new SQLNonTransientException(msg); } + + super.visitorParse(schema, rrs, stmt, visitor); RouterUtil.routeForTableMeta(rrs, schemaInfo.schemaConfig, schemaInfo.table, rrs.getStatement()); rrs.setFinishedRoute(true); - return; + return schema; } } else { SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, (SQLExprTableSource) tableSource); @@ -66,6 +70,7 @@ public class DruidUpdateParser extends DefaultDruidParser { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; throw new SQLNonTransientException(msg); } + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); if(!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.UPDATE)){ String msg = "The statement DML privilege check is not passed, sql:" + stmt; throw new SQLNonTransientException(msg); @@ -74,10 +79,11 @@ public class DruidUpdateParser extends DefaultDruidParser { String tableName = schemaInfo.table; TableConfig tc = schema.getTables().get(tableName); + super.visitorParse(schema, rrs, stmt, visitor); if (RouterUtil.isNoSharding(schema, tableName)) {//整个schema都不分库或者该表不拆分 RouterUtil.routeForTableMeta(rrs, schema, tableName, rrs.getStatement()); rrs.setFinishedRoute(true); - return; + return schema; } if (GlobalTableUtil.useGlobleTableCheck() && tc.isGlobalTable()) { @@ -86,7 +92,7 @@ public class DruidUpdateParser extends DefaultDruidParser { rrs.setStatement(sql); RouterUtil.routeToMultiNode(false, rrs, tc.getDataNodes(), sql, tc.isGlobalTable()); rrs.setFinishedRoute(true); - return; + return schema; } String partitionColumn = tc.getPartitionColumn(); String joinKey = tc.getJoinKey(); @@ -100,6 +106,7 @@ public class DruidUpdateParser extends DefaultDruidParser { } ctx.setSql(RouterUtil.getFixedSql(RouterUtil.removeSchema(ctx.getSql(),schemaInfo.schema))); } + return schema; } private String convertUpdateSQL(SchemaInfo schemaInfo, MySqlUpdateStatement update){ long opTimestamp = new Date().getTime(); diff --git a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidAlterTableParser.java b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidAlterTableParser.java index d3aec8ead..ff4d0f766 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidAlterTableParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidAlterTableParser.java @@ -43,16 +43,10 @@ import io.mycat.util.StringUtil; */ public class DruidAlterTableParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { - - } - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) - throws SQLNonTransientException { - String schemaName = schema == null ? null : schema.getName(); SQLAlterTableStatement alterTable = (SQLAlterTableStatement) stmt; + String schemaName = schema == null ? null : schema.getName(); SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, alterTable.getTableSource()); if (schemaInfo == null) { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; @@ -82,16 +76,18 @@ public class DruidAlterTableParser extends DefaultDruidParser { } if (GlobalTableUtil.useGlobleTableCheck() && GlobalTableUtil.isGlobalTable(schemaInfo.schemaConfig, schemaInfo.table)) { - String sql = modifyColumnIfAlter(schemaInfo.schemaConfig, ctx.getSql(), alterTable); + String sql = modifyColumnIfAlter(schemaInfo, ctx.getSql(), alterTable); ctx.setSql(sql); rrs.setStatement(sql); rrs.setSqlStatement(alterTable); } rrs = RouterUtil.routeToDDLNode(schemaInfo, rrs, ctx.getSql()); + return schemaInfo.schemaConfig; } - private String modifyColumnIfAlter(SchemaConfig schema, String sql, SQLAlterTableStatement alterStatement) throws SQLNonTransientException { - SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schema.getName(), alterStatement.getTableSource()); + + + private String modifyColumnIfAlter(SchemaInfo schemaInfo, String sql, SQLAlterTableStatement alterStatement) throws SQLNonTransientException { TableMeta orgTbMeta = MycatServer.getInstance().getTmManager().getSyncTableMeta(schemaInfo.schema, schemaInfo.table); if (orgTbMeta == null) return sql; diff --git a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateIndexParser.java b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateIndexParser.java index d99fa11c7..d52662c69 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateIndexParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateIndexParser.java @@ -17,21 +17,19 @@ import io.mycat.server.util.SchemaUtil.SchemaInfo; public class DruidCreateIndexParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) { - } - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException { - String schemaName = schema == null ? null : schema.getName(); + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, + MycatSchemaStatVisitor visitor) throws SQLNonTransientException { SQLCreateIndexStatement createStmt = (SQLCreateIndexStatement) stmt; SQLTableSource tableSource = createStmt.getTable(); if (tableSource instanceof SQLExprTableSource) { + String schemaName = schema == null ? null : schema.getName(); SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, (SQLExprTableSource) tableSource); if (schemaInfo == null) { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; throw new SQLNonTransientException(msg); } rrs = RouterUtil.routeToDDLNode(schemaInfo, rrs, ctx.getSql()); + return schemaInfo.schemaConfig; } else { String msg = "The DDL is not supported, sql:" + stmt; throw new SQLNonTransientException(msg); diff --git a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateTableParser.java b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateTableParser.java index 55f358daf..8c5cf54d5 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateTableParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidCreateTableParser.java @@ -22,18 +22,15 @@ import io.mycat.util.StringUtil; public class DruidCreateTableParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) { - } - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException { - String schemaName = schema == null ? null : schema.getName(); + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + throws SQLNonTransientException { MySqlCreateTableStatement createStmt = (MySqlCreateTableStatement)stmt; if(createStmt.getSelect() != null) { String msg = "create table from other table not supported :" + stmt; LOGGER.warn(msg); throw new SQLNonTransientException(msg); } + String schemaName = schema == null ? null : schema.getName(); SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, createStmt.getTableSource()); if (schemaInfo == null) { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; @@ -47,6 +44,7 @@ public class DruidCreateTableParser extends DefaultDruidParser { rrs.setSqlStatement(createStmt); } rrs = RouterUtil.routeToDDLNode(schemaInfo, rrs, ctx.getSql()); + return schemaInfo.schemaConfig; } private String addColumnIfCreate(String sql, MySqlCreateTableStatement createStmt) { diff --git a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropIndexParser.java b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropIndexParser.java index 33ddad25a..4af5c38ee 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropIndexParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropIndexParser.java @@ -20,21 +20,16 @@ import io.mycat.server.util.SchemaUtil.SchemaInfo; */ public class DruidDropIndexParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) throws SQLNonTransientException { - - } - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) - throws SQLNonTransientException { - String schemaName = schema == null ? null : schema.getName(); SQLDropIndexStatement dropStmt = (SQLDropIndexStatement)stmt; + String schemaName = schema == null ? null : schema.getName(); SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, dropStmt.getTableName()); if (schemaInfo == null) { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; throw new SQLNonTransientException(msg); } rrs = RouterUtil.routeToDDLNode(schemaInfo, rrs, ctx.getSql()); + return schemaInfo.schemaConfig; } } diff --git a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropTableParser.java b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropTableParser.java index 1967dcbf1..cbcca5141 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropTableParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidDropTableParser.java @@ -15,22 +15,20 @@ import io.mycat.server.util.SchemaUtil.SchemaInfo; public class DruidDropTableParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) { - } - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException { - String schemaName = schema == null ? null : schema.getName(); + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + throws SQLNonTransientException { SQLDropTableStatement dropTable = (SQLDropTableStatement) stmt; if(dropTable.getTableSources().size()>1){ String msg = "dropping multi-tables is not supported, sql:" + stmt; throw new SQLNonTransientException(msg); } + String schemaName = schema == null ? null : schema.getName(); SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, dropTable.getTableSources().get(0)); if (schemaInfo == null) { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; throw new SQLNonTransientException(msg); } rrs = RouterUtil.routeToDDLNode(schemaInfo, rrs, ctx.getSql()); + return schemaInfo.schemaConfig; } } diff --git a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidTruncateTableParser.java b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidTruncateTableParser.java index ca4461710..b99381617 100644 --- a/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidTruncateTableParser.java +++ b/src/main/java/io/mycat/route/parser/druid/impl/ddl/DruidTruncateTableParser.java @@ -15,18 +15,15 @@ import io.mycat.server.util.SchemaUtil.SchemaInfo; public class DruidTruncateTableParser extends DefaultDruidParser { @Override - public void visitorParse(RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) { - } - - @Override - public void statementParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt) throws SQLNonTransientException { - String schemaName = schema == null ? null : schema.getName(); + public SchemaConfig visitorParse(SchemaConfig schema, RouteResultset rrs, SQLStatement stmt, MycatSchemaStatVisitor visitor) + throws SQLNonTransientException { SQLTruncateStatement truncateTable = (SQLTruncateStatement) stmt; - SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, truncateTable.getTableSources().get(0)); + SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schema.getName(), truncateTable.getTableSources().get(0)); if (schemaInfo == null) { String msg = "No MyCAT Database is selected Or defined, sql:" + stmt; throw new SQLNonTransientException(msg); } rrs = RouterUtil.routeToDDLNode(schemaInfo, rrs, ctx.getSql()); + return schemaInfo.schemaConfig; } } diff --git a/src/main/java/io/mycat/route/sequence/BatchInsertSequence.java b/src/main/java/io/mycat/route/sequence/BatchInsertSequence.java index bc4b7f4de..0a1e688f3 100644 --- a/src/main/java/io/mycat/route/sequence/BatchInsertSequence.java +++ b/src/main/java/io/mycat/route/sequence/BatchInsertSequence.java @@ -1,7 +1,9 @@ package io.mycat.route.sequence; -import io.mycat.route.sequence.handler.*; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.sql.SQLNonTransientException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; @@ -14,16 +16,26 @@ import io.mycat.MycatServer; import io.mycat.cache.LayerCachePool; import io.mycat.catlets.Catlet; import io.mycat.config.ErrorCode; +import io.mycat.config.MycatPrivileges; +import io.mycat.config.MycatPrivileges.Checktype; import io.mycat.config.model.SchemaConfig; import io.mycat.config.model.SystemConfig; import io.mycat.config.model.TableConfig; import io.mycat.route.RouteResultset; import io.mycat.route.RouteResultsetNode; import io.mycat.route.factory.RouteStrategyFactory; +import io.mycat.route.sequence.handler.DistributedSequenceHandler; +import io.mycat.route.sequence.handler.IncrSequenceMySQLHandler; +import io.mycat.route.sequence.handler.IncrSequencePropHandler; +import io.mycat.route.sequence.handler.IncrSequenceTimeHandler; +import io.mycat.route.sequence.handler.IncrSequenceZKHandler; +import io.mycat.route.sequence.handler.SequenceHandler; +import io.mycat.route.util.RouterUtil; import io.mycat.server.ServerConnection; import io.mycat.server.parser.ServerParse; +import io.mycat.server.util.SchemaUtil; +import io.mycat.server.util.SchemaUtil.SchemaInfo; import io.mycat.sqlengine.EngineCtx; -import io.mycat.util.StringUtil; /** * 执行批量插入sequence Id @@ -82,7 +94,19 @@ public class BatchInsertSequence implements Catlet { SQLStatement statement = parser.parseStatement(); MySqlInsertStatement insert = (MySqlInsertStatement)statement; if(insert.getValuesList()!=null){ - String tableName = StringUtil.getTableName(realSQL).toUpperCase(); + String schemaName = schema == null ? null : schema.getName(); + SchemaInfo schemaInfo = SchemaUtil.getSchemaInfo(schemaName, insert.getTableSource()); + if (schemaInfo == null) { + String msg = "No MyCAT Database is selected Or defined"; + throw new SQLNonTransientException(msg); + } + rrs.setStatement(RouterUtil.removeSchema(rrs.getStatement(), schemaInfo.schema)); + if(!MycatPrivileges.checkPrivilege(rrs, schemaInfo.schema, schemaInfo.table, Checktype.INSERT)){ + String msg = "The statement DML privilege check is not passed, sql:" + realSQL; + throw new SQLNonTransientException(msg); + } + schema = schemaInfo.schemaConfig; + String tableName = schemaInfo.table; TableConfig tableConfig = schema.getTables().get(tableName); String primaryKey = tableConfig.getPrimaryKey();//获得表的主键字段 diff --git a/src/main/java/io/mycat/route/sequence/handler/IncrSequenceMySQLHandler.java b/src/main/java/io/mycat/route/sequence/handler/IncrSequenceMySQLHandler.java index 3cb6ad240..18295c493 100644 --- a/src/main/java/io/mycat/route/sequence/handler/IncrSequenceMySQLHandler.java +++ b/src/main/java/io/mycat/route/sequence/handler/IncrSequenceMySQLHandler.java @@ -1,7 +1,5 @@ package io.mycat.route.sequence.handler; -import java.io.IOException; -import java.io.InputStream; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -10,8 +8,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import io.mycat.route.util.PropertiesUtil; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.mycat.MycatServer; import io.mycat.backend.BackendConnection; @@ -20,8 +18,10 @@ import io.mycat.backend.mysql.nio.handler.ResponseHandler; import io.mycat.config.MycatConfig; import io.mycat.config.util.ConfigException; import io.mycat.net.mysql.ErrorPacket; +import io.mycat.net.mysql.FieldPacket; import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultsetNode; +import io.mycat.route.util.PropertiesUtil; import io.mycat.server.parser.ServerParse; public class IncrSequenceMySQLHandler implements SequenceHandler { @@ -208,7 +208,7 @@ class FetchMySQLSequnceHandler implements ResponseHandler { } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { RowDataPacket rowDataPkg = new RowDataPacket(1); rowDataPkg.read(row); byte[] columnData = rowDataPkg.fieldValues.get(0); @@ -221,10 +221,11 @@ class FetchMySQLSequnceHandler implements ResponseHandler { } else { seqVal.dbretVal = columnVal; } + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { ((SequenceVal) conn.getAttachment()).dbfinished = true; conn.release(); } @@ -251,11 +252,21 @@ class FetchMySQLSequnceHandler implements ResponseHandler { } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + + } + } class SequenceVal { diff --git a/src/main/java/io/mycat/route/util/RouterUtil.java b/src/main/java/io/mycat/route/util/RouterUtil.java index 4d581f573..20a723b76 100644 --- a/src/main/java/io/mycat/route/util/RouterUtil.java +++ b/src/main/java/io/mycat/route/util/RouterUtil.java @@ -1,7 +1,6 @@ package io.mycat.route.util; import java.sql.SQLNonTransientException; -import java.sql.SQLSyntaxErrorException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -9,15 +8,18 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.wall.spi.WallVisitorUtils; import io.mycat.MycatServer; @@ -29,14 +31,15 @@ import io.mycat.route.RouteResultset; import io.mycat.route.RouteResultsetNode; import io.mycat.route.SessionSQLPair; import io.mycat.route.function.AbstractPartitionAlgorithm; +import io.mycat.route.parser.druid.DruidParser; import io.mycat.route.parser.druid.DruidShardingParseInfo; +import io.mycat.route.parser.druid.MycatSchemaStatVisitor; import io.mycat.route.parser.druid.RouteCalculateUnit; import io.mycat.server.ServerConnection; import io.mycat.server.parser.ServerParse; import io.mycat.server.util.SchemaUtil.SchemaInfo; import io.mycat.sqlengine.mpp.ColumnRoutePair; import io.mycat.sqlengine.mpp.LoadData; -import io.mycat.util.StringUtil; /** * 从ServerRouterUtil中抽取的一些公用方法,路由解析工具类 @@ -76,7 +79,7 @@ public class RouterUtil { int endE=upStmt.lastIndexOf("'"); StringBuilder sb = new StringBuilder(); - while (indx > 0) { + while (indx >= 0) { sb.append(stmt.substring(strtPos, indx)); strtPos = indx + upSchema.length(); if (flag) { @@ -109,6 +112,60 @@ public class RouterUtil { return count; } + public static RouteResultset routeFromParser(DruidParser druidParser, SchemaConfig schema, RouteResultset rrs, SQLStatement statement, String originSql,LayerCachePool cachePool,MycatSchemaStatVisitor visitor) throws SQLNonTransientException{ + schema = druidParser.parser(schema, rrs, statement, originSql,cachePool,visitor); + if(rrs.isFinishedExecute()){ + return null; + } + // DruidParser 解析过程中已完成了路由的直接返回 + if ( rrs.isFinishedRoute() ) { + return rrs; + } + + /** + * 没有from的select语句或其他 + */ + DruidShardingParseInfo ctx= druidParser.getCtx() ; + if((ctx.getTables() == null || ctx.getTables().size() == 0)&&(ctx.getTableAliasMap()==null||ctx.getTableAliasMap().isEmpty())) + { + return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), druidParser.getCtx().getSql()); + } + + if(druidParser.getCtx().getRouteCalculateUnits().size() == 0) { + RouteCalculateUnit routeCalculateUnit = new RouteCalculateUnit(); + druidParser.getCtx().addRouteCalculateUnit(routeCalculateUnit); + } + + SortedSet nodeSet = new TreeSet(); + for(RouteCalculateUnit unit: druidParser.getCtx().getRouteCalculateUnits()) { + RouteResultset rrsTmp = RouterUtil.tryRouteForTables(schema, druidParser.getCtx(), unit, rrs, isSelect(statement), cachePool); + if(rrsTmp != null) { + for(RouteResultsetNode node :rrsTmp.getNodes()) { + nodeSet.add(node); + } + } + } + + RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSet.size()]; + int i = 0; + for (RouteResultsetNode aNodeSet : nodeSet) { + nodes[i] = aNodeSet; + i++; + } + rrs.setNodes(nodes); + + + return rrs; + } + /** + * SELECT 语句 + */ + private static boolean isSelect(SQLStatement statement) { + if(statement instanceof SQLSelectStatement) { + return true; + } + return false; + } /** * 获取第一个节点作为路由 * @@ -464,164 +521,6 @@ public class RouterUtil { // } } - public static boolean processInsert(SchemaConfig schema, int sqlType, - String origSQL, ServerConnection sc) throws SQLNonTransientException { - String tableName = StringUtil.getTableName(origSQL); - if (schema.getLowerCase() == 1) { - tableName = tableName.toUpperCase(); - } - TableConfig tableConfig = schema.getTables().get(tableName); - boolean processedInsert=false; - //判断是有自增字段 - if (null != tableConfig && tableConfig.isAutoIncrement()) { - String primaryKey = tableConfig.getPrimaryKey(); - processedInsert=processInsert(sc,schema,sqlType,origSQL,tableName,primaryKey); - } - return processedInsert; - } - - private static boolean isPKInFields(String origSQL,String primaryKey,int firstLeftBracketIndex,int firstRightBracketIndex){ - - if (primaryKey == null) { - throw new RuntimeException("please make sure the primaryKey's config is not null in schemal.xml"); - } - - boolean isPrimaryKeyInFields = false; - String upperSQL = origSQL.substring(firstLeftBracketIndex, firstRightBracketIndex + 1).toUpperCase(); - for (int pkOffset = 0, primaryKeyLength = primaryKey.length(), pkStart = 0;;) { - pkStart = upperSQL.indexOf(primaryKey, pkOffset); - if (pkStart >= 0 && pkStart < firstRightBracketIndex) { - char pkSide = upperSQL.charAt(pkStart - 1); - if (pkSide <= ' ' || pkSide == '`' || pkSide == ',' || pkSide == '(') { - pkSide = upperSQL.charAt(pkStart + primaryKey.length()); - isPrimaryKeyInFields = pkSide <= ' ' || pkSide == '`' || pkSide == ',' || pkSide == ')'; - } - if (isPrimaryKeyInFields) { - break; - } - pkOffset = pkStart + primaryKeyLength; - } else { - break; - } - } - return isPrimaryKeyInFields; - } - - /*tableName has changed to UpperCase if LowerCase==1 */ - public static boolean processInsert(ServerConnection sc,SchemaConfig schema, - int sqlType,String origSQL,String tableName,String primaryKey) throws SQLNonTransientException { - - int firstLeftBracketIndex = origSQL.indexOf("("); - int firstRightBracketIndex = origSQL.indexOf(")"); - String upperSql = origSQL.toUpperCase(); - int valuesIndex = upperSql.indexOf("VALUES"); - int selectIndex = upperSql.indexOf("SELECT"); - int fromIndex = upperSql.indexOf("FROM"); - //屏蔽insert into table1 select * from table2语句 - if(firstLeftBracketIndex < 0) { - String msg = "invalid sql:" + origSQL; - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - //屏蔽批量插入 - if(selectIndex > 0 &&fromIndex>0&&selectIndex>firstRightBracketIndex&&valuesIndex<0) { - String msg = "multi insert not provided" ; - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - //插入语句必须提供列结构,因为MyCat默认对于表结构无感知 - if(valuesIndex + "VALUES".length() <= firstLeftBracketIndex) { - throw new SQLSyntaxErrorException("insert must provide ColumnList"); - } - //如果主键不在插入语句的fields中,则需要进一步处理 - boolean processedInsert=!isPKInFields(origSQL,primaryKey,firstLeftBracketIndex,firstRightBracketIndex); - if(processedInsert){ - List insertSQLs = handleBatchInsert(origSQL, valuesIndex); - for(String insertSQL:insertSQLs) { - processInsert(sc, schema, sqlType, insertSQL, tableName, primaryKey, firstLeftBracketIndex + 1, insertSQL.indexOf('(', firstRightBracketIndex) + 1); - } - } - return processedInsert; - } - - public static List handleBatchInsert(String origSQL, int valuesIndex){ - List handledSQLs = new LinkedList<>(); - String prefix = origSQL.substring(0,valuesIndex + "VALUES".length()); - String values = origSQL.substring(valuesIndex + "VALUES".length()); - int flag = 0; - StringBuilder currentValue = new StringBuilder(); - currentValue.append(prefix); - for (int i = 0; i < values.length(); i++) { - char j = values.charAt(i); - if(j=='(' && flag == 0){ - flag = 1; - currentValue.append(j); - }else if(j=='\"' && flag == 1){ - flag = 2; - currentValue.append(j); - } else if(j=='\'' && flag == 1){ - flag = 2; - currentValue.append(j); - } else if(j=='\\' && flag == 2){ - flag = 3; - currentValue.append(j); - } else if (flag == 3){ - flag = 2; - currentValue.append(j); - }else if(j=='\"' && flag == 2){ - flag = 1; - currentValue.append(j); - } else if(j=='\'' && flag == 2){ - flag = 1; - currentValue.append(j); - } else if (j==')' && flag == 1){ - flag = 0; - currentValue.append(j); - handledSQLs.add(currentValue.toString()); - currentValue = new StringBuilder(); - currentValue.append(prefix); - } else if(j == ',' && flag == 0){ - continue; - } else { - currentValue.append(j); - } - } - return handledSQLs; - } - - - private static void processInsert(ServerConnection sc, SchemaConfig schema, int sqlType, String origSQL, - String tableName, String primaryKey, int afterFirstLeftBracketIndex, int afterLastLeftBracketIndex) { - /** - * 对于主键不在插入语句的fields中的SQL,需要改写。比如hotnews主键为id,插入语句为: - * insert into hotnews(title) values('aaa'); - * 需要改写成: - * insert into hotnews(id, title) values(next value for MYCATSEQ_hotnews,'aaa'); - */ - int primaryKeyLength = primaryKey.length(); - int insertSegOffset = afterFirstLeftBracketIndex; - String mycatSeqPrefix = "next value for MYCATSEQ_"; - int mycatSeqPrefixLength = mycatSeqPrefix.length(); - int tableNameLength = tableName.length(); - - char[] newSQLBuf = new char[origSQL.length() + primaryKeyLength + mycatSeqPrefixLength + tableNameLength + 2]; - origSQL.getChars(0, afterFirstLeftBracketIndex, newSQLBuf, 0); - primaryKey.getChars(0, primaryKeyLength, newSQLBuf, insertSegOffset); - insertSegOffset += primaryKeyLength; - newSQLBuf[insertSegOffset] = ','; - insertSegOffset++; - origSQL.getChars(afterFirstLeftBracketIndex, afterLastLeftBracketIndex, newSQLBuf, insertSegOffset); - insertSegOffset += afterLastLeftBracketIndex - afterFirstLeftBracketIndex; - mycatSeqPrefix.getChars(0, mycatSeqPrefixLength, newSQLBuf, insertSegOffset); - insertSegOffset += mycatSeqPrefixLength; - tableName.getChars(0, tableNameLength, newSQLBuf, insertSegOffset); - insertSegOffset += tableNameLength; - newSQLBuf[insertSegOffset] = ','; - insertSegOffset++; - origSQL.getChars(afterLastLeftBracketIndex, origSQL.length(), newSQLBuf, insertSegOffset); - processSQL(sc, schema, new String(newSQLBuf), sqlType); - } - public static RouteResultset routeToMultiNode(boolean cache,RouteResultset rrs, Collection dataNodes, String stmt) { RouteResultsetNode[] nodes = new RouteResultsetNode[dataNodes.size()]; int i = 0; @@ -830,11 +729,6 @@ public class RouterUtil { if (schema.getLowerCase() == 1) { tableName = tableName.toUpperCase(); } - TableConfig tableConfig = schema.getTables().get(tableName); - if(tableConfig.isDistTable()){ - routeToDistTableNode(tableName,schema, rrs, ctx.getSql(), tablesAndConditions, cachePool, isSelect); - return rrs; - } if(retNodesSet.size() > 1 && isAllGlobalTable(ctx, schema)) { // mulit routes ,not cache route result @@ -876,10 +770,7 @@ public class RouterUtil { LOGGER.warn(msg); throw new SQLNonTransientException(msg); } - - if(tc.isDistTable()){ - return routeToDistTableNode(tableName,schema,rrs,ctx.getSql(), routeUnit.getTablesAndConditions(), cachePool,isSelect); - } + if(tc.isGlobalTable()) {//全局表 if(isSelect) { @@ -916,100 +807,7 @@ public class RouterUtil { } } } - /*tableName has changed to UpperCase if LowerCase==1 */ - private static RouteResultset routeToDistTableNode(String tableName, SchemaConfig schema, RouteResultset rrs, - String orgSql, Map>> tablesAndConditions, - LayerCachePool cachePool, boolean isSelect) throws SQLNonTransientException { - - TableConfig tableConfig = schema.getTables().get(tableName); - if(tableConfig == null) { - String msg = "can't find table define in schema " + tableName + " schema:" + schema.getName(); - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - if(tableConfig.isGlobalTable()){ - String msg = "can't suport district table " + tableName + " schema:" + schema.getName() + " for global table "; - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - String partionCol = tableConfig.getPartitionColumn(); -// String primaryKey = tableConfig.getPrimaryKey(); - boolean isLoadData=false; - - Set tablesRouteSet = new HashSet(); - - List dataNodes = tableConfig.getDataNodes(); - if(dataNodes.size()>1){ - String msg = "can't suport district table " + tableName + " schema:" + schema.getName() + " for mutiple dataNode " + dataNodes; - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - String dataNode = dataNodes.get(0); - - //主键查找缓存暂时不实现 - if(tablesAndConditions.isEmpty()){ - List subTables = tableConfig.getDistTables(); - tablesRouteSet.addAll(subTables); - } - - for(Map.Entry>> entry : tablesAndConditions.entrySet()) { - boolean isFoundPartitionValue = partionCol != null && entry.getValue().get(partionCol) != null; - Map> columnsMap = entry.getValue(); - - Set partitionValue = columnsMap.get(partionCol); - if(partitionValue == null || partitionValue.size() == 0) { - tablesRouteSet.addAll(tableConfig.getDistTables()); - } else { - for(ColumnRoutePair pair : partitionValue) { - AbstractPartitionAlgorithm algorithm = tableConfig.getRule().getRuleAlgorithm(); - if(pair.colValue != null) { - Integer tableIndex = algorithm.calculate(pair.colValue); - if(tableIndex == null) { - String msg = "can't find any valid datanode :" + tableConfig.getName() - + " -> " + tableConfig.getPartitionColumn() + " -> " + pair.colValue; - LOGGER.warn(msg); - throw new SQLNonTransientException(msg); - } - String subTable = tableConfig.getDistTables().get(tableIndex); - if(subTable != null) { - tablesRouteSet.add(subTable); - } - } - if(pair.rangeValue != null) { - Integer[] tableIndexs = algorithm - .calculateRange(pair.rangeValue.beginValue.toString(), pair.rangeValue.endValue.toString()); - for(Integer idx : tableIndexs) { - String subTable = tableConfig.getDistTables().get(idx); - if(subTable != null) { - tablesRouteSet.add(subTable); - } - } - } - } - } - } - - Object[] subTables = tablesRouteSet.toArray(); - RouteResultsetNode[] nodes = new RouteResultsetNode[subTables.length]; - for(int i=0;i>> entry : tablesAndConditions.entrySet()) { - String tableName = entry.getKey(); + String tableName = RouterUtil.getFixedSql(RouterUtil.removeSchema(entry.getKey(), schema.getName())); if (schema.getLowerCase() == 1) { tableName = tableName.toUpperCase(); } @@ -1032,9 +830,6 @@ public class RouterUtil { LOGGER.warn(msg); throw new SQLNonTransientException(msg); } - if(tableConfig.getDistTables()!=null && tableConfig.getDistTables().size()>0){ - routeToDistTableNode(tableName,schema,rrs,sql, tablesAndConditions, cachePool,isSelect); - } //全局表或者不分库的表略过(全局表后面再计算) if(tableConfig.isGlobalTable() || schema.getTables().get(tableName).getDataNodes().size() == 1) { continue; @@ -1213,7 +1008,7 @@ public class RouterUtil { } else { for(Map.Entry> condition : routeUnit.getTablesAndConditions().get(tableName).entrySet()) { - String colName = condition.getKey(); + String colName = RouterUtil.getFixedSql(RouterUtil.removeSchema(condition.getKey(), schema.getName())); //条件字段是拆分字段 if(colName.equals(tc.getPartitionColumn())) { hasRequiredValue = true; diff --git a/src/main/java/io/mycat/server/NonBlockingSession.java b/src/main/java/io/mycat/server/NonBlockingSession.java index 8cc2d3a15..7b3764921 100644 --- a/src/main/java/io/mycat/server/NonBlockingSession.java +++ b/src/main/java/io/mycat/server/NonBlockingSession.java @@ -24,10 +24,12 @@ package io.mycat.server; import java.nio.ByteBuffer; +import java.sql.SQLSyntaxErrorException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -35,6 +37,8 @@ import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; + import io.mycat.MycatServer; import io.mycat.backend.BackendConnection; import io.mycat.backend.datasource.PhysicalDBNode; @@ -45,17 +49,23 @@ import io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler; import io.mycat.backend.mysql.nio.handler.ResponseHandler; import io.mycat.backend.mysql.nio.handler.SingleNodeHandler; import io.mycat.backend.mysql.nio.handler.UnLockTablesHandler; +import io.mycat.backend.mysql.nio.handler.builder.HandlerBuilder; +import io.mycat.backend.mysql.nio.handler.query.impl.OutputHandler; import io.mycat.backend.mysql.nio.handler.transaction.CommitNodesHandler; import io.mycat.backend.mysql.nio.handler.transaction.RollbackNodesHandler; import io.mycat.backend.mysql.nio.handler.transaction.normal.NormalCommitNodesHandler; import io.mycat.backend.mysql.nio.handler.transaction.normal.NormalRollbackNodesHandler; import io.mycat.backend.mysql.nio.handler.transaction.xa.XACommitNodesHandler; import io.mycat.backend.mysql.nio.handler.transaction.xa.XARollbackNodesHandler; +import io.mycat.backend.mysql.store.memalloc.MemSizeController; import io.mycat.backend.mysql.xa.TxState; import io.mycat.config.ErrorCode; import io.mycat.config.MycatConfig; import io.mycat.net.FrontendConnection; import io.mycat.net.mysql.OkPacket; +import io.mycat.plan.PlanNode; +import io.mycat.plan.optimizer.MyOptimizer; +import io.mycat.plan.visitor.MySQLPlanNodeVisitor; import io.mycat.route.RouteResultset; import io.mycat.route.RouteResultsetNode; import io.mycat.server.parser.ServerParse; @@ -78,11 +88,31 @@ public class NonBlockingSession implements Session { private volatile TxState xaState; private boolean prepared; + + private ResponseHandler responseHandler; + private OutputHandler outputHandler; + private volatile boolean terminated; + private volatile boolean inTransactionKilled; + + // 以链接为单位,对链接中使用的join,orderby以及其它内存使用进行控制 + private MemSizeController joinBufferMC; + private MemSizeController orderBufferMC; + private MemSizeController otherBufferMC; + public NonBlockingSession(ServerConnection source) { this.source = source; - this.target = new ConcurrentHashMap(2, 0.75f); + this.target = new ConcurrentHashMap(2, 1f); + this.joinBufferMC = new MemSizeController(4 * 1024 * 1024); + this.orderBufferMC = new MemSizeController(4 * 1024 * 1024); + this.otherBufferMC = new MemSizeController(4 * 1024 * 1024); } + public OutputHandler getOutputHandler() { + return outputHandler; + } + public void setOutputHandler(OutputHandler outputHandler) { + this.outputHandler = outputHandler; + } @Override public ServerConnection getSource() { return source; @@ -125,9 +155,13 @@ public class NonBlockingSession implements Session { // 检查路由结果是否为空 RouteResultsetNode[] nodes = rrs.getNodes(); if (nodes == null || nodes.length == 0 || nodes[0].getName() == null || nodes[0].getName().equals("")) { - source.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, - "No dataNode found ,please check tables defined in schema:" + source.getSchema()); - return; + if (rrs.isNeedOptimizer()) { + executeMultiSelect(rrs); + } else { + source.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, + "No dataNode found ,please check tables defined in schema:" + source.getSchema()); + } + return; } if (this.getSessionXaID() != null && this.xaState == TxState.TX_INITIALIZE_STATE) { this.xaState = TxState.TX_STARTED_STATE; @@ -144,7 +178,10 @@ public class NonBlockingSession implements Session { LOGGER.warn(new StringBuilder().append(source).append(rrs).toString(), e); source.writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString()); } - } else { + if (this.isPrepared()) { + this.setPrepared(false); + } + } else { multiNodeHandler = new MultiNodeQueryHandler(type, rrs, this); if (this.isPrepared()) { multiNodeHandler.setPrepared(true); @@ -156,13 +193,78 @@ public class NonBlockingSession implements Session { LOGGER.warn(new StringBuilder().append(source).append(rrs).toString(), e); source.writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString()); } - } - - if (this.isPrepared()) { - this.setPrepared(false); + if (this.isPrepared()) { + this.setPrepared(false); + } } } + private void executeMultiSelect(RouteResultset rrs){ +// if (this.source.isTxInterrupted()) { +// sendErrorPacket(ErrorCode.ER_YES, "Transaction error, need to rollback."); +// return; +// } + SQLSelectStatement ast = (SQLSelectStatement)rrs.getSqlStatement(); + MySQLPlanNodeVisitor visitor = new MySQLPlanNodeVisitor(this.getSource().getSchema()); + visitor.visit(ast); + PlanNode node = visitor.getTableNode(); + node.setSql(rrs.getStatement()); + node.setUpFields(); + node = MyOptimizer.optimize(this.getSource().getSchema(), node); +// if (LOGGER.isInfoEnabled()) { +// long currentTime = System.nanoTime(); +// StringBuilder builder = new StringBuilder(); +// builder.append(toString()).append("| sql optimize's elapsedTime is ") +// .append(currentTime - getExecutedNanos()); +// logger.info(builder.toString()); +// setExecutedNanos(currentTime); +// } + execute(node); + } + + public void execute(PlanNode node) { + init(); + HandlerBuilder builder = new HandlerBuilder(node, this); + try { + builder.build(false);//no next + } catch (SQLSyntaxErrorException e) { + LOGGER.warn(new StringBuilder().append(source).append(" execute plan is : ").append(node).toString(), e); +// source.setCurrentSQL(null); + source.writeErrMessage(ErrorCode.ER_YES, "optimizer build error"); + } catch (NoSuchElementException e) { + LOGGER.warn(new StringBuilder().append(source).append(" execute plan is : ").append(node).toString(), e); +// source.setCurrentSQL(null); + this.terminate(); + source.writeErrMessage(ErrorCode.ER_NO_VALID_CONNECTION, "no valid connection"); + } catch (Exception e) { + LOGGER.warn(new StringBuilder().append(source).append(" execute plan is : ").append(node).toString(), e); +// source.setCurrentSQL(null); + this.terminate(); + source.writeErrMessage(ErrorCode.ER_HANDLE_DATA, e.toString()); + } + } + + private void init() { + this.outputHandler = null; + this.responseHandler = null; + this.terminated = false; + if (inTransactionKilled) { + //TODO:YHQ + // kill query is asynchronized, wait for last query is killed. +// for (BackendConnection conn : target.values()) { +// while (conn.isRunning()) { +// LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(500)); +// } +// } + inTransactionKilled = false; + } + } + public void onQueryError(byte[] message) { +// source.unlockTable(); +// source.getIsRunning().set(false); + if (outputHandler != null) + outputHandler.backendConnError(message); + } private CommitNodesHandler createCommitNodesHandler() { if (commitHandler == null) { if (this.getSessionXaID() == null) { @@ -293,9 +395,6 @@ public class NonBlockingSession implements Session { public void releaseConnectionIfSafe(BackendConnection conn, boolean debug, boolean needRollBack) { RouteResultsetNode node = (RouteResultsetNode) conn.getAttachment(); if (node != null) { - if (node.isDisctTable()) { - return; - } if ((this.source.isAutocommit() || conn.isFromSlaveDB())&&!this.source.isTxstart() && !this.source.isLocked()) { releaseConnection((RouteResultsetNode) conn.getAttachment(), LOGGER.isDebugEnabled(), needRollBack); } @@ -531,7 +630,7 @@ public class NonBlockingSession implements Session { if(!newConn.setResponseHandler(queryHandler)){ return errConn; } - this.getTargetMap().put(node, newConn); + this.bindConnection(node, newConn); return newConn; } catch (Exception e) { return errConn; @@ -563,4 +662,15 @@ public class NonBlockingSession implements Session { MycatServer.getInstance().getTmManager().updateMetaData(schema, sql, isSuccess); } } + public MemSizeController getJoinBufferMC() { + return joinBufferMC; + } + + public MemSizeController getOrderBufferMC() { + return orderBufferMC; + } + + public MemSizeController getOtherBufferMC() { + return otherBufferMC; + } } diff --git a/src/main/java/io/mycat/server/ServerConnection.java b/src/main/java/io/mycat/server/ServerConnection.java index 5e76966e1..05efbcb42 100644 --- a/src/main/java/io/mycat/server/ServerConnection.java +++ b/src/main/java/io/mycat/server/ServerConnection.java @@ -36,11 +36,8 @@ import io.mycat.config.model.SchemaConfig; import io.mycat.log.transaction.TxnLogHelper; import io.mycat.net.FrontendConnection; import io.mycat.route.RouteResultset; -import io.mycat.server.handler.MysqlInformationSchemaHandler; -import io.mycat.server.handler.MysqlProcHandler; import io.mycat.server.parser.ServerParse; import io.mycat.server.response.Heartbeat; -import io.mycat.server.response.InformationSchemaProfiling; import io.mycat.server.response.Ping; import io.mycat.server.util.SchemaUtil; import io.mycat.util.SplitUtil; @@ -177,69 +174,24 @@ public class ServerConnection extends FrontendConnection { // 检查当前使用的DB String db = this.schema; - boolean isDefault = true; if (db == null) { - db = SchemaUtil.detectDefaultDb(sql, type); - isDefault = false; + db = SchemaUtil.detectDefaultDb(type); } - // 兼容PhpAdmin's, 支持对MySQL元数据的模拟返回 - //// TODO: 2016/5/20 支持更多information_schema特性 - if (ServerParse.SELECT == type - && db.equalsIgnoreCase("information_schema") ) { - MysqlInformationSchemaHandler.handle(sql, this); - return; - } - - if (ServerParse.SELECT == type - && sql.contains("mysql") - && sql.contains("proc")) { - - SchemaUtil.SchemaInfo schemaInfo = SchemaUtil.parseSchema(sql); - if (schemaInfo != null - && "mysql".equalsIgnoreCase(schemaInfo.schema) - && "proc".equalsIgnoreCase(schemaInfo.table)) { - - // 兼容MySQLWorkbench - MysqlProcHandler.handle(sql, this); - return; - } - } - SchemaConfig schema = null; + + SchemaConfig schemaConfig = null; if (db != null){ - schema = MycatServer.getInstance().getConfig().getSchemas().get(db); - if (schema == null) { + schemaConfig = MycatServer.getInstance().getConfig().getSchemas().get(db); + if (schemaConfig == null) { writeErrMessage(ErrorCode.ERR_BAD_LOGICDB, "Unknown MyCAT Database '" + db + "'"); return; } } - - //fix navicat SELECT STATE AS `State`, ROUND(SUM(DURATION),7) AS `Duration`, CONCAT(ROUND(SUM(DURATION)/*100,3), '%') AS `Percentage` FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID= GROUP BY STATE ORDER BY SEQ - if(ServerParse.SELECT == type &&sql.contains(" INFORMATION_SCHEMA.PROFILING ")&&sql.contains("CONCAT(ROUND(SUM(DURATION)/*100,3)")) - { - InformationSchemaProfiling.response(this); - return; - } - - /* 当已经设置默认schema时,可以通过在sql中指定其它schema的方式执行 - * 相关sql,已经在mysql客户端中验证。 - * 所以在此处增加关于sql中指定Schema方式的支持。 - */ - if (ServerParse.SELECT==type && isDefault && schema.isCheckSQLSchema() ) { - SchemaUtil.SchemaInfo schemaInfo = SchemaUtil.parseSchema(sql); - if (schemaInfo != null && schemaInfo.schema != null && !schemaInfo.schema.equals(db)) { - SchemaConfig schemaConfig = MycatServer.getInstance().getConfig().getSchemas().get(schemaInfo.schema); - if (schemaConfig != null) - schema = schemaConfig; - } - } - - routeEndExecuteSQL(sql, type, schema); + routeEndExecuteSQL(sql, type, schemaConfig); } public RouteResultset routeSQL(String sql, int type) { - // 检查当前使用的DB String db = this.schema; if (db == null) { diff --git a/src/main/java/io/mycat/server/handler/ExplainHandler.java b/src/main/java/io/mycat/server/handler/ExplainHandler.java index 1d6613a77..cb7938027 100644 --- a/src/main/java/io/mycat/server/handler/ExplainHandler.java +++ b/src/main/java/io/mycat/server/handler/ExplainHandler.java @@ -27,7 +27,8 @@ import java.nio.ByteBuffer; import java.util.List; import java.util.regex.Pattern; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement; @@ -124,7 +125,7 @@ public class ExplainHandler { String db = c.getSchema(); int sqlType = ServerParse.parse(stmt) & 0xff; if (db == null) { - db = SchemaUtil.detectDefaultDb(stmt, sqlType); + db = SchemaUtil.detectDefaultDb(sqlType); if(db==null) { diff --git a/src/main/java/io/mycat/server/handler/MysqlInformationSchemaHandler.java b/src/main/java/io/mycat/server/handler/MysqlInformationSchemaHandler.java index bb324c0e8..0321006ee 100644 --- a/src/main/java/io/mycat/server/handler/MysqlInformationSchemaHandler.java +++ b/src/main/java/io/mycat/server/handler/MysqlInformationSchemaHandler.java @@ -9,7 +9,7 @@ import io.mycat.net.mysql.FieldPacket; import io.mycat.net.mysql.OkPacket; import io.mycat.net.mysql.ResultSetHeaderPacket; import io.mycat.server.ServerConnection; -import io.mycat.server.util.SchemaUtil; +import io.mycat.server.util.SchemaUtil.SchemaInfo; /** @@ -58,9 +58,7 @@ public class MysqlInformationSchemaHandler { } - public static void handle(String sql, ServerConnection c) { - - SchemaUtil.SchemaInfo schemaInfo = SchemaUtil.parseSchema(sql); + public static void handle(SchemaInfo schemaInfo, ServerConnection c) { if ( schemaInfo != null ) { if ( schemaInfo.table.toUpperCase().equals("CHARACTER_SETS") ) { diff --git a/src/main/java/io/mycat/server/util/SchemaUtil.java b/src/main/java/io/mycat/server/util/SchemaUtil.java index dd97ace52..b1af726ea 100644 --- a/src/main/java/io/mycat/server/util/SchemaUtil.java +++ b/src/main/java/io/mycat/server/util/SchemaUtil.java @@ -12,13 +12,9 @@ import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; import com.alibaba.druid.sql.ast.statement.SQLTableSource; -import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; -import com.alibaba.druid.sql.parser.SQLStatementParser; -import com.alibaba.druid.sql.visitor.SchemaStatVisitor; import io.mycat.MycatServer; import io.mycat.config.model.SchemaConfig; -import io.mycat.route.parser.druid.MycatSchemaStatVisitor; import io.mycat.server.parser.ServerParse; import io.mycat.util.StringUtil; @@ -27,38 +23,27 @@ import io.mycat.util.StringUtil; */ public class SchemaUtil { - public static SchemaInfo parseSchema(String sql) { - SQLStatementParser parser = new MySqlStatementParser(sql); - return parseTables(parser.parseStatement(), new MycatSchemaStatVisitor()); - } - public static String detectDefaultDb(String sql, int type) - { + public static final String MYSQL_SCHEMA = "mysql"; + public static final String INFORMATION_SCHEMA = "information_schema"; + public static final String TABLE_PROC = "proc"; + public static final String TABLE_PROFILING = "PROFILING"; + + public static String detectDefaultDb(int type) { String db = null; - Map schemaConfigMap = MycatServer.getInstance().getConfig().getSchemas(); - if (ServerParse.SELECT == type) { - SchemaUtil.SchemaInfo schemaInfo = SchemaUtil.parseSchema(sql); - if ((schemaInfo == null || schemaInfo.table == null) && !schemaConfigMap.isEmpty()) { - db = schemaConfigMap.entrySet().iterator().next().getKey(); - } - if (schemaInfo != null && schemaInfo.schema != null) { - if (schemaConfigMap.containsKey(schemaInfo.schema)) { - db = schemaInfo.schema; - /** - * 对 MySQL 自带的元数据库 information_schema 进行返回 - */ - } else if ("information_schema".equalsIgnoreCase(schemaInfo.schema)) { - db = "information_schema"; - } - } - } else if ((ServerParse.SHOW == type || ServerParse.USE == type || ServerParse.EXPLAIN == type - || ServerParse.SET == type || ServerParse.HELP == type || ServerParse.DESCRIBE == type) - && !schemaConfigMap.isEmpty()) { - // 兼容mysql gui 不填默认database - db = schemaConfigMap.entrySet().iterator().next().getKey(); + if ((ServerParse.SHOW == type || ServerParse.USE == type || ServerParse.EXPLAIN == type + || ServerParse.SET == type || ServerParse.HELP == type || ServerParse.DESCRIBE == type)) { + db = getRandomDb(); } return db; - } + } + public static String getRandomDb() { + Map schemaConfigMap = MycatServer.getInstance().getConfig().getSchemas(); + if (!schemaConfigMap.isEmpty()) { + return schemaConfigMap.entrySet().iterator().next().getKey(); + } + return null; + } public static String parseShowTableSchema(String sql) { @@ -70,28 +55,6 @@ public class SchemaUtil return null; } - private static SchemaInfo parseTables(SQLStatement stmt, SchemaStatVisitor schemaStatVisitor) { - stmt.accept(schemaStatVisitor); - String key = schemaStatVisitor.getCurrentTable(); - if (key != null && key.contains("`")) { - key = key.replaceAll("`", ""); - } - - if (key != null) { - SchemaInfo schemaInfo = new SchemaInfo(); - int pos = key.indexOf("."); - if (pos > 0) { - schemaInfo.schema = key.substring(0, pos); - schemaInfo.table = key.substring(pos + 1); - } else { - schemaInfo.table = key; - } - return schemaInfo; - } - - return null; - } - public static SchemaInfo getSchemaInfo(String schema, SQLExprTableSource tableSource) { SchemaInfo schemaInfo = new SchemaInfo(); SQLExpr expr = tableSource.getExpr(); @@ -108,11 +71,11 @@ public class SchemaUtil return null; } SchemaConfig schemaConfig = MycatServer.getInstance().getConfig().getSchemas().get(schemaInfo.schema); - if (schemaConfig == null) { + if (schemaConfig == null && !MYSQL_SCHEMA.equalsIgnoreCase(schemaInfo.schema)&& !INFORMATION_SCHEMA.equalsIgnoreCase(schemaInfo.schema)) { return null; } schemaInfo.schemaConfig = schemaConfig; - if (schemaConfig.getLowerCase() == 1) { + if (schemaConfig != null && schemaConfig.getLowerCase() == 1) { schemaInfo.table = schemaInfo.table.toUpperCase(); } return schemaInfo; diff --git a/src/main/java/io/mycat/sqlengine/SQLJob.java b/src/main/java/io/mycat/sqlengine/SQLJob.java index b7645c47a..744df0d47 100644 --- a/src/main/java/io/mycat/sqlengine/SQLJob.java +++ b/src/main/java/io/mycat/sqlengine/SQLJob.java @@ -13,6 +13,8 @@ import io.mycat.backend.mysql.nio.handler.ResponseHandler; import io.mycat.config.ErrorCode; import io.mycat.config.MycatConfig; import io.mycat.net.mysql.ErrorPacket; +import io.mycat.net.mysql.FieldPacket; +import io.mycat.net.mysql.RowDataPacket; import io.mycat.route.RouteResultsetNode; import io.mycat.server.parser.ServerParse; @@ -150,24 +152,24 @@ public class SQLJob implements ResponseHandler, Runnable { } @Override - public void fieldEofResponse(byte[] header, List fields, - byte[] eof, BackendConnection conn) { + public void fieldEofResponse(byte[] header, List fields, List fieldPackets, byte[] eof, + boolean isLeft, BackendConnection conn) { jobHandler.onHeader(dataNodeOrDatabase, header, fields); } @Override - public void rowResponse(byte[] row, BackendConnection conn) { + public boolean rowResponse(byte[] row, RowDataPacket rowPacket, boolean isLeft, BackendConnection conn) { boolean finsihed = jobHandler.onRowData(dataNodeOrDatabase, row); if (finsihed) { conn.release(); doFinished(false); } - + return false; } @Override - public void rowEofResponse(byte[] eof, BackendConnection conn) { + public void rowEofResponse(byte[] eof, boolean isLeft, BackendConnection conn) { conn.release(); doFinished(false); } @@ -193,4 +195,12 @@ public class SQLJob implements ResponseHandler, Runnable { + jobHandler + "]"; } + @Override + public void relayPacketResponse(byte[] relayPacket, BackendConnection conn) { + } + + @Override + public void endPacketResponse(byte[] endPacket, BackendConnection conn) { + } + } diff --git a/src/main/java/io/mycat/util/CompareLike.java b/src/main/java/io/mycat/util/CompareLike.java new file mode 100644 index 000000000..2a4d7b80a --- /dev/null +++ b/src/main/java/io/mycat/util/CompareLike.java @@ -0,0 +1,156 @@ +package io.mycat.util; + +public class CompareLike { + private enum CompareType { + IS_NULL, FULL_MATCH, LIKE + } + + private static final int MATCH = 0, ONE = 1, ANY = 2; + private final CompareType type; + + private char[] patternChars; + private String patternString; + private int[] patternTypes; + private int patternLength; + + public CompareLike(String pattern) { + this(pattern, ""); + } + + public CompareLike(String pattern, String escape) { + if ("%".equals(pattern)) { + type = CompareType.IS_NULL; + } else { + initPattern(pattern, getEscapeChar(escape)); + if (isFullMatch()) { + type = CompareType.FULL_MATCH; + } else { + type = CompareType.LIKE; + } + } + } + + private Character getEscapeChar(String es) { + Character esc; + if (es == null) { + esc = getEscapeChar("//"); + } else if (es.length() == 0) { + esc = null; + } else { + esc = es.charAt(0); + } + return esc; + } + + private void initPattern(String p, Character escapeChar) { + patternLength = 0; + if (p == null) { + patternTypes = null; + patternChars = null; + return; + } + int len = p.length(); + patternChars = new char[len]; + patternTypes = new int[len]; + boolean lastAny = false; + for (int i = 0; i < len; i++) { + char c = p.charAt(i); + int type; + if (escapeChar != null && escapeChar == c) { + if (i >= len - 1) { + // invalidPattern = true; + return; + } + c = p.charAt(++i); + type = MATCH; + lastAny = false; + } else if (c == '%') { + if (lastAny) { + continue; + } + type = ANY; + lastAny = true; + } else if (c == '_') { + type = ONE; + } else { + type = MATCH; + lastAny = false; + } + patternTypes[patternLength] = type; + patternChars[patternLength++] = c; + } + for (int i = 0; i < patternLength - 1; i++) { + if ((patternTypes[i] == ANY) && (patternTypes[i + 1] == ONE)) { + patternTypes[i] = ONE; + patternTypes[i + 1] = ANY; + } + } + patternString = new String(patternChars, 0, patternLength); + } + + private boolean isFullMatch() { + if (patternTypes == null) { + return false; + } + for (int type : patternTypes) { + if (type != MATCH) { + return false; + } + } + return true; + } + + public boolean compare(String s) { + switch (type) { + case IS_NULL: + return s != null; + case FULL_MATCH: + return StringUtil.equalsIgnoreCase(patternString, s); + default: + return compareAt(s, 0, 0, s.length(), patternChars, patternTypes); + } + } + + private boolean compareAt(String s, int pi, int si, int sLen, char[] pattern, int[] types) { + for (; pi < patternLength; pi++) { + switch (types[pi]) { + case MATCH: + if ((si >= sLen) || !compare(pattern, s, pi, si++)) { + return false; + } + break; + case ONE: + if (si++ >= sLen) { + return false; + } + break; + case ANY: + if (++pi >= patternLength) { + return true; + } + while (si < sLen) { + if (compare(pattern, s, pi, si) && compareAt(s, pi, si, sLen, pattern, types)) { + return true; + } + si++; + } + return false; + } + } + return si == sLen; + } + + private boolean compare(char[] pattern, String s, int pi, int si) { + return pattern[pi] == s.charAt(si) || equalsChars(patternString, pi, s, si, true); + } + + public boolean equalsChars(String a, int ai, String b, int bi, boolean ignoreCase) { + char ca = a.charAt(ai); + char cb = b.charAt(bi); + if (ignoreCase) { + ca = Character.toUpperCase(ca); + cb = Character.toUpperCase(cb); + } + return ca == cb; + } +} diff --git a/src/main/java/io/mycat/util/ConcurrentHashSet.java b/src/main/java/io/mycat/util/ConcurrentHashSet.java new file mode 100644 index 000000000..60bafbd75 --- /dev/null +++ b/src/main/java/io/mycat/util/ConcurrentHashSet.java @@ -0,0 +1,59 @@ +package io.mycat.util; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class ConcurrentHashSet extends AbstractSet implements Set, Serializable { + + + private static final long serialVersionUID = 8966440120855782809L; + + private transient Map map; + + // Dummy value to associate with an Object in the backing Map + private static final Object PRESENT = new Object(); + + public ConcurrentHashSet() { + this.map = new ConcurrentHashMap(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + @Override + public boolean add(E e) { + return map.put(e, PRESENT) == null; + } + + @Override + public boolean remove(Object o) { + return map.remove(o) == PRESENT; + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Iterator iterator() { + return map.keySet().iterator(); + } + + @Override + public int size() { + return map.size(); + } + +} diff --git a/src/main/java/io/mycat/util/FairLinkedBlockingDeque.java b/src/main/java/io/mycat/util/FairLinkedBlockingDeque.java new file mode 100644 index 000000000..0de5a5e87 --- /dev/null +++ b/src/main/java/io/mycat/util/FairLinkedBlockingDeque.java @@ -0,0 +1,1198 @@ +package io.mycat.util; + +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class FairLinkedBlockingDeque extends AbstractQueue implements BlockingDeque, java.io.Serializable { + + /* + * Implemented as a simple doubly-linked list protected by a single lock and + * using conditions to manage blocking. + * + * To implement weakly consistent iterators, it appears we need to keep all + * Nodes GC-reachable from a predecessor dequeued Node. That would cause two + * problems: - allow a rogue Iterator to cause unbounded memory retention - + * cause cross-generational linking of old Nodes to new Nodes if a Node was + * tenured while live, which generational GCs have a hard time dealing with, + * causing repeated major collections. However, only non-deleted Nodes need + * to be reachable from dequeued Nodes, and reachability does not + * necessarily have to be of the kind understood by the GC. We use the trick + * of linking a Node that has just been dequeued to itself. Such a self-link + * implicitly means to advance to head. + */ + + /* + * We have "diamond" multiple interface/abstract class inheritance here, and + * that introduces ambiguities. Often we want the BlockingDeque javadoc + * combined with the AbstractQueue implementation, so a lot of method specs + * are duplicated here. + */ + + private static final long serialVersionUID = -387911632671998426L; + + /** Doubly-linked list node class */ + static final class Node { + /** + * The item, or null if this node has been removed. + */ + + E item; + + /** + * One of: - the real predecessor Node - this Node, meaning the + * predecessor is tail - null, meaning there is no predecessor + */ + + Node prev; + + /** + * One of: - the real successor Node - this Node, meaning the successor + * is head - null, meaning there is no successor + */ + + Node next; + + Node(E x, Node p, Node n) { + item = x; + prev = p; + next = n; + } + } + + /** Pointer to first node */ + transient Node first; + /** Pointer to last node */ + transient Node last; + /** Number of items in the deque */ + private transient int count; + /** Maximum number of items in the deque */ + private final int capacity; + /** Main lock guarding all access */ + final ReentrantLock lock = new ReentrantLock(true); + /** Condition for waiting takes */ + private final Condition notEmpty = lock.newCondition(); + /** Condition for waiting puts */ + private final Condition notFull = lock.newCondition(); + + /** + * Creates a LinkedBlockingDeque with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public FairLinkedBlockingDeque() { + this(Integer.MAX_VALUE); + } + + /** + * Creates a LinkedBlockingDeque with the given (fixed) capacity. + * + * @param capacity + * the capacity of this deque + * @throws IllegalArgumentException + * if capacity is less than 1 + */ + public FairLinkedBlockingDeque(int capacity) { + if (capacity <= 0) + throw new IllegalArgumentException(); + this.capacity = capacity; + } + + /** + * Creates a LinkedBlockingDeque with a capacity of + * {@link Integer#MAX_VALUE}, initially containing the elements of the given + * collection, added in traversal order of the collection's iterator. + * + * @param c + * the collection of elements to initially contain + * @throws NullPointerException + * if the specified collection or any of its elements are null + */ + public FairLinkedBlockingDeque(Collection c) { + this(Integer.MAX_VALUE); + final ReentrantLock lock = this.lock; + lock.lock(); // Never contended, but necessary for visibility + try { + for (E e : c) { + if (e == null) + throw new NullPointerException(); + if (!linkLast(e)) + throw new IllegalStateException("Deque full"); + } + } finally { + lock.unlock(); + } + } + + /** + * wait until the queue's size >= size + */ + public void waitUtilCount(int size) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + while (count < size) { + try { + notEmpty.await(); + } catch (InterruptedException e) { + notEmpty.signalAll(); + } + } + } finally { + lock.unlock(); + } + } + + // Basic linking and unlinking operations, called only while holding lock + + /** + * Links e as first element, or returns false if full. + */ + private boolean linkFirst(E e) { + // assert lock.isHeldByCurrentThread(); + if (count >= capacity) + return false; + Node f = first; + Node x = new Node(e, null, f); + first = x; + if (last == null) + last = x; + else + f.prev = x; + ++count; + notEmpty.signal(); + return true; + } + + /** + * Links e as last element, or returns false if full. + */ + private boolean linkLast(E e) { + // assert lock.isHeldByCurrentThread(); + if (count >= capacity) + return false; + Node l = last; + Node x = new Node(e, l, null); + last = x; + if (first == null) + first = x; + else + l.next = x; + ++count; + notEmpty.signal(); + return true; + } + + /** + * Removes and returns first element, or null if empty. + */ + private E unlinkFirst() { + // assert lock.isHeldByCurrentThread(); + Node f = first; + if (f == null) + return null; + Node n = f.next; + E item = f.item; + f.item = null; + f.next = f; // help GC + first = n; + if (n == null) + last = null; + else + n.prev = null; + --count; + notFull.signal(); + return item; + } + + /** + * Removes and returns last element, or null if empty. + */ + private E unlinkLast() { + // assert lock.isHeldByCurrentThread(); + Node l = last; + if (l == null) + return null; + Node p = l.prev; + E item = l.item; + l.item = null; + l.prev = l; // help GC + + last = p; + if (p == null) + first = null; + else + p.next = null; + --count; + notFull.signal(); + return item; + } + + /** + * Unlinks x + */ + void unlink(Node x) { + // assert lock.isHeldByCurrentThread(); + Node p = x.prev; + Node n = x.next; + if (p == null) { + unlinkFirst(); + } else if (n == null) { + unlinkLast(); + } else { + p.next = n; + n.prev = p; + x.item = null; + // Don't mess with x's links. They may still be in use by + // an iterator. + --count; + notFull.signal(); + } + } + + // BlockingDeque methods + + /** + * @throws IllegalStateException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + */ + public void addFirst(E e) { + if (!offerFirst(e)) + throw new IllegalStateException("Deque full"); + } + + /** + * @throws IllegalStateException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + */ + public void addLast(E e) { + if (!offerLast(e)) + throw new IllegalStateException("Deque full"); + } + + /** + * @throws NullPointerException + * {@inheritDoc} + */ + public boolean offerFirst(E e) { + if (e == null) + throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return linkFirst(e); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException + * {@inheritDoc} + */ + public boolean offerLast(E e) { + if (e == null) + throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return linkLast(e); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException + * {@inheritDoc} + * @throws InterruptedException + * {@inheritDoc} + */ + public void putFirst(E e) throws InterruptedException { + if (e == null) + throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + while (!linkFirst(e)) + notFull.await(); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException + * {@inheritDoc} + * @throws InterruptedException + * {@inheritDoc} + */ + public void putLast(E e) throws InterruptedException { + if (e == null) + throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + while (!linkLast(e)) + notFull.await(); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException + * {@inheritDoc} + * @throws InterruptedException + * {@inheritDoc} + */ + public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException { + if (e == null) + throw new NullPointerException(); + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (!linkFirst(e)) { + if (nanos <= 0) + return false; + nanos = notFull.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException + * {@inheritDoc} + * @throws InterruptedException + * {@inheritDoc} + */ + public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException { + if (e == null) + throw new NullPointerException(); + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (!linkLast(e)) { + if (nanos <= 0) + return false; + nanos = notFull.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + /** + * @throws NoSuchElementException + * {@inheritDoc} + */ + public E removeFirst() { + E x = pollFirst(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + /** + * @throws NoSuchElementException + * {@inheritDoc} + */ + public E removeLast() { + E x = pollLast(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + /** + * 添加一个元素到队列的最后,如果队列满,则替换最后一个元素 + */ + public E addOrReplaceLast(E e) throws InterruptedException { + if (e == null) + throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + boolean added = linkLast(e); + if (!added) { + Node l = last; + if (l == null) + return null; + E item = l.item; + l.item = e; + return item; + } else { + return null; + } + } finally { + lock.unlock(); + } + } + + public E pollFirst() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return unlinkFirst(); + } finally { + lock.unlock(); + } + } + + public E pollLast() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return unlinkLast(); + } finally { + lock.unlock(); + } + } + + public E takeFirst() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + E x; + while ((x = unlinkFirst()) == null) + notEmpty.await(); + return x; + } finally { + lock.unlock(); + } + } + + public E takeLast() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + E x; + while ((x = unlinkLast()) == null) + notEmpty.await(); + return x; + } finally { + lock.unlock(); + } + } + + public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + E x; + while ((x = unlinkFirst()) == null) { + if (nanos <= 0) + return null; + nanos = notEmpty.awaitNanos(nanos); + } + return x; + } finally { + lock.unlock(); + } + } + + public E pollLast(long timeout, TimeUnit unit) throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + E x; + while ((x = unlinkLast()) == null) { + if (nanos <= 0) + return null; + nanos = notEmpty.awaitNanos(nanos); + } + return x; + } finally { + lock.unlock(); + } + } + + /** + * @throws NoSuchElementException + * {@inheritDoc} + */ + public E getFirst() { + E x = peekFirst(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + /** + * @throws NoSuchElementException + * {@inheritDoc} + */ + public E getLast() { + E x = peekLast(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + public E peekFirst() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return (first == null) ? null : first.item; + } finally { + lock.unlock(); + } + } + + public E peekLast() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return (last == null) ? null : last.item; + } finally { + lock.unlock(); + } + } + + public boolean removeFirstOccurrence(Object o) { + if (o == null) + return false; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node p = first; p != null; p = p.next) { + if (o.equals(p.item)) { + unlink(p); + return true; + } + } + return false; + } finally { + lock.unlock(); + } + } + + public boolean removeLastOccurrence(Object o) { + if (o == null) + return false; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node p = last; p != null; p = p.prev) { + if (o.equals(p.item)) { + unlink(p); + return true; + } + } + return false; + } finally { + lock.unlock(); + } + } + + // BlockingQueue methods + + /** + * Inserts the specified element at the end of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, it + * is generally preferable to use method {@link #offer(Object) offer}. + * + *

+ * This method is equivalent to {@link #addLast}. + * + * @throws IllegalStateException + * if the element cannot be added at this time due to capacity + * restrictions + * @throws NullPointerException + * if the specified element is null + */ + public boolean add(E e) { + addLast(e); + return true; + } + + /** + * @throws NullPointerException + * if the specified element is null + */ + public boolean offer(E e) { + return offerLast(e); + } + + /** + * @throws NullPointerException + * {@inheritDoc} + * @throws InterruptedException + * {@inheritDoc} + */ + public void put(E e) throws InterruptedException { + putLast(e); + } + + /** + * @throws NullPointerException + * {@inheritDoc} + * @throws InterruptedException + * {@inheritDoc} + */ + public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { + return offerLast(e, timeout, unit); + } + + /** + * Retrieves and removes the head of the queue represented by this deque. + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + *

+ * This method is equivalent to {@link #removeFirst() removeFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException + * if this deque is empty + */ + public E remove() { + return removeFirst(); + } + + public E poll() { + return pollFirst(); + } + + public E take() throws InterruptedException { + return takeFirst(); + } + + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + return pollFirst(timeout, unit); + } + + /** + * Retrieves, but does not remove, the head of the queue represented by this + * deque. This method differs from {@link #peek peek} only in that it throws + * an exception if this deque is empty. + * + *

+ * This method is equivalent to {@link #getFirst() getFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException + * if this deque is empty + */ + public E element() { + return getFirst(); + } + + public E peek() { + return peekFirst(); + } + + /** + * Returns the number of additional elements that this deque can ideally (in + * the absence of memory or resource constraints) accept without blocking. + * This is always equal to the initial capacity of this deque less the + * current size of this deque. + * + *

+ * Note that you cannot always tell if an attempt to insert an + * element will succeed by inspecting remainingCapacity because it + * may be the case that another thread is about to insert or remove an + * element. + */ + public int remainingCapacity() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return capacity - count; + } finally { + lock.unlock(); + } + } + + /** + * @throws UnsupportedOperationException + * {@inheritDoc} + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + public int drainTo(Collection c) { + return drainTo(c, Integer.MAX_VALUE); + } + + /** + * @throws UnsupportedOperationException + * {@inheritDoc} + * @throws ClassCastException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + * @throws IllegalArgumentException + * {@inheritDoc} + */ + public int drainTo(Collection c, int maxElements) { + if (c == null) + throw new NullPointerException(); + if (c == this) + throw new IllegalArgumentException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + int n = Math.min(maxElements, count); + for (int i = 0; i < n; i++) { + c.add(first.item); // In this order, in case add() throws. + unlinkFirst(); + } + return n; + } finally { + lock.unlock(); + } + } + + // Stack methods + + /** + * @throws IllegalStateException + * {@inheritDoc} + * @throws NullPointerException + * {@inheritDoc} + */ + public void push(E e) { + addFirst(e); + } + + /** + * @throws NoSuchElementException + * {@inheritDoc} + */ + public E pop() { + return removeFirst(); + } + + // Collection methods + + /** + * Removes the first occurrence of the specified element from this deque. If + * the deque does not contain the element, it is unchanged. More formally, + * removes the first element e such that o.equals(e) (if + * such an element exists). Returns true if this deque contained + * the specified element (or equivalently, if this deque changed as a result + * of the call). + * + *

+ * This method is equivalent to {@link #removeFirstOccurrence(Object) + * removeFirstOccurrence}. + * + * @param o + * element to be removed from this deque, if present + * @return true if this deque changed as a result of the call + */ + public boolean remove(Object o) { + return removeFirstOccurrence(o); + } + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return count; + } finally { + lock.unlock(); + } + } + + /** + * Returns true if this deque contains the specified element. More + * formally, returns true if and only if this deque contains at + * least one element e such that o.equals(e). + * + * @param o + * object to be checked for containment in this deque + * @return true if this deque contains the specified element + */ + public boolean contains(Object o) { + if (o == null) + return false; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node p = first; p != null; p = p.next) + if (o.equals(p.item)) + return true; + return false; + } finally { + lock.unlock(); + } + } + + /* + * TODO: Add support for more efficient bulk operations. + * + * We don't want to acquire the lock for every iteration, but we also want + * other threads a chance to interact with the collection, especially when + * count is close to capacity. + */ + + // /** + // * Adds all of the elements in the specified collection to this + // * queue. Attempts to addAll of a queue to itself result in + // * {@code IllegalArgumentException}. Further, the behavior of + // * this operation is undefined if the specified collection is + // * modified while the operation is in progress. + // * + // * @param c collection containing elements to be added to this queue + // * @return {@code true} if this queue changed as a result of the call + // * @throws ClassCastException {@inheritDoc} + // * @throws NullPointerException {@inheritDoc} + // * @throws IllegalArgumentException {@inheritDoc} + // * @throws IllegalStateException {@inheritDoc} + // * @see #add(Object) + // */ + // public boolean addAll(Collection c) { + // if (c == null) + // throw new NullPointerException(); + // if (c == this) + // throw new IllegalArgumentException(); + // final ReentrantLock lock = this.lock; + // lock.lock(); + // try { + // boolean modified = false; + // for (E e : c) + // if (linkLast(e)) + // modified = true; + // return modified; + // } finally { + // lock.unlock(); + // } + // } + + /** + * Returns an array containing all of the elements in this deque, in proper + * sequence (from first to last element). + * + *

+ * The returned array will be "safe" in that no references to it are + * maintained by this deque. (In other words, this method must allocate a + * new array). The caller is thus free to modify the returned array. + * + *

+ * This method acts as bridge between array-based and collection-based APIs. + * + * @return an array containing all of the elements in this deque + */ + // @SuppressWarnings("unchecked") + public Object[] toArray() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Object[] a = new Object[count]; + int k = 0; + for (Node p = first; p != null; p = p.next) + a[k++] = p.item; + return a; + } finally { + lock.unlock(); + } + } + + /** + * Returns an array containing all of the elements in this deque, in proper + * sequence; the runtime type of the returned array is that of the specified + * array. If the deque fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of this deque. + * + *

+ * If this deque fits in the specified array with room to spare (i.e., the + * array has more elements than this deque), the element in the array + * immediately following the end of the deque is set to null. + * + *

+ * Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, under + * certain circumstances, be used to save allocation costs. + * + *

+ * Suppose x is a deque known to contain only strings. The + * following code can be used to dump the deque into a newly allocated array + * of String: + * + *

+	 * String[] y = x.toArray(new String[0]);
+	 * 
+ * + * Note that toArray(new Object[0]) is identical in function to + * toArray(). + * + * @param a + * the array into which the elements of the deque are to be + * stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose + * @return an array containing all of the elements in this deque + * @throws ArrayStoreException + * if the runtime type of the specified array is not a supertype + * of the runtime type of every element in this deque + * @throws NullPointerException + * if the specified array is null + */ + public T[] toArray(T[] a) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (a.length < count) + a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), count); + + int k = 0; + for (Node p = first; p != null; p = p.next) + a[k++] = (T) p.item; + if (a.length > k) + a[k] = null; + return a; + } finally { + lock.unlock(); + } + } + + public String toString() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return super.toString(); + } finally { + lock.unlock(); + } + } + + /** + * Atomically removes all of the elements from this deque. The deque will be + * empty after this call returns. + */ + public void clear() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node f = first; f != null;) { + f.item = null; + Node n = f.next; + f.prev = null; + f.next = null; + f = n; + } + first = last = null; + count = 0; + notFull.signalAll(); + } finally { + lock.unlock(); + } + } + + /** + * Returns an iterator over the elements in this deque in proper sequence. + * The elements will be returned in order from first (head) to last (tail). + * The returned Iterator is a "weakly consistent" iterator that + * will never throw {@link ConcurrentModificationException}, and guarantees + * to traverse elements as they existed upon construction of the iterator, + * and may (but is not guaranteed to) reflect any modifications subsequent + * to construction. + * + * @return an iterator over the elements in this deque in proper sequence + */ + public Iterator iterator() { + return new Itr(); + } + + /** + * Returns an iterator over the elements in this deque in reverse sequential + * order. The elements will be returned in order from last (tail) to first + * (head). The returned Iterator is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications + * subsequent to construction. + */ + public Iterator descendingIterator() { + return new DescendingItr(); + } + + /** + * Base class for Iterators for LinkedBlockingDeque + */ + private abstract class AbstractItr implements Iterator { + /** + * The next node to return in next() + */ + Node next; + + /** + * nextItem holds on to item fields because once we claim that an + * element exists in hasNext(), we must return item read under lock (in + * advance()) even if it was in the process of being removed when + * hasNext() was called. + */ + E nextItem; + + /** + * Node returned by most recent call to next. Needed by remove. Reset to + * null if this element is deleted by a call to remove. + */ + private Node lastRet; + + abstract Node firstNode(); + + abstract Node nextNode(Node n); + + AbstractItr() { + // set to initial position + final ReentrantLock lock = FairLinkedBlockingDeque.this.lock; + lock.lock(); + try { + next = firstNode(); + nextItem = (next == null) ? null : next.item; + } finally { + lock.unlock(); + } + } + + /** + * Advances next. + */ + + void advance() { + final ReentrantLock lock = FairLinkedBlockingDeque.this.lock; + lock.lock(); + try { + // assert next != null; + Node s = nextNode(next); + if (s == next) { + next = firstNode(); + } else { + // Skip over removed nodes. + // May be necessary if multiple interior Nodes are removed. + while (s != null && s.item == null) + s = nextNode(s); + next = s; + } + nextItem = (next == null) ? null : next.item; + } finally { + lock.unlock(); + } + } + + public boolean hasNext() { + return next != null; + } + + public E next() { + if (next == null) + throw new NoSuchElementException(); + lastRet = next; + E x = nextItem; + advance(); + return x; + } + + public void remove() { + Node n = lastRet; + if (n == null) + throw new IllegalStateException(); + lastRet = null; + + final ReentrantLock lock = FairLinkedBlockingDeque.this.lock; + lock.lock(); + try { + if (n.item != null) + unlink(n); + } finally { + lock.unlock(); + } + } + } + + /** Forward iterator */ + private class Itr extends AbstractItr { + Node firstNode() { + return first; + } + + Node nextNode(Node n) { + return n.next; + } + } + + /** Descending iterator */ + private class DescendingItr extends AbstractItr { + Node firstNode() { + return last; + } + + Node nextNode(Node n) { + return n.prev; + } + } + + /** + * Save the state of this deque to a stream (that is, serialize it). + * + * @serialData The capacity (int), followed by elements (each an + * Object) in the proper order, followed by a null + * @param s + * the stream + */ + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + // Write out capacity and any hidden stuff + s.defaultWriteObject(); + // Write out all elements in the proper order. + for (Node p = first; p != null; p = p.next) + s.writeObject(p.item); + // Use trailing null as sentinel + s.writeObject(null); + } finally { + lock.unlock(); + } + } + + /** + * Reconstitute this deque from a stream (that is, deserialize it). + * + * @param s + * the stream + */ + private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + count = 0; + first = null; + last = null; + // Read in all elements and place in queue + for (;;) { + // @SuppressWarnings("unchecked") + E item = (E) s.readObject(); + if (item == null) + break; + add(item); + } + } +} diff --git a/src/main/java/io/mycat/util/MinHeap.java b/src/main/java/io/mycat/util/MinHeap.java new file mode 100644 index 000000000..280f3ea34 --- /dev/null +++ b/src/main/java/io/mycat/util/MinHeap.java @@ -0,0 +1,14 @@ +package io.mycat.util; + +import java.util.Collection; + +public interface MinHeap extends Collection { + + public E poll(); + + public E peak(); + + public void replaceTop(E e); + + public E find(E e); +} \ No newline at end of file diff --git a/src/main/java/io/mycat/util/RBTreeList.java b/src/main/java/io/mycat/util/RBTreeList.java new file mode 100644 index 000000000..c950f06d6 --- /dev/null +++ b/src/main/java/io/mycat/util/RBTreeList.java @@ -0,0 +1,575 @@ +package io.mycat.util; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +public class RBTreeList extends AbstractList { + + private final Comparator comparator; + private static final boolean RED = false; + private static final boolean BLACK = true; + + private Object[] elementData; + private int size; + private RBTNode root; + + public RBTreeList(int initialCapacity, Comparator comparator) { + super(); + this.comparator = comparator; + this.elementData = new Object[initialCapacity]; + } + + public void ensureCapacity(int minCapacity) { + modCount++; + int oldCapacity = elementData.length; + if (minCapacity > oldCapacity) { + Object oldData[] = elementData; + int newCapacity = (oldCapacity * 3) / 2 + 1; + newCapacity = Math.max(newCapacity, minCapacity); + // minCapacity is usually close to size, so this is a win: + elementData = Arrays.copyOf(oldData, newCapacity); + } + } + + @Override + public boolean add(E e) { + ensureCapacity(size + 1); // Increments modCount!! + int index = size++; + elementData[index] = e; + RBTNode node = new RBTNode(index, BLACK, e); + insert(node); + return true; + } + + private void insert(RBTNode node) { + if (root == null) { + root = node; + return; + } + + int cmp; + RBTNode x = this.root; + RBTNode parent; + do { + parent = x; + cmp = comparator.compare(node.getValue(), x.getValue()); + if (cmp < 0) + x = x.left; + else + x = x.right; + } while (x != null); + node.parent = parent; + if (cmp < 0) + parent.left = node; + else + parent.right = node; + + fixAfterInsertion(node); + } + + private void fixAfterInsertion(RBTNode x) { + x.color = RED; + while (x != null && x != root && x.parent.color == RED) { + // parent is grandparent's left child + if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { + + RBTNode y = rightOf(parentOf(parentOf(x))); + // uncle's color is red + if (colorOf(y) == RED) { + setColor(parentOf(x), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(x)), RED); + x = parentOf(parentOf(x)); + } else { + if (x == rightOf(parentOf(x))) { + x = parentOf(x); + rotateLeft(x); + } + setColor(parentOf(x), BLACK); + setColor(parentOf(parentOf(x)), RED); + rotateRight(parentOf(parentOf(x))); + } + } else { + RBTNode y = leftOf(parentOf(parentOf(x))); + if (colorOf(y) == RED) { + setColor(parentOf(x), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(x)), RED); + x = parentOf(parentOf(x)); + } else { + if (x == leftOf(parentOf(x))) { + x = parentOf(x); + rotateRight(x); + } + setColor(parentOf(x), BLACK); + setColor(parentOf(parentOf(x)), RED); + rotateLeft(parentOf(parentOf(x))); + } + } + } + root.color = BLACK; + } + + /** + *
+	 * 
+	 *      px                              px
+	 *     /                               /
+	 *    x                               y                
+	 *   /  \      --(rotate left)-.     / \                #
+	 *  lx   y                          x  ry     
+	 *     /   \                       /  \
+	 *    ly   ry                     lx  ly
+	 * 
+ * + * @param p + */ + private void rotateLeft(RBTNode p) { + if (p != null) { + RBTNode r = p.right; + p.right = r.left; + if (r.left != null) + r.left.parent = p; + r.parent = p.parent; + if (p.parent == null) + root = r; + else if (p.parent.left == p) + p.parent.left = r; + else + p.parent.right = r; + r.left = p; + p.parent = r; + } + } + + /** + *
+	 * 
+	 *            py                               py
+	 *           /                                /
+	 *          y                                x                  
+	 *         /  \      --(rotate right)-.     /  \                     #
+	 *        x   ry                           lx   y  
+	 *       / \                                   / \                   #
+	 *      lx  rx                                rx  ry
+	 * 
+ * + * @param p + */ + private void rotateRight(RBTNode p) { + if (p != null) { + RBTNode l = p.left; + p.left = l.right; + if (l.right != null) + l.right.parent = p; + l.parent = p.parent; + if (p.parent == null) + root = l; + else if (p.parent.right == p) + p.parent.right = l; + else + p.parent.left = l; + l.right = p; + p.parent = l; + } + } + + private boolean colorOf(RBTNode node) { + return (node == null ? BLACK : node.color); + } + + private RBTNode parentOf(RBTNode node) { + return (node == null ? null : node.parent); + } + + private void setColor(RBTNode node, boolean c) { + if (node != null) + node.color = c; + } + + private RBTNode leftOf(RBTNode node) { + return (node == null) ? null : node.left; + } + + private RBTNode rightOf(RBTNode node) { + return (node == null) ? null : node.right; + } + + @Override + public E set(int index, E element) { + RangeCheck(index); + E oldValue = (E) elementData[index]; + elementData[index] = element; + RBTNode oldNode = find(oldValue); + delete(oldNode); + RBTNode newNode = new RBTNode(index, BLACK, element); + insert(newNode); + return oldValue; + } + + @Override + public void add(int index, E element) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + + ensureCapacity(size + 1); // Increments modCount!! + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + RBTNode node = new RBTNode(index, BLACK, element); + insert(node); + size++; + } + + @Override + public E remove(int index) { + RangeCheck(index); + + modCount++; + E oldValue = (E) elementData[index]; + + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index + 1, elementData, index, numMoved); + elementData[--size] = null; // Let gc do its work + RBTNode node = find(oldValue); + if (node != null) + delete(node); + return oldValue; + } + + /** + * find the minimum node which value >= t.value + * + * @param t + * @return + */ + private RBTNode successor(RBTNode t) { + if (t == null) + return null; + + if (t.right != null) { + RBTNode p = t.right; + while (p.left != null) + p = p.left; + return p; + } + + RBTNode p = t.parent; + RBTNode ch = t; + // only child is parent's left node can return parent + while (p != null && ch == p.right) { + ch = p; + p = p.parent; + } + return p; + + } + + /** + * find the maximum node which value <= t.value + * + * @param t + * @return + */ + public RBTNode predecessor(RBTNode t) { + if (t == null) + return null; + + if (t.left != null) { + RBTNode p = t.left; + while (p.right != null) + p = p.right; + return p; + } + + RBTNode p = t.parent; + RBTNode ch = t; + // only child is parent's right node can return parent + while (p != null && ch == p.left) { + ch = p; + p = p.parent; + } + return p; + + } + + private void delete(RBTNode node) { + // If strictly internal, copy successor's element to node and then make + // p + // point to successor. + if (node.left != null && node.right != null) { + RBTNode s = successor(node); + node.index = s.index; + node.value = s.value; + node = s; + } // node has 2 children + + // Start fixup at replacement node, if it exists. + RBTNode replacement = (node.left != null ? node.left : node.right); + + if (replacement != null) { + // Link replacement to parent + replacement.parent = node.parent; + if (node.parent == null) + root = replacement; + else if (node == node.parent.left) + node.parent.left = replacement; + else + node.parent.right = replacement; + + // Null out links so they are OK to use by fixAfterDeletion. + node.left = node.right = node.parent = null; + + // Fix replacement + if (node.color == BLACK) + fixAfterDeletion(replacement); + } else if (node.parent == null) { // return if we are the only node. + root = null; + } else { // No children. Use self as phantom replacement and unlink. + if (node.color == BLACK) + fixAfterDeletion(node); + + if (node.parent != null) { + if (node == node.parent.left) + node.parent.left = null; + else if (node == node.parent.right) + node.parent.right = null; + node.parent = null; + } + } + } + + private void fixAfterDeletion(RBTNode x) { + while (x != root && colorOf(x) == BLACK) { + if (x == leftOf(parentOf(x))) { + RBTNode sib = rightOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateLeft(parentOf(x)); + sib = rightOf(parentOf(x)); + } + + if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(rightOf(sib)) == BLACK) { + setColor(leftOf(sib), BLACK); + setColor(sib, RED); + rotateRight(sib); + sib = rightOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(rightOf(sib), BLACK); + rotateLeft(parentOf(x)); + x = root; + } + } else { // symmetric + RBTNode sib = leftOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateRight(parentOf(x)); + sib = leftOf(parentOf(x)); + } + + if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(leftOf(sib)) == BLACK) { + setColor(rightOf(sib), BLACK); + setColor(sib, RED); + rotateLeft(sib); + sib = leftOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(leftOf(sib), BLACK); + rotateRight(parentOf(x)); + x = root; + } + } + } + + setColor(x, BLACK); + } + + private RBTNode find(E e) { + RBTNode t = root; + if (t == null) + return t; + while (t != null) { + int cmp = comparator.compare(e, t.value); + if (cmp < 0) + t = t.left; + else if (cmp > 0) + t = t.right; + else + return t; + } + return t; + + } + + @Override + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + @Override + public int indexOf(Object o) { + RBTNode t = find((E) o); + if (t == null) + return -1; + return t.getIndex(); + } + + @Override + public void clear() { + modCount++; + + // Let gc do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + destory(root); + root = null; + size = 0; + } + + private void destory(RBTNode node) { + if (node == null) + return; + + if (node.left != null) { + destory(node.left); + node.left = null; + } + if (node.right != null) { + destory(node.right); + node.right = null; + } + node.parent = null; + node.value = null; + } + + @Override + public boolean addAll(int index, Collection c) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacity(size + numNew); // Increments modCount + + int numMoved = size - index; + if (numMoved > 0) + System.arraycopy(elementData, index, elementData, index + numNew, numMoved); + + System.arraycopy(a, 0, elementData, index, numNew); + for (int i = 0; i < a.length; i++) { + int idx = index + i; + RBTNode node = new RBTNode(idx, BLACK, (E) a[i]); + insert(node); + } + size += numNew; + return numNew != 0; + } + + @Override + public boolean addAll(Collection c) { + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacity(size + numNew); // Increments modCount + System.arraycopy(a, 0, elementData, size, numNew); + for (int i = 0; i < a.length; i++) { + int idx = size + i; + RBTNode node = new RBTNode(idx, BLACK, (E) a[i]); + insert(node); + } + size += numNew; + return numNew != 0; + } + + @Override + public E get(int index) { + RangeCheck(index); + return (E) elementData[index]; + } + + @Override + public int size() { + return this.size; + } + + @Override + public Object[] toArray() { + Object[] obj = inOrder(); + return Arrays.copyOf(obj, size); + } + + @Override + public T[] toArray(T[] a) { + Object[] obj = inOrder(); + if (a.length < size) + // Make a new array of a's runtime type, but my contents: + return (T[]) Arrays.copyOf(obj, size, a.getClass()); + System.arraycopy(obj, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + + private void inOrder(RBTNode node, List list) { + if (node != null) { + inOrder(node.left, list); + list.add(node.getValue()); + inOrder(node.right, list); + } + } + + private Object[] inOrder() { + List list = new ArrayList(size); + inOrder(root, list); + return list.toArray(); + } + + private void RangeCheck(int index) { + if (index >= size) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + + static class RBTNode { + private int index; + private boolean color; + private E value; + private RBTNode left; + private RBTNode right; + private RBTNode parent; + + public RBTNode(int index, boolean color, E value) { + this.index = index; + this.color = color; + this.value = value; + } + + public E getValue() { + return value; + } + + public int getIndex() { + return index; + } + + } + +} diff --git a/src/main/java/io/mycat/util/StringUtil.java b/src/main/java/io/mycat/util/StringUtil.java index c552e8c3f..f452db04c 100644 --- a/src/main/java/io/mycat/util/StringUtil.java +++ b/src/main/java/io/mycat/util/StringUtil.java @@ -30,8 +30,6 @@ import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.mycat.sqlengine.mpp.LoadData; - /** * @author mycat */ @@ -469,81 +467,6 @@ public class StringUtil { return new String(chars, 0, len); } - /** - * insert into tablexxx - * - * @param oriSql - * @return - */ - public static String getTableName(String oriSql) { - //此处应该优化为去掉sql中的注释,或兼容注释 - String sql=null; - if(oriSql.startsWith(LoadData.loadDataHint)) - { - sql=oriSql.substring(LoadData.loadDataHint.length()) ; - } else - { - sql=oriSql; - } - int pos = 0; - boolean insertFound = false; - boolean intoFound = false; - int tableStartIndx = -1; - int tableEndIndex = -1; - while (pos < sql.length()) { - char ch = sql.charAt(pos); - // 忽略处理注释 /* */ BEN - if(ch == '/' && pos+4 < sql.length() && sql.charAt(pos+1) == '*') { - if(sql.substring(pos+2).indexOf("*/") != -1) { - pos += sql.substring(pos+2).indexOf("*/")+4; - continue; - } else { - // 不应该发生这类情况。 - throw new IllegalArgumentException("sql 注释 语法错误"); - } - } else if (ch <= ' ' || ch == '(' || ch=='`') {// - if (tableStartIndx > 0) { - tableEndIndex = pos; - break; - } else { - pos++; - continue; - } - } else if (ch == 'i' || ch == 'I') { - if (intoFound) { - if (tableStartIndx == -1 && ch!='`') { - tableStartIndx = pos; - } - pos++; - } else if (insertFound) {// into start - // 必须全部都为INTO才认为是into - if(pos+5 < sql.length() && (sql.charAt(pos+1) == 'n' || sql.charAt(pos+1) == 'N') && (sql.charAt(pos+2) == 't' || sql.charAt(pos+2) == 'T') && (sql.charAt(pos+3) == 'o' || sql.charAt(pos+3) == 'O') && (sql.charAt(pos+4) <= ' ')) { - pos = pos + 5; - intoFound = true; - } else { - pos++; - } - } else { - // 矫正必须全部都为 INSERT才认为是insert - // insert start - if(pos+7 < sql.length() && (sql.charAt(pos+1) == 'n' || sql.charAt(pos+1) == 'N') && (sql.charAt(pos+2) == 's' || sql.charAt(pos+2) == 'S') && (sql.charAt(pos+3) == 'e' || sql.charAt(pos+3) == 'E') && (sql.charAt(pos+4) == 'r' || sql.charAt(pos+4) == 'R') && (sql.charAt(pos+5) == 't' || sql.charAt(pos+5) == 'T') && (sql.charAt(pos+6) <= ' ')) { - pos = pos + 7; - insertFound = true; - } else { - pos++; - } - } - } else { - if (tableStartIndx == -1) { - tableStartIndx = pos; - } - pos++; - } - - } - return sql.substring(tableStartIndx, tableEndIndex); - } - /** * 移除`符号, ```tablename` may not correct * @param str @@ -560,15 +483,4 @@ public class StringUtil { } return str; } - - public static void main(String[] args) { - System.out.println(getTableName("insert into ssd (id) values (s)")); - System.out.println(getTableName("insert into ssd(id) values (s)")); - System.out.println(getTableName(" insert into ssd(id) values (s)")); - System.out.println(getTableName(" insert into isd(id) values (s)")); - System.out.println(getTableName("INSERT INTO test_activity_input (id,vip_no")); - System.out.println(getTableName("/* ApplicationName=DBeaver 3.3.1 - Main connection */ insert into employee(id,name,sharding_id) values(4,’myhome’,10011)")); - System.out.println(countChar("insert into ssd (id) values (s) ,(s),(7);",'(')); - } - } \ No newline at end of file diff --git a/src/main/java/io/mycat/util/exception/NotSupportException.java b/src/main/java/io/mycat/util/exception/NotSupportException.java new file mode 100644 index 000000000..5d13c9198 --- /dev/null +++ b/src/main/java/io/mycat/util/exception/NotSupportException.java @@ -0,0 +1,29 @@ +package io.mycat.util.exception; + +public class NotSupportException extends RuntimeException { + private static final long serialVersionUID = 7394431636300968222L; + + public NotSupportException(String errorCode, String errorDesc, Throwable cause) { + super(errorCode + ":" + errorDesc, cause); + } + + public NotSupportException(String errorCode, String errorDesc) { + super(errorCode + ":" + errorDesc); + } + + public NotSupportException(String errorCode, Throwable cause) { + super(errorCode, cause); + } + + public NotSupportException(String errorCode) { + super(errorCode); + } + + public NotSupportException(Throwable cause) { + super(cause); + } + + public NotSupportException() { + super("not support yet!"); + } +} diff --git a/src/main/java/io/mycat/util/exception/TmpFileException.java b/src/main/java/io/mycat/util/exception/TmpFileException.java new file mode 100644 index 000000000..319aea413 --- /dev/null +++ b/src/main/java/io/mycat/util/exception/TmpFileException.java @@ -0,0 +1,124 @@ +package io.mycat.util.exception; + +import java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Properties; + + +public class TmpFileException extends RuntimeException { + private static final long serialVersionUID = 1L; + private static final Properties MESSAGES = new Properties(); + static { + try { + InputStream in = TmpFileException.class.getResourceAsStream("/io/mycat/util/exception/res/_messages_en.prop"); + if (in != null) { + MESSAGES.load(in); + } + String language = Locale.getDefault().getLanguage(); + if (!"en".equals(language)) { + + } + } catch (IOException e) { + } + } + + private TmpFileException(String message) { + super(message, null); + } + + private TmpFileException(String message, Throwable cause) { + super(message, cause); + } + + public static TmpFileException get(int errorCode, String... params) { + String message = translate(String.valueOf(errorCode), params); + return new TmpFileException(message); + } + + public static TmpFileException get(int errorCode, Throwable cause, String... params) { + String message = translate(String.valueOf(errorCode), params); + return new TmpFileException(message, cause); + } + + private static String translate(String key, String... params) { + String message = null; + if (MESSAGES != null) { + // Tomcat sets final static fields to null sometimes + message = MESSAGES.getProperty(key); + } + if (message == null) { + message = "(Message " + key + " not found)"; + } + if (params != null) { + for (int i = 0; i < params.length; i++) { + String s = params[i]; + if (s != null && s.length() > 0) { + params[i] = quoteIdentifier(s); + } + } + message = MessageFormat.format(message, (Object[]) params); + } + return message; + } + + private static String quoteIdentifier(String s) { + int length = s.length(); + StringBuilder buff = new StringBuilder(length + 2); + buff.append('\"'); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == '"') { + buff.append(c); + } + buff.append(c); + } + return buff.append('\"').toString(); + } + /** + * Create a ares exception for a specific error code. + * + * @param errorCode + * the error code + * @param p1 + * the first parameter of the message + * @return the exception + */ + public static TmpFileException get(int errorCode, String p1) { + return get(errorCode, new String[] { p1 }); + } + + /** + * Convert an IO exception to a database exception. + * + * @param e + * the root cause + * @param message + * the message or null + * @return the database exception object + */ + public static TmpFileException convertIOException(int errorCode, IOException e, String message) { + if (message == null) { + Throwable t = e.getCause(); + if (t instanceof TmpFileException) { + return (TmpFileException) t; + } + return get(errorCode, e.toString()); + } + return get(errorCode * 10, e.toString(), message); + } + + /** + * Gets a exception meaning this value is invalid. + * + * @param param + * the name of the parameter + * @param value + * the value passed + * @return the IllegalArgumentException object + */ + public static TmpFileException getInvalidValueException(int errorCode, String param, Object value) { + return get(errorCode, value == null ? "null" : value.toString(), param); + } +} diff --git a/src/main/java/io/mycat/util/exception/res/_messages_en.prop b/src/main/java/io/mycat/util/exception/res/_messages_en.prop new file mode 100644 index 000000000..f81e7917e --- /dev/null +++ b/src/main/java/io/mycat/util/exception/res/_messages_en.prop @@ -0,0 +1,13 @@ +5301=Could not open file {0} +5302=Could not force file {0} +5303=Could not sync file {0} +5304=Reading from {0} failed +5305=Writing to {0} failed +5306=Error while renaming file {0} to {1} +5307=Cannot delete file {0} +5308=IO Exception: {0} +5309=Reading from {0} failed,index out of bounds +5310=Hex a decimal string with odd number of characters: {0} +5311=Hex a decimal string contains non-hex character: {0} +5312=Invalid value {0} for parameter {1} +5313=Error while creating file {0} \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 6f144b5af..f2724f565 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -22,7 +22,7 @@ - + diff --git a/src/main/resources/schema.dtd b/src/main/resources/schema.dtd index 30ceab468..f4fc71f34 100644 --- a/src/main/resources/schema.dtd +++ b/src/main/resources/schema.dtd @@ -18,7 +18,6 @@ - @@ -30,7 +29,6 @@ - diff --git a/src/test/java/demo/test/Testparser.java b/src/test/java/demo/test/Testparser.java index 3f20b61f0..17b4cceaf 100644 --- a/src/test/java/demo/test/Testparser.java +++ b/src/test/java/demo/test/Testparser.java @@ -3,8 +3,18 @@ package demo.test; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLCaseExpr; +import com.alibaba.druid.sql.ast.expr.SQLCastExpr; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLInListExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLNotExpr; import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLUnaryExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddColumn; import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddConstraint; import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddIndex; @@ -20,16 +30,22 @@ import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement; import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement; import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.ast.statement.SQLTruncateStatement; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique; import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlExtractExpr; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableChangeColumn; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableModifyColumn; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUnionQuery; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement; import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; import com.alibaba.druid.sql.parser.SQLStatementParser; @@ -113,11 +129,67 @@ public class Testparser { // obj.test(strUpdateSql); // strUpdateSql = "UPDATE EmployeeS AS P SET P.LEAGUENO = '2000' WHERE P.EmployeeNO = 95;"; // obj.test(strUpdateSql); - String strCreateIndex = ""; - strCreateIndex = "CREATE INDEX part_of_name ON customer (name(10));"; - obj.test(strCreateIndex); - strCreateIndex = "CREATE UNIQUE INDEX part_of_name ON customer (name(10));"; - obj.test(strCreateIndex); +// String strCreateIndex = ""; +// strCreateIndex = "CREATE INDEX part_of_name ON customer (name(10));"; +// obj.test(strCreateIndex); +// strCreateIndex = "CREATE UNIQUE INDEX part_of_name ON customer (name(10));"; +// obj.test(strCreateIndex); + +// String selectSQl = "select udf(char_columns.id), BIT_AND(char_columns.ID),BIT_OR(char_columns.ID),bit_xor(char_columns.ID),STD(char_columns.ID),STDDEV_SAMP(char_columns.ID), VAR_SAMP(char_columns.ID),VARIANCE(char_columns.ID),sum(id),avg(id),MIN(distinct char_columns.ID),MAX(distinct char_columns.ID),COUNT(char_columns.ID) from char_columns where id =1 and name = 'x';"; +// obj.test(selectSQl); + String selectSQl = "select @@tx_read_only;"; + obj.test(selectSQl); +// selectSQl = "SELECT ABS(-1);"; +// obj.test(selectSQl); +// selectSQl = "SELECT NOT 10,1 AND 1,IF(1<2,'yes','no'),'Monty!' REGEXP '.*';"; +// obj.test(selectSQl); +// selectSQl = "select 'David_' LIKE 'David|_' ESCAPE '|','a' LIKE 'ae' COLLATE utf8_general_ci"; +// obj.test(selectSQl); +// selectSQl = "SELECT 1 IS NULL,1 IS NOT UNKNOWN,1 IS TRUE, 0 IS FALSE,2 IN (0,3,5,7), 2 >= 2,1 = 0,2 BETWEEN 1 AND 3;"; +// obj.test(selectSQl); +// selectSQl = "select CAST(expr AS datetime(6) ), CAST(expr AS date ), CAST(expr AS time(6) ) from char_columns where id =1 and name = 'x';"; +// TODO:NOT SUPPORTED + selectSQl = "select CAST(expr AS nchar(2) CHARACTER SET utf8),CAST(expr AS char(2)), CAST(expr AS char(2) CHARACTER SET utf8 ),CAST(expr AS char(2) CHARACTER SET latin1 ) from char_columns where id =1 and name = 'x';"; +// selectSQl = "select CAST(expr AS char(2) CHARACTER SET utf8 ),CAST(expr AS SIGNED INT ),CAST(expr AS unSIGNED INT ) from char_columns where id =1 and name = 'x';"; +// obj.test(selectSQl); +// selectSQl = "select CONVERT(expr , char(2)) from char_columns where id =1 and name = 'x';"; +// obj.test(selectSQl); +// selectSQl = "SELECT CASE 1 WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'more' END, CASE WHEN 1>0 THEN 'true' ELSE 'false' END;"; +// obj.test(selectSQl); +// selectSQl = "SELECT - (2), 3/5;"; +// obj.test(selectSQl); +// selectSQl = "SELECT ~5 , SELECT 29 & 15;"; +// obj.test(selectSQl); +// selectSQl = "SELECT LTRIM(' barbar'),TRIM(LEADING 'x' FROM 'xxxbarxxx'),SOUNDEX('Hello'), CONVERT('abc' USING utf8);"; +// obj.test(selectSQl); +// selectSQl = "SELECT SOUNDEX('Hello'),CHAR(77,121,83,81,'76');"; +// obj.test(selectSQl); +// selectSQl = "SELECT student_name, GROUP_CONCAT(DISTINCT test_score order by id asc,test_score2 DESC SEPARATOR \",,\") FROM student GROUP BY student_name;"; +// obj.test(selectSQl); +// selectSQl = "select char_columns.id from char_columns where id =1 order by id desc;"; +// obj.test(selectSQl); +// selectSQl = "select * from (select * from char_columns where id =1 order by id) x;"; +// obj.test(selectSQl); +// selectSQl = "select * from char_columns where id in(select id from char_columns where id =1 ) ;"; +// obj.test(selectSQl); +// selectSQl = "SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33;"; +// obj.test(selectSQl); +// selectSQl = "SELECT * FROM t1 inner join t2 on t1.id =t2.id WHERE t1.f1 > 30;"; +// obj.test(selectSQl); + +// selectSQl = "SELECT id,sum(x),(select * from id) FROM t1 group by id desc having count(*)<3 limit 1;"; +// obj.test(selectSQl); +// selectSQl = "SELECT id FROM (select id,name from tb) x where id =1;"; +// obj.test(selectSQl); +// selectSQl = "SELECT id FROM x where id =1 union all SELECT id FROM y where id =1 ;"; +// obj.test(selectSQl); +// +// selectSQl = "SELECT TIMESTAMPADD(WEEK,1,'2003-01-02'), TIMESTAMPDIFF(MONTH,'2003-02-01','2003-05-01'),DATE_ADD(OrderDate,INTERVAL 2 DAY) AS OrderPayDate,ADDDATE('2008-01-02', INTERVAL 31 DAY),ADDDATE('2008-01-02', 31),EXTRACT(YEAR FROM '2009-07-02') FROM Orders;"; +// obj.test(selectSQl); +// selectSQl = "SELECT * FROM Orders as t;"; +// obj.test(selectSQl); +// selectSQl = "select 1,null,'x','xxx';"; +// obj.test(selectSQl); } public void test(String sql){ SQLStatementParser parser = new MySqlStatementParser(sql); @@ -236,7 +308,108 @@ public class Testparser { SQLTableSource tableSource = stament.getTable(); System.out.println(sql + ":getTableSource:" + tableSource.getClass().toString() + "\n"); System.out.println(sql + stament.getType()); - }else{ + }else if(statement instanceof SQLSelectStatement){ + SQLSelectStatement stament = (SQLSelectStatement)statement; + SQLSelectQuery sqlSelectQuery = stament.getSelect().getQuery(); + if (sqlSelectQuery instanceof MySqlSelectQueryBlock) { + MySqlSelectQueryBlock selectQueryBlock = (MySqlSelectQueryBlock)sqlSelectQuery; +// System.out.println(sql + ": selectQueryBlock.getFrom() :"+selectQueryBlock.getFrom().getClass().toString() + "\n"); + for(SQLSelectItem item :selectQueryBlock.getSelectList()){ + if(item.getExpr()!=null){ + SQLExpr func = item.getExpr(); + if(func instanceof SQLAggregateExpr){ + System.out.println( "SQLAggregateExpr:" ); + SQLAggregateExpr agg = (SQLAggregateExpr)func; + System.out.println( "MethodName:" +agg.getMethodName()+",getArguments size ="+agg.getArguments().size()+",Option:"+agg.getOption()); + System.out.println( "---------------------------" ); + } + else if(func instanceof SQLMethodInvokeExpr){ + System.out.println( "SQLMethodInvokeExpr:" ); + SQLMethodInvokeExpr method = (SQLMethodInvokeExpr)func; + System.out.println( "MethodName:" +method.getMethodName()+",getArguments size ="+method.getParameters().size()+",OWNER:"+method.getOwner()); + System.out.println( "---------------------------" ); + }else if(func instanceof SQLCastExpr){ + SQLCastExpr cast = (SQLCastExpr)func; + System.out.println( "SQLCastExpr:" ); + System.out.println( "Expr:" +cast.getExpr().getClass()+",getDataType:"+cast.getDataType() ); + System.out.println( "---------------------------" ); + } + else if(func instanceof SQLBinaryOpExpr){ + SQLBinaryOpExpr Op = (SQLBinaryOpExpr)func; + System.out.println( "SQLBinaryOpExpr:" ); + System.out.println( "left:" +Op.getLeft().getClass() ); + System.out.println( "right:"+Op.getRight().getClass()); + System.out.println( "operator:"+Op.getOperator().getClass() ); + System.out.println( "dbtype:"+Op.getDbType() ); + System.out.println( "---------------------------" ); + }else if(func instanceof SQLUnaryExpr){ + SQLUnaryExpr Op = (SQLUnaryExpr)func; + System.out.println( "SQLUnaryExpr:" ); + System.out.println( "EXPR:" +Op.getExpr().getClass() ); + System.out.println( "operator:"+Op.getOperator().getClass() ); + System.out.println( "---------------------------" ); + } + else if(func instanceof SQLBetweenExpr){ + SQLBetweenExpr between = (SQLBetweenExpr)func; + System.out.println( "SQLBetweenExpr:" ); + System.out.println( "begin EXPR:" +between.getBeginExpr() ); + System.out.println( "end EXPR:" +between.getEndExpr()); + System.out.println( "isnot :"+between.isNot() ); + System.out.println( "test :"+between.getTestExpr() ); + System.out.println( "---------------------------" ); + }else if(func instanceof SQLInListExpr){ + SQLInListExpr in = (SQLInListExpr)func; + System.out.println( "SQLInListExpr:" ); + System.out.println( "EXPR:" +in.getExpr()); + System.out.println( "isnot :"+in.isNot() ); + System.out.println( "getTargetList size :"+in.getTargetList().size()); + System.out.println( "---------------------------" ); + }else if(func instanceof SQLNotExpr){ + SQLNotExpr not = (SQLNotExpr)func; + System.out.println( "SQLNotExpr:" ); + System.out.println( "EXPR:" +not.getExpr()); + System.out.println( "---------------------------" ); + } + else if(func instanceof MySqlExtractExpr){ + MySqlExtractExpr extract = (MySqlExtractExpr)func; + System.out.println( "MySqlExtractExpr:" ); + System.out.println( "value:" +extract.getValue()); + System.out.println( "unit:" +extract.getUnit()); + System.out.println( "---------------------------" ); + } + else if(func instanceof SQLCaseExpr){ + SQLCaseExpr Case = (SQLCaseExpr)func; + System.out.println( "SQLCaseExpr:" ); + System.out.println( "value:" +Case.getValueExpr()); + System.out.println( "else:" +Case.getElseExpr()); + System.out.println( "items size:" +Case.getItems().size()); + System.out.println( "---------------------------" ); + } + else if(func instanceof SQLVariantRefExpr){ + SQLVariantRefExpr variant = (SQLVariantRefExpr)func; + System.out.println( "SQLVariantRefExpr:" ); + System.out.println( "name:" +variant.getName()); + System.out.println( "Global:" +variant.isGlobal()); + System.out.println( "index:" +variant.getIndex()); + System.out.println( "---------------------------" ); + } +// SQLAllColumnExpr + else{ +// MySqlOutputVisitor + System.out.println( "item.getExpr(): :"+item.getExpr().getClass().toString() + "\n"); + } + } + } +// SQLBinaryOpExpr +// MySqlIntervalUnit +// SQLIntegerExpr +// SQLOrderBy +// SQLSelect +// SQLInSubQueryExpr + } else if (sqlSelectQuery instanceof MySqlUnionQuery) { + } + + }else{ // to do further } } diff --git a/src/test/java/io/mycat/buffer/TestByteBufferArena.java b/src/test/java/io/mycat/buffer/TestByteBufferArena.java deleted file mode 100644 index eaf5296ae..000000000 --- a/src/test/java/io/mycat/buffer/TestByteBufferArena.java +++ /dev/null @@ -1,141 +0,0 @@ -package io.mycat.buffer; - -import junit.framework.Assert; -import org.junit.Test; -import sun.nio.ch.DirectBuffer; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 仿照Netty的思路,针对MyCat内存缓冲策略优化 - * 测试ByteBufferArena - * - * @author Hash Zhang - * @version 1.0 - * @time 17:19 2016/5/17 - * @see @https://github.com/netty/netty - */ -public class TestByteBufferArena { - int pageSize = 256; - int chunkSize = 1024 * 8; - int chunkCount = 8*128; - @Test - public void testAllocate() { - int allocTimes = 1024 ; - ByteBufferArena byteBufferArena = new ByteBufferArena(chunkSize,pageSize,chunkCount,8); - long start = System.currentTimeMillis(); - for (int i = 0; i < allocTimes; i++) { -// System.out.println("allocate "+i); -// long start=System.nanoTime(); - int size = (i % 1024) + 1 ; - ByteBuffer byteBufer = byteBufferArena.allocate(size); - ByteBuffer byteBufer2 = byteBufferArena.allocate(size); - ByteBuffer byteBufer3 = byteBufferArena.allocate(size); -// System.out.println("alloc "+size+" usage "+(System.nanoTime()-start)); -// start=System.nanoTime(); - byteBufferArena.recycle(byteBufer); - byteBufferArena.recycle(byteBufer3); -// System.out.println("recycle usage "+(System.nanoTime()-start)); - } - long used = (System.currentTimeMillis() - start); - System.out.println("ByteBufferArena total used time " + used + " avg speed " + allocTimes / used); - } - - @Test - public void testAllocateDirect() { - int pageSize = 1024 * 1024 * 100; - int allocTimes = 1024; - DirectByteBufferPool pool = new DirectByteBufferPool(pageSize, (short) 256, (short) 8,0); - long start = System.currentTimeMillis(); - for (int i = 0; i < allocTimes; i++) { - //System.out.println("allocate "+i); - //long start=System.nanoTime(); - int size = (i % 1024) + 1 ; - ByteBuffer byteBufer = pool.allocate(size); - ByteBuffer byteBufer2 = pool.allocate(size); - ByteBuffer byteBufer3 = pool.allocate(size); - //System.out.println("alloc "+size+" usage "+(System.nanoTime()-start)); - //start=System.nanoTime(); - pool.recycle(byteBufer); - pool.recycle(byteBufer3); - //System.out.println("recycle usage "+(System.nanoTime()-start)); - } - long used = (System.currentTimeMillis() - start); - System.out.println("DirectByteBufferPool total used time " + used + " avg speed " + allocTimes / used); - } - - @Test - public void testExpansion(){ - ByteBufferArena byteBufferArena = new ByteBufferArena(1024,8,1,8); - for (int i = 0; i < 1 ; i++) { - ByteBuffer byteBufer = byteBufferArena.allocate(256); - ByteBuffer byteBufer2 = byteBufferArena.allocate(256); - ByteBuffer byteBufer3 = byteBufferArena.allocate(256); - - byteBufferArena.recycle(byteBufer); - } - } - - @Test - public void testAllocateWithDifferentAddress() { - int size = 256; - int pageSize = size * 4; - int allocTimes = 8; - ByteBufferArena byteBufferArena = new ByteBufferArena(256*4,256,2,8); - Map buffs = new HashMap(8); - ByteBuffer byteBuffer = null; - DirectBuffer directBuffer = null; - ByteBuffer temp = null; - long address; - boolean failure = false; - for (int i = 0; i < allocTimes; i++) { - byteBuffer = byteBufferArena.allocate(size); - if (byteBuffer == null) { - Assert.fail("Should have enough memory"); - } - directBuffer = (DirectBuffer) byteBuffer; - address = directBuffer.address(); - System.out.println(address); - temp = buffs.get(address); - buffs.put(address, byteBuffer); - if (null != temp) { - failure = true; - break; - } - } - - for (ByteBuffer buff : buffs.values()) { - byteBufferArena.recycle(buff); - } - - if (failure == true) { - Assert.fail("Allocate with same address"); - } - } - - @Test - public void testAllocateNullWhenOutOfMemory() { - int size = 256; - int pageSize = size * 4; - int allocTimes = 9; - ByteBufferArena pool = new ByteBufferArena(256*4,256,2,8);; - long start = System.currentTimeMillis(); - ByteBuffer byteBuffer = null; - List buffs = new ArrayList(); - int i = 0; - for (; i < allocTimes; i++) { - byteBuffer = pool.allocate(size); - if (byteBuffer == null) { - break; - } - buffs.add(byteBuffer); - } - for (ByteBuffer buff : buffs) { - pool.recycle(buff); - } - } -} diff --git a/src/test/java/io/mycat/plan/visitor/TestMySQLItemVisitor.java b/src/test/java/io/mycat/plan/visitor/TestMySQLItemVisitor.java new file mode 100644 index 000000000..2d354e575 --- /dev/null +++ b/src/test/java/io/mycat/plan/visitor/TestMySQLItemVisitor.java @@ -0,0 +1,134 @@ +package io.mycat.plan.visitor; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOrderingExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.parser.SQLStatementParser; + +import io.mycat.plan.common.item.Item; + +public class TestMySQLItemVisitor { + + private String currentDb="test_schema"; + @Test + public void testGroupby() { + MySqlSelectQueryBlock query = getQuery("select col1,col2 from table1 group by col1,col2"); + SQLSelectGroupByClause groupBy = query.getGroupBy(); + int i = 0; + for (SQLExpr p : groupBy.getItems()) { + i++; + String groupCol = "col" + i; + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + p.accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, groupCol.equals(item.getItemName())); + } + } + @Test + public void testGroupbyOrder() { + MySqlSelectQueryBlock query = getQuery("select col1,col2 from table1 group by col1 desc,col2 asc "); + SQLSelectGroupByClause groupBy = query.getGroupBy(); + int i = 0; + for (SQLExpr p : groupBy.getItems()) { + i++; + String groupCol = "col" + i; + MySqlOrderingExpr groupitem = (MySqlOrderingExpr) p; + SQLExpr q = groupitem.getExpr(); + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + q.accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, groupCol.equals(item.getItemName())); + } + } + @Test + public void testGroupbyHaving() { + MySqlSelectQueryBlock query = getQuery("select col1 from table1 group by col1 having count(*)>1 "); + SQLSelectGroupByClause groupBy = query.getGroupBy(); + SQLExpr q = groupBy.getHaving(); + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + q.accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, "COUNT(*) > 1".equals(item.getItemName())); + } + + @Test + public void testOrderby() { + MySqlSelectQueryBlock query = getQuery("select col1,col2 from table1 order by col1 asc, col2 desc "); + SQLOrderBy orderBy = query.getOrderBy(); + int i = 0; + for (SQLSelectOrderByItem p : orderBy.getItems()) { + i++; + String orderCol = "col" + i; + SQLExpr expr = p.getExpr(); + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + expr.accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, orderCol.equals(item.getItemName())); + } + } + + //TODO:ORDER BY /GROUP BY position + @Test + public void testWhere() { + MySqlSelectQueryBlock query = getQuery("select col1,col2 from table1 where a =1 "); + SQLExpr expr = query.getWhere(); + + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + expr.accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, "a = 1".equals(item.getItemName())); + } + + @Test + public void testJoinCondition() { + MySqlSelectQueryBlock query = getQuery("select a.col1,b.col2 from table1 a inner join table2 b on a.id =b.id"); + SQLJoinTableSource from = (SQLJoinTableSource)query.getFrom(); + + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + from.getCondition().accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, "a.id = b.id".equals(item.getItemName())); + } + @Test + public void testSelectItem() { + MySqlSelectQueryBlock query = getQuery("select sum(col1) from table1 where a >1 "); + List items = query.getSelectList(); + + MySQLItemVisitor v = new MySQLItemVisitor(this.currentDb); + items.get(0).accept(v); + Item item = v.getItem(); + Assert.assertEquals(true, "SUM(col1)".equals(item.getItemName())); + } + + //TODO:SELECTITEM(function) + private MySqlSelectQueryBlock getQuery(String sql){ + SQLSelect select = getSelect(sql); + return (MySqlSelectQueryBlock)select.getQuery(); + + } +// private MySqlUnionQuery getUnionQuery(String sql){ +// SQLSelect select = getSelect(sql); +// return (MySqlUnionQuery)select.getQuery(); +// } + private SQLSelect getSelect(String sql){ + SQLSelectStatement stament = getSelectStatement(sql); + return stament.getSelect(); + } + private SQLSelectStatement getSelectStatement(String sql){ + SQLStatementParser parser = new MySqlStatementParser(sql); + return (SQLSelectStatement)parser.parseStatement(); + } +} diff --git a/src/test/java/io/mycat/plan/visitor/TestMySQLPlanNodeVisitor.java b/src/test/java/io/mycat/plan/visitor/TestMySQLPlanNodeVisitor.java new file mode 100644 index 000000000..62600ed38 --- /dev/null +++ b/src/test/java/io/mycat/plan/visitor/TestMySQLPlanNodeVisitor.java @@ -0,0 +1,26 @@ +package io.mycat.plan.visitor; + +import org.junit.Assert; +import org.junit.Test; + +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.parser.SQLStatementParser; + +import io.mycat.plan.PlanNode; + +public class TestMySQLPlanNodeVisitor { + @Test + public void testNoraml() { + PlanNode tableNode = getPlanNode("select * from table1"); + System.out.println(tableNode); + Assert.assertEquals(true, tableNode !=null ); + } + private PlanNode getPlanNode(String sql){ + SQLStatementParser parser = new MySqlStatementParser(sql); + SQLSelectStatement ast = (SQLSelectStatement)parser.parseStatement(); + MySQLPlanNodeVisitor visitor = new MySQLPlanNodeVisitor("test"); + visitor.visit(ast); + return visitor.getTableNode(); + } +} diff --git a/src/test/java/io/mycat/route/util/RouterUtilTest.java b/src/test/java/io/mycat/route/util/RouterUtilTest.java index f33ece835..74a43bc81 100644 --- a/src/test/java/io/mycat/route/util/RouterUtilTest.java +++ b/src/test/java/io/mycat/route/util/RouterUtilTest.java @@ -3,11 +3,6 @@ package io.mycat.route.util; import org.junit.Assert; import org.junit.Test; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; - /** * @author Hash Zhang * @version 1.0.0 @@ -17,16 +12,6 @@ public class RouterUtilTest { - @Test - public void testBatchInsert() { - String sql = "insert into hotnews(title,name) values('test1',\"name\"),('(test)',\"(test)\"),('\\\"',\"\\'\"),(\")\",\"\\\"\\')\");"; - List values = RouterUtil.handleBatchInsert(sql, sql.toUpperCase().indexOf("VALUES")); - Assert.assertTrue(values.get(0).equals("insert into hotnews(title,name) values('test1',\"name\")")); - Assert.assertTrue(values.get(1).equals("insert into hotnews(title,name) values('(test)',\"(test)\")")); - Assert.assertTrue(values.get(2).equals("insert into hotnews(title,name) values('\\\"',\"\\'\")")); - Assert.assertTrue(values.get(3).equals("insert into hotnews(title,name) values(\")\",\"\\\"\\')\")")); - } - @Test public void testRemoveSchema() { diff --git a/src/test/java/io/mycat/util/StringUtilTest.java b/src/test/java/io/mycat/util/StringUtilTest.java deleted file mode 100644 index 16af266a2..000000000 --- a/src/test/java/io/mycat/util/StringUtilTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software;Designed and Developed mainly by many Chinese - * opensource volunteers. you can redistribute it and/or modify it under the - * terms of the GNU General Public License version 2 only, as published by the - * Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Any questions about this component can be directed to it's project Web address - * https://code.google.com/p/opencloudb/. - * - */ -package io.mycat.util; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import junit.framework.Assert; - -import org.junit.Test; - -import io.mycat.util.StringUtil; - -/** - * @author mycat - */ -public class StringUtilTest { - - @Test - public void test() { - String oriSql = "insert into ssd (id) values (s)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("ssd", tableName); - } - - @Test - public void test1() { - String oriSql = "insert into ssd(id) values (s)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("ssd", tableName); - } - - @Test - public void test2() { - String oriSql = " insert into ssd(id) values (s)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("ssd", tableName); - } - - @Test - public void test3() { - String oriSql = " insert into isd(id) values (s)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("isd", tableName); - } - - @Test - public void test4() { - String oriSql = "INSERT INTO test_activity_input (id,vip_no"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("test_activity_input", tableName); - } - - @Test - public void test5() { - String oriSql = " /* ApplicationName=DBeaver 3.3.1 - Main connection */ insert into employee(id,name,sharding_id) values(4, 'myhome', 10011)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - - @Test - public void test6() { - String oriSql = " /* insert int a (id, name) value(1, 'ben') */ insert into employee(id,name,sharding_id) values(4, 'myhome', 10011)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - - @Test - public void test7() { - String oriSql = " /**/ insert into employee(id,name,sharding_id) values(4, 'myhome', 10011)"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - - @Test - public void test8() { - String oriSql = " /* */ insert into employee(id,name,sharding_id) values(4, 'myhome', 10011) /**/"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - - @Test - public void test9() { - String oriSql = " /* hint1 insert */ /**/ /* hint3 insert */ insert into employee(id,name,sharding_id) values(4, 'myhome', 10011) /**/"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - - @Test - public void test10() { - String oriSql = " /* hint1 insert */ /* // */ /* hint3 insert */ insert into employee(id,name,sharding_id) values(4, 'myhome', 10011) /**/"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - - @Test - public void test11() { - String oriSql = " /* hint1 insert */ /* // */ /* hint3 insert */ insert /* */ into employee(id,name,sharding_id) values(4, 'myhome', 10011) /**/"; - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - @Test - public void test12() { - StringWriter sw=new StringWriter(); - PrintWriter pw=new PrintWriter(sw); - pw.println("insert into"); - pw.println(" employee(id,name,sharding_id) values(4, 'myhome', 10011)"); - pw.flush(); - String oriSql = sw.toString(); - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } - @Test - public void test13() { - StringWriter sw=new StringWriter(); - PrintWriter pw=new PrintWriter(sw); - pw.println("insert into"); - pw.println("employee(id,name,sharding_id) values(4, 'myhome', 10011)"); - pw.flush(); - String oriSql = sw.toString(); - String tableName = StringUtil.getTableName(oriSql); - Assert.assertEquals("employee", tableName); - } -} \ No newline at end of file