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