Skip to content

Commit 8be6ce5

Browse files
authored
Store error vector and SQL state in DatabaseException (asfernandes#27)
1 parent ea07414 commit 8be6ce5

4 files changed

Lines changed: 219 additions & 5 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@
2424
### Tests
2525
- Run the Boost.Test suite with `ctest --preset default --verbose`.
2626
- Boost.Test options can be used with environments variables like `BOOST_TEST_LOG_LEVEL=all`.
27+
- Prefer fewer, comprehensive test cases over many small single-assertion tests. A single test case should verify
28+
all related properties together (e.g., check the full error vector structure in one test instead of separate tests
29+
for each field).

src/fb-cpp/Exception.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "Exception.h"
2626
#include "Client.h"
2727
#include <string>
28+
#include <vector>
2829
#include <cassert>
2930

3031
using namespace fbcpp;
@@ -84,3 +85,91 @@ std::string DatabaseException::buildMessage(Client& client, const std::intptr_t*
8485

8586
return message;
8687
}
88+
89+
void DatabaseException::copyErrorVector(const std::intptr_t* statusVector)
90+
{
91+
if (!statusVector)
92+
return;
93+
94+
const auto* p = statusVector;
95+
96+
while (*p != isc_arg_end)
97+
{
98+
const auto argType = *p++;
99+
100+
switch (argType)
101+
{
102+
case isc_arg_gds:
103+
case isc_arg_number:
104+
errorVector.push_back(argType);
105+
errorVector.push_back(*p++);
106+
break;
107+
108+
case isc_arg_string:
109+
case isc_arg_interpreted:
110+
case isc_arg_sql_state:
111+
errorVector.push_back(argType);
112+
errorStrings.emplace_back(reinterpret_cast<const char*>(*p++));
113+
errorVector.push_back(0); // placeholder for string pointer
114+
break;
115+
116+
case isc_arg_cstring:
117+
{
118+
const auto len = static_cast<size_t>(*p++);
119+
const auto str = reinterpret_cast<const char*>(*p++);
120+
errorVector.push_back(isc_arg_string);
121+
errorStrings.emplace_back(str, len);
122+
errorVector.push_back(0); // placeholder for string pointer
123+
break;
124+
}
125+
126+
default:
127+
errorVector.push_back(argType);
128+
errorVector.push_back(*p++);
129+
break;
130+
}
131+
}
132+
133+
errorVector.push_back(isc_arg_end);
134+
135+
fixupStringPointers();
136+
}
137+
138+
void DatabaseException::fixupStringPointers()
139+
{
140+
size_t strIdx = 0;
141+
size_t i = 0;
142+
143+
while (i < errorVector.size() && errorVector[i] != isc_arg_end)
144+
{
145+
const auto argType = errorVector[i];
146+
147+
if (argType == isc_arg_string || argType == isc_arg_interpreted || argType == isc_arg_sql_state)
148+
errorVector[i + 1] = reinterpret_cast<std::intptr_t>(errorStrings[strIdx++].c_str());
149+
150+
i += 2;
151+
}
152+
}
153+
154+
std::string DatabaseException::extractSqlState(const std::intptr_t* statusVector)
155+
{
156+
if (!statusVector)
157+
return {};
158+
159+
const auto* p = statusVector;
160+
161+
while (*p != isc_arg_end)
162+
{
163+
const auto argType = *p++;
164+
165+
if (argType == isc_arg_sql_state)
166+
return reinterpret_cast<const char*>(*p);
167+
168+
if (argType == isc_arg_cstring)
169+
p += 2;
170+
else
171+
p++;
172+
}
173+
174+
return {};
175+
}

src/fb-cpp/Exception.h

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "fb-api.h"
2929
#include <stdexcept>
3030
#include <string>
31+
#include <vector>
3132
#include <cstdint>
3233

3334

@@ -199,18 +200,69 @@ namespace fbcpp
199200
class DatabaseException final : public FbCppException
200201
{
201202
public:
202-
using FbCppException::FbCppException;
203-
204203
///
205204
/// Constructs a DatabaseException from a Firebird status vector.
206205
///
207-
explicit DatabaseException(Client& client, const std::intptr_t* status)
208-
: FbCppException{buildMessage(client, status)}
206+
explicit DatabaseException(Client& client, const std::intptr_t* statusVector)
207+
: FbCppException{buildMessage(client, statusVector)},
208+
sqlState{extractSqlState(statusVector)}
209+
{
210+
copyErrorVector(statusVector);
211+
}
212+
213+
DatabaseException(const DatabaseException& other)
214+
: FbCppException{static_cast<const FbCppException&>(other)},
215+
errorVector{other.errorVector},
216+
errorStrings{other.errorStrings},
217+
sqlState{other.sqlState}
218+
{
219+
fixupStringPointers();
220+
}
221+
222+
DatabaseException(DatabaseException&&) = default;
223+
224+
DatabaseException& operator=(const DatabaseException&) = delete;
225+
DatabaseException& operator=(DatabaseException&&) = delete;
226+
227+
///
228+
/// Returns the Firebird error vector.
229+
/// The vector is terminated by isc_arg_end.
230+
///
231+
const std::vector<std::intptr_t>& getErrors() const noexcept
232+
{
233+
return errorVector;
234+
}
235+
236+
///
237+
/// Returns the primary ISC error code (first isc_arg_gds value), or 0 if none.
238+
///
239+
std::intptr_t getErrorCode() const noexcept
240+
{
241+
if (errorVector.size() >= 2 && errorVector[0] == isc_arg_gds)
242+
return errorVector[1];
243+
return 0;
244+
}
245+
246+
///
247+
/// Returns the SQL state string (e.g. "42000") if present in the original status vector,
248+
/// or empty otherwise.
249+
///
250+
const std::string& getSqlState() const noexcept
209251
{
252+
return sqlState;
210253
}
211254

212255
private:
213-
static std::string buildMessage(Client& client, const std::intptr_t* status);
256+
static std::string buildMessage(Client& client, const std::intptr_t* statusVector);
257+
static std::string extractSqlState(const std::intptr_t* statusVector);
258+
259+
void copyErrorVector(const std::intptr_t* statusVector);
260+
void fixupStringPointers();
261+
262+
private:
263+
std::vector<std::intptr_t> errorVector;
264+
std::vector<std::string> errorStrings;
265+
std::string sqlState;
214266
};
215267
} // namespace fbcpp
216268

