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 logger.log("Query cannot return anything due to conflicting clauses"); 060 ps = null; 061 rs = null; 062 eof = true; 063 return; 064 } else { 065 eof = false; 066 } 067 if (logger.isLogEnabled()) { 068 logger.logSQL(q.selectInfo.sql, q.selectParams); 069 } 070 ps = mapper.connection.prepareStatement(q.selectInfo.sql, ResultSet.TYPE_SCROLL_INSENSITIVE, 071 ResultSet.CONCUR_READ_ONLY); 072 int i = 1; 073 for (Serializable object : q.selectParams) { 074 mapper.setToPreparedStatement(ps, i++, object); 075 } 076 rs = ps.executeQuery(); 077 mapper.countExecute(); 078 // rs.setFetchDirection(ResultSet.FETCH_UNKNOWN); fails in H2 079 } 080 081 protected static void closePreparedStatement(PreparedStatement ps) throws SQLException { 082 try { 083 ps.close(); 084 } catch (IllegalArgumentException e) { 085 // ignore 086 // http://bugs.mysql.com/35489 with JDBC 4 and driver <= 5.1.6 087 } 088 } 089 090 @Override 091 public void close() { 092 if (rs == null) { 093 return; 094 } 095 try { 096 rs.close(); 097 closePreparedStatement(ps); 098 } catch (SQLException e) { 099 logger.error("Error closing statement: " + e.getMessage(), e); 100 } finally { 101 pos = -1; 102 rs = null; 103 ps = null; 104 } 105 } 106 107 @Override 108 public boolean isLife() { 109 return rs != null; 110 } 111 112 @Override 113 public boolean mustBeClosed() { 114 return rs != null; 115 } 116 117 public static class ClosedIteratorException extends IllegalStateException { 118 119 private static final long serialVersionUID = 1L; 120 121 public final QueryMaker.Query query; 122 123 protected ClosedIteratorException(QueryMaker.Query q) { 124 super("Query results iterator closed (" + q.selectInfo.sql + ")"); 125 this.query = q; 126 } 127 128 } 129 130 protected void checkNotClosed() { 131 if (rs == null) { 132 throw new ClosedIteratorException(q); 133 } 134 } 135 136 @Override 137 public long size() { 138 checkNotClosed(); 139 if (size != -1) { 140 return size; 141 } 142 try { 143 // save cursor pos 144 int old = rs.isBeforeFirst() ? -1 : rs.isAfterLast() ? -2 : rs.getRow(); 145 // find size 146 rs.last(); 147 size = rs.getRow(); 148 // set back cursor 149 if (old == -1) { 150 rs.beforeFirst(); 151 } else if (old == -2) { 152 rs.afterLast(); 153 } else if (old != 0) { 154 rs.absolute(old); 155 } 156 return size; 157 } catch (SQLException e) { 158 throw new RuntimeException(e); 159 } 160 } 161 162 @Override 163 public long pos() { 164 checkNotClosed(); 165 return pos; 166 } 167 168 @Override 169 public void skipTo(long pos) { 170 checkNotClosed(); 171 try { 172 boolean available = rs.absolute((int) pos + 1); 173 if (available) { 174 next = fetchCurrent(); 175 eof = false; 176 this.pos = pos; 177 } else { 178 // after last row 179 next = null; 180 eof = true; 181 this.pos = -1; // XXX 182 } 183 } catch (SQLException e) { 184 logger.error("Error skipping to: " + pos + ": " + e.getMessage(), e); 185 } 186 } 187 188 @Override 189 public Iterator<Map<String, Serializable>> iterator() { 190 checkNotClosed(); 191 return this; 192 } 193 194 protected Map<String, Serializable> fetchNext() throws SQLException { 195 checkNotClosed(); 196 if (!rs.next()) { 197 if (logger.isLogEnabled()) { 198 logger.log(" -> END"); 199 } 200 return null; 201 } 202 return fetchCurrent(); 203 } 204 205 protected Map<String, Serializable> fetchCurrent() throws SQLException { 206 checkNotClosed(); 207 Map<String, Serializable> map = q.selectInfo.mapMaker.makeMap(rs); 208 if (logger.isLogEnabled()) { 209 logger.logMap(map); 210 } 211 return map; 212 } 213 214 @Override 215 public boolean hasNext() { 216 checkNotClosed(); 217 if (next != null) { 218 return true; 219 } 220 if (eof) { 221 return false; 222 } 223 try { 224 next = fetchNext(); 225 } catch (SQLException e) { 226 logger.error("Error fetching next: " + e.getMessage(), e); 227 } 228 eof = next == null; 229 return !eof; 230 } 231 232 @Override 233 public Map<String, Serializable> next() { 234 checkNotClosed(); 235 if (!hasNext()) { 236 pos = -1; 237 throw new NoSuchElementException(); 238 } 239 Map<String, Serializable> n = next; 240 next = null; 241 pos++; 242 return n; 243 } 244 245 @Override 246 public void remove() { 247 throw new UnsupportedOperationException(); 248 } 249 250}