@mimersql/node-mimer 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -48
- package/binding.gyp +66 -0
- package/lib/native.js +6 -23
- package/package.json +25 -3
- package/prebuilds/darwin-arm64/@mimersql+node-mimer.node +0 -0
- package/prebuilds/darwin-x64/@mimersql+node-mimer.node +0 -0
- package/prebuilds/linux-arm64/@mimersql+node-mimer.node +0 -0
- package/prebuilds/linux-x64/@mimersql+node-mimer.node +0 -0
- package/prebuilds/win32-x64/@mimersql+node-mimer.node +0 -0
- package/scripts/check-mimer.js +149 -0
- package/scripts/find-mimer-windows.js +179 -0
- package/src/connection.cc +473 -0
- package/src/connection.h +77 -0
- package/src/helpers.cc +440 -0
- package/src/helpers.h +79 -0
- package/src/mimer_addon.cc +49 -0
- package/src/resultset.cc +161 -0
- package/src/resultset.h +74 -0
- package/src/statement.cc +201 -0
- package/src/statement.h +74 -0
- package/lib/find-mimer-library.js +0 -154
- package/lib/koffi-binding.js +0 -970
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mimer Information Technology
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
//
|
|
21
|
+
// See license for more details.
|
|
22
|
+
|
|
23
|
+
#include "connection.h"
|
|
24
|
+
#include "statement.h"
|
|
25
|
+
#include "resultset.h"
|
|
26
|
+
#include "helpers.h"
|
|
27
|
+
#include <sstream>
|
|
28
|
+
#include <cstring>
|
|
29
|
+
#include <cstdlib>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the Connection class and export it to JavaScript
|
|
33
|
+
*/
|
|
34
|
+
Napi::Object MimerConnection::Init(Napi::Env env, Napi::Object exports) {
|
|
35
|
+
Napi::Function func = DefineClass(env, "Connection", {
|
|
36
|
+
InstanceMethod("connect", &MimerConnection::Connect),
|
|
37
|
+
InstanceMethod("close", &MimerConnection::Close),
|
|
38
|
+
InstanceMethod("execute", &MimerConnection::Execute),
|
|
39
|
+
InstanceMethod("beginTransaction", &MimerConnection::BeginTransaction),
|
|
40
|
+
InstanceMethod("commit", &MimerConnection::Commit),
|
|
41
|
+
InstanceMethod("rollback", &MimerConnection::Rollback),
|
|
42
|
+
InstanceMethod("isConnected", &MimerConnection::IsConnected),
|
|
43
|
+
InstanceMethod("prepare", &MimerConnection::Prepare),
|
|
44
|
+
InstanceMethod("executeQuery", &MimerConnection::ExecuteQuery)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
Napi::FunctionReference* constructor = new Napi::FunctionReference();
|
|
48
|
+
*constructor = Napi::Persistent(func);
|
|
49
|
+
env.SetInstanceData(constructor);
|
|
50
|
+
|
|
51
|
+
exports.Set("Connection", func);
|
|
52
|
+
return exports;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Constructor
|
|
57
|
+
*/
|
|
58
|
+
MimerConnection::MimerConnection(const Napi::CallbackInfo& info)
|
|
59
|
+
: Napi::ObjectWrap<MimerConnection>(info), session_(nullptr), connected_(false) {
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Destructor - invalidate open statements and close connection
|
|
64
|
+
*/
|
|
65
|
+
MimerConnection::~MimerConnection() {
|
|
66
|
+
// Invalidate all open result sets before destroying the session
|
|
67
|
+
for (auto* rs : openResultSets_) {
|
|
68
|
+
rs->Invalidate();
|
|
69
|
+
}
|
|
70
|
+
openResultSets_.clear();
|
|
71
|
+
|
|
72
|
+
// Invalidate all open statements before destroying the session
|
|
73
|
+
for (auto* stmt : openStatements_) {
|
|
74
|
+
stmt->Invalidate();
|
|
75
|
+
}
|
|
76
|
+
openStatements_.clear();
|
|
77
|
+
|
|
78
|
+
if (connected_ && session_ != nullptr) {
|
|
79
|
+
MimerEndSession(&session_);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void MimerConnection::RegisterStatement(MimerStmtWrapper* stmt) {
|
|
84
|
+
openStatements_.insert(stmt);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void MimerConnection::UnregisterStatement(MimerStmtWrapper* stmt) {
|
|
88
|
+
openStatements_.erase(stmt);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
void MimerConnection::RegisterResultSet(MimerResultSetWrapper* rs) {
|
|
92
|
+
openResultSets_.insert(rs);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void MimerConnection::UnregisterResultSet(MimerResultSetWrapper* rs) {
|
|
96
|
+
openResultSets_.erase(rs);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Connect to database
|
|
101
|
+
* Arguments: dsn (string), user (string), password (string)
|
|
102
|
+
*/
|
|
103
|
+
Napi::Value MimerConnection::Connect(const Napi::CallbackInfo& info) {
|
|
104
|
+
Napi::Env env = info.Env();
|
|
105
|
+
|
|
106
|
+
if (info.Length() < 3) {
|
|
107
|
+
Napi::TypeError::New(env, "Expected 3 arguments: dsn, user, password")
|
|
108
|
+
.ThrowAsJavaScriptException();
|
|
109
|
+
return env.Undefined();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
|
|
113
|
+
Napi::TypeError::New(env, "All arguments must be strings")
|
|
114
|
+
.ThrowAsJavaScriptException();
|
|
115
|
+
return env.Undefined();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
std::string dsn = info[0].As<Napi::String>().Utf8Value();
|
|
119
|
+
std::string user = info[1].As<Napi::String>().Utf8Value();
|
|
120
|
+
std::string password = info[2].As<Napi::String>().Utf8Value();
|
|
121
|
+
|
|
122
|
+
// Use the UTF-8 variant of MimerBeginSession
|
|
123
|
+
int rc = MimerBeginSession8(dsn.c_str(), user.c_str(), password.c_str(), &session_);
|
|
124
|
+
|
|
125
|
+
if (rc < 0) {
|
|
126
|
+
CheckError(rc, "MimerBeginSession8");
|
|
127
|
+
return env.Undefined();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
connected_ = true;
|
|
131
|
+
return Napi::Boolean::New(env, true);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Close the database connection.
|
|
136
|
+
* Invalidates all open prepared statements first.
|
|
137
|
+
*/
|
|
138
|
+
Napi::Value MimerConnection::Close(const Napi::CallbackInfo& info) {
|
|
139
|
+
Napi::Env env = info.Env();
|
|
140
|
+
|
|
141
|
+
if (!connected_) {
|
|
142
|
+
return Napi::Boolean::New(env, true);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Invalidate all open result sets before closing the session.
|
|
146
|
+
for (auto* rs : openResultSets_) {
|
|
147
|
+
rs->Invalidate();
|
|
148
|
+
}
|
|
149
|
+
openResultSets_.clear();
|
|
150
|
+
|
|
151
|
+
// Invalidate all open statements before closing the session.
|
|
152
|
+
// Invalidate() closes their Mimer handles without trying to
|
|
153
|
+
// unregister from this connection (we clear the set ourselves).
|
|
154
|
+
for (auto* stmt : openStatements_) {
|
|
155
|
+
stmt->Invalidate();
|
|
156
|
+
}
|
|
157
|
+
openStatements_.clear();
|
|
158
|
+
|
|
159
|
+
if (session_ != nullptr) {
|
|
160
|
+
int rc = MimerEndSession(&session_);
|
|
161
|
+
if (rc < 0) {
|
|
162
|
+
CheckError(rc, "MimerEndSession");
|
|
163
|
+
}
|
|
164
|
+
connected_ = false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return Napi::Boolean::New(env, true);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Execute SQL statement
|
|
172
|
+
* Arguments: sql (string), params (optional array)
|
|
173
|
+
* Returns: result object with rows and metadata
|
|
174
|
+
*/
|
|
175
|
+
Napi::Value MimerConnection::Execute(const Napi::CallbackInfo& info) {
|
|
176
|
+
Napi::Env env = info.Env();
|
|
177
|
+
|
|
178
|
+
if (!connected_) {
|
|
179
|
+
Napi::Error::New(env, "Not connected to database")
|
|
180
|
+
.ThrowAsJavaScriptException();
|
|
181
|
+
return env.Undefined();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
185
|
+
Napi::TypeError::New(env, "Expected SQL string as first argument")
|
|
186
|
+
.ThrowAsJavaScriptException();
|
|
187
|
+
return env.Undefined();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
std::string sql = info[0].As<Napi::String>().Utf8Value();
|
|
191
|
+
|
|
192
|
+
// Check for optional params array
|
|
193
|
+
bool hasParams = (info.Length() >= 2 && info[1].IsArray()
|
|
194
|
+
&& info[1].As<Napi::Array>().Length() > 0);
|
|
195
|
+
|
|
196
|
+
// Try to prepare the statement using the UTF-8 variant
|
|
197
|
+
MimerStatement stmt = MIMERNULLHANDLE;
|
|
198
|
+
int rc = MimerBeginStatement8(session_, sql.c_str(), MIMER_FORWARD_ONLY, &stmt);
|
|
199
|
+
|
|
200
|
+
// DDL statements (CREATE, DROP, ALTER, etc.) cannot be prepared.
|
|
201
|
+
// Fall back to direct execution via MimerExecuteStatement8.
|
|
202
|
+
if (rc == MIMER_STATEMENT_CANNOT_BE_PREPARED) {
|
|
203
|
+
Napi::Object result = Napi::Object::New(env);
|
|
204
|
+
rc = MimerExecuteStatement8(session_, sql.c_str());
|
|
205
|
+
if (rc < 0) {
|
|
206
|
+
CheckError(rc, "MimerExecuteStatement8");
|
|
207
|
+
return env.Undefined();
|
|
208
|
+
}
|
|
209
|
+
result.Set("rowCount", Napi::Number::New(env, 0));
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (rc < 0) {
|
|
214
|
+
CheckError(rc, "MimerBeginStatement8");
|
|
215
|
+
return env.Undefined();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Bind parameters if provided
|
|
219
|
+
if (hasParams) {
|
|
220
|
+
Napi::Array params = info[1].As<Napi::Array>();
|
|
221
|
+
BindParameters(env, stmt, params);
|
|
222
|
+
if (env.IsExceptionPending()) {
|
|
223
|
+
MimerEndStatement(&stmt);
|
|
224
|
+
return env.Undefined();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Use MimerColumnCount to determine if this is a SELECT (has result columns)
|
|
229
|
+
int columnCount = MimerColumnCount(stmt);
|
|
230
|
+
bool hasResultSet = (columnCount > 0);
|
|
231
|
+
|
|
232
|
+
Napi::Object result = Napi::Object::New(env);
|
|
233
|
+
|
|
234
|
+
if (hasResultSet) {
|
|
235
|
+
// Build column metadata before fetching rows
|
|
236
|
+
result.Set("fields", BuildFieldsArray(env, stmt, columnCount));
|
|
237
|
+
|
|
238
|
+
// Open cursor for SELECT statements
|
|
239
|
+
rc = MimerOpenCursor(stmt);
|
|
240
|
+
if (rc < 0) {
|
|
241
|
+
CheckError(rc, "MimerOpenCursor");
|
|
242
|
+
MimerEndStatement(&stmt);
|
|
243
|
+
return env.Undefined();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
Napi::Array rows = FetchResults(env, stmt, columnCount);
|
|
247
|
+
result.Set("rows", rows);
|
|
248
|
+
result.Set("rowCount", Napi::Number::New(env, rows.Length()));
|
|
249
|
+
} else {
|
|
250
|
+
// DML statement (INSERT, UPDATE, DELETE)
|
|
251
|
+
rc = MimerExecute(stmt);
|
|
252
|
+
if (rc < 0) {
|
|
253
|
+
CheckError(rc, "MimerExecute");
|
|
254
|
+
MimerEndStatement(&stmt);
|
|
255
|
+
return env.Undefined();
|
|
256
|
+
}
|
|
257
|
+
result.Set("rowCount", Napi::Number::New(env, rc));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Clean up statement
|
|
261
|
+
MimerEndStatement(&stmt);
|
|
262
|
+
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Begin a transaction
|
|
268
|
+
*/
|
|
269
|
+
Napi::Value MimerConnection::BeginTransaction(const Napi::CallbackInfo& info) {
|
|
270
|
+
Napi::Env env = info.Env();
|
|
271
|
+
|
|
272
|
+
if (!connected_) {
|
|
273
|
+
Napi::Error::New(env, "Not connected to database")
|
|
274
|
+
.ThrowAsJavaScriptException();
|
|
275
|
+
return env.Undefined();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Begin an explicit transaction (disables auto-commit until commit/rollback)
|
|
279
|
+
int rc = MimerBeginTransaction(session_, MIMER_TRANS_READWRITE);
|
|
280
|
+
if (rc < 0) {
|
|
281
|
+
CheckError(rc, "MimerBeginTransaction");
|
|
282
|
+
return env.Undefined();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return Napi::Boolean::New(env, true);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Commit current transaction
|
|
290
|
+
*/
|
|
291
|
+
Napi::Value MimerConnection::Commit(const Napi::CallbackInfo& info) {
|
|
292
|
+
Napi::Env env = info.Env();
|
|
293
|
+
|
|
294
|
+
if (!connected_) {
|
|
295
|
+
Napi::Error::New(env, "Not connected to database")
|
|
296
|
+
.ThrowAsJavaScriptException();
|
|
297
|
+
return env.Undefined();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
int rc = MimerEndTransaction(session_, MIMER_COMMIT);
|
|
301
|
+
if (rc < 0) {
|
|
302
|
+
CheckError(rc, "MimerEndTransaction (commit)");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return Napi::Boolean::New(env, true);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Rollback current transaction
|
|
310
|
+
*/
|
|
311
|
+
Napi::Value MimerConnection::Rollback(const Napi::CallbackInfo& info) {
|
|
312
|
+
Napi::Env env = info.Env();
|
|
313
|
+
|
|
314
|
+
if (!connected_) {
|
|
315
|
+
Napi::Error::New(env, "Not connected to database")
|
|
316
|
+
.ThrowAsJavaScriptException();
|
|
317
|
+
return env.Undefined();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
int rc = MimerEndTransaction(session_, MIMER_ROLLBACK);
|
|
321
|
+
if (rc < 0) {
|
|
322
|
+
CheckError(rc, "MimerEndTransaction (rollback)");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return Napi::Boolean::New(env, true);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check if connected
|
|
330
|
+
*/
|
|
331
|
+
Napi::Value MimerConnection::IsConnected(const Napi::CallbackInfo& info) {
|
|
332
|
+
return Napi::Boolean::New(info.Env(), connected_);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Prepare a SQL statement for repeated execution
|
|
337
|
+
* Arguments: sql (string)
|
|
338
|
+
* Returns: MimerStmtWrapper object
|
|
339
|
+
*/
|
|
340
|
+
Napi::Value MimerConnection::Prepare(const Napi::CallbackInfo& info) {
|
|
341
|
+
Napi::Env env = info.Env();
|
|
342
|
+
|
|
343
|
+
if (!connected_) {
|
|
344
|
+
Napi::Error::New(env, "Not connected to database")
|
|
345
|
+
.ThrowAsJavaScriptException();
|
|
346
|
+
return env.Undefined();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
350
|
+
Napi::TypeError::New(env, "Expected SQL string as first argument")
|
|
351
|
+
.ThrowAsJavaScriptException();
|
|
352
|
+
return env.Undefined();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Create a MimerStmtWrapper, passing session and SQL
|
|
356
|
+
Napi::Object stmtObj = MimerStmtWrapper::NewInstance(env, session_, info[0].As<Napi::String>());
|
|
357
|
+
if (env.IsExceptionPending()) {
|
|
358
|
+
return env.Undefined();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Register the statement so we can invalidate it if the connection closes
|
|
362
|
+
MimerStmtWrapper* stmt = MimerStmtWrapper::Unwrap(stmtObj);
|
|
363
|
+
stmt->SetParentConnection(this);
|
|
364
|
+
openStatements_.insert(stmt);
|
|
365
|
+
|
|
366
|
+
return stmtObj;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Execute a SELECT query and return an open cursor (MimerResultSetWrapper).
|
|
371
|
+
* Arguments: sql (string), params (optional array)
|
|
372
|
+
* Returns: MimerResultSetWrapper (native object)
|
|
373
|
+
*/
|
|
374
|
+
Napi::Value MimerConnection::ExecuteQuery(const Napi::CallbackInfo& info) {
|
|
375
|
+
Napi::Env env = info.Env();
|
|
376
|
+
|
|
377
|
+
if (!connected_) {
|
|
378
|
+
Napi::Error::New(env, "Not connected to database")
|
|
379
|
+
.ThrowAsJavaScriptException();
|
|
380
|
+
return env.Undefined();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
384
|
+
Napi::TypeError::New(env, "Expected SQL string as first argument")
|
|
385
|
+
.ThrowAsJavaScriptException();
|
|
386
|
+
return env.Undefined();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
std::string sql = info[0].As<Napi::String>().Utf8Value();
|
|
390
|
+
bool hasParams = (info.Length() >= 2 && info[1].IsArray()
|
|
391
|
+
&& info[1].As<Napi::Array>().Length() > 0);
|
|
392
|
+
|
|
393
|
+
MimerStatement stmt = MIMERNULLHANDLE;
|
|
394
|
+
int rc = MimerBeginStatement8(session_, sql.c_str(), MIMER_FORWARD_ONLY, &stmt);
|
|
395
|
+
|
|
396
|
+
if (rc == MIMER_STATEMENT_CANNOT_BE_PREPARED) {
|
|
397
|
+
Napi::Error::New(env, "queryCursor only supports SELECT statements (DDL cannot be prepared)")
|
|
398
|
+
.ThrowAsJavaScriptException();
|
|
399
|
+
return env.Undefined();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (rc < 0) {
|
|
403
|
+
CheckError(rc, "MimerBeginStatement8");
|
|
404
|
+
return env.Undefined();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Bind parameters if provided
|
|
408
|
+
if (hasParams) {
|
|
409
|
+
Napi::Array params = info[1].As<Napi::Array>();
|
|
410
|
+
BindParameters(env, stmt, params);
|
|
411
|
+
if (env.IsExceptionPending()) {
|
|
412
|
+
MimerEndStatement(&stmt);
|
|
413
|
+
return env.Undefined();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
int columnCount = MimerColumnCount(stmt);
|
|
418
|
+
if (columnCount <= 0) {
|
|
419
|
+
MimerEndStatement(&stmt);
|
|
420
|
+
Napi::Error::New(env, "queryCursor only supports SELECT statements (DML has no result columns)")
|
|
421
|
+
.ThrowAsJavaScriptException();
|
|
422
|
+
return env.Undefined();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Open cursor
|
|
426
|
+
rc = MimerOpenCursor(stmt);
|
|
427
|
+
if (rc < 0) {
|
|
428
|
+
CheckError(rc, "MimerOpenCursor");
|
|
429
|
+
MimerEndStatement(&stmt);
|
|
430
|
+
return env.Undefined();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Create ResultSet wrapper — transfers ownership of stmt
|
|
434
|
+
Napi::Object rsObj = MimerResultSetWrapper::NewInstance(env, stmt, columnCount);
|
|
435
|
+
if (env.IsExceptionPending()) {
|
|
436
|
+
MimerCloseCursor(stmt);
|
|
437
|
+
MimerEndStatement(&stmt);
|
|
438
|
+
return env.Undefined();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Register for lifecycle tracking
|
|
442
|
+
MimerResultSetWrapper* rs = MimerResultSetWrapper::Unwrap(rsObj);
|
|
443
|
+
rs->SetParentConnection(this);
|
|
444
|
+
openResultSets_.insert(rs);
|
|
445
|
+
|
|
446
|
+
return rsObj;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Check for errors and throw structured JavaScript exception if error occurred
|
|
451
|
+
*/
|
|
452
|
+
void MimerConnection::CheckError(int rc, const std::string& operation) {
|
|
453
|
+
if (rc < 0) {
|
|
454
|
+
std::string detail = GetErrorMessage();
|
|
455
|
+
ThrowMimerError(Env(), rc, operation, detail);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get detailed error message from Mimer
|
|
461
|
+
*/
|
|
462
|
+
std::string MimerConnection::GetErrorMessage() {
|
|
463
|
+
if (session_ != nullptr) {
|
|
464
|
+
int32_t errCode;
|
|
465
|
+
char buffer[1024];
|
|
466
|
+
// MimerGetError8 signature: (void*, int32_t*, char*, size_t)
|
|
467
|
+
int rc = MimerGetError8(session_, &errCode, buffer, sizeof(buffer));
|
|
468
|
+
if (rc > 0) {
|
|
469
|
+
return std::string(buffer);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return "Unknown error";
|
|
473
|
+
}
|
package/src/connection.h
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mimer Information Technology
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
//
|
|
21
|
+
// See license for more details.
|
|
22
|
+
|
|
23
|
+
#ifndef MIMER_CONNECTION_H
|
|
24
|
+
#define MIMER_CONNECTION_H
|
|
25
|
+
|
|
26
|
+
#include <napi.h>
|
|
27
|
+
#include <mimerapi.h>
|
|
28
|
+
#include <string>
|
|
29
|
+
#include <set>
|
|
30
|
+
|
|
31
|
+
class MimerStmtWrapper; // forward declaration
|
|
32
|
+
class MimerResultSetWrapper; // forward declaration
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* MimerConnection wraps a Mimer database connection
|
|
36
|
+
* Corresponds to MimerAPI session handle
|
|
37
|
+
*/
|
|
38
|
+
class MimerConnection : public Napi::ObjectWrap<MimerConnection> {
|
|
39
|
+
public:
|
|
40
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
41
|
+
MimerConnection(const Napi::CallbackInfo& info);
|
|
42
|
+
~MimerConnection();
|
|
43
|
+
|
|
44
|
+
// Statement tracking — called by MimerStmtWrapper
|
|
45
|
+
void RegisterStatement(MimerStmtWrapper* stmt);
|
|
46
|
+
void UnregisterStatement(MimerStmtWrapper* stmt);
|
|
47
|
+
|
|
48
|
+
// Result set tracking — called by MimerResultSetWrapper
|
|
49
|
+
void RegisterResultSet(MimerResultSetWrapper* rs);
|
|
50
|
+
void UnregisterResultSet(MimerResultSetWrapper* rs);
|
|
51
|
+
|
|
52
|
+
private:
|
|
53
|
+
// Connection handle
|
|
54
|
+
MimerSession session_;
|
|
55
|
+
bool connected_;
|
|
56
|
+
|
|
57
|
+
// Open statements and result sets created by this connection
|
|
58
|
+
std::set<MimerStmtWrapper*> openStatements_;
|
|
59
|
+
std::set<MimerResultSetWrapper*> openResultSets_;
|
|
60
|
+
|
|
61
|
+
// Methods exposed to JavaScript
|
|
62
|
+
Napi::Value Connect(const Napi::CallbackInfo& info);
|
|
63
|
+
Napi::Value Close(const Napi::CallbackInfo& info);
|
|
64
|
+
Napi::Value Execute(const Napi::CallbackInfo& info);
|
|
65
|
+
Napi::Value BeginTransaction(const Napi::CallbackInfo& info);
|
|
66
|
+
Napi::Value Commit(const Napi::CallbackInfo& info);
|
|
67
|
+
Napi::Value Rollback(const Napi::CallbackInfo& info);
|
|
68
|
+
Napi::Value IsConnected(const Napi::CallbackInfo& info);
|
|
69
|
+
Napi::Value Prepare(const Napi::CallbackInfo& info);
|
|
70
|
+
Napi::Value ExecuteQuery(const Napi::CallbackInfo& info);
|
|
71
|
+
|
|
72
|
+
// Helper methods
|
|
73
|
+
void CheckError(int rc, const std::string& operation);
|
|
74
|
+
std::string GetErrorMessage();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
#endif // MIMER_CONNECTION_H
|