001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.storage.sql.jdbc; 020 021import java.io.Serializable; 022import java.sql.PreparedStatement; 023import java.sql.ResultSet; 024import java.sql.SQLException; 025import java.util.Iterator; 026import java.util.Map; 027import java.util.NoSuchElementException; 028 029import org.nuxeo.ecm.core.api.IterableQueryResult; 030import org.nuxeo.ecm.core.query.QueryFilter; 031import org.nuxeo.ecm.core.storage.sql.Session.PathResolver; 032 033/** 034 * Iterable query result implemented as a cursor on a SQL {@link ResultSet}. 035 */ 036public class ResultSetQueryResult implements IterableQueryResult, Iterator<Map<String, Serializable>> { 037 038 private QueryMaker.Query q; 039 040 private PreparedStatement ps; 041 042 private ResultSet rs; 043 044 private Map<String, Serializable> next; 045 046 private boolean eof; 047 048 private long pos; 049 050 private long size = -1; 051 052 private final JDBCLogger logger; 053 054 public ResultSetQueryResult(QueryMaker queryMaker, String query, QueryFilter queryFilter, PathResolver pathResolver, 055 JDBCMapper mapper, Object... params) throws SQLException { 056 logger = mapper.logger; 057 q = queryMaker.buildQuery(mapper.sqlInfo, mapper.model, pathResolver, query, queryFilter, params); 058 if (q == null) { 059 // no result 060 size = 0; 061 ps = null; 062 rs = null; 063 eof = true; 064 return; 065 } else { 066 eof = false; 067 } 068 if (logger.isLogEnabled()) { 069 logger.logSQL(q.selectInfo.sql, q.selectParams); 070 } 071 ps = mapper.connection.prepareStatement(q.selectInfo.sql, ResultSet.TYPE_SCROLL_INSENSITIVE, 072 ResultSet.CONCUR_READ_ONLY); 073 int i = 1; 074 for (Serializable object : q.selectParams) { 075 mapper.setToPreparedStatement(ps, i++, object); 076 } 077 rs = ps.executeQuery(); 078 mapper.countExecute(); 079 // rs.setFetchDirection(ResultSet.FETCH_UNKNOWN); fails in H2 080 } 081 082 protected static void closePreparedStatement(PreparedStatement ps) throws SQLException { 083 try { 084 ps.close(); 085 } catch (IllegalArgumentException e) { 086 // ignore 087 // http://bugs.mysql.com/35489 with JDBC 4 and driver <= 5.1.6 088 } 089 } 090 091 @Override 092 public void close() { 093 if (rs == null) { 094 return; 095 } 096 try { 097 rs.close(); 098 closePreparedStatement(ps); 099 } catch (SQLException e) { 100 logger.error("Error closing statement: " + e.getMessage(), e); 101 } finally { 102 pos = -1; 103 rs = null; 104 ps = null; 105 } 106 } 107 108 @Override 109 public boolean isLife() { 110 return rs != null; 111 } 112 113 @Override 114 public boolean mustBeClosed() { 115 return rs != null; 116 } 117 118 public static class ClosedIteratorException extends IllegalStateException { 119 120 private static final long serialVersionUID = 1L; 121 122 public final QueryMaker.Query query; 123 124 protected ClosedIteratorException(QueryMaker.Query q) { 125 super("Query results iterator closed (" + q.selectInfo.sql + ")"); 126 this.query = q; 127 } 128 129 } 130 131 protected void checkNotClosed() { 132 if (rs == null) { 133 throw new ClosedIteratorException(q); 134 } 135 } 136 137 @Override 138 public long size() { 139 if (size != -1) { 140 return size; 141 } 142 checkNotClosed(); 143 try { 144 // save cursor pos 145 int old = rs.isBeforeFirst() ? -1 : rs.isAfterLast() ? -2 : rs.getRow(); 146 // find size 147 rs.last(); 148 size = rs.getRow(); 149 // set back cursor 150 if (old == -1) { 151 rs.beforeFirst(); 152 } else if (old == -2) { 153 rs.afterLast(); 154 } else if (old != 0) { 155 rs.absolute(old); 156 } 157 return size; 158 } catch (SQLException e) { 159 throw new RuntimeException(e); 160 } 161 } 162 163 @Override 164 public long pos() { 165 checkNotClosed(); 166 return pos; 167 } 168 169 @Override 170 public void skipTo(long pos) { 171 checkNotClosed(); 172 try { 173 boolean available = rs.absolute((int) pos + 1); 174 if (available) { 175 next = fetchCurrent(); 176 eof = false; 177 this.pos = pos; 178 } else { 179 // after last row 180 next = null; 181 eof = true; 182 this.pos = -1; // XXX 183 } 184 } catch (SQLException e) { 185 logger.error("Error skipping to: " + pos + ": " + e.getMessage(), e); 186 } 187 } 188 189 @Override 190 public Iterator<Map<String, Serializable>> iterator() { 191 checkNotClosed(); 192 return this; 193 } 194 195 protected Map<String, Serializable> fetchNext() throws SQLException { 196 checkNotClosed(); 197 if (!rs.next()) { 198 if (logger.isLogEnabled()) { 199 logger.log(" -> END"); 200 } 201 return null; 202 } 203 return fetchCurrent(); 204 } 205 206 protected Map<String, Serializable> fetchCurrent() throws SQLException { 207 checkNotClosed(); 208 Map<String, Serializable> map = q.selectInfo.mapMaker.makeMap(rs); 209 if (logger.isLogEnabled()) { 210 logger.logMap(map); 211 } 212 return map; 213 } 214 215 @Override 216 public boolean hasNext() { 217 if (eof) { 218 return false; 219 } 220 checkNotClosed(); 221 if (next != null) { 222 return true; 223 } 224 try { 225 next = fetchNext(); 226 } catch (SQLException e) { 227 logger.error("Error fetching next: " + e.getMessage(), e); 228 } 229 eof = next == null; 230 return !eof; 231 } 232 233 @Override 234 public Map<String, Serializable> next() { 235 checkNotClosed(); 236 if (!hasNext()) { 237 pos = -1; 238 throw new NoSuchElementException(); 239 } 240 Map<String, Serializable> n = next; 241 next = null; 242 pos++; 243 return n; 244 } 245 246 @Override 247 public void remove() { 248 throw new UnsupportedOperationException(); 249 } 250 251}