/* * Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden * Distributed under the terms shown in the file COPYRIGHT * found in the root folder of this project or at * http://eng.tada.se/osprojects/COPYRIGHT.html */ package org.postgresql.pljava.internal; import java.sql.SQLException; import java.util.Collections; import java.util.Map; import java.util.LinkedHashMap; /** * The <code>ExecutionPlan</code> correspons to the execution plan obtained * using an internal PostgreSQL <code>SPI_prepare</code> call. * * @author Thomas Hallgren */ public class ExecutionPlan { static final int INITIAL_CACHE_CAPACITY = 29; static final float CACHE_LOAD_FACTOR = 0.75f; private long m_pointer; /** * MRU cache for prepared plans. */ static final class PlanCache extends LinkedHashMap { private final int m_cacheSize; public PlanCache(int cacheSize) { super(INITIAL_CACHE_CAPACITY, CACHE_LOAD_FACTOR, true); m_cacheSize = cacheSize; } protected boolean removeEldestEntry(Map.Entry eldest) { if(this.size() <= m_cacheSize) return false; ExecutionPlan evicted = (ExecutionPlan)eldest.getValue(); synchronized(Backend.THREADLOCK) { if(evicted.m_pointer != 0) { _invalidate(evicted.m_pointer); evicted.m_pointer = 0; } } return true; } }; static final class PlanKey { private final int m_hashCode; private final String m_stmt; private final Oid[] m_argTypes; PlanKey(String stmt, Oid[] argTypes) { m_stmt = stmt; m_hashCode = stmt.hashCode() + 1; m_argTypes = argTypes; } public boolean equals(Object o) { if(!(o instanceof PlanKey)) return false; PlanKey pk = (PlanKey)o; if(!pk.m_stmt.equals(m_stmt)) return false; Oid[] pat = pk.m_argTypes; Oid[] mat = m_argTypes; int idx = pat.length; if(mat.length != idx) return false; while(--idx >= 0) if(!pat[idx].equals(mat[idx])) return false; return true; } public int hashCode() { return m_hashCode; } } private static final Map s_planCache; private final Object m_key; static { int cacheSize = Backend.getStatementCacheSize(); s_planCache = Collections.synchronizedMap(new PlanCache(cacheSize < 11 ? 11 : cacheSize)); } private ExecutionPlan(Object key, long pointer) { m_key = key; m_pointer = pointer; } /** * Close the plan. */ public void close() { ExecutionPlan old = (ExecutionPlan)s_planCache.put(m_key, this); if(old != null && old.m_pointer != 0) { synchronized(Backend.THREADLOCK) { _invalidate(old.m_pointer); old.m_pointer = 0; } } } /** * Set up a cursor that will execute the plan using the internal * <code>SPI_cursor_open</code> function * * @param cursorName Name of the cursor or <code>null</code> for a system * generated name. * @param parameters Values for the parameters. * @return The <code>Portal</code> that represents the opened cursor. * @throws SQLException If the underlying native structure has gone stale. */ public Portal cursorOpen(String cursorName, Object[] parameters) throws SQLException { synchronized(Backend.THREADLOCK) { return _cursorOpen(m_pointer, System.identityHashCode(Thread .currentThread()), cursorName, parameters); } } /** * Checks if this <code>ExecutionPlan</code> can create a <code>Portal * </code> * using {@link #cursorOpen}. This is true if the plan contains only one * regular <code>SELECT</code> query. * * @return <code>true</code> if the plan can create a <code>Portal</code> * @throws SQLException If the underlying native structure has gone stale. */ public boolean isCursorPlan() throws SQLException { synchronized(Backend.THREADLOCK) { return _isCursorPlan(m_pointer); } } /** * Execute the plan using the internal <code>SPI_execp</code> function. * * @param parameters Values for the parameters. * @param rowCount The maximum number of tuples to create. A value of * <code>rowCount</code> of zero is interpreted as no limit, * i.e., run to completion. * @return One of the status codes declared in class {@link SPI}. * @throws SQLException If the underlying native structure has gone stale. */ public int execute(Object[] parameters, int rowCount) throws SQLException { synchronized(Backend.THREADLOCK) { return _execute(m_pointer, System.identityHashCode(Thread .currentThread()), parameters, rowCount); } } /** * Create an execution plan for a statement to be executed later using the * internal <code>SPI_prepare</code> function. * * @param statement The command string. * @param argTypes SQL types of argument types. * @return An execution plan for the prepared statement. * @throws SQLException * @see java.sql.Types */ public static ExecutionPlan prepare(String statement, Oid[] argTypes) throws SQLException { Object key = (argTypes == null) ? (Object)statement : (Object)new PlanKey(statement, argTypes); ExecutionPlan plan = (ExecutionPlan)s_planCache.remove(key); if(plan == null) { synchronized(Backend.THREADLOCK) { plan = new ExecutionPlan(key, _prepare( System.identityHashCode(Thread.currentThread()), statement, argTypes)); } } return plan; } private static native Portal _cursorOpen(long pointer, long threadId, String cursorName, Object[] parameters) throws SQLException; private static native boolean _isCursorPlan(long pointer) throws SQLException; private static native int _execute(long pointer, long threadId, Object[] parameters, int rowCount) throws SQLException; private static native long _prepare(long threadId, String statement, Oid[] argTypes) throws SQLException; private static native void _invalidate(long pointer); }