]> git.stg.codes - stg.git/blob - stglibs/ibpp.lib/database.cpp
Better handling of errors from server. Refactoring.
[stg.git] / stglibs / ibpp.lib / database.cpp
1 ///////////////////////////////////////////////////////////////////////////////\r
2 //\r
3 //      File    : $Id: database.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $\r
4 //      Subject : IBPP, Database class implementation\r
5 //\r
6 ///////////////////////////////////////////////////////////////////////////////\r
7 //\r
8 //      (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org)\r
9 //\r
10 //      The contents of this file are subject to the IBPP License (the "License");\r
11 //      you may not use this file except in compliance with the License.  You may\r
12 //      obtain a copy of the License at http://www.ibpp.org or in the 'license.txt'\r
13 //      file which must have been distributed along with this file.\r
14 //\r
15 //      This software, distributed under the License, is distributed on an "AS IS"\r
16 //      basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the\r
17 //      License for the specific language governing rights and limitations\r
18 //      under the License.\r
19 //\r
20 ///////////////////////////////////////////////////////////////////////////////\r
21 //\r
22 //      COMMENTS\r
23 //      * Tabulations should be set every four characters when editing this file.\r
24 //\r
25 ///////////////////////////////////////////////////////////////////////////////\r
26 \r
27 #ifdef _MSC_VER\r
28 #pragma warning(disable: 4786 4996)\r
29 #ifndef _DEBUG\r
30 #pragma warning(disable: 4702)\r
31 #endif\r
32 #endif\r
33 \r
34 #include "_ibpp.h"\r
35 \r
36 #ifdef HAS_HDRSTOP\r
37 #pragma hdrstop\r
38 #endif\r
39 \r
40 #include <algorithm>\r
41 \r
42 using namespace ibpp_internals;\r
43 \r
44 //      (((((((( OBJECT INTERFACE IMPLEMENTATION ))))))))\r
45 \r
46 void DatabaseImpl::Create(int dialect)\r
47 {\r
48         if (mHandle != 0)\r
49                 throw LogicExceptionImpl("Database::Create", _("Database is already connected."));\r
50         if (mDatabaseName.empty())\r
51                 throw LogicExceptionImpl("Database::Create", _("Unspecified database name."));\r
52         if (mUserName.empty())\r
53                 throw LogicExceptionImpl("Database::Create", _("Unspecified user name."));\r
54         if (dialect != 1 && dialect != 3)\r
55                 throw LogicExceptionImpl("Database::Create", _("Only dialects 1 and 3 are supported."));\r
56 \r
57         // Build the SQL Create Statement\r
58         std::string create;\r
59         create.assign("CREATE DATABASE '");\r
60         if (! mServerName.empty()) create.append(mServerName).append(":");\r
61         create.append(mDatabaseName).append("' ");\r
62 \r
63         create.append("USER '").append(mUserName).append("' ");\r
64         if (! mUserPassword.empty())\r
65                 create.append("PASSWORD '").append(mUserPassword).append("' ");\r
66 \r
67         if (! mCreateParams.empty()) create.append(mCreateParams);\r
68 \r
69         // Call ExecuteImmediate to create the database\r
70         isc_tr_handle tr_handle = 0;\r
71         IBS status;\r
72     (*gds.Call()->m_dsql_execute_immediate)(status.Self(), &mHandle, &tr_handle,\r
73         0, const_cast<char*>(create.c_str()), short(dialect), NULL);\r
74     if (status.Errors())\r
75                 throw SQLExceptionImpl(status, "Database::Create", _("isc_dsql_execute_immediate failed"));\r
76 \r
77         Disconnect();\r
78 }\r
79 \r
80 void DatabaseImpl::Connect()\r
81 {\r
82         if (mHandle != 0) return;       // Already connected\r
83 \r
84         if (mDatabaseName.empty())\r
85                 throw LogicExceptionImpl("Database::Connect", _("Unspecified database name."));\r
86         if (mUserName.empty())\r
87                 throw LogicExceptionImpl("Database::Connect", _("Unspecified user name."));\r
88 \r
89     // Build a DPB based on the properties\r
90         DPB dpb;\r
91     dpb.Insert(isc_dpb_user_name, mUserName.c_str());\r
92     dpb.Insert(isc_dpb_password, mUserPassword.c_str());\r
93     if (! mRoleName.empty()) dpb.Insert(isc_dpb_sql_role_name, mRoleName.c_str());\r
94     if (! mCharSet.empty()) dpb.Insert(isc_dpb_lc_ctype, mCharSet.c_str());\r
95 \r
96         std::string connect;\r
97         if (! mServerName.empty())\r
98                 connect.assign(mServerName).append(":");\r
99         connect.append(mDatabaseName);\r
100 \r
101         IBS status;\r
102         (*gds.Call()->m_attach_database)(status.Self(), (short)connect.size(),\r
103                 const_cast<char*>(connect.c_str()), &mHandle, dpb.Size(), dpb.Self());\r
104     if (status.Errors())\r
105     {\r
106         mHandle = 0;     // Should be, but better be sure...\r
107                 throw SQLExceptionImpl(status, "Database::Connect", _("isc_attach_database failed"));\r
108     }\r
109 \r
110         // Now, get ODS version information and dialect.\r
111         // If ODS major is lower of equal to 9, we reject the connection.\r
112         // If ODS major is 10 or higher, this is at least an InterBase 6.x Server\r
113         // OR FireBird 1.x Server.\r
114 \r
115         char items[] = {isc_info_ods_version,\r
116                                         isc_info_db_SQL_dialect,\r
117                                         isc_info_end};\r
118         RB result(100);\r
119 \r
120         status.Reset();\r
121         (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items,\r
122                 result.Size(), result.Self());\r
123         if (status.Errors())\r
124         {\r
125                 status.Reset();\r
126             (*gds.Call()->m_detach_database)(status.Self(), &mHandle);\r
127         mHandle = 0;     // Should be, but better be sure...\r
128                 throw SQLExceptionImpl(status, "Database::Connect", _("isc_database_info failed"));\r
129         }\r
130 \r
131         int ODS = result.GetValue(isc_info_ods_version);\r
132         if (ODS <= 9)\r
133         {\r
134                 status.Reset();\r
135             (*gds.Call()->m_detach_database)(status.Self(), &mHandle);\r
136         mHandle = 0;     // Should be, but better be sure...\r
137                 throw LogicExceptionImpl("Database::Connect",\r
138                         _("Unsupported Server : wrong ODS version (%d), at least '10' required."), ODS);\r
139         }\r
140 \r
141         mDialect = result.GetValue(isc_info_db_SQL_dialect);\r
142         if (mDialect != 1 && mDialect != 3)\r
143         {\r
144                 status.Reset();\r
145             (*gds.Call()->m_detach_database)(status.Self(), &mHandle);\r
146         mHandle = 0;     // Should be, but better be sure...\r
147                 throw LogicExceptionImpl("Database::Connect", _("Dialect 1 or 3 required"));\r
148         }\r
149 \r
150         // Now, verify the GDS32.DLL we are using is compatible with the server\r
151         if (ODS >= 10 && gds.Call()->mGDSVersion < 60)\r
152         {\r
153                 status.Reset();\r
154             (*gds.Call()->m_detach_database)(status.Self(), &mHandle);\r
155         mHandle = 0;     // Should be, but better be sure...\r
156                 throw LogicExceptionImpl("Database::Connect", _("GDS32.DLL version 5 against IBSERVER 6"));\r
157         }\r
158 }\r
159 \r
160 void DatabaseImpl::Inactivate()\r
161 {\r
162         if (mHandle == 0) return;       // Not connected anyway\r
163 \r
164     IBS status;\r
165 \r
166     // Rollback any started transaction...\r
167         for (unsigned i = 0; i < mTransactions.size(); i++)\r
168         {\r
169                 if (mTransactions[i]->Started())\r
170                         mTransactions[i]->Rollback();\r
171         }\r
172 \r
173         // Cancel all pending event traps\r
174         for (unsigned i = 0; i < mEvents.size(); i++)\r
175                 mEvents[i]->Clear();\r
176 \r
177         // Let's detach from all Blobs\r
178         while (mBlobs.size() > 0)\r
179                 mBlobs.back()->DetachDatabaseImpl();\r
180 \r
181         // Let's detach from all Arrays\r
182         while (mArrays.size() > 0)\r
183                 mArrays.back()->DetachDatabaseImpl();\r
184 \r
185         // Let's detach from all Statements\r
186         while (mStatements.size() > 0)\r
187                 mStatements.back()->DetachDatabaseImpl();\r
188 \r
189         // Let's detach from all Transactions\r
190         while (mTransactions.size() > 0)\r
191                 mTransactions.back()->DetachDatabaseImpl(this);\r
192 \r
193         // Let's detach from all Events\r
194         while (mEvents.size() > 0)\r
195                 mEvents.back()->DetachDatabaseImpl();\r
196 }\r
197 \r
198 void DatabaseImpl::Disconnect()\r
199 {\r
200         if (mHandle == 0) return;       // Not connected anyway\r
201 \r
202         // Put the connection to rest\r
203         Inactivate();\r
204 \r
205         // Detach from the server\r
206         IBS status;\r
207     (*gds.Call()->m_detach_database)(status.Self(), &mHandle);\r
208 \r
209     // Should we throw, set mHandle to 0 first, because Disconnect() may\r
210         // be called from Database destructor (keeps the object coherent).\r
211         mHandle = 0;\r
212     if (status.Errors())\r
213                 throw SQLExceptionImpl(status, "Database::Disconnect", _("isc_detach_database failed"));\r
214 }\r
215 \r
216 void DatabaseImpl::Drop()\r
217 {\r
218         if (mHandle == 0)\r
219                 throw LogicExceptionImpl("Database::Drop", _("Database must be connected."));\r
220 \r
221         // Put the connection to a rest\r
222         Inactivate();\r
223 \r
224         IBS vector;\r
225         (*gds.Call()->m_drop_database)(vector.Self(), &mHandle);\r
226     if (vector.Errors())\r
227         throw SQLExceptionImpl(vector, "Database::Drop", _("isc_drop_database failed"));\r
228 \r
229     mHandle = 0;\r
230 }\r
231 \r
232 void DatabaseImpl::Info(int* ODSMajor, int* ODSMinor,\r
233         int* PageSize, int* Pages, int* Buffers, int* Sweep,\r
234         bool* Sync, bool* Reserve)\r
235 {\r
236         if (mHandle == 0)\r
237                 throw LogicExceptionImpl("Database::Info", _("Database is not connected."));\r
238 \r
239         char items[] = {isc_info_ods_version,\r
240                                         isc_info_ods_minor_version,\r
241                                         isc_info_page_size,\r
242                                         isc_info_allocation,\r
243                                         isc_info_num_buffers,\r
244                                         isc_info_sweep_interval,\r
245                                         isc_info_forced_writes,\r
246                                         isc_info_no_reserve,\r
247                                         isc_info_end};\r
248     IBS status;\r
249         RB result(256);\r
250 \r
251         status.Reset();\r
252         (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items,\r
253                 result.Size(), result.Self());\r
254         if (status.Errors())\r
255                 throw SQLExceptionImpl(status, "Database::Info", _("isc_database_info failed"));\r
256 \r
257         if (ODSMajor != 0) *ODSMajor = result.GetValue(isc_info_ods_version);\r
258         if (ODSMinor != 0) *ODSMinor = result.GetValue(isc_info_ods_minor_version);\r
259         if (PageSize != 0) *PageSize = result.GetValue(isc_info_page_size);\r
260         if (Pages != 0) *Pages = result.GetValue(isc_info_allocation);\r
261         if (Buffers != 0) *Buffers = result.GetValue(isc_info_num_buffers);\r
262         if (Sweep != 0) *Sweep = result.GetValue(isc_info_sweep_interval);\r
263         if (Sync != 0)\r
264                 *Sync = result.GetValue(isc_info_forced_writes) == 1 ? true : false;\r
265         if (Reserve != 0)\r
266                 *Reserve = result.GetValue(isc_info_no_reserve) == 1 ? false : true;\r
267 }\r
268 \r
269 void DatabaseImpl::Statistics(int* Fetches, int* Marks, int* Reads, int* Writes)\r
270 {\r
271         if (mHandle == 0)\r
272                 throw LogicExceptionImpl("Database::Statistics", _("Database is not connected."));\r
273 \r
274         char items[] = {isc_info_fetches,\r
275                                         isc_info_marks,\r
276                                         isc_info_reads,\r
277                                         isc_info_writes,\r
278                                         isc_info_end};\r
279     IBS status;\r
280         RB result(128);\r
281 \r
282         status.Reset();\r
283         (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items,\r
284                 result.Size(), result.Self());\r
285         if (status.Errors())\r
286                 throw SQLExceptionImpl(status, "Database::Statistics", _("isc_database_info failed"));\r
287 \r
288         if (Fetches != 0) *Fetches = result.GetValue(isc_info_fetches);\r
289         if (Marks != 0) *Marks = result.GetValue(isc_info_marks);\r
290         if (Reads != 0) *Reads = result.GetValue(isc_info_reads);\r
291         if (Writes != 0) *Writes = result.GetValue(isc_info_writes);\r
292 }\r
293 \r
294 void DatabaseImpl::Counts(int* Insert, int* Update, int* Delete, \r
295         int* ReadIdx, int* ReadSeq)\r
296 {\r
297         if (mHandle == 0)\r
298                 throw LogicExceptionImpl("Database::Counts", _("Database is not connected."));\r
299 \r
300         char items[] = {isc_info_insert_count,\r
301                                         isc_info_update_count,\r
302                                         isc_info_delete_count,\r
303                                         isc_info_read_idx_count,\r
304                                         isc_info_read_seq_count,\r
305                                         isc_info_end};\r
306     IBS status;\r
307         RB result(1024);\r
308 \r
309         status.Reset();\r
310         (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items,\r
311                 result.Size(), result.Self());\r
312         if (status.Errors())\r
313                 throw SQLExceptionImpl(status, "Database::Counts", _("isc_database_info failed"));\r
314 \r
315         if (Insert != 0) *Insert = result.GetCountValue(isc_info_insert_count);\r
316         if (Update != 0) *Update = result.GetCountValue(isc_info_update_count);\r
317         if (Delete != 0) *Delete = result.GetCountValue(isc_info_delete_count);\r
318         if (ReadIdx != 0) *ReadIdx = result.GetCountValue(isc_info_read_idx_count);\r
319         if (ReadSeq != 0) *ReadSeq = result.GetCountValue(isc_info_read_seq_count);\r
320 }\r
321 \r
322 void DatabaseImpl::Users(std::vector<std::string>& users)\r
323 {\r
324         if (mHandle == 0)\r
325                 throw LogicExceptionImpl("Database::Users", _("Database is not connected."));\r
326 \r
327         char items[] = {isc_info_user_names,\r
328                                         isc_info_end};\r
329     IBS status;\r
330         RB result(8000);\r
331 \r
332         status.Reset();\r
333         (*gds.Call()->m_database_info)(status.Self(), &mHandle, sizeof(items), items,\r
334                 result.Size(), result.Self());\r
335         if (status.Errors())\r
336         {\r
337                 status.Reset();\r
338                 throw SQLExceptionImpl(status, "Database::Users", _("isc_database_info failed"));\r
339         }\r
340 \r
341         users.clear();\r
342         char* p = result.Self();\r
343         while (*p == isc_info_user_names)\r
344         {\r
345                 p += 3;         // Get to the length byte (there are two undocumented bytes which we skip)\r
346                 int len = (int)(*p);\r
347                 ++p;            // Get to the first char of username\r
348         if (len != 0) users.push_back(std::string().append(p, len));\r
349                 p += len;       // Skip username\r
350     }\r
351         return;\r
352 }\r
353 \r
354 IBPP::IDatabase* DatabaseImpl::AddRef()\r
355 {\r
356         ASSERTION(mRefCount >= 0);\r
357         ++mRefCount;\r
358         return this;\r
359 }\r
360 \r
361 void DatabaseImpl::Release()\r
362 {\r
363         // Release cannot throw, except in DEBUG builds on assertion\r
364         ASSERTION(mRefCount >= 0);\r
365         --mRefCount;\r
366         try { if (mRefCount <= 0) delete this; }\r
367                 catch (...) { }\r
368 }\r
369 \r
370 //      (((((((( OBJECT INTERNAL METHODS ))))))))\r
371 \r
372 void DatabaseImpl::AttachTransactionImpl(TransactionImpl* tr)\r
373 {\r
374         if (tr == 0)\r
375                 throw LogicExceptionImpl("Database::AttachTransaction",\r
376                                         _("Transaction object is null."));\r
377 \r
378         mTransactions.push_back(tr);\r
379 }\r
380 \r
381 void DatabaseImpl::DetachTransactionImpl(TransactionImpl* tr)\r
382 {\r
383         if (tr == 0)\r
384                 throw LogicExceptionImpl("Database::DetachTransaction",\r
385                                 _("ITransaction object is null."));\r
386 \r
387         mTransactions.erase(std::find(mTransactions.begin(), mTransactions.end(), tr));\r
388 }\r
389 \r
390 void DatabaseImpl::AttachStatementImpl(StatementImpl* st)\r
391 {\r
392         if (st == 0)\r
393                 throw LogicExceptionImpl("Database::AttachStatement",\r
394                                         _("Can't attach a null Statement object."));\r
395 \r
396         mStatements.push_back(st);\r
397 }\r
398 \r
399 void DatabaseImpl::DetachStatementImpl(StatementImpl* st)\r
400 {\r
401         if (st == 0)\r
402                 throw LogicExceptionImpl("Database::DetachStatement",\r
403                                 _("Can't detach a null Statement object."));\r
404 \r
405         mStatements.erase(std::find(mStatements.begin(), mStatements.end(), st));\r
406 }\r
407 \r
408 void DatabaseImpl::AttachBlobImpl(BlobImpl* bb)\r
409 {\r
410         if (bb == 0)\r
411                 throw LogicExceptionImpl("Database::AttachBlob",\r
412                                         _("Can't attach a null Blob object."));\r
413 \r
414         mBlobs.push_back(bb);\r
415 }\r
416 \r
417 void DatabaseImpl::DetachBlobImpl(BlobImpl* bb)\r
418 {\r
419         if (bb == 0)\r
420                 throw LogicExceptionImpl("Database::DetachBlob",\r
421                                 _("Can't detach a null Blob object."));\r
422 \r
423         mBlobs.erase(std::find(mBlobs.begin(), mBlobs.end(), bb));\r
424 }\r
425 \r
426 void DatabaseImpl::AttachArrayImpl(ArrayImpl* ar)\r
427 {\r
428         if (ar == 0)\r
429                 throw LogicExceptionImpl("Database::AttachArray",\r
430                                         _("Can't attach a null Array object."));\r
431 \r
432         mArrays.push_back(ar);\r
433 }\r
434 \r
435 void DatabaseImpl::DetachArrayImpl(ArrayImpl* ar)\r
436 {\r
437         if (ar == 0)\r
438                 throw LogicExceptionImpl("Database::DetachArray",\r
439                                 _("Can't detach a null Array object."));\r
440 \r
441         mArrays.erase(std::find(mArrays.begin(), mArrays.end(), ar));\r
442 }\r
443 \r
444 void DatabaseImpl::AttachEventsImpl(EventsImpl* ev)\r
445 {\r
446         if (ev == 0)\r
447                 throw LogicExceptionImpl("Database::AttachEventsImpl",\r
448                                         _("Can't attach a null Events object."));\r
449 \r
450         mEvents.push_back(ev);\r
451 }\r
452 \r
453 void DatabaseImpl::DetachEventsImpl(EventsImpl* ev)\r
454 {\r
455         if (ev == 0)\r
456                 throw LogicExceptionImpl("Database::DetachEventsImpl",\r
457                                 _("Can't detach a null Events object."));\r
458 \r
459         mEvents.erase(std::find(mEvents.begin(), mEvents.end(), ev));\r
460 }\r
461 \r
462 DatabaseImpl::DatabaseImpl(const std::string& ServerName, const std::string& DatabaseName,\r
463                                                    const std::string& UserName, const std::string& UserPassword,\r
464                                                    const std::string& RoleName, const std::string& CharSet,\r
465                                                    const std::string& CreateParams) :\r
466 \r
467         mRefCount(0), mHandle(0),\r
468         mServerName(ServerName), mDatabaseName(DatabaseName),\r
469         mUserName(UserName), mUserPassword(UserPassword), mRoleName(RoleName),\r
470         mCharSet(CharSet), mCreateParams(CreateParams),\r
471         mDialect(3)\r
472 {\r
473 }\r
474 \r
475 DatabaseImpl::~DatabaseImpl()\r
476 {\r
477         try { if (Connected()) Disconnect(); }\r
478                 catch(...) { }\r
479 }\r
480 \r
481 //\r
482 //      EOF\r
483 //\r