/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.db.sql; import java.io.InputStream; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.db.block.Block; import com.caucho.db.jdbc.GeneratedKeysResultSet; import com.caucho.db.table.TableIterator; import com.caucho.db.table.Column.ColumnType; import com.caucho.db.xa.DbTransaction; import com.caucho.inject.Module; import com.caucho.util.FreeList; import com.caucho.util.L10N; /** * Represents the state of the query at any particular time. */ @Module public class QueryContext { private static final Logger log = Logger.getLogger(QueryContext.class.getName()); private static final L10N L = new L10N(QueryContext.class); private static final long LOCK_TIMEOUT = 120000; private static final FreeList<QueryContext> _freeList = new FreeList<QueryContext>(64); private DbTransaction _xa; private TableIterator []_tableIterators; private boolean _isWrite; private Data []_parameters = new Data[16]; private GroupItem _tempGroupItem; private GroupItem _groupItem; private boolean _isReturnGeneratedKeys; private SelectResult _result; private GeneratedKeysResultSet _generatedKeys; private int _rowUpdateCount; private int _limit = -1; private Block []_blockLocks; private int _blockLockLength; private boolean _isLocked; private boolean _isNonLocking; private HashMap<GroupItem,GroupItem> _groupMap; private byte []_buffer = new byte[256]; private Thread _thread; private QueryContext() { _tempGroupItem = GroupItem.allocate(new boolean[8]); } /** * Returns a new query context. */ public static QueryContext allocate() { QueryContext queryContext = _freeList.allocate(); if (queryContext == null) queryContext = new QueryContext(); queryContext.clearParameters(); queryContext._limit = -1; queryContext._isNonLocking = false; return queryContext; } public void setNonLocking() { _isNonLocking = true; } public void clearParameters() { for (int i = _parameters.length - 1; i >= 0; i--) { if (_parameters[i] == null) _parameters[i] = new Data(); _parameters[i].clear(); } } /** * Initializes the query state. */ public void init(DbTransaction xa, TableIterator []tableIterators, boolean isReadOnly) { if (_isLocked) throw new IllegalStateException(); Thread thread = Thread.currentThread(); if (_thread != null && _thread != thread) throw new IllegalStateException(toString() + " attempted query reuse without close"); _thread = thread; _xa = xa; _isWrite = ! isReadOnly; _tableIterators = tableIterators; _blockLockLength = tableIterators.length; if (_blockLocks == null || _blockLocks.length < _blockLockLength) _blockLocks = new Block[_blockLockLength]; else { for (int i = _blockLockLength - 1; i >= 0; i--) _blockLocks[i] = null; } _rowUpdateCount = 0; _groupItem = _tempGroupItem; _groupItem.init(0, null); } /** * Initializes the group. */ public void initGroup(int size, boolean []isGroupByFields) { _groupItem = _tempGroupItem; _groupItem.init(size, isGroupByFields); if (_groupMap == null) _groupMap = new HashMap<GroupItem,GroupItem>(); } /** * Selects the actual group item. */ public void selectGroup() { GroupItem item = _groupMap.get(_groupItem); if (item == null) { item = _groupItem.allocateCopy(); _groupMap.put(item, item); } _groupItem = item; } /** * Returns the group results. */ Iterator<GroupItem> groupResults() { if (_groupMap == null) return com.caucho.util.NullIterator.create(); Iterator<GroupItem> results = _groupMap.values().iterator(); _groupMap = null; return results; } /** * Sets the current result. */ void setGroupItem(GroupItem item) { _groupItem = item; } /** * Returns the table iterator. */ public TableIterator []getTableIterators() { return _tableIterators; } /** * Sets the transaction. */ public void setTransaction(DbTransaction xa) { _xa = xa; } /** * Returns the transaction. */ public DbTransaction getTransaction() { return _xa; } /** * Returns the temp buffer. */ public byte []getBuffer() { return _buffer; } /** * Returns the number of rows updated. */ public int getRowUpdateCount() { return _rowUpdateCount; } /** * Sets the number of rows updated. */ public void setRowUpdateCount(int count) { _rowUpdateCount = count; } /** * Set if the query should return the generated keys. */ public boolean isReturnGeneratedKeys() { return _isReturnGeneratedKeys; } /** * Set if the query should return the generated keys. */ public void setReturnGeneratedKeys(boolean isReturnGeneratedKeys) { _isReturnGeneratedKeys = isReturnGeneratedKeys; if (_isReturnGeneratedKeys && _generatedKeys != null) _generatedKeys.init(); } /** * The max rows returned in a select */ public void setLimit(int limit) { _limit = limit; } /** * The max rows returned in a select */ public int getLimit() { return _limit; } /** * Sets the indexed group field. */ public boolean isGroupNull(int index) { return _groupItem.isNull(index); } /** * Sets the indexed group field. */ public void setGroupString(int index, String value) { _groupItem.setString(index, value); } /** * Sets the indexed group field. */ public String getGroupString(int index) { String value = _groupItem.getString(index); return value; } /** * Sets the indexed group field as a long. */ public void setGroupLong(int index, long value) { _groupItem.setLong(index, value); } /** * Sets the indexed group field as a long. */ public long getGroupLong(int index) { return _groupItem.getLong(index); } /** * Sets the indexed group field as a double. */ public void setGroupDouble(int index, double value) { _groupItem.setDouble(index, value); } /** * Sets the indexed group field as a double. */ public double getGroupDouble(int index) { return _groupItem.getDouble(index); } /** * Returns the indexed group field. */ public Data getGroupData(int index) { return _groupItem.getData(index); } /** * Set a null parameter. */ public void setNull(int index) { _parameters[index - 1].setString(null); } /** * Returns the null parameter. */ public boolean isNull(int index) { return _parameters[index - 1].isNull(); } /** * Set a long parameter. */ public void setLong(int index, long value) { _parameters[index - 1].setLong(value); } /** * Returns the boolean parameter. */ public int getBoolean(int index) { return _parameters[index - 1].getBoolean(); } /** * Set a boolean parameter. */ public void setBoolean(int index, boolean value) { _parameters[index - 1].setBoolean(value); } /** * Returns the long parameter. */ public long getLong(int index) { return _parameters[index - 1].getLong(); } /** * Returns the date parameter. */ public long getDate(int index) { return _parameters[index - 1].getDate(); } /** * Returns the date parameter. */ public void setDate(int index, long date) { _parameters[index - 1].setDate(date); } /** * Set a double parameter. */ public void setDouble(int index, double value) { _parameters[index - 1].setDouble(value); } /** * Returns the double parameter. */ public double getDouble(int index) { return _parameters[index - 1].getDouble(); } /** * Set a string parameter. */ public void setString(int index, String value) { _parameters[index - 1].setString(value); } /** * Returns the string parameter. */ public String getString(int index) { return _parameters[index - 1].getString(); } public boolean isBinaryStream(int index) { return _parameters[index - 1].isBinaryStream(); } /** * Set a binary stream parameter. */ public void setBinaryStream(int index, InputStream is, int length) { _parameters[index - 1].setBinaryStream(is, length); } /** * Returns the binary stream parameter. */ public InputStream getBinaryStream(int index) { return _parameters[index - 1].getBinaryStream(); } /** * Set a binary stream parameter. */ public void setBytes(int index, byte []bytes) { _parameters[index - 1].setBytes(bytes); } /** * Returns the binary stream parameter. */ public byte []getBytes(int index) { return _parameters[index - 1].getBytes(); } public ColumnType getType(int index) { return _parameters[index - 1].getType(); } /** * Sets the result set. */ public void setResult(SelectResult result) { _result = result; } /** * Gets the result set. */ public SelectResult getResult() { return _result; } /** * Gets the generated keys result set. */ public GeneratedKeysResultSet getGeneratedKeysResultSet() { if (! _isReturnGeneratedKeys) return null; if (_generatedKeys == null) _generatedKeys = new GeneratedKeysResultSet(); return _generatedKeys; } /** * Lock the blocks. The blocks are locked in ascending block id * order to avoid deadlocks. * * @param isWrite if true, the block should be locked for writing */ public void lock() throws SQLException { if (_isNonLocking) return; if (_isLocked) { throw new IllegalStateException(L.l("blocks are already locked")); } _isLocked = true; if (_thread != Thread.currentThread()) throw new IllegalStateException(); int len = _tableIterators.length; for (int i = 0; i < len; i++) { Block bestBlock = null; long bestId = Long.MAX_VALUE; loop: for (int j = 0; j < len; j++) { TableIterator iter = _tableIterators[j]; if (iter == null) continue; Block block = iter.getBlock(); if (block == null) continue; long id = block.getBlockId(); if (bestId <= id) continue; for (int k = 0; k < i; k++) { if (_blockLocks[k] == block) continue loop; } bestId = id; bestBlock = block; } try { if (bestBlock == null) { } else if (_isWrite) { bestBlock.getWriteLock().tryLock(_xa.getTimeout(), TimeUnit.MILLISECONDS); } else { bestBlock.getReadLock().tryLock(_xa.getTimeout(), TimeUnit.MILLISECONDS); } } catch (Exception e) { throw new IllegalStateException(e); } // assignment must be after obtaining lock because the unlock // requires a lock _blockLocks[i] = bestBlock; } } /** * Unlock the blocks. The blocks are unlocked in descending order. * * @param isWrite if true, the block should be unlocked for writing */ public void unlock() throws SQLException { if (_isNonLocking) return; if (! _isLocked) { return; } _isLocked = false; if (_thread != null && _thread != Thread.currentThread()) throw new IllegalStateException(String.valueOf(_thread) + " current " + Thread.currentThread()); int len = _blockLocks.length; // need to unlock first since the writeData/commit will wait for // write locks to clear before committing for (int i = len - 1; i >= 0; i--) { Block block = _blockLocks[i]; if (block == null) { } else if (_isWrite) { block.getWriteLock().unlock(); } else { block.getReadLock().unlock(); } } try { _xa.writeData(); } finally { for (int i = len - 1; i >= 0; i--) { Block block = _blockLocks[i]; _blockLocks[i] = null; if (block == null) { } else if (_isWrite) { try { block.commit(); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); } } } } } public void close() throws SQLException { Thread thread = _thread; _thread = null; unlock(); if (thread != null && thread != Thread.currentThread()) { throw new IllegalStateException(); } DbTransaction xa = _xa; _xa = null; // db/0a10 if (xa != null && xa.isAutoCommit()) xa.commit(); } public static void free(QueryContext cxt) { _freeList.free(cxt); } }