/** * Parse and prepare the given SQL statement. This method also checks if the connection has been * closed. * * @param sql the SQL statement * @return the prepared statement */ public Command prepareLocal(String sql) { if (closed) { throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed"); } Command command; if (queryCacheSize > 0) { if (queryCache == null) { queryCache = SmallLRUCache.newInstance(queryCacheSize); modificationMetaID = database.getModificationMetaId(); } else { long newModificationMetaID = database.getModificationMetaId(); if (newModificationMetaID != modificationMetaID) { queryCache.clear(); modificationMetaID = newModificationMetaID; } command = queryCache.get(sql); if (command != null && command.canReuse()) { command.reuse(); return command; } } } Parser parser = new Parser(this); command = parser.prepareCommand(sql); if (queryCache != null) { if (command.isCacheable()) { queryCache.put(sql, command); } } return command; }
/** * Get or create a trace object for this module. * * @param module the module name * @return the trace object */ public synchronized Trace getTrace(String module) { Trace t = traces.get(module); if (t == null) { t = new Trace(writer, module); traces.put(module, t); } return t; }
public PlanItem getBestPlanItem(Session session, int[] masks) { PlanItem item = new PlanItem(); item.cost = index.getCost(session, masks); IntArray masksArray = new IntArray(masks == null ? Utils.EMPTY_INT_ARRAY : masks); ViewIndex i2 = indexCache.get(masksArray); if (i2 == null || i2.getSession() != session) { i2 = new ViewIndex(this, index, session, masks); indexCache.put(masksArray, i2); } item.setIndex(i2); return item; }
public double getCost(Session session, int[] masks) { if (recursive) { return 1000; } IntArray masksArray = new IntArray(masks == null ? Utils.EMPTY_INT_ARRAY : masks); CostElement cachedCost = costCache.get(masksArray); if (cachedCost != null) { long time = System.currentTimeMillis(); if (time < cachedCost.evaluatedAt + Constants.VIEW_COST_CACHE_MAX_AGE) { return cachedCost.cost; } } Query q = (Query) session.prepare(querySQL, true); if (masks != null) { IntArray paramIndex = new IntArray(); for (int i = 0; i < masks.length; i++) { int mask = masks[i]; if (mask == 0) { continue; } paramIndex.add(i); } int len = paramIndex.size(); for (int i = 0; i < len; i++) { int idx = paramIndex.get(i); int mask = masks[idx]; int nextParamIndex = q.getParameters().size() + view.getParameterOffset(); if ((mask & IndexCondition.EQUALITY) != 0) { Parameter param = new Parameter(nextParamIndex); q.addGlobalCondition(param, idx, Comparison.EQUAL_NULL_SAFE); } else { if ((mask & IndexCondition.START) != 0) { Parameter param = new Parameter(nextParamIndex); q.addGlobalCondition(param, idx, Comparison.BIGGER_EQUAL); } if ((mask & IndexCondition.END) != 0) { Parameter param = new Parameter(nextParamIndex); q.addGlobalCondition(param, idx, Comparison.SMALLER_EQUAL); } } } String sql = q.getPlanSQL(); q = (Query) session.prepare(sql, true); } double cost = q.getCost(); cachedCost = new CostElement(); cachedCost.evaluatedAt = System.currentTimeMillis(); cachedCost.cost = cost; costCache.put(masksArray, cachedCost); return cost; }
private void writeValue(Value v) throws IOException { if (v.getType() == Value.CLOB || v.getType() == Value.BLOB) { if (v instanceof ValueLobDb) { ValueLobDb lob = (ValueLobDb) v; if (lob.isStored()) { long id = lob.getLobId(); lobs.put(id, new CachedInputStream(null)); } } } transfer.writeValue(v); }
/** * Create a new trace system object. * * @param fileName the file name * @param init if the trace system should be initialized */ public TraceSystem(String fileName, boolean init) { this.fileName = fileName; updateLevel(); traces = SmallLRUCache.newInstance(100); dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss "); if (fileName != null && init) { try { openWriter(); } catch (Exception e) { logWritingError(e); } } }
/** One server thread is opened per client connection. */ public class TcpServerThread implements Runnable { protected final Transfer transfer; private final TcpServer server; private Session session; private boolean stop; private Thread thread; private Command commit; private final SmallMap cache = new SmallMap(SysProperties.SERVER_CACHED_OBJECTS); // 默认缓存64个对象 private final SmallLRUCache<Long, CachedInputStream> lobs = SmallLRUCache.newInstance( Math.max( SysProperties.SERVER_CACHED_OBJECTS, SysProperties.SERVER_RESULT_SET_FETCH_SIZE * 5)); private final int threadId; private int clientVersion; private String sessionId; TcpServerThread(Socket socket, TcpServer server, int id) { this.server = server; this.threadId = id; transfer = new Transfer(null); transfer.setSocket(socket); } private void trace(String s) { server.trace(this + " " + s); } @Override public void run() { try { transfer.init(); trace("Connect"); // TODO server: should support a list of allowed databases // and a list of allowed clients try { // 如果没有加-tcpAllowOthers参数,那么只接受本地连接 if (!server.allow(transfer.getSocket())) { throw DbException.get(ErrorCode.REMOTE_CONNECTION_NOT_ALLOWED); } int minClientVersion = transfer.readInt(); if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) { throw DbException.get( ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6); } else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_12) { throw DbException.get( ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_12); } int maxClientVersion = transfer.readInt(); if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_12) { clientVersion = Constants.TCP_PROTOCOL_VERSION_12; } else { clientVersion = minClientVersion; } transfer.setVersion(clientVersion); String db = transfer.readString(); String originalURL = transfer.readString(); if (db == null && originalURL == null) { String targetSessionId = transfer.readString(); int command = transfer.readInt(); stop = true; if (command == SessionRemote.SESSION_CANCEL_STATEMENT) { // cancel a running statement int statementId = transfer.readInt(); server.cancelStatement(targetSessionId, statementId); } else if (command == SessionRemote.SESSION_CHECK_KEY) { // check if this is the correct server db = server.checkKeyAndGetDatabaseName(targetSessionId); if (!targetSessionId.equals(db)) { transfer.writeInt(SessionRemote.STATUS_OK); } else { transfer.writeInt(SessionRemote.STATUS_ERROR); } } } // 启动TcpServer时加"-baseDir"或者像这样System.setProperty("h2.baseDir", "E:\\H2\\baseDir") String baseDir = server.getBaseDir(); if (baseDir == null) { baseDir = SysProperties.getBaseDir(); } // 例如启动TcpServer时,指定了"-key mydb mydatabase", // 如果db变量是mydb,那么实际上就是mydatabase,相当于做一次映射 // 如果db变量不是mydb,那么抛错: org.h2.jdbc.JdbcSQLException: Wrong user name or password [28000-170] db = server.checkKeyAndGetDatabaseName(db); ConnectionInfo ci = new ConnectionInfo(db); ci.setOriginalURL(originalURL); ci.setUserName(transfer.readString()); // password参数的值已经转换成userPasswordHash和filePasswordHash了, // 不能由userPasswordHash和filePasswordHash得到原始的password ci.setUserPasswordHash(transfer.readBytes()); ci.setFilePasswordHash(transfer.readBytes()); // 只有指定"CIPHER"参数时filePasswordHash才是非null的 int len = transfer.readInt(); for (int i = 0; i < len; i++) { ci.setProperty(transfer.readString(), transfer.readString()); } // override client's requested properties with server settings if (baseDir != null) { ci.setBaseDir(baseDir); } if (server.getIfExists()) { // 启动TcpServer时加"-ifExists",限制只有数据库存在时客户端才能连接,也就是不允许在客户端创建数据库 ci.setProperty("IFEXISTS", "TRUE"); } session = Engine.getInstance().createSession(ci); transfer.setSession(session); transfer.writeInt(SessionRemote.STATUS_OK); transfer.writeInt(clientVersion); transfer.flush(); // 每建立一个新的Session对象时,把它保存到内存数据库management_db_9092的SESSIONS表 server.addConnection(threadId, originalURL, ci.getUserName()); trace("Connected"); } catch (Throwable e) { sendError(e); stop = true; } while (!stop) { try { process(); } catch (Throwable e) { sendError(e); } } trace("Disconnect"); } catch (Throwable e) { server.traceError(e); } finally { close(); } } private void closeSession() { if (session != null) { RuntimeException closeError = null; try { Command rollback = session.prepareLocal("ROLLBACK"); rollback.executeUpdate(); } catch (RuntimeException e) { closeError = e; server.traceError(e); } catch (Exception e) { server.traceError(e); } try { session.close(); server.removeConnection(threadId); } catch (RuntimeException e) { if (closeError == null) { closeError = e; server.traceError(e); } } catch (Exception e) { server.traceError(e); } finally { session = null; } if (closeError != null) { throw closeError; } } } /** Close a connection. */ void close() { try { stop = true; closeSession(); } catch (Exception e) { server.traceError(e); } finally { transfer.close(); trace("Close"); server.remove(this); } } private void sendError(Throwable t) { try { SQLException e = DbException.convert(t).getSQLException(); StringWriter writer = new StringWriter(); e.printStackTrace(new PrintWriter(writer)); String trace = writer.toString(); String message; String sql; if (e instanceof JdbcSQLException) { JdbcSQLException j = (JdbcSQLException) e; message = j.getOriginalMessage(); sql = j.getSQL(); } else { message = e.getMessage(); sql = null; } transfer .writeInt(SessionRemote.STATUS_ERROR) .writeString(e.getSQLState()) .writeString(message) .writeString(sql) .writeInt(e.getErrorCode()) .writeString(trace) .flush(); } catch (Exception e2) { if (!transfer.isClosed()) { server.traceError(e2); } // if writing the error does not work, close the connection stop = true; } } private void setParameters(Command command) throws IOException { int len = transfer.readInt(); ArrayList<? extends ParameterInterface> params = command.getParameters(); for (int i = 0; i < len; i++) { Parameter p = (Parameter) params.get(i); p.setValue(transfer.readValue()); } } // 总共18条命令,这里包含16条 // 下面这两条在run方法中特殊处理 // SESSION_CANCEL_STATEMENT // SESSION_CHECK_KEY // 分三种级别,从大到小: SESSION级、COMMAND级、RESULT级 private void process() throws IOException { int operation = transfer.readInt(); switch (operation) { case SessionRemote.SESSION_PREPARE_READ_PARAMS: case SessionRemote.SESSION_PREPARE: { int id = transfer.readInt(); String sql = transfer.readString(); int old = session.getModificationId(); Command command = session.prepareLocal(sql); boolean readonly = command.isReadOnly(); cache.addObject(id, command); boolean isQuery = command.isQuery(); ArrayList<? extends ParameterInterface> params = command.getParameters(); transfer .writeInt(getState(old)) .writeBoolean(isQuery) .writeBoolean(readonly) .writeInt(params.size()); if (operation == SessionRemote.SESSION_PREPARE_READ_PARAMS) { for (ParameterInterface p : params) { ParameterRemote.writeMetaData(transfer, p); } } transfer.flush(); break; } case SessionRemote.SESSION_CLOSE: { stop = true; closeSession(); transfer.writeInt(SessionRemote.STATUS_OK).flush(); close(); break; } case SessionRemote.COMMAND_COMMIT: { // 并不是通过org.h2.jdbc.JdbcConnection.commit()触发,此方法是通过发送COMMIT SQL触发 // 触发SessionRemote.COMMAND_COMMIT的是在集群环境下,通过org.h2.engine.SessionRemote.autoCommitIfCluster()触发 if (commit == null) { commit = session.prepareLocal("COMMIT"); } int old = session.getModificationId(); commit.executeUpdate(); transfer.writeInt(getState(old)).flush(); break; } case SessionRemote.COMMAND_GET_META_DATA: { int id = transfer.readInt(); int objectId = transfer.readInt(); Command command = (Command) cache.getObject(id, false); ResultInterface result = command.getMetaData(); cache.addObject(objectId, result); int columnCount = result.getVisibleColumnCount(); transfer.writeInt(SessionRemote.STATUS_OK).writeInt(columnCount).writeInt(0); for (int i = 0; i < columnCount; i++) { ResultColumn.writeColumn(transfer, result, i); } transfer.flush(); break; } case SessionRemote.COMMAND_EXECUTE_QUERY: { int id = transfer.readInt(); int objectId = transfer.readInt(); int maxRows = transfer.readInt(); int fetchSize = transfer.readInt(); Command command = (Command) cache.getObject(id, false); setParameters(command); int old = session.getModificationId(); ResultInterface result; synchronized (session) { result = command.executeQuery(maxRows, false); } cache.addObject(objectId, result); int columnCount = result.getVisibleColumnCount(); int state = getState(old); transfer.writeInt(state).writeInt(columnCount); int rowCount = result.getRowCount(); transfer.writeInt(rowCount); for (int i = 0; i < columnCount; i++) { ResultColumn.writeColumn(transfer, result, i); } int fetch = Math.min(rowCount, fetchSize); for (int i = 0; i < fetch; i++) { sendRow(result); } transfer.flush(); break; } case SessionRemote.COMMAND_EXECUTE_UPDATE: { int id = transfer.readInt(); Command command = (Command) cache.getObject(id, false); // if(command!=null)throw new Error(); setParameters(command); int old = session.getModificationId(); int updateCount; synchronized (session) { updateCount = command.executeUpdate(); } int status; if (session.isClosed()) { status = SessionRemote.STATUS_CLOSED; } else { status = getState(old); } transfer.writeInt(status).writeInt(updateCount).writeBoolean(session.getAutoCommit()); transfer.flush(); break; } case SessionRemote.COMMAND_CLOSE: { int id = transfer.readInt(); Command command = (Command) cache.getObject(id, true); if (command != null) { command.close(); cache.freeObject(id); } break; } case SessionRemote.RESULT_FETCH_ROWS: { int id = transfer.readInt(); int count = transfer.readInt(); ResultInterface result = (ResultInterface) cache.getObject(id, false); transfer.writeInt(SessionRemote.STATUS_OK); for (int i = 0; i < count; i++) { sendRow(result); } transfer.flush(); break; } case SessionRemote.RESULT_RESET: { int id = transfer.readInt(); ResultInterface result = (ResultInterface) cache.getObject(id, false); result.reset(); break; } case SessionRemote.RESULT_CLOSE: { int id = transfer.readInt(); ResultInterface result = (ResultInterface) cache.getObject(id, true); if (result != null) { result.close(); cache.freeObject(id); } break; } case SessionRemote.CHANGE_ID: { int oldId = transfer.readInt(); int newId = transfer.readInt(); Object obj = cache.getObject(oldId, false); cache.freeObject(oldId); cache.addObject(newId, obj); break; } case SessionRemote.SESSION_SET_ID: { sessionId = transfer.readString(); transfer.writeInt(SessionRemote.STATUS_OK).flush(); break; } case SessionRemote.SESSION_SET_AUTOCOMMIT: { boolean autoCommit = transfer.readBoolean(); session.setAutoCommit(autoCommit); transfer.writeInt(SessionRemote.STATUS_OK).flush(); break; } case SessionRemote.SESSION_HAS_PENDING_TRANSACTION: { transfer .writeInt(SessionRemote.STATUS_OK) .writeInt(session.hasPendingTransaction() ? 1 : 0) .flush(); break; } case SessionRemote.LOB_READ: { long lobId = transfer.readLong(); byte[] hmac; CachedInputStream in; boolean verifyMac; if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_11) { if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_12) { hmac = transfer.readBytes(); verifyMac = true; } else { hmac = null; verifyMac = false; } in = lobs.get(lobId); if (in == null && verifyMac) { in = new CachedInputStream(null); lobs.put(lobId, in); } } else { verifyMac = false; hmac = null; in = lobs.get(lobId); } long offset = transfer.readLong(); int length = transfer.readInt(); if (verifyMac) { transfer.verifyLobMac(hmac, lobId); } if (in == null) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } if (in.getPos() != offset) { LobStorageInterface lobStorage = session.getDataHandler().getLobStorage(); InputStream lobIn = lobStorage.getInputStream(lobId, hmac, -1); in = new CachedInputStream(lobIn); lobs.put(lobId, in); lobIn.skip(offset); } // limit the buffer size length = Math.min(16 * Constants.IO_BUFFER_SIZE, length); byte[] buff = new byte[length]; length = IOUtils.readFully(in, buff, 0, length); transfer.writeInt(SessionRemote.STATUS_OK); transfer.writeInt(length); transfer.writeBytes(buff, 0, length); transfer.flush(); break; } default: trace("Unknown operation: " + operation); closeSession(); close(); } } private int getState(int oldModificationId) { if (session.getModificationId() == oldModificationId) { return SessionRemote.STATUS_OK; } return SessionRemote.STATUS_OK_STATE_CHANGED; } private void sendRow(ResultInterface result) throws IOException { if (result.next()) { transfer.writeBoolean(true); Value[] v = result.currentRow(); for (int i = 0; i < result.getVisibleColumnCount(); i++) { if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_12) { transfer.writeValue(v[i]); } else { writeValue(v[i]); } } } else { transfer.writeBoolean(false); } } private void writeValue(Value v) throws IOException { if (v.getType() == Value.CLOB || v.getType() == Value.BLOB) { if (v instanceof ValueLobDb) { ValueLobDb lob = (ValueLobDb) v; if (lob.isStored()) { long id = lob.getLobId(); lobs.put(id, new CachedInputStream(null)); } } } transfer.writeValue(v); } void setThread(Thread thread) { this.thread = thread; } Thread getThread() { return thread; } /** * Cancel a running statement. * * @param targetSessionId the session id * @param statementId the statement to cancel */ void cancelStatement(String targetSessionId, int statementId) { if (StringUtils.equals(targetSessionId, this.sessionId)) { Command cmd = (Command) cache.getObject(statementId, false); cmd.cancel(); } } /** An input stream with a position. */ static class CachedInputStream extends FilterInputStream { private static final ByteArrayInputStream DUMMY = new ByteArrayInputStream(new byte[0]); private long pos; CachedInputStream(InputStream in) { super(in == null ? DUMMY : in); if (in == null) { pos = -1; } } @Override public int read(byte[] buff, int off, int len) throws IOException { len = super.read(buff, off, len); if (len > 0) { pos += len; } return len; } @Override public int read() throws IOException { int x = in.read(); if (x >= 0) { pos++; } return x; } @Override public long skip(long n) throws IOException { n = super.skip(n); if (n > 0) { pos += n; } return n; } public long getPos() { return pos; } } }
// 总共18条命令,这里包含16条 // 下面这两条在run方法中特殊处理 // SESSION_CANCEL_STATEMENT // SESSION_CHECK_KEY // 分三种级别,从大到小: SESSION级、COMMAND级、RESULT级 private void process() throws IOException { int operation = transfer.readInt(); switch (operation) { case SessionRemote.SESSION_PREPARE_READ_PARAMS: case SessionRemote.SESSION_PREPARE: { int id = transfer.readInt(); String sql = transfer.readString(); int old = session.getModificationId(); Command command = session.prepareLocal(sql); boolean readonly = command.isReadOnly(); cache.addObject(id, command); boolean isQuery = command.isQuery(); ArrayList<? extends ParameterInterface> params = command.getParameters(); transfer .writeInt(getState(old)) .writeBoolean(isQuery) .writeBoolean(readonly) .writeInt(params.size()); if (operation == SessionRemote.SESSION_PREPARE_READ_PARAMS) { for (ParameterInterface p : params) { ParameterRemote.writeMetaData(transfer, p); } } transfer.flush(); break; } case SessionRemote.SESSION_CLOSE: { stop = true; closeSession(); transfer.writeInt(SessionRemote.STATUS_OK).flush(); close(); break; } case SessionRemote.COMMAND_COMMIT: { // 并不是通过org.h2.jdbc.JdbcConnection.commit()触发,此方法是通过发送COMMIT SQL触发 // 触发SessionRemote.COMMAND_COMMIT的是在集群环境下,通过org.h2.engine.SessionRemote.autoCommitIfCluster()触发 if (commit == null) { commit = session.prepareLocal("COMMIT"); } int old = session.getModificationId(); commit.executeUpdate(); transfer.writeInt(getState(old)).flush(); break; } case SessionRemote.COMMAND_GET_META_DATA: { int id = transfer.readInt(); int objectId = transfer.readInt(); Command command = (Command) cache.getObject(id, false); ResultInterface result = command.getMetaData(); cache.addObject(objectId, result); int columnCount = result.getVisibleColumnCount(); transfer.writeInt(SessionRemote.STATUS_OK).writeInt(columnCount).writeInt(0); for (int i = 0; i < columnCount; i++) { ResultColumn.writeColumn(transfer, result, i); } transfer.flush(); break; } case SessionRemote.COMMAND_EXECUTE_QUERY: { int id = transfer.readInt(); int objectId = transfer.readInt(); int maxRows = transfer.readInt(); int fetchSize = transfer.readInt(); Command command = (Command) cache.getObject(id, false); setParameters(command); int old = session.getModificationId(); ResultInterface result; synchronized (session) { result = command.executeQuery(maxRows, false); } cache.addObject(objectId, result); int columnCount = result.getVisibleColumnCount(); int state = getState(old); transfer.writeInt(state).writeInt(columnCount); int rowCount = result.getRowCount(); transfer.writeInt(rowCount); for (int i = 0; i < columnCount; i++) { ResultColumn.writeColumn(transfer, result, i); } int fetch = Math.min(rowCount, fetchSize); for (int i = 0; i < fetch; i++) { sendRow(result); } transfer.flush(); break; } case SessionRemote.COMMAND_EXECUTE_UPDATE: { int id = transfer.readInt(); Command command = (Command) cache.getObject(id, false); // if(command!=null)throw new Error(); setParameters(command); int old = session.getModificationId(); int updateCount; synchronized (session) { updateCount = command.executeUpdate(); } int status; if (session.isClosed()) { status = SessionRemote.STATUS_CLOSED; } else { status = getState(old); } transfer.writeInt(status).writeInt(updateCount).writeBoolean(session.getAutoCommit()); transfer.flush(); break; } case SessionRemote.COMMAND_CLOSE: { int id = transfer.readInt(); Command command = (Command) cache.getObject(id, true); if (command != null) { command.close(); cache.freeObject(id); } break; } case SessionRemote.RESULT_FETCH_ROWS: { int id = transfer.readInt(); int count = transfer.readInt(); ResultInterface result = (ResultInterface) cache.getObject(id, false); transfer.writeInt(SessionRemote.STATUS_OK); for (int i = 0; i < count; i++) { sendRow(result); } transfer.flush(); break; } case SessionRemote.RESULT_RESET: { int id = transfer.readInt(); ResultInterface result = (ResultInterface) cache.getObject(id, false); result.reset(); break; } case SessionRemote.RESULT_CLOSE: { int id = transfer.readInt(); ResultInterface result = (ResultInterface) cache.getObject(id, true); if (result != null) { result.close(); cache.freeObject(id); } break; } case SessionRemote.CHANGE_ID: { int oldId = transfer.readInt(); int newId = transfer.readInt(); Object obj = cache.getObject(oldId, false); cache.freeObject(oldId); cache.addObject(newId, obj); break; } case SessionRemote.SESSION_SET_ID: { sessionId = transfer.readString(); transfer.writeInt(SessionRemote.STATUS_OK).flush(); break; } case SessionRemote.SESSION_SET_AUTOCOMMIT: { boolean autoCommit = transfer.readBoolean(); session.setAutoCommit(autoCommit); transfer.writeInt(SessionRemote.STATUS_OK).flush(); break; } case SessionRemote.SESSION_HAS_PENDING_TRANSACTION: { transfer .writeInt(SessionRemote.STATUS_OK) .writeInt(session.hasPendingTransaction() ? 1 : 0) .flush(); break; } case SessionRemote.LOB_READ: { long lobId = transfer.readLong(); byte[] hmac; CachedInputStream in; boolean verifyMac; if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_11) { if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_12) { hmac = transfer.readBytes(); verifyMac = true; } else { hmac = null; verifyMac = false; } in = lobs.get(lobId); if (in == null && verifyMac) { in = new CachedInputStream(null); lobs.put(lobId, in); } } else { verifyMac = false; hmac = null; in = lobs.get(lobId); } long offset = transfer.readLong(); int length = transfer.readInt(); if (verifyMac) { transfer.verifyLobMac(hmac, lobId); } if (in == null) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } if (in.getPos() != offset) { LobStorageInterface lobStorage = session.getDataHandler().getLobStorage(); InputStream lobIn = lobStorage.getInputStream(lobId, hmac, -1); in = new CachedInputStream(lobIn); lobs.put(lobId, in); lobIn.skip(offset); } // limit the buffer size length = Math.min(16 * Constants.IO_BUFFER_SIZE, length); byte[] buff = new byte[length]; length = IOUtils.readFully(in, buff, 0, length); transfer.writeInt(SessionRemote.STATUS_OK); transfer.writeInt(length); transfer.writeBytes(buff, 0, length); transfer.flush(); break; } default: trace("Unknown operation: " + operation); closeSession(); close(); } }
/** A view is a virtual table that is defined by a query. */ public class TableView extends Table { private static final long ROW_COUNT_APPROXIMATION = 100; private String querySQL; private ArrayList<Table> tables; private final String[] columnNames; private Query viewQuery; private ViewIndex index; private boolean recursive; private DbException createException; private SmallLRUCache<IntArray, ViewIndex> indexCache = SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE); private long lastModificationCheck; private long maxDataModificationId; private User owner; private Query topQuery; private ResultInterface recursiveResult; private boolean tableExpression; public TableView( Schema schema, int id, String name, String querySQL, ArrayList<Parameter> params, String[] columnNames, Session session, boolean recursive) { super(schema, id, name, false, true); this.querySQL = querySQL; this.columnNames = columnNames; this.recursive = recursive; index = new ViewIndex(this, querySQL, params, recursive); initColumnsAndTables(session); } /** * Re-compile the query, updating the SQL statement. * * @param session the session * @return the query */ public Query recompileQuery(Session session) { Prepared p = session.prepare(querySQL); if (!(p instanceof Query)) { throw DbException.getSyntaxError(querySQL, 0); } Query query = (Query) p; querySQL = query.getPlanSQL(); return query; } private void initColumnsAndTables(Session session) { Column[] cols; removeViewFromTables(); try { Query query = recompileQuery(session); tables = New.arrayList(query.getTables()); ArrayList<Expression> expressions = query.getExpressions(); ArrayList<Column> list = New.arrayList(); for (int i = 0; i < query.getColumnCount(); i++) { Expression expr = expressions.get(i); String name = null; if (columnNames != null && columnNames.length > i) { name = columnNames[i]; } if (name == null) { name = expr.getAlias(); } int type = expr.getType(); long precision = expr.getPrecision(); int scale = expr.getScale(); int displaySize = expr.getDisplaySize(); Column col = new Column(name, type, precision, scale, displaySize); col.setTable(this, i); list.add(col); } cols = new Column[list.size()]; list.toArray(cols); createException = null; viewQuery = query; } catch (DbException e) { createException = e; // if it can't be compiled, then it's a 'zero column table' // this avoids problems when creating the view when opening the // database tables = New.arrayList(); cols = new Column[0]; if (recursive && columnNames != null) { cols = new Column[columnNames.length]; for (int i = 0; i < columnNames.length; i++) { cols[i] = new Column(columnNames[i], Value.STRING); } index.setRecursive(true); createException = null; } } setColumns(cols); if (getId() != 0) { addViewToTables(); } } /** * Check if this view is currently invalid. * * @return true if it is */ public boolean isInvalid() { return createException != null; } public PlanItem getBestPlanItem(Session session, int[] masks) { PlanItem item = new PlanItem(); item.cost = index.getCost(session, masks); IntArray masksArray = new IntArray(masks == null ? Utils.EMPTY_INT_ARRAY : masks); ViewIndex i2 = indexCache.get(masksArray); if (i2 == null || i2.getSession() != session) { i2 = new ViewIndex(this, index, session, masks); indexCache.put(masksArray, i2); } item.setIndex(i2); return item; } public String getDropSQL() { StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "); buff.append(getSQL()); return buff.toString(); } public String getCreateSQL() { return getCreateSQL(false, true); } /** * Generate "CREATE" SQL statement for the view. * * @param orReplace if true, then include the OR REPLACE clause * @param force if true, then include the FORCE clause * @return the SQL statement */ public String getCreateSQL(boolean orReplace, boolean force) { StatementBuilder buff = new StatementBuilder("CREATE "); if (orReplace) { buff.append("OR REPLACE "); } if (force) { buff.append("FORCE "); } buff.append("VIEW "); buff.append(getSQL()); if (comment != null) { buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment)); } if (columns.length > 0) { buff.append('('); for (Column c : columns) { buff.appendExceptFirst(", "); buff.append(c.getSQL()); } buff.append(')'); } else if (columnNames != null) { buff.append('('); for (String n : columnNames) { buff.appendExceptFirst(", "); buff.append(n); } buff.append(')'); } return buff.append(" AS\n").append(querySQL).toString(); } public void checkRename() { // ok } public void lock(Session session, boolean exclusive, boolean force) { // exclusive lock means: the view will be dropped } public void close(Session session) { // nothing to do } public void unlock(Session s) { // nothing to do } public boolean isLockedExclusively() { return false; } public Index addIndex( Session session, String indexName, int indexId, IndexColumn[] cols, IndexType indexType, boolean create, String indexComment) { throw DbException.getUnsupportedException("VIEW"); } public void removeRow(Session session, Row row) { throw DbException.getUnsupportedException("VIEW"); } public void addRow(Session session, Row row) { throw DbException.getUnsupportedException("VIEW"); } public void checkSupportAlter() { throw DbException.getUnsupportedException("VIEW"); } public void truncate(Session session) { throw DbException.getUnsupportedException("VIEW"); } public long getRowCount(Session session) { throw DbException.throwInternalError(); } public boolean canGetRowCount() { // TODO view: could get the row count, but not that easy return false; } public boolean canDrop() { return true; } public String getTableType() { return Table.VIEW; } public void removeChildrenAndResources(Session session) { removeViewFromTables(); super.removeChildrenAndResources(session); database.removeMeta(session, getId()); querySQL = null; index = null; invalidate(); } public String getSQL() { if (isTemporary()) { return "(" + querySQL + ")"; } return super.getSQL(); } public String getQuery() { return querySQL; } public Index getScanIndex(Session session) { if (createException != null) { String msg = createException.getMessage(); throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, createException, getSQL(), msg); } PlanItem item = getBestPlanItem(session, null); return item.getIndex(); } public ArrayList<Index> getIndexes() { return null; } /** * Re-compile the view query. * * @param session the session */ public void recompile(Session session) { for (Table t : tables) { t.removeView(this); } tables.clear(); initColumnsAndTables(session); } public long getMaxDataModificationId() { if (createException != null) { return Long.MAX_VALUE; } if (viewQuery == null) { return Long.MAX_VALUE; } // if nothing was modified in the database since the last check, and the // last is known, then we don't need to check again // this speeds up nested views long dbMod = database.getModificationDataId(); if (dbMod > lastModificationCheck && maxDataModificationId <= dbMod) { maxDataModificationId = viewQuery.getMaxDataModificationId(); lastModificationCheck = dbMod; } return maxDataModificationId; } public Index getUniqueIndex() { return null; } private void removeViewFromTables() { if (tables != null) { for (Table t : tables) { t.removeView(this); } tables.clear(); } } private void addViewToTables() { for (Table t : tables) { t.addView(this); } } private void setOwner(User owner) { this.owner = owner; } public User getOwner() { return owner; } /** * Create a temporary view out of the given query. * * @param session the session * @param owner the owner of the query * @param name the view name * @param query the query * @param topQuery the top level query * @return the view table */ public static TableView createTempView( Session session, User owner, String name, Query query, Query topQuery) { Schema mainSchema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN); String querySQL = query.getPlanSQL(); TableView v = new TableView(mainSchema, 0, name, querySQL, query.getParameters(), null, session, false); v.setTopQuery(topQuery); if (v.createException != null) { throw v.createException; } v.setOwner(owner); v.setTemporary(true); return v; } private void setTopQuery(Query topQuery) { this.topQuery = topQuery; } public long getRowCountApproximation() { return ROW_COUNT_APPROXIMATION; } public int getParameterOffset() { return topQuery == null ? 0 : topQuery.getParameters().size(); } public boolean isDeterministic() { if (recursive) { return false; } return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC); } public void setRecursiveResult(ResultInterface recursiveResult) { this.recursiveResult = recursiveResult; } public ResultInterface getRecursiveResult() { return recursiveResult; } public void setTableExpression(boolean tableExpression) { this.tableExpression = tableExpression; } public boolean isTableExpression() { return tableExpression; } }
/** * This object represents a virtual index for a query. Actually it only represents a prepared SELECT * statement. */ public class ViewIndex extends BaseIndex { private final TableView view; private final String querySQL; private final ArrayList<Parameter> originalParameters; private final SmallLRUCache<IntArray, CostElement> costCache = SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE); private boolean recursive; private int[] indexMasks; private String planSQL; private Query query; private Session createSession; public ViewIndex( TableView view, String querySQL, ArrayList<Parameter> originalParameters, boolean recursive) { initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false)); this.view = view; this.querySQL = querySQL; this.originalParameters = originalParameters; this.recursive = recursive; columns = new Column[0]; } public ViewIndex(TableView view, ViewIndex index, Session session, int[] masks) { initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false)); this.view = view; this.querySQL = index.querySQL; this.originalParameters = index.originalParameters; this.recursive = index.recursive; this.indexMasks = masks; this.createSession = session; columns = new Column[0]; if (!recursive) { query = getQuery(session, masks); planSQL = query.getPlanSQL(); } } public Session getSession() { return createSession; } public String getPlanSQL() { return planSQL; } public void close(Session session) { // nothing to do } public void add(Session session, Row row) { throw DbException.getUnsupportedException("VIEW"); } public void remove(Session session, Row row) { throw DbException.getUnsupportedException("VIEW"); } /** A calculated cost value. */ static class CostElement { /** The time in milliseconds when this cost was calculated. */ long evaluatedAt; /** The cost. */ double cost; } public double getCost(Session session, int[] masks) { if (recursive) { return 1000; } IntArray masksArray = new IntArray(masks == null ? Utils.EMPTY_INT_ARRAY : masks); CostElement cachedCost = costCache.get(masksArray); if (cachedCost != null) { long time = System.currentTimeMillis(); if (time < cachedCost.evaluatedAt + Constants.VIEW_COST_CACHE_MAX_AGE) { return cachedCost.cost; } } Query q = (Query) session.prepare(querySQL, true); if (masks != null) { IntArray paramIndex = new IntArray(); for (int i = 0; i < masks.length; i++) { int mask = masks[i]; if (mask == 0) { continue; } paramIndex.add(i); } int len = paramIndex.size(); for (int i = 0; i < len; i++) { int idx = paramIndex.get(i); int mask = masks[idx]; int nextParamIndex = q.getParameters().size() + view.getParameterOffset(); if ((mask & IndexCondition.EQUALITY) != 0) { Parameter param = new Parameter(nextParamIndex); q.addGlobalCondition(param, idx, Comparison.EQUAL_NULL_SAFE); } else { if ((mask & IndexCondition.START) != 0) { Parameter param = new Parameter(nextParamIndex); q.addGlobalCondition(param, idx, Comparison.BIGGER_EQUAL); } if ((mask & IndexCondition.END) != 0) { Parameter param = new Parameter(nextParamIndex); q.addGlobalCondition(param, idx, Comparison.SMALLER_EQUAL); } } } String sql = q.getPlanSQL(); q = (Query) session.prepare(sql, true); } double cost = q.getCost(); cachedCost = new CostElement(); cachedCost.evaluatedAt = System.currentTimeMillis(); cachedCost.cost = cost; costCache.put(masksArray, cachedCost); return cost; } public Cursor find(Session session, SearchRow first, SearchRow last) { if (recursive) { if (view.getRecursiveResult() != null) { ResultInterface r = view.getRecursiveResult(); r.reset(); return new ViewCursor(table, r); } if (query == null) { query = (Query) createSession.prepare(querySQL, true); planSQL = query.getPlanSQL(); } if (!(query instanceof SelectUnion)) { throw DbException.get(ErrorCode.SYNTAX_ERROR_2, "recursive queries without UNION ALL"); } SelectUnion union = (SelectUnion) query; if (union.getUnionType() != SelectUnion.UNION_ALL) { throw DbException.get(ErrorCode.SYNTAX_ERROR_2, "recursive queries without UNION ALL"); } Query left = union.getLeft(); ResultInterface r = left.query(0); LocalResult result = union.getEmptyResult(); while (r.next()) { result.addRow(r.currentRow()); } Query right = union.getRight(); r.reset(); view.setRecursiveResult(r); while (true) { r = right.query(0); if (r.getRowCount() == 0) { break; } while (r.next()) { result.addRow(r.currentRow()); } r.reset(); view.setRecursiveResult(r); } return new ViewCursor(table, result); } ArrayList<Parameter> paramList = query.getParameters(); for (int i = 0; originalParameters != null && i < originalParameters.size(); i++) { Parameter orig = originalParameters.get(i); int idx = orig.getIndex(); Value value = orig.getValue(session); setParameter(paramList, idx, value); } int len; if (first != null) { len = first.getColumnCount(); } else if (last != null) { len = last.getColumnCount(); } else { len = 0; } int idx = originalParameters == null ? 0 : originalParameters.size(); idx += view.getParameterOffset(); for (int i = 0; i < len; i++) { if (first != null) { Value v = first.getValue(i); if (v != null) { int x = idx++; setParameter(paramList, x, v); } } // for equality, only one parameter is used (first == last) if (last != null && indexMasks[i] != IndexCondition.EQUALITY) { Value v = last.getValue(i); if (v != null) { int x = idx++; setParameter(paramList, x, v); } } } ResultInterface result = query.query(0); return new ViewCursor(table, result); } private void setParameter(ArrayList<Parameter> paramList, int x, Value v) { if (x >= paramList.size()) { // the parameter may be optimized away as in // select * from (select null as x) where x=1; return; } Parameter param = paramList.get(x); param.setValue(v); } private Query getQuery(Session session, int[] masks) { Query q = (Query) session.prepare(querySQL, true); if (masks == null) { return q; } int firstIndexParam = originalParameters == null ? 0 : originalParameters.size(); firstIndexParam += view.getParameterOffset(); IntArray paramIndex = new IntArray(); for (int i = 0; i < masks.length; i++) { int mask = masks[i]; if (mask == 0) { continue; } paramIndex.add(i); if ((mask & IndexCondition.RANGE) == IndexCondition.RANGE) { // two parameters for range queries: >= x AND <= y paramIndex.add(i); } } int len = paramIndex.size(); columns = new Column[len]; for (int i = 0; i < len; ) { int idx = paramIndex.get(i); Column col = table.getColumn(idx); columns[i] = col; int mask = masks[idx]; if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) { Parameter param = new Parameter(firstIndexParam + i); q.addGlobalCondition(param, idx, Comparison.EQUAL_NULL_SAFE); i++; } else { if ((mask & IndexCondition.START) == IndexCondition.START) { Parameter param = new Parameter(firstIndexParam + i); q.addGlobalCondition(param, idx, Comparison.BIGGER_EQUAL); i++; } if ((mask & IndexCondition.END) == IndexCondition.END) { Parameter param = new Parameter(firstIndexParam + i); q.addGlobalCondition(param, idx, Comparison.SMALLER_EQUAL); i++; } } } String sql = q.getPlanSQL(); q = (Query) session.prepare(sql, true); return q; } public void remove(Session session) { throw DbException.getUnsupportedException("VIEW"); } public void truncate(Session session) { throw DbException.getUnsupportedException("VIEW"); } public void checkRename() { throw DbException.getUnsupportedException("VIEW"); } public boolean needRebuild() { return false; } public boolean canGetFirstOrLast() { return false; } public Cursor findFirstOrLast(Session session, boolean first) { throw DbException.getUnsupportedException("VIEW"); } public void setRecursive(boolean value) { this.recursive = value; } public long getRowCount(Session session) { return 0; } public long getRowCountApproximation() { return 0; } public boolean isRecursive() { return recursive; } }