Mercurial > hg > orthanc-databases
comparison Framework/Odbc/OdbcPreparedStatement.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 "OdbcPreparedStatement.h" | |
23 | |
24 #include "../Common/InputFileValue.h" | |
25 #include "../Common/Integer64Value.h" | |
26 #include "../Common/Utf8StringValue.h" | |
27 #include "OdbcResult.h" | |
28 | |
29 #include <Logging.h> | |
30 #include <OrthancException.h> | |
31 | |
32 #include <sqlext.h> | |
33 | |
34 | |
35 namespace OrthancDatabases | |
36 { | |
37 void OdbcPreparedStatement::Setup(const Query& query) | |
38 { | |
39 formatter_.SetNamedDialect(Dialect_MSSQL); /* ODBC uses "?" to name its parameters */ | |
40 | |
41 std::string sql; | |
42 query.Format(sql, formatter_); | |
43 | |
44 LOG(INFO) << "Preparing ODBC statement: " << sql; | |
45 SQLCHAR* s = const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(sql.c_str())); | |
46 | |
47 if (!SQL_SUCCEEDED(SQLPrepare(statement_.GetHandle(), s, SQL_NTS))) | |
48 { | |
49 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
50 "Cannot prepare ODBC statement: " + sql); | |
51 } | |
52 | |
53 paramsIndex_.resize(formatter_.GetParametersCount()); | |
54 | |
55 size_t countInt64 = 0; | |
56 size_t countStrings = 0; | |
57 | |
58 for (size_t i = 0; i < paramsIndex_.size(); i++) | |
59 { | |
60 switch (formatter_.GetParameterType(i)) | |
61 { | |
62 case ValueType_Integer64: | |
63 paramsIndex_[i] = countInt64; | |
64 countInt64++; | |
65 break; | |
66 | |
67 case ValueType_InputFile: | |
68 case ValueType_Utf8String: | |
69 paramsIndex_[i] = countStrings; | |
70 countStrings++; | |
71 break; | |
72 | |
73 default: | |
74 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
75 } | |
76 } | |
77 | |
78 paramsInt64_.resize(countInt64); | |
79 paramsString_.resize(countStrings); | |
80 } | |
81 | |
82 | |
83 OdbcPreparedStatement::OdbcPreparedStatement(SQLHSTMT databaseHandle, | |
84 Dialect dialect, | |
85 const Query& query) : | |
86 statement_(databaseHandle), | |
87 formatter_(dialect) | |
88 { | |
89 Setup(query); | |
90 } | |
91 | |
92 | |
93 OdbcPreparedStatement::OdbcPreparedStatement(SQLHSTMT databaseHandle, | |
94 Dialect dialect, | |
95 const std::string& sql) : | |
96 statement_(databaseHandle), | |
97 formatter_(dialect) | |
98 { | |
99 Query query(sql); | |
100 Setup(query); | |
101 } | |
102 | |
103 | |
104 IResult* OdbcPreparedStatement::Execute() | |
105 { | |
106 Dictionary parameters; | |
107 return Execute(parameters); | |
108 } | |
109 | |
110 | |
111 IResult* OdbcPreparedStatement::Execute(const Dictionary& parameters) | |
112 { | |
113 /** | |
114 * NB: This class creates a copy of all the string parameters from | |
115 * "parameters", because "SQLBindParameter()" assumes that the | |
116 * lifetime of the bound values must be larger than the lifetime | |
117 * of the cursor. This is no problem for the index plugin, but | |
118 * might be problematic if storing large files in the storage area | |
119 * (as this doubles RAM requirements). | |
120 **/ | |
121 | |
122 for (size_t i = 0; i < formatter_.GetParametersCount(); i++) | |
123 { | |
124 const std::string& name = formatter_.GetParameterName(i); | |
125 | |
126 if (!parameters.HasKey(name)) | |
127 { | |
128 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, | |
129 "Missing parameter to SQL prepared statement: " + name); | |
130 } | |
131 else | |
132 { | |
133 const IValue& value = parameters.GetValue(name); | |
134 if (value.GetType() == ValueType_Null) | |
135 { | |
136 SQLSMALLINT cType, sqlType; | |
137 | |
138 switch (formatter_.GetParameterType(i)) | |
139 { | |
140 case ValueType_Integer64: | |
141 cType = SQL_C_SBIGINT; | |
142 sqlType = SQL_BIGINT; | |
143 break; | |
144 | |
145 case ValueType_Utf8String: | |
146 cType = SQL_C_CHAR; | |
147 sqlType = SQL_VARCHAR; | |
148 break; | |
149 | |
150 default: | |
151 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
152 } | |
153 | |
154 SQLLEN null = SQL_NULL_DATA; | |
155 if (!SQL_SUCCEEDED(SQLBindParameter(statement_.GetHandle(), i + 1, SQL_PARAM_INPUT, cType, sqlType, | |
156 0 /* ignored */, 0 /* ignored */, NULL, 0, &null))) | |
157 { | |
158 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
159 "Cannot bind NULL parameter: " + statement_.FormatError()); | |
160 } | |
161 } | |
162 else if (value.GetType() != formatter_.GetParameterType(i)) | |
163 { | |
164 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, | |
165 "Parameter \"" + name + "\" should be of type \"" + | |
166 EnumerationToString(formatter_.GetParameterType(i)) + | |
167 "\", found \"" + EnumerationToString(value.GetType()) + "\""); | |
168 } | |
169 else | |
170 { | |
171 assert(i < paramsIndex_.size()); | |
172 size_t index = paramsIndex_[i]; | |
173 | |
174 switch (value.GetType()) | |
175 { | |
176 case ValueType_Integer64: | |
177 assert(index < paramsInt64_.size()); | |
178 paramsInt64_[index] = dynamic_cast<const Integer64Value&>(value).GetValue(); | |
179 | |
180 if (!SQL_SUCCEEDED(SQLBindParameter(statement_.GetHandle(), i + 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, | |
181 0 /* ignored */, 0 /* ignored */, ¶msInt64_[index], | |
182 sizeof(int64_t), NULL))) | |
183 { | |
184 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
185 "Cannot bind integer parameter: " + statement_.FormatError()); | |
186 } | |
187 | |
188 break; | |
189 | |
190 case ValueType_Utf8String: | |
191 { | |
192 assert(index < paramsString_.size()); | |
193 paramsString_[index] = dynamic_cast<const Utf8StringValue&>(value).GetContent(); | |
194 | |
195 const char* content = (paramsString_[index].empty() ? "" : paramsString_[index].c_str()); | |
196 | |
197 if (!SQL_SUCCEEDED(SQLBindParameter( | |
198 statement_.GetHandle(), i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, | |
199 0 /* ignored */, 0 /* ignored */, const_cast<char*>(content), paramsString_[index].size(), NULL))) | |
200 { | |
201 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
202 "Cannot bind UTF-8 parameter: " + statement_.FormatError()); | |
203 } | |
204 | |
205 break; | |
206 } | |
207 | |
208 case ValueType_InputFile: | |
209 { | |
210 assert(index < paramsString_.size()); | |
211 paramsString_[index] = dynamic_cast<const InputFileValue&>(value).GetContent(); | |
212 | |
213 const char* content = (paramsString_[index].empty() ? NULL : paramsString_[index].c_str()); | |
214 | |
215 SQLLEN a = paramsString_[index].size(); | |
216 if (!SQL_SUCCEEDED(SQLBindParameter( | |
217 statement_.GetHandle(), i + 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, | |
218 paramsString_[index].size() /* only used by MSSQL */, | |
219 0 /* ignored */, const_cast<char*>(content), 0, &a))) | |
220 { | |
221 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
222 "Cannot bind binary parameter: " + statement_.FormatError()); | |
223 } | |
224 | |
225 break; | |
226 } | |
227 | |
228 default: | |
229 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
230 } | |
231 } | |
232 } | |
233 } | |
234 | |
235 const Dialect dialect = formatter_.GetAutoincrementDialect(); | |
236 | |
237 SQLRETURN code = SQLExecute(statement_.GetHandle()); | |
238 | |
239 if (code == SQL_SUCCESS || | |
240 code == SQL_NO_DATA /* this is the case of DELETE in PostgreSQL and MSSQL */) | |
241 { | |
242 return new OdbcResult(statement_, dialect); | |
243 } | |
244 else | |
245 { | |
246 statement_.CheckCollision(dialect); | |
247 | |
248 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, | |
249 "Cannot execute ODBC statement:\n" + statement_.FormatError()); | |
250 } | |
251 } | |
252 } |