Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ODBC.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_Foundation_Common_Platform_Windows
7#include <windows.h>
8#endif
9
10#if qStroika_HasComponent_ODBC
11#include <sql.h>
12#include <sqlext.h>
13#endif
14
17#include "Stroika/Foundation/Database/Exception.h"
18
19#include "ODBC.h"
20
21using namespace Stroika::Foundation;
23using namespace Stroika::Foundation::Database;
24using namespace Stroika::Foundation::Database::SQL::ODBC;
25using namespace Debug;
26
28
29#if qStroika_HasComponent_ODBC
30
31namespace {
32 void ThrowIfSQLError_ (SQLRETURN r, const String& message)
33 {
34 if ((r != SQL_SUCCESS) and (r != SQL_SUCCESS_WITH_INFO)) [[unlikely]] {
35 Execution::Throw (Exception{message}); // mistake? why would I throw SUCCESS WITH INFO? Maybe just log it?
36 }
37 }
38}
39
40namespace {
41 using Connection::Options;
42 struct Rep_ final : Stroika::Foundation::Database::SQL::ODBC::Connection::IRep {
43 SQLHDBC fConnectionHandle{nullptr};
44 SQLHENV fODBCEnvironmentHandle{nullptr};
45
46 Rep_ (const Options& options)
47 {
48 TraceContextBumper ctx{"SQLite::Connection::Rep_::Rep_"};
49 if (not options.fDSN) {
50 Execution::Throw (Exception{"DSN Required"sv});
51 }
52 try {
53 ThrowIfSQLError_ (::SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE, &fODBCEnvironmentHandle), "Error AllocHandle"sv);
54 ThrowIfSQLError_ (::SQLSetEnvAttr (fODBCEnvironmentHandle, SQL_ATTR_ODBC_VERSION, reinterpret_cast<void*> (SQL_OV_ODBC3), 0),
55 "Error SetEnv"sv);
56 ThrowIfSQLError_ (::SQLAllocHandle (SQL_HANDLE_DBC, fODBCEnvironmentHandle, &fConnectionHandle), "Error AllocHDB"sv);
57
58 ::SQLSetConnectAttr (fConnectionHandle, SQL_LOGIN_TIMEOUT, reinterpret_cast<SQLPOINTER*> (5), 0);
59 {
60 // See the docs on SQLConnect - the error handling needs to be VASTLY more complex. We need some mechanism to return
61 // warnings to the caller (to be ignored or whatever).
62 // And ONLY thorw exceptions on ERROR!
63 SQLRETURN return_value =
64 ::SQLConnect (fConnectionHandle, reinterpret_cast<SQLTCHAR*> (const_cast<TCHAR*> (options.fDSN->AsSDKString ().c_str ())),
65 SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS);
66 if ((return_value != SQL_SUCCESS) && (return_value != SQL_SUCCESS_WITH_INFO)) {
67 // This logic for producing an error message completely sucks and is largely incorrect
68 StringBuilder errorString = "Error SQLConnect: "_k;
69 SQLTCHAR sqlState[6];
70 SQLINTEGER errorCode;
71 SQLSMALLINT messageLength;
72 SQLTCHAR errorMessage[1024];
74 long errValue = ::SQLGetDiagRec (SQL_HANDLE_DBC, fConnectionHandle, 1, reinterpret_cast<SQLTCHAR*> (sqlState), &errorCode,
75 reinterpret_cast<SQLTCHAR*> (errorMessage), Memory::NEltsOf (errorMessage), &messageLength);
76 DISABLE_COMPILER_MSC_WARNING_END (4267)
77 if (errValue == SQL_SUCCESS) {
78 // TCHAR isn't the same SQLTCHAR for 'ANSI' because for some crazy reason, they
79 // used unsigned char for SQLCHAR!
80 errorString += String::FromSDKString (reinterpret_cast<TCHAR*> (errorMessage));
81 }
82 else if (errValue == SQL_SUCCESS_WITH_INFO) {
83 errorString = "Error message too long at"_k;
84 }
85 else if (errValue == SQL_ERROR) {
86 errorString += "RecNumber was negative or 0 or BufferLength was less than 0"_k;
87 }
88 else if (errValue == SQL_NO_DATA) {
89 errorString += "SQL no data"_k;
90 }
91 Execution::Throw (Exception{errorString});
92 }
93 }
94 }
95 catch (...) {
96 if (fConnectionHandle != nullptr) {
97 ::SQLFreeHandle (SQL_HANDLE_DBC, fConnectionHandle);
98 fConnectionHandle = nullptr;
99 }
100 if (fODBCEnvironmentHandle != nullptr) {
101 ::SQLFreeHandle (SQL_HANDLE_ENV, fODBCEnvironmentHandle);
102 fODBCEnvironmentHandle = nullptr;
103 }
104 }
105 }
106 ~Rep_ ()
107 {
108 if (fConnectionHandle != nullptr) {
109 ::SQLFreeHandle (SQL_HANDLE_DBC, fConnectionHandle);
110 }
111 if (fODBCEnvironmentHandle != nullptr) {
112 ::SQLFreeHandle (SQL_HANDLE_ENV, fODBCEnvironmentHandle);
113 }
114 }
115 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
116 {
118 struct MyEngineProperties_ final : EngineProperties {
119 virtual String GetEngineName () const override
120 {
121 return "ODBC"sv; // must indirect to connection to get more info (from dns at least? not clear)
122 }
123 virtual String GetSQL ([[maybe_unused]] NonStandardSQL n) const override
124 {
125 // see https://stackoverflow.com/questions/167576/check-if-table-exists-in-sql-server
127 return String{};
128 }
129 virtual bool RequireStatementResetAfterModifyingStatmentToCompleteTransaction () const override
130 {
131 return false;
132 }
133 virtual bool SupportsNestedTransactions () const override
134 {
135 return false;
136 }
137 };
138 return make_shared<const MyEngineProperties_> (); // dynamic info based on connection/dsn
139 }
140 virtual SQL::Statement mkStatement (const String& sql) override
141 {
142 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
143 return Statement{conn, sql};
144 }
145 virtual SQL::Transaction mkTransaction () override
146 {
147 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
148 return Transaction{conn};
149 }
150 virtual void Exec (const String& /*sql*/) override
151 {
153 }
154 };
155}
156
157/*
158 ********************************************************************************
159 ************************* SQL::ODBC::Connection::Ptr ***************************
160 ********************************************************************************
161 */
162SQL::ODBC::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
163 : inherited{src}
164{
165#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
166 if (src != nullptr) {
167 fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->fAssertExternallySynchronizedMutex.GetSharedContext ());
168 }
169#endif
170}
171
172/*
173 ********************************************************************************
174 ************************ SQL::ODBC::Connection::New ****************************
175 ********************************************************************************
176 */
177auto SQL::ODBC::Connection::New (const Options& options) -> Ptr
178{
179 return Ptr{make_shared<Rep_> (options)};
180}
181
182/*
183 ********************************************************************************
184 ******************************* SQLite::Statement ******************************
185 ********************************************************************************
186 */
187struct Statement::MyRep_ : IRep {
188 MyRep_ (const Connection::Ptr& db, const String& query)
189 : fConnectionPtr_{db}
190 {
191#if USE_NOISY_TRACE_IN_THIS_MODULE_
192 TraceContextBumper ctx{"SQLite::Statement::MyRep_::CTOR",
193 Stroika_Foundation_Debug_OptionalizeTraceArgs (L "db=%p, query='%s'", db, query.As<wstring> ().c_str ())};
194#endif
195 RequireNotNull (db);
196#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
197 _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (
198 fConnectionPtr_.fAssertExternallySynchronizedMutex.GetSharedContext ());
199#endif
200 u8string queryUTF8 = query.AsUTF8 ();
201 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
203 }
204 ~MyRep_ ()
205 {
206 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
207 }
208 virtual String GetSQL ([[maybe_unused]] WhichSQLFlag whichSQL) const override
209 {
210 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
212 return String{};
213 }
214 virtual Sequence<ColumnDescription> GetColumns () const override
215 {
216 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
219 };
220 virtual Sequence<ParameterDescription> GetParameters () const override
221 {
222 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
223 return fParameters_;
224 };
225 virtual void Bind () override
226 {
227 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
228 for (auto i = fParameters_.begin (); i != fParameters_.end (); ++i) {
229 auto p = *i;
230 p.fValue = VariantValue{};
231 fParameters_.Update (i, p, &i);
232 }
234 }
235 virtual void Bind (unsigned int parameterIndex, const VariantValue& v) override
236 {
237 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
238 fParameters_ (parameterIndex).fValue = v;
240 }
241 virtual void Bind (const String& parameterName, const VariantValue& v) override
242 {
243 Require (not parameterName.empty ());
244 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
246 String pn = parameterName;
247 if (pn[0] != ':') {
248 pn = ":"_k + pn;
249 }
250 for (unsigned int i = 0; i < fParameters_.length (); ++i) {
251 if (fParameters_[i].fName == pn) {
252 Bind (i, v);
253 return;
254 }
255 }
256 DbgTrace ("Statement::Bind: Parameter '{}' not found in list {}"_f, parameterName,
257 fParameters_.Map<vector<String>> ([] (const auto& i) { return i.fName; }));
258 RequireNotReached (); // invalid parameter name provided
259 }
260 virtual void Reset () override
261 {
262#if USE_NOISY_TRACE_IN_THIS_MODULE_
263 TraceContextBumper ctx{"SQLite::Statement::MyRep_::Statement::Reset"};
264#endif
265 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
267 }
268 virtual optional<Row> GetNextRow () override
269 {
270#if USE_NOISY_TRACE_IN_THIS_MODULE_
271 TraceContextBumper ctx{"SQLite::Statement::MyRep_::Statement::GetNextRow"};
272#endif
273 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
275 return nullopt;
276 }
277
278 Connection::Ptr fConnectionPtr_;
279 vector<ColumnDescription> fColumns_;
281};
282
283Statement::Statement (const Connection::Ptr& db, const String& query)
284 : inherited{make_unique<MyRep_> (db, query)}
285{
286}
287
288/*
289 ********************************************************************************
290 ******************************* SQLite::Transaction ****************************
291 ********************************************************************************
292 */
293struct Transaction::MyRep_ : public SQL::Transaction::IRep {
294 MyRep_ (const Connection::Ptr& db)
295 : fConnectionPtr_{db}
296 {
298 }
299 virtual void Commit () override
300 {
301 Require (not fCompleted_);
302 fCompleted_ = true;
303 fConnectionPtr_->Exec ("COMMIT;"sv);
304 }
305 virtual void Rollback () override
306 {
307 Require (not fCompleted_);
308 fCompleted_ = true;
309 fConnectionPtr_->Exec ("ROLLBACK;"sv);
310 }
311 virtual Disposition GetDisposition () const override
312 {
313 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
314 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
315 }
316 Connection::Ptr fConnectionPtr_;
317 bool fCompleted_{false};
318};
319Transaction::Transaction (const Connection::Ptr& db)
320 : inherited{make_unique<MyRep_> (db)}
321{
322}
323#endif
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotReached()
Definition Assertions.h:385
#define RequireNotNull(p)
Definition Assertions.h:347
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
EngineProperties captures the features associated with a given database engine (being talked to throu...
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43