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.table; |
7 | |
8 | import java.sql.ResultSet; |
9 | import java.sql.ResultSetMetaData; |
10 | import java.sql.SQLException; |
11 | import java.util.ArrayList; |
12 | |
13 | import org.h2.api.ErrorCode; |
14 | import org.h2.engine.Session; |
15 | import org.h2.expression.Expression; |
16 | import org.h2.expression.FunctionCall; |
17 | import org.h2.expression.TableFunction; |
18 | import org.h2.index.FunctionIndex; |
19 | import org.h2.index.Index; |
20 | import org.h2.index.IndexType; |
21 | import org.h2.message.DbException; |
22 | import org.h2.result.LocalResult; |
23 | import org.h2.result.ResultInterface; |
24 | import org.h2.result.Row; |
25 | import org.h2.schema.Schema; |
26 | import org.h2.value.DataType; |
27 | import org.h2.value.Value; |
28 | import org.h2.value.ValueNull; |
29 | import org.h2.value.ValueResultSet; |
30 | |
31 | /** |
32 | * A table backed by a system or user-defined function that returns a result |
33 | * set. |
34 | */ |
35 | public class FunctionTable extends Table { |
36 | |
37 | private final FunctionCall function; |
38 | private final long rowCount; |
39 | private Expression functionExpr; |
40 | private LocalResult cachedResult; |
41 | private Value cachedValue; |
42 | |
43 | public FunctionTable(Schema schema, Session session, |
44 | Expression functionExpr, FunctionCall function) { |
45 | super(schema, 0, function.getName(), false, true); |
46 | this.functionExpr = functionExpr; |
47 | this.function = function; |
48 | if (function instanceof TableFunction) { |
49 | rowCount = ((TableFunction) function).getRowCount(); |
50 | } else { |
51 | rowCount = Long.MAX_VALUE; |
52 | } |
53 | function.optimize(session); |
54 | int type = function.getType(); |
55 | if (type != Value.RESULT_SET) { |
56 | throw DbException.get( |
57 | ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1, function.getName()); |
58 | } |
59 | Expression[] args = function.getArgs(); |
60 | int numParams = args.length; |
61 | Expression[] columnListArgs = new Expression[numParams]; |
62 | for (int i = 0; i < numParams; i++) { |
63 | args[i] = args[i].optimize(session); |
64 | columnListArgs[i] = args[i]; |
65 | } |
66 | ValueResultSet template = function.getValueForColumnList( |
67 | session, columnListArgs); |
68 | if (template == null) { |
69 | throw DbException.get( |
70 | ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1, function.getName()); |
71 | } |
72 | ResultSet rs = template.getResultSet(); |
73 | try { |
74 | ResultSetMetaData meta = rs.getMetaData(); |
75 | int columnCount = meta.getColumnCount(); |
76 | Column[] cols = new Column[columnCount]; |
77 | for (int i = 0; i < columnCount; i++) { |
78 | cols[i] = new Column(meta.getColumnName(i + 1), |
79 | DataType.getValueTypeFromResultSet(meta, i + 1), |
80 | meta.getPrecision(i + 1), |
81 | meta.getScale(i + 1), meta.getColumnDisplaySize(i + 1)); |
82 | } |
83 | setColumns(cols); |
84 | } catch (SQLException e) { |
85 | throw DbException.convert(e); |
86 | } |
87 | } |
88 | |
89 | @Override |
90 | public boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc) { |
91 | // nothing to do |
92 | return false; |
93 | } |
94 | |
95 | @Override |
96 | public void close(Session session) { |
97 | // nothing to do |
98 | } |
99 | |
100 | @Override |
101 | public void unlock(Session s) { |
102 | // nothing to do |
103 | } |
104 | |
105 | @Override |
106 | public boolean isLockedExclusively() { |
107 | return false; |
108 | } |
109 | |
110 | @Override |
111 | public Index addIndex(Session session, String indexName, int indexId, |
112 | IndexColumn[] cols, IndexType indexType, boolean create, |
113 | String indexComment) { |
114 | throw DbException.getUnsupportedException("ALIAS"); |
115 | } |
116 | |
117 | @Override |
118 | public void removeRow(Session session, Row row) { |
119 | throw DbException.getUnsupportedException("ALIAS"); |
120 | } |
121 | |
122 | @Override |
123 | public void truncate(Session session) { |
124 | throw DbException.getUnsupportedException("ALIAS"); |
125 | } |
126 | |
127 | @Override |
128 | public boolean canDrop() { |
129 | throw DbException.throwInternalError(); |
130 | } |
131 | |
132 | @Override |
133 | public void addRow(Session session, Row row) { |
134 | throw DbException.getUnsupportedException("ALIAS"); |
135 | } |
136 | |
137 | @Override |
138 | public void checkSupportAlter() { |
139 | throw DbException.getUnsupportedException("ALIAS"); |
140 | } |
141 | |
142 | @Override |
143 | public String getTableType() { |
144 | return null; |
145 | } |
146 | |
147 | @Override |
148 | public Index getScanIndex(Session session) { |
149 | return new FunctionIndex(this, IndexColumn.wrap(columns)); |
150 | } |
151 | |
152 | @Override |
153 | public ArrayList<Index> getIndexes() { |
154 | return null; |
155 | } |
156 | |
157 | @Override |
158 | public boolean canGetRowCount() { |
159 | return rowCount != Long.MAX_VALUE; |
160 | } |
161 | |
162 | @Override |
163 | public long getRowCount(Session session) { |
164 | return rowCount; |
165 | } |
166 | |
167 | @Override |
168 | public String getCreateSQL() { |
169 | return null; |
170 | } |
171 | |
172 | @Override |
173 | public String getDropSQL() { |
174 | return null; |
175 | } |
176 | |
177 | @Override |
178 | public void checkRename() { |
179 | throw DbException.getUnsupportedException("ALIAS"); |
180 | } |
181 | |
182 | /** |
183 | * Read the result from the function. This method buffers the result in a |
184 | * temporary file. |
185 | * |
186 | * @param session the session |
187 | * @return the result |
188 | */ |
189 | public ResultInterface getResult(Session session) { |
190 | ValueResultSet v = getValueResultSet(session); |
191 | if (v == null) { |
192 | return null; |
193 | } |
194 | if (cachedResult != null && cachedValue == v) { |
195 | cachedResult.reset(); |
196 | return cachedResult; |
197 | } |
198 | LocalResult result = LocalResult.read(session, v.getResultSet(), 0); |
199 | if (function.isDeterministic()) { |
200 | cachedResult = result; |
201 | cachedValue = v; |
202 | } |
203 | return result; |
204 | } |
205 | |
206 | /** |
207 | * Read the result set from the function. This method doesn't cache. |
208 | * |
209 | * @param session the session |
210 | * @return the result set |
211 | */ |
212 | public ResultSet getResultSet(Session session) { |
213 | ValueResultSet v = getValueResultSet(session); |
214 | return v == null ? null : v.getResultSet(); |
215 | } |
216 | |
217 | private ValueResultSet getValueResultSet(Session session) { |
218 | functionExpr = functionExpr.optimize(session); |
219 | Value v = functionExpr.getValue(session); |
220 | if (v == ValueNull.INSTANCE) { |
221 | return null; |
222 | } |
223 | return (ValueResultSet) v; |
224 | } |
225 | |
226 | public boolean isBufferResultSetToLocalTemp() { |
227 | return function.isBufferResultSetToLocalTemp(); |
228 | } |
229 | |
230 | @Override |
231 | public long getMaxDataModificationId() { |
232 | // TODO optimization: table-as-a-function currently doesn't know the |
233 | // last modified date |
234 | return Long.MAX_VALUE; |
235 | } |
236 | |
237 | @Override |
238 | public Index getUniqueIndex() { |
239 | return null; |
240 | } |
241 | |
242 | @Override |
243 | public String getSQL() { |
244 | return function.getSQL(); |
245 | } |
246 | |
247 | @Override |
248 | public long getRowCountApproximation() { |
249 | return rowCount; |
250 | } |
251 | |
252 | @Override |
253 | public long getDiskSpaceUsed() { |
254 | return 0; |
255 | } |
256 | |
257 | @Override |
258 | public boolean isDeterministic() { |
259 | return function.isDeterministic(); |
260 | } |
261 | |
262 | @Override |
263 | public boolean canReference() { |
264 | return false; |
265 | } |
266 | |
267 | } |