comparison Framework/Odbc/OdbcResult.cpp @ 329:b5fb8b77ce4d

initial commit of ODBC framework
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 10 Aug 2021 20:08:53 +0200
parents
children 16aac0287485
comparison
equal deleted inserted replaced
328:6a49c495c940 329:b5fb8b77ce4d
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2021 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #include "OdbcResult.h"
23
24 #include "../Common/BinaryStringValue.h"
25 #include "../Common/Integer64Value.h"
26 #include "../Common/NullValue.h"
27 #include "../Common/Utf8StringValue.h"
28
29 #include <ChunkedBuffer.h>
30 #include <Logging.h>
31 #include <OrthancException.h>
32 #include <Toolbox.h>
33
34 #include <boost/lexical_cast.hpp>
35 #include <sqlext.h>
36
37
38 namespace OrthancDatabases
39 {
40 static void ThrowCannotReadString()
41 {
42 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot read text field");
43 }
44
45
46 void OdbcResult::LoadFirst()
47 {
48 if (first_)
49 {
50 Next();
51 first_ = false;
52 }
53 }
54
55
56 void OdbcResult::SetValue(size_t index,
57 IValue* value)
58 {
59 std::unique_ptr<IValue> raii(value);
60
61 if (index >= values_.size())
62 {
63 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
64 }
65 else if (value == NULL)
66 {
67 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
68 }
69 else
70 {
71 if (values_[index] != NULL)
72 {
73 delete values_[index];
74 }
75
76 values_[index] = raii.release();
77 }
78 }
79
80
81 void OdbcResult::ReadString(std::string& target,
82 size_t column,
83 bool isBinary)
84 {
85 // https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/getting-long-data
86
87 std::string buffer;
88 buffer.resize(1024 * 1024);
89
90 const SQLSMALLINT targetType = (isBinary ? SQL_BINARY : SQL_C_CHAR);
91
92 SQLLEN length;
93 SQLRETURN code = SQLGetData(statement_.GetHandle(), column + 1, targetType, &buffer[0], buffer.size(), &length);
94 if (code == SQL_NO_DATA)
95 {
96 target.clear();
97 }
98 else if (code == SQL_SUCCESS)
99 {
100 if (length == -1)
101 {
102 target.clear(); // No data available
103 }
104 else
105 {
106 // The "buffer" was large enough to store the text value, plus the null termination
107 target.assign(buffer.c_str(), length);
108 }
109 }
110 else if (code == SQL_SUCCESS_WITH_INFO)
111 {
112 Orthanc::ChunkedBuffer chunks;
113
114 if (isBinary)
115 {
116 chunks.AddChunk(buffer.c_str(), buffer.size());
117 }
118 else
119 {
120 /**
121 * WARNING: At this point, in the MSSQL driver, "length"
122 * contains the number of Unicode characters! This is
123 * different from the actual number of **bytes** that are
124 * required to store the UTF-8 string. As a consequence, the
125 * "length" cannot be used to determine the final size of
126 * the "target" string.
127 **/
128 chunks.AddChunk(buffer.c_str(), buffer.size() - 1);
129 }
130
131 for (;;)
132 {
133 code = SQLGetData(statement_.GetHandle(), column + 1, targetType, &buffer[0], buffer.size(), &length);
134
135 if (code == SQL_SUCCESS)
136 {
137 // This is the last chunk
138 if (length == 0 ||
139 length > static_cast<SQLLEN>(buffer.size()))
140 {
141 ThrowCannotReadString();
142 }
143
144 chunks.AddChunk(buffer.c_str(), length);
145 break;
146 }
147 else if (code == SQL_SUCCESS_WITH_INFO)
148 {
149 // This is an intermediate chunk
150 if (isBinary)
151 {
152 chunks.AddChunk(buffer.c_str(), buffer.size());
153 }
154 else
155 {
156 chunks.AddChunk(buffer.c_str(), buffer.size() - 1);
157 }
158 }
159 else
160 {
161 ThrowCannotReadString();
162 }
163 }
164
165 chunks.Flatten(target);
166 }
167 else
168 {
169 statement_.CheckCollision(dialect_);
170 ThrowCannotReadString();
171 }
172 }
173
174
175 OdbcResult::OdbcResult(OdbcStatement& statement,
176 Dialect dialect) :
177 statement_(statement),
178 dialect_(dialect),
179 first_(true),
180 done_(false)
181 {
182 SQLSMALLINT count;
183 if (!SQL_SUCCEEDED(SQLNumResultCols(statement_.GetHandle(), &count)))
184 {
185 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
186 }
187
188 types_.resize(count);
189 typeNames_.resize(count);
190 values_.resize(count);
191
192 for (size_t i = 0; i < values_.size(); i++)
193 {
194 /**
195 * NB: Don't use "SQLDescribeCol()", as it is less flexible
196 * (cf. OMSSQL-7: "SQLDescribeParam()" doesn't work with
197 * encrypted columns)
198 **/
199
200 if (!SQL_SUCCEEDED(SQLColAttribute(statement_.GetHandle(), i + 1, SQL_DESC_TYPE, NULL, -1, NULL, &types_[i])))
201 {
202 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
203 }
204
205 SQLCHAR buffer[1024];
206 SQLSMALLINT length;
207
208 if (!SQL_SUCCEEDED(SQLColAttribute(statement_.GetHandle(), i + 1, SQL_DESC_TYPE_NAME,
209 buffer, sizeof(buffer) - 1, &length, NULL)))
210 {
211 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
212 }
213
214 std::string name(reinterpret_cast<const char*>(buffer), length);
215 Orthanc::Toolbox::ToLowerCase(typeNames_[i], name);
216 }
217 }
218
219
220 OdbcResult::~OdbcResult()
221 {
222 for (size_t i = 0; i < values_.size(); i++)
223 {
224 if (values_[i] != NULL)
225 {
226 delete values_[i];
227 }
228 }
229
230 if (!first_ &&
231 !SQL_SUCCEEDED(SQLCloseCursor(statement_.GetHandle())))
232 {
233 LOG(WARNING) << "Cannot close the ODBC cursor: " << std::endl << statement_.FormatError();
234 }
235 }
236
237
238 void OdbcResult::SetExpectedType(size_t field,
239 ValueType type)
240 {
241 if (field >= types_.size())
242 {
243 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
244 }
245 else
246 {
247 // Ignore this information
248 }
249 }
250
251
252 bool OdbcResult::IsDone() const
253 {
254 const_cast<OdbcResult&>(*this).LoadFirst();
255 return done_;
256 }
257
258
259 void OdbcResult::Next()
260 {
261 SQLRETURN code = SQLFetch(statement_.GetHandle());
262
263 if (code == SQL_NO_DATA)
264 {
265 done_ = true;
266 }
267 else if (code == SQL_SUCCESS)
268 {
269 done_ = false;
270 }
271 else
272 {
273 statement_.CheckCollision(dialect_);
274 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot fetch new row");
275 }
276
277 assert(values_.size() == types_.size() &&
278 values_.size() == typeNames_.size());
279
280 for (size_t i = 0; i < values_.size(); i++)
281 {
282 SQLLEN type = types_[i];
283 const std::string& name = typeNames_[i];
284
285 if (done_)
286 {
287 SetValue(i, new NullValue);
288 }
289 else if (type == SQL_INTEGER)
290 {
291 int32_t value;
292 SQLLEN length;
293 if (SQL_SUCCEEDED(SQLGetData(statement_.GetHandle(), i + 1, SQL_INTEGER, &value, sizeof(value), &length)))
294 {
295 if (length == SQL_NULL_DATA)
296 {
297 SetValue(i, new NullValue);
298 }
299 else
300 {
301 SetValue(i, new Integer64Value(value));
302 }
303 }
304 else
305 {
306 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot get int32_t field");
307 }
308 }
309 else if (type == SQL_BIGINT ||
310 (dialect_ == Dialect_PostgreSQL && name == "bigserial"))
311 {
312 int64_t value;
313 SQLLEN length;
314 if (SQL_SUCCEEDED(SQLGetData(statement_.GetHandle(), i + 1, SQL_C_SBIGINT, &value, sizeof(value), &length)))
315 {
316 if (length == SQL_NULL_DATA)
317 {
318 SetValue(i, new NullValue);
319 }
320 else
321 {
322 SetValue(i, new Integer64Value(value));
323 }
324 }
325 else
326 {
327 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Cannot get int64_t field");
328 }
329 }
330 else if (type == SQL_VARCHAR ||
331 name == "varchar" ||
332 (dialect_ == Dialect_MSSQL && name == "nvarchar") || // This means UTF-16
333 (dialect_ == Dialect_MySQL && name == "longtext") ||
334 (dialect_ == Dialect_MySQL && name.empty() && type == -9) || // Seen in "SQLTables()"
335 (dialect_ == Dialect_PostgreSQL && name == "text") ||
336 (dialect_ == Dialect_SQLite && name == "text") ||
337 (dialect_ == Dialect_SQLite && name == "wvarchar")) // Seen on Windows with sqliteodbc-0.9998-win32.exe
338 {
339 std::string value;
340 ReadString(value, i, false /* not binary */);
341 SetValue(i, new Utf8StringValue(value));
342 }
343 else if (type == SQL_NUMERIC)
344 {
345 /**
346 * SQL_NUMERIC_STRUCT could be used here, but is much more
347 * complex to deal with:
348 * https://stackoverflow.com/a/9188737/881731
349 **/
350
351 std::string value;
352 ReadString(value, i, false /* not binary */);
353 SetValue(i, new Integer64Value(boost::lexical_cast<int64_t>(value)));
354 }
355 else if (type == SQL_BINARY ||
356 (dialect_ == Dialect_PostgreSQL && name == "bytea") ||
357 (dialect_ == Dialect_MySQL && name == "longblob") ||
358 (dialect_ == Dialect_MSSQL && name == "varbinary"))
359 {
360 std::string value;
361 ReadString(value, i, true /* binary */);
362 SetValue(i, new BinaryStringValue(value));
363 }
364 else
365 {
366 throw Orthanc::OrthancException(
367 Orthanc::ErrorCode_NotImplemented,
368 "Unknown type in result: " + name + " (" + boost::lexical_cast<std::string>(type) + ")");
369 }
370 }
371 }
372
373
374 const IValue& OdbcResult::GetField(size_t field) const
375 {
376 if (field >= values_.size())
377 {
378 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
379 }
380 else if (IsDone())
381 {
382 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
383 }
384 else if (values_[field] == NULL)
385 {
386 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
387 }
388 else
389 {
390 return *values_[field];
391 }
392 }
393 }