1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.result; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.Arrays; |
10 | |
11 | import org.h2.command.ddl.CreateTableData; |
12 | import org.h2.engine.Constants; |
13 | import org.h2.engine.Database; |
14 | import org.h2.engine.Session; |
15 | import org.h2.expression.Expression; |
16 | import org.h2.index.Cursor; |
17 | import org.h2.index.Index; |
18 | import org.h2.index.IndexType; |
19 | import org.h2.schema.Schema; |
20 | import org.h2.table.Column; |
21 | import org.h2.table.IndexColumn; |
22 | import org.h2.table.Table; |
23 | import org.h2.value.Value; |
24 | import org.h2.value.ValueNull; |
25 | |
26 | /** |
27 | * This class implements the temp table buffer for the LocalResult class. |
28 | */ |
29 | public class ResultTempTable implements ResultExternal { |
30 | |
31 | private static final String COLUMN_NAME = "DATA"; |
32 | private final boolean distinct; |
33 | private final SortOrder sort; |
34 | private Index index; |
35 | private Session session; |
36 | private Table table; |
37 | private Cursor resultCursor; |
38 | private int rowCount; |
39 | private int columnCount; |
40 | |
41 | private final ResultTempTable parent; |
42 | private boolean closed; |
43 | private int childCount; |
44 | private boolean containsLob; |
45 | |
46 | ResultTempTable(Session session, Expression[] expressions, boolean distinct, SortOrder sort) { |
47 | this.session = session; |
48 | this.distinct = distinct; |
49 | this.sort = sort; |
50 | this.columnCount = expressions.length; |
51 | Schema schema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN); |
52 | CreateTableData data = new CreateTableData(); |
53 | for (int i = 0; i < expressions.length; i++) { |
54 | int type = expressions[i].getType(); |
55 | Column col = new Column(COLUMN_NAME + i, |
56 | type); |
57 | if (type == Value.CLOB || type == Value.BLOB) { |
58 | containsLob = true; |
59 | } |
60 | data.columns.add(col); |
61 | } |
62 | data.id = session.getDatabase().allocateObjectId(); |
63 | data.tableName = "TEMP_RESULT_SET_" + data.id; |
64 | data.temporary = true; |
65 | data.persistIndexes = false; |
66 | data.persistData = true; |
67 | data.create = true; |
68 | data.session = session; |
69 | table = schema.createTable(data); |
70 | if (sort != null || distinct) { |
71 | createIndex(); |
72 | } |
73 | parent = null; |
74 | } |
75 | |
76 | private ResultTempTable(ResultTempTable parent) { |
77 | this.parent = parent; |
78 | this.columnCount = parent.columnCount; |
79 | this.distinct = parent.distinct; |
80 | this.session = parent.session; |
81 | this.table = parent.table; |
82 | this.index = parent.index; |
83 | this.rowCount = parent.rowCount; |
84 | this.sort = parent.sort; |
85 | this.containsLob = parent.containsLob; |
86 | reset(); |
87 | } |
88 | |
89 | private void createIndex() { |
90 | IndexColumn[] indexCols = null; |
91 | if (sort != null) { |
92 | int[] colIndex = sort.getQueryColumnIndexes(); |
93 | indexCols = new IndexColumn[colIndex.length]; |
94 | for (int i = 0; i < colIndex.length; i++) { |
95 | IndexColumn indexColumn = new IndexColumn(); |
96 | indexColumn.column = table.getColumn(colIndex[i]); |
97 | indexColumn.sortType = sort.getSortTypes()[i]; |
98 | indexColumn.columnName = COLUMN_NAME + i; |
99 | indexCols[i] = indexColumn; |
100 | } |
101 | } else { |
102 | indexCols = new IndexColumn[columnCount]; |
103 | for (int i = 0; i < columnCount; i++) { |
104 | IndexColumn indexColumn = new IndexColumn(); |
105 | indexColumn.column = table.getColumn(i); |
106 | indexColumn.columnName = COLUMN_NAME + i; |
107 | indexCols[i] = indexColumn; |
108 | } |
109 | } |
110 | String indexName = table.getSchema().getUniqueIndexName(session, |
111 | table, Constants.PREFIX_INDEX); |
112 | int indexId = session.getDatabase().allocateObjectId(); |
113 | IndexType indexType = IndexType.createNonUnique(true); |
114 | index = table.addIndex(session, indexName, indexId, indexCols, |
115 | indexType, true, null); |
116 | } |
117 | |
118 | @Override |
119 | public synchronized ResultExternal createShallowCopy() { |
120 | if (parent != null) { |
121 | return parent.createShallowCopy(); |
122 | } |
123 | if (closed) { |
124 | return null; |
125 | } |
126 | childCount++; |
127 | return new ResultTempTable(this); |
128 | } |
129 | |
130 | @Override |
131 | public int removeRow(Value[] values) { |
132 | Row row = convertToRow(values); |
133 | Cursor cursor = find(row); |
134 | if (cursor != null) { |
135 | row = cursor.get(); |
136 | table.removeRow(session, row); |
137 | rowCount--; |
138 | } |
139 | return rowCount; |
140 | } |
141 | |
142 | @Override |
143 | public boolean contains(Value[] values) { |
144 | return find(convertToRow(values)) != null; |
145 | } |
146 | |
147 | @Override |
148 | public int addRow(Value[] values) { |
149 | Row row = convertToRow(values); |
150 | if (distinct) { |
151 | Cursor cursor = find(row); |
152 | if (cursor == null) { |
153 | table.addRow(session, row); |
154 | rowCount++; |
155 | } |
156 | } else { |
157 | table.addRow(session, row); |
158 | rowCount++; |
159 | } |
160 | return rowCount; |
161 | } |
162 | |
163 | @Override |
164 | public int addRows(ArrayList<Value[]> rows) { |
165 | // speeds up inserting, but not really needed: |
166 | if (sort != null) { |
167 | sort.sort(rows); |
168 | } |
169 | for (Value[] values : rows) { |
170 | addRow(values); |
171 | } |
172 | return rowCount; |
173 | } |
174 | |
175 | private synchronized void closeChild() { |
176 | if (--childCount == 0 && closed) { |
177 | dropTable(); |
178 | } |
179 | } |
180 | |
181 | @Override |
182 | public synchronized void close() { |
183 | if (closed) { |
184 | return; |
185 | } |
186 | closed = true; |
187 | if (parent != null) { |
188 | parent.closeChild(); |
189 | } else { |
190 | if (childCount == 0) { |
191 | dropTable(); |
192 | } |
193 | } |
194 | } |
195 | |
196 | private void dropTable() { |
197 | if (table == null) { |
198 | return; |
199 | } |
200 | if (containsLob) { |
201 | // contains BLOB or CLOB: can not truncate now, |
202 | // otherwise the BLOB and CLOB entries are removed |
203 | return; |
204 | } |
205 | try { |
206 | Database database = session.getDatabase(); |
207 | // Need to lock because not all of the code-paths |
208 | // that reach here have already taken this lock, |
209 | // notably via the close() paths. |
210 | synchronized (session) { |
211 | synchronized (database) { |
212 | table.truncate(session); |
213 | } |
214 | } |
215 | // This session may not lock the sys table (except if it already has |
216 | // locked it) because it must be committed immediately, otherwise |
217 | // other threads can not access the sys table. If the table is not |
218 | // removed now, it will be when the database is opened the next |
219 | // time. (the table is truncated, so this is just one record) |
220 | if (!database.isSysTableLocked()) { |
221 | Session sysSession = database.getSystemSession(); |
222 | table.removeChildrenAndResources(sysSession); |
223 | if (index != null) { |
224 | // need to explicitly do this, |
225 | // as it's not registered in the system session |
226 | session.removeLocalTempTableIndex(index); |
227 | } |
228 | // the transaction must be committed immediately |
229 | // TODO this synchronization cascade is very ugly |
230 | synchronized (session) { |
231 | synchronized (sysSession) { |
232 | synchronized (database) { |
233 | sysSession.commit(false); |
234 | } |
235 | } |
236 | } |
237 | } |
238 | } finally { |
239 | table = null; |
240 | } |
241 | } |
242 | |
243 | @Override |
244 | public void done() { |
245 | // nothing to do |
246 | } |
247 | |
248 | @Override |
249 | public Value[] next() { |
250 | if (resultCursor == null) { |
251 | Index idx; |
252 | if (distinct || sort != null) { |
253 | idx = index; |
254 | } else { |
255 | idx = table.getScanIndex(session); |
256 | } |
257 | if (session.getDatabase().getMvStore() != null) { |
258 | // sometimes the transaction is already committed, |
259 | // in which case we can't use the session |
260 | if (idx.getRowCount(session) == 0 && rowCount > 0) { |
261 | // this means querying is not transactional |
262 | resultCursor = idx.find((Session) null, null, null); |
263 | } else { |
264 | // the transaction is still open |
265 | resultCursor = idx.find(session, null, null); |
266 | } |
267 | } else { |
268 | resultCursor = idx.find(session, null, null); |
269 | } |
270 | } |
271 | if (!resultCursor.next()) { |
272 | return null; |
273 | } |
274 | Row row = resultCursor.get(); |
275 | return row.getValueList(); |
276 | } |
277 | |
278 | @Override |
279 | public void reset() { |
280 | resultCursor = null; |
281 | } |
282 | |
283 | private Row convertToRow(Value[] values) { |
284 | if (values.length < columnCount) { |
285 | Value[] v2 = Arrays.copyOf(values, columnCount); |
286 | for (int i = values.length; i < columnCount; i++) { |
287 | v2[i] = ValueNull.INSTANCE; |
288 | } |
289 | values = v2; |
290 | } |
291 | return new Row(values, Row.MEMORY_CALCULATE); |
292 | } |
293 | |
294 | private Cursor find(Row row) { |
295 | if (index == null) { |
296 | // for the case "in(select ...)", the query might |
297 | // use an optimization and not create the index |
298 | // up front |
299 | createIndex(); |
300 | } |
301 | Cursor cursor = index.find(session, row, row); |
302 | while (cursor.next()) { |
303 | SearchRow found = cursor.getSearchRow(); |
304 | boolean ok = true; |
305 | Database db = session.getDatabase(); |
306 | for (int i = 0; i < row.getColumnCount(); i++) { |
307 | if (!db.areEqual(row.getValue(i), found.getValue(i))) { |
308 | ok = false; |
309 | break; |
310 | } |
311 | } |
312 | if (ok) { |
313 | return cursor; |
314 | } |
315 | } |
316 | return null; |
317 | } |
318 | |
319 | } |
320 | |