src/test/Exception.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2026 F.D.Castel
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
#include "TestUtil.h"
26+
#include "fb-cpp/Exception.h"
27+
#include "fb-cpp/Statement.h"
28+
#include "fb-cpp/Transaction.h"
29+
#include <string>
30+
31+
32+
BOOST_AUTO_TEST_SUITE(DatabaseExceptionSuite)
33+
34+
BOOST_AUTO_TEST_CASE(syntaxErrorExceptionProperties)
35+
{
36+
const auto database = getTempFile("Exception-syntaxErrorExceptionProperties.fdb");
37+
38+
Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)};
39+
FbDropDatabase attachmentDrop{attachment};
40+
41+
Transaction transaction{attachment};
42+
43+
try
44+
{
45+
Statement stmt{attachment, transaction, "INVALID SQL STATEMENT !!!"};
46+
BOOST_FAIL("Expected DatabaseException was not thrown");
47+
}
48+
catch (const DatabaseException& ex)
49+
{
50+
// what() should contain a formatted error message
51+
std::string message = ex.what();
52+
BOOST_CHECK(!message.empty());
53+
54+
// Error vector should start with isc_arg_gds and end with isc_arg_end
55+
const auto& errors = ex.getErrors();
56+
BOOST_REQUIRE(errors.size() >= 2);
57+
BOOST_CHECK_EQUAL(errors[0], isc_arg_gds);
58+
BOOST_CHECK_EQUAL(errors.back(), isc_arg_end);
59+
60+
// getErrorCode() should return the first GDS code
61+
BOOST_CHECK_NE(ex.getErrorCode(), 0);
62+
BOOST_CHECK_EQUAL(ex.getErrorCode(), errors[1]);
63+
64+
// SQL state, when present, should be a 5-character string
65+
if (!ex.getSqlState().empty())
66+
BOOST_CHECK_EQUAL(ex.getSqlState().size(), 5u);
67+
}
68+
}
69+
70+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)