@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.
@@ -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
+ }
@@ -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