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.jdbc; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.InputStream; |
10 | import java.io.OutputStream; |
11 | import java.io.PipedInputStream; |
12 | import java.io.PipedOutputStream; |
13 | import java.io.Reader; |
14 | import java.io.StringReader; |
15 | import java.io.StringWriter; |
16 | import java.io.Writer; |
17 | import java.sql.Clob; |
18 | import java.sql.NClob; |
19 | import java.sql.SQLException; |
20 | |
21 | import org.h2.api.ErrorCode; |
22 | import org.h2.engine.Constants; |
23 | import org.h2.message.DbException; |
24 | import org.h2.message.TraceObject; |
25 | import org.h2.util.IOUtils; |
26 | import org.h2.util.Task; |
27 | import org.h2.value.Value; |
28 | |
29 | /** |
30 | * Represents a CLOB value. |
31 | */ |
32 | public class JdbcClob extends TraceObject implements NClob |
33 | { |
34 | |
35 | Value value; |
36 | private final JdbcConnection conn; |
37 | |
38 | /** |
39 | * INTERNAL |
40 | */ |
41 | public JdbcClob(JdbcConnection conn, Value value, int id) { |
42 | setTrace(conn.getSession().getTrace(), TraceObject.CLOB, id); |
43 | this.conn = conn; |
44 | this.value = value; |
45 | } |
46 | |
47 | /** |
48 | * Returns the length. |
49 | * |
50 | * @return the length |
51 | */ |
52 | @Override |
53 | public long length() throws SQLException { |
54 | try { |
55 | debugCodeCall("length"); |
56 | checkClosed(); |
57 | if (value.getType() == Value.CLOB) { |
58 | long precision = value.getPrecision(); |
59 | if (precision > 0) { |
60 | return precision; |
61 | } |
62 | } |
63 | return IOUtils.copyAndCloseInput(value.getReader(), null, Long.MAX_VALUE); |
64 | } catch (Exception e) { |
65 | throw logAndConvert(e); |
66 | } |
67 | } |
68 | |
69 | /** |
70 | * [Not supported] Truncates the object. |
71 | */ |
72 | @Override |
73 | public void truncate(long len) throws SQLException { |
74 | throw unsupported("LOB update"); |
75 | } |
76 | |
77 | /** |
78 | * Returns the input stream. |
79 | * |
80 | * @return the input stream |
81 | */ |
82 | @Override |
83 | public InputStream getAsciiStream() throws SQLException { |
84 | try { |
85 | debugCodeCall("getAsciiStream"); |
86 | checkClosed(); |
87 | String s = value.getString(); |
88 | return IOUtils.getInputStreamFromString(s); |
89 | } catch (Exception e) { |
90 | throw logAndConvert(e); |
91 | } |
92 | } |
93 | |
94 | /** |
95 | * [Not supported] Returns an output stream. |
96 | */ |
97 | @Override |
98 | public OutputStream setAsciiStream(long pos) throws SQLException { |
99 | throw unsupported("LOB update"); |
100 | } |
101 | |
102 | /** |
103 | * Returns the reader. |
104 | * |
105 | * @return the reader |
106 | */ |
107 | @Override |
108 | public Reader getCharacterStream() throws SQLException { |
109 | try { |
110 | debugCodeCall("getCharacterStream"); |
111 | checkClosed(); |
112 | return value.getReader(); |
113 | } catch (Exception e) { |
114 | throw logAndConvert(e); |
115 | } |
116 | } |
117 | |
118 | /** |
119 | * Get a writer to update the Clob. This is only supported for new, empty |
120 | * Clob objects that were created with Connection.createClob() or |
121 | * createNClob(). The Clob is created in a separate thread, and the object |
122 | * is only updated when Writer.close() is called. The position must be 1, |
123 | * meaning the whole Clob data is set. |
124 | * |
125 | * @param pos where to start writing (the first character is at position 1) |
126 | * @return a writer |
127 | */ |
128 | @Override |
129 | public Writer setCharacterStream(long pos) throws SQLException { |
130 | try { |
131 | if (isDebugEnabled()) { |
132 | debugCodeCall("setCharacterStream(" + pos + ");"); |
133 | } |
134 | checkClosed(); |
135 | if (pos != 1) { |
136 | throw DbException.getInvalidValueException("pos", pos); |
137 | } |
138 | if (value.getPrecision() != 0) { |
139 | throw DbException.getInvalidValueException("length", value.getPrecision()); |
140 | } |
141 | final JdbcConnection c = conn; |
142 | // PipedReader / PipedWriter are a lot slower |
143 | // than PipedInputStream / PipedOutputStream |
144 | // (Sun/Oracle Java 1.6.0_20) |
145 | final PipedInputStream in = new PipedInputStream(); |
146 | final Task task = new Task() { |
147 | @Override |
148 | public void call() { |
149 | value = c.createClob(IOUtils.getReader(in), -1); |
150 | } |
151 | }; |
152 | PipedOutputStream out = new PipedOutputStream(in) { |
153 | @Override |
154 | public void close() throws IOException { |
155 | super.close(); |
156 | try { |
157 | task.get(); |
158 | } catch (Exception e) { |
159 | throw DbException.convertToIOException(e); |
160 | } |
161 | } |
162 | }; |
163 | task.execute(); |
164 | return IOUtils.getBufferedWriter(out); |
165 | } catch (Exception e) { |
166 | throw logAndConvert(e); |
167 | } |
168 | } |
169 | |
170 | /** |
171 | * Returns a substring. |
172 | * |
173 | * @param pos the position (the first character is at position 1) |
174 | * @param length the number of characters |
175 | * @return the string |
176 | */ |
177 | @Override |
178 | public String getSubString(long pos, int length) throws SQLException { |
179 | try { |
180 | if (isDebugEnabled()) { |
181 | debugCode("getSubString(" + pos + ", " + length + ");"); |
182 | } |
183 | checkClosed(); |
184 | if (pos < 1) { |
185 | throw DbException.getInvalidValueException("pos", pos); |
186 | } |
187 | if (length < 0) { |
188 | throw DbException.getInvalidValueException("length", length); |
189 | } |
190 | StringWriter writer = new StringWriter( |
191 | Math.min(Constants.IO_BUFFER_SIZE, length)); |
192 | Reader reader = value.getReader(); |
193 | try { |
194 | IOUtils.skipFully(reader, pos - 1); |
195 | IOUtils.copyAndCloseInput(reader, writer, length); |
196 | } finally { |
197 | reader.close(); |
198 | } |
199 | return writer.toString(); |
200 | } catch (Exception e) { |
201 | throw logAndConvert(e); |
202 | } |
203 | } |
204 | |
205 | /** |
206 | * Fills the Clob. This is only supported for new, empty Clob objects that |
207 | * were created with Connection.createClob() or createNClob(). The position |
208 | * must be 1, meaning the whole Clob data is set. |
209 | * |
210 | * @param pos where to start writing (the first character is at position 1) |
211 | * @param str the string to add |
212 | * @return the length of the added text |
213 | */ |
214 | @Override |
215 | public int setString(long pos, String str) throws SQLException { |
216 | try { |
217 | if (isDebugEnabled()) { |
218 | debugCode("setString(" + pos + ", " + quote(str) + ");"); |
219 | } |
220 | checkClosed(); |
221 | if (pos != 1) { |
222 | throw DbException.getInvalidValueException("pos", pos); |
223 | } else if (str == null) { |
224 | throw DbException.getInvalidValueException("str", str); |
225 | } |
226 | value = conn.createClob(new StringReader(str), -1); |
227 | return str.length(); |
228 | } catch (Exception e) { |
229 | throw logAndConvert(e); |
230 | } |
231 | } |
232 | |
233 | /** |
234 | * [Not supported] Sets a substring. |
235 | */ |
236 | @Override |
237 | public int setString(long pos, String str, int offset, int len) |
238 | throws SQLException { |
239 | throw unsupported("LOB update"); |
240 | } |
241 | |
242 | /** |
243 | * [Not supported] Searches a pattern and return the position. |
244 | */ |
245 | @Override |
246 | public long position(String pattern, long start) throws SQLException { |
247 | throw unsupported("LOB search"); |
248 | } |
249 | |
250 | /** |
251 | * [Not supported] Searches a pattern and return the position. |
252 | */ |
253 | @Override |
254 | public long position(Clob clobPattern, long start) throws SQLException { |
255 | throw unsupported("LOB search"); |
256 | } |
257 | |
258 | /** |
259 | * Release all resources of this object. |
260 | */ |
261 | @Override |
262 | public void free() { |
263 | debugCodeCall("free"); |
264 | value = null; |
265 | } |
266 | |
267 | /** |
268 | * [Not supported] Returns the reader, starting from an offset. |
269 | */ |
270 | @Override |
271 | public Reader getCharacterStream(long pos, long length) throws SQLException { |
272 | throw unsupported("LOB subset"); |
273 | } |
274 | |
275 | private void checkClosed() { |
276 | conn.checkClosed(); |
277 | if (value == null) { |
278 | throw DbException.get(ErrorCode.OBJECT_CLOSED); |
279 | } |
280 | } |
281 | |
282 | /** |
283 | * INTERNAL |
284 | */ |
285 | @Override |
286 | public String toString() { |
287 | return getTraceObjectName() + ": " + (value == null ? |
288 | "null" : value.getTraceSQL()); |
289 | } |
290 | |
291 | } |