sedna 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/CHANGES +18 -0
  2. data/README +35 -13
  3. data/Rakefile +3 -3
  4. data/ext/extconf.rb +4 -9
  5. data/ext/sedna.c +429 -118
  6. data/test/sedna_test.rb +447 -317
  7. metadata +2 -2
data/CHANGES CHANGED
@@ -1,3 +1,21 @@
1
+ === 0.4.0
2
+
3
+ * Released on January 4th, 2009.
4
+ * Added Sedna#reset, which closes the current connection and reconnects.
5
+ * Added Sedna#connected?, which returns whether or not the current connection
6
+ is still active.
7
+ * Sedna#transaction can now also be used without a block. Use the newly added
8
+ methods Sedna#commit and Sedna#rollback to finish such a declarative
9
+ transaction. Note that this style is not recommended if a transaction can
10
+ be wrapped in a block.
11
+ * Sedna.connect no longer blocks other threads in Ruby 1.9.1+. Use Sedna.blocking?
12
+ to discover if support for non-blocking behaviour is enabled.
13
+ * Strings in the result Array of Sedna#execute are now in UTF-8 encoding in
14
+ Ruby 1.9, rather than binary.
15
+ * Fully tested in Ruby 1.8.5+ (including the latest version, 1.9.1 RC1).
16
+ * Now also compiles with Ruby 1.9.0.x, but non-blocking behaviour is disabled
17
+ for these versions.
18
+
1
19
  === 0.3.0
2
20
 
3
21
  * Released on December 21st, 2008.
data/README CHANGED
@@ -16,7 +16,7 @@ at http://modis.ispras.ru/sedna
16
16
 
17
17
  Author: Rolf Timmermans (r.timmermans <i>at</i> voormedia.com)
18
18
 
19
- Copyright 2008 Voormedia B.V.
19
+ Copyright 2008, 2009 Voormedia B.V.
20
20
 
21
21
  Licensed under the Apache License, Version 2.0 (the "License");
22
22
  you may not use this file except in compliance with the License.
@@ -32,9 +32,8 @@ limitations under the License.
32
32
 
33
33
  === Current version
34
34
 
35
- The current version of this library is 0.3.0. This release is a <b>beta version</b>.
36
- The API may change significantly between minor versions. For a complete overview
37
- all recent and previous changes, see CHANGES.
35
+ The current version of this library is 0.4.0. This release is a <b>beta version</b>.
36
+ For a complete overview all recent and previous changes, see CHANGES.
38
37
 
39
38
  === Requirements
40
39
 
@@ -43,8 +42,7 @@ header files of the C driver. They are shipped and installed as part of the bina
43
42
  distribution of \Sedna. When installing, choose either <tt>/usr/local/sedna</tt> or
44
43
  <tt>/opt/sedna</tt> as target locations.
45
44
 
46
- The library has been tested with Ruby 1.8.5 and above, including Ruby 1.9.1
47
- (preview 2), but excluding Ruby 1.9.0.
45
+ The library has been tested with Ruby 1.8.5 and above, including Ruby 1.9.
48
46
 
49
47
  === Installation
50
48
 
@@ -69,7 +67,8 @@ using it.
69
67
 
70
68
  To start querying a database, create a new connection with the Sedna.connect
71
69
  method. When a block is given, the Sedna connection object will be returned
72
- which can be used to execute database statements.
70
+ which can be used to execute database statements. See the documentation of the
71
+ Sedna class for more details.
73
72
 
74
73
  connection_details = {
75
74
  :database => "my_db",
@@ -77,13 +76,36 @@ which can be used to execute database statements.
77
76
  :username => "SYSTEM",
78
77
  :password => "MANAGER",
79
78
  }
80
- Sedna.connect(connection_details) do |sedna|
79
+
80
+ Sedna.connect connection_details do |sedna|
81
81
  # Start querying the database.
82
- sedna.execute("create document 'mydoc'") #=> nil
83
- sedna.execute("update insert <msg>Hello world!</msg> into doc('mydoc')") #=> nil
84
- sedna.execute("doc('mydoc')/msg/text()") #=> ["Hello world!"]
82
+ sedna.execute "create document 'my_doc'" #=> nil
83
+ sedna.execute "update insert <msg>Hello world!</msg> into doc('my_doc')" #=> nil
84
+ sedna.execute "doc('my_doc')/msg/text()" #=> ["Hello world!"]
85
85
  # The connection is closed automatically for us.
86
86
  end
87
87
 
88
- See the documentation of the Sedna class -- the methods Sedna.connect and
89
- Sedna#execute in particular -- for more details.
88
+ Use Sedna#load_document to load a document into the database from a string or
89
+ file.
90
+
91
+ sedna.load_document "<msg>Hello world!</msg>", "my_doc", "my_collection"
92
+ sedna.execute "doc('my_doc', 'my_collection')/msg/text()" #=> ["Hello world!"]
93
+
94
+ File.open "document.xml" do |file|
95
+ sedna.load_document file, "my_doc2", "my_collection"
96
+ end
97
+
98
+ Use Sedna#transaction to wrap multiple database statements in a transaction.
99
+ This ensures that either all or none of these statements are executed. If
100
+ something goes wrong halfway, the previously executed statements that are part
101
+ of the transaction will be rolled back.
102
+
103
+ sedna.transaction do
104
+ amount = 100
105
+ sedna.execute "update replace $balance in doc('my_account')/balance
106
+ with <balance>{$balance - #{amount}}</balance>"
107
+ sedna.execute "update replace $balance in doc('your_account')/balance
108
+ with <balance>{$balance + #{amount}}</balance>"
109
+ # If the second statement fails, the first will be rolled back. Only if
110
+ # both statements succeed will the changes become permanent.
111
+ end
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2008 Voormedia B.V.
1
+ # Copyright 2008, 2009 Voormedia B.V.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ RDOC_FILES = FileList["[A-Z][A-Z]*", "ext/**/*.c"].to_a
30
30
  task :default => [:rebuild, :test]
31
31
 
32
32
  task :multi do
33
- exec "export EXCLUDED_VERSIONS=v1_9_0 && multiruby -S rake"
33
+ exec "multiruby -S rake"
34
34
  end
35
35
 
36
36
  desc "Build the Ruby extension"
@@ -57,7 +57,7 @@ end
57
57
 
58
58
  gem_spec = Gem::Specification.new do |s|
59
59
  s.name = "sedna"
60
- s.version = "0.3.0"
60
+ s.version = "0.4.0"
61
61
 
62
62
  s.summary = "Sedna XML DBMS client library."
63
63
  s.description = %{Ruby extension that provides a client library for the Sedna XML DBMS, making use of the official C driver of the Sedna project.}
data/ext/extconf.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2008 Voormedia B.V.
1
+ # Copyright 2008, 2009 Voormedia B.V.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -68,13 +68,8 @@ passing the -fPIC option to gcc.
68
68
  end
69
69
  end
70
70
 
71
- if have_func("rb_thread_blocking_region") and !have_func("rb_mutex_synchronize")
72
- $stderr.write %{==============================================================================
73
- This Ruby version incorrectly defines Mutex#synchronize. Please upgrade to a
74
- newer version of Ruby (1.9.1+).
75
- ==============================================================================
76
- }
77
- exit 5
78
- end
71
+ have_func "rb_thread_blocking_region"
72
+ have_func "rb_mutex_synchronize"
73
+ have_func "rb_enc_str_buf_cat"
79
74
 
80
75
  create_makefile "sedna"
data/ext/sedna.c CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2008 Voormedia B.V.
2
+ * Copyright 2008, 2009 Voormedia B.V.
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -49,31 +49,73 @@
49
49
  #define PROTO_TO_STRING(s) PROTO_STRINGIFY(s)
50
50
  #define PROTOCOL_VERSION PROTO_TO_STRING(SE_CURRENT_SOCKET_PROTOCOL_VERSION_MAJOR) "." PROTO_TO_STRING(SE_CURRENT_SOCKET_PROTOCOL_VERSION_MINOR)
51
51
 
52
- // Default connection arguments
52
+ // Default connection arguments.
53
53
  #define DEFAULT_HOST "localhost"
54
- #define DEFAULT_DATABASE "test"
54
+ #define DEFAULT_DB "test"
55
55
  #define DEFAULT_USER "SYSTEM"
56
- #define DEFAULT_PASSWORD "MANAGER"
56
+ #define DEFAULT_PW "MANAGER"
57
+
58
+ // Instance variable names.
59
+ #define IV_HOST "@host"
60
+ #define IV_DB "@database"
61
+ #define IV_USER "@username"
62
+ #define IV_PW "@password"
63
+ #define IV_AUTOCOMMIT "@autocommit"
64
+ #define IV_MUTEX "@mutex"
57
65
 
58
66
  // Define a shorthand for the common SednaConnection structure.
59
67
  typedef struct SednaConnection SC;
60
68
 
61
- #ifdef HAVE_RB_THREAD_BLOCKING_REGION
62
- #define NON_BLOCKING
63
- #define SEDNA_BLOCKING Qfalse
69
+ // Define a struct for database queries.
70
+ struct SednaQuery {
71
+ void *conn;
72
+ void *query;
73
+ };
74
+ typedef struct SednaQuery SQ;
75
+
76
+ // Define a struct for database connection arguments.
77
+ struct SednaConnArgs {
78
+ void *conn;
79
+ void *host;
80
+ void *db;
81
+ void *user;
82
+ void *pw;
83
+ };
84
+ typedef struct SednaConnArgs SCA;
85
+
86
+ // Always create UTF-8 strings with STR_CAT, if supported (Ruby 1.9).
87
+ #ifdef HAVE_RB_ENC_STR_BUF_CAT
88
+ #ifndef RUBY_ENCODING_H
89
+ #include "ruby/encoding.h"
90
+ #endif
91
+ #define STR_CAT(str, buf, bytes) rb_enc_str_buf_cat(str, buf, bytes, rb_utf8_encoding())
92
+ #else
93
+ #define STR_CAT(str, buf, bytes) rb_str_buf_cat(str, buf, bytes)
94
+ #endif
64
95
 
65
- struct SednaQuery {
66
- void *conn;
67
- void *query;
68
- };
69
- typedef struct SednaQuery SQ;
96
+ // Define whether or not non-blocking behaviour will be built in.
97
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION) && defined(HAVE_RB_MUTEX_SYNCHRONIZE)
98
+ #define NON_BLOCKING 1
99
+ #define SEDNA_BLOCKING Qfalse
70
100
  #else
71
101
  #define SEDNA_BLOCKING Qtrue
72
102
  #endif
73
103
 
104
+ // Define execute and connect functions.
105
+ #ifdef NON_BLOCKING
106
+ // Non-blocking variants for >= 1.9.
107
+ // Synchronize across threads using this instance and execute.
108
+ #define SEDNA_CONNECT(self, c) rb_mutex_synchronize(rb_iv_get(self, IV_MUTEX), (void*)sedna_non_blocking_connect, (VALUE)c);
109
+ #define SEDNA_EXECUTE(self, q) rb_mutex_synchronize(rb_iv_get(self, IV_MUTEX), (void*)sedna_non_blocking_execute, (VALUE)q);
110
+ #else
111
+ // Blocking variants for < 1.9.
112
+ #define SEDNA_CONNECT(self, c) sedna_blocking_connect(c);
113
+ #define SEDNA_EXECUTE(self, q) sedna_blocking_execute(q);
114
+ #endif
115
+
74
116
  // Ruby classes.
75
117
  static VALUE cSedna;
76
- //static VALUE cSednaSet; //Unused so far.
118
+ //static VALUE cSednaSet; // Stick to Array for result sets.
77
119
  static VALUE cSednaException;
78
120
  static VALUE cSednaAuthError;
79
121
  static VALUE cSednaConnError;
@@ -146,18 +188,44 @@ static void sedna_free(SC *conn)
146
188
  static void sedna_mark(SC *conn)
147
189
  { /* Unused. */ }
148
190
 
149
- #ifdef NON_BLOCKING
150
- static int sedna_blocking_execute(SQ *q)
191
+ // Connect to the server.
192
+ static int sedna_blocking_connect(SCA *c)
151
193
  {
152
- return SEexecute(q->conn, q->query);
194
+ return SEconnect(c->conn, c->host, c->db, c->user, c->pw);
153
195
  }
154
196
 
155
- static int sedna_execute(SQ *q)
197
+ #ifdef NON_BLOCKING
198
+ static int sedna_non_blocking_connect(SCA *c)
156
199
  {
157
- return rb_thread_blocking_region((void*)sedna_blocking_execute, q, RUBY_UBF_IO, NULL);
200
+ return rb_thread_blocking_region((void*)sedna_blocking_connect, c, RUBY_UBF_IO, NULL);
158
201
  }
159
202
  #endif
160
203
 
204
+ static void sedna_connect(VALUE self, SCA *c)
205
+ {
206
+ int res = SEDNA_CONNECT(self, c);
207
+ if(res != SEDNA_SESSION_OPEN) {
208
+ // We have to set the connection status to closed explicitly here,
209
+ // because the GC routine sedna_free() will test for this status, but
210
+ // the socket is already closed by SEconnect(). If we do not change the
211
+ // status, sedna_free() will attempt to close the connection again by
212
+ // calling SEclose(), which will definitely lead to unpredictable
213
+ // results.
214
+ ((SC*)c->conn)->isConnectionOk = SEDNA_CONNECTION_CLOSED;
215
+ sedna_err(c->conn, res);
216
+ }
217
+ }
218
+
219
+ // Close the connection to the server.
220
+ static void sedna_close(SC *conn)
221
+ {
222
+ int res;
223
+ if(SEconnectionStatus(conn) != SEDNA_CONNECTION_CLOSED) {
224
+ res = SEclose(conn);
225
+ VERIFY_RES(SEDNA_SESSION_CLOSED, res, conn);
226
+ }
227
+ }
228
+
161
229
  // Read one record completely and return it as a Ruby String object.
162
230
  static VALUE sedna_read(SC *conn, int strip_n)
163
231
  {
@@ -177,11 +245,11 @@ static VALUE sedna_read(SC *conn, int strip_n)
177
245
  // except the first. Strip them! This a known issue in the
178
246
  // network protocol and serialization mechanism.
179
247
  // See: http://sourceforge.net/mailarchive/forum.php?thread_name=3034886f0812030132v3bbd8e2erd86480d3dc640664%40mail.gmail.com&forum_name=sedna-discussion
180
- rb_str_buf_cat(str, buffer + 1, bytes_read - 1);
248
+ STR_CAT(str, buffer + 1, bytes_read - 1);
181
249
  // Do not strip newlines from subsequent buffer reads.
182
250
  strip_n = 0;
183
251
  } else {
184
- rb_str_buf_cat(str, buffer, bytes_read);
252
+ STR_CAT(str, buffer, bytes_read);
185
253
  }
186
254
  }
187
255
  }
@@ -194,6 +262,7 @@ static VALUE sedna_read(SC *conn, int strip_n)
194
262
  static VALUE sedna_get_results(SC *conn)
195
263
  {
196
264
  int res, strip_n = 0;
265
+ // Can be replaced with: rb_funcall(cSednaSet, rb_intern("new"), 0, NULL);
197
266
  VALUE set = rb_ary_new();
198
267
 
199
268
  while((res = SEnext(conn)) != SEDNA_RESULT_END) {
@@ -207,6 +276,18 @@ static VALUE sedna_get_results(SC *conn)
207
276
  return set;
208
277
  }
209
278
 
279
+ static int sedna_blocking_execute(SQ *q)
280
+ {
281
+ return SEexecute(q->conn, q->query);
282
+ }
283
+
284
+ #ifdef NON_BLOCKING
285
+ static int sedna_non_blocking_execute(SQ *q)
286
+ {
287
+ return rb_thread_blocking_region((void*)sedna_blocking_execute, q, RUBY_UBF_IO, NULL);
288
+ }
289
+ #endif
290
+
210
291
  // Enable or disable autocommit.
211
292
  static void sedna_autocommit(SC *conn, int value)
212
293
  {
@@ -215,24 +296,73 @@ static void sedna_autocommit(SC *conn, int value)
215
296
  }
216
297
 
217
298
  // Begin a transaction.
218
- static void sedna_tr_begin(SC *conn)
299
+ static void sedna_begin(SC *conn)
219
300
  {
220
- int res = SEbegin(conn);
301
+ int res;
302
+
303
+ // Disable autocommit mode.
304
+ SEDNA_AUTOCOMMIT_DISABLE(conn);
305
+
306
+ // Start the transaction.
307
+ res = SEbegin(conn);
221
308
  VERIFY_RES(SEDNA_BEGIN_TRANSACTION_SUCCEEDED, res, conn);
222
309
  }
223
310
 
224
311
  // Commit a transaction.
225
312
  static void sedna_tr_commit(SC *conn)
226
313
  {
227
- int res = SEcommit(conn);
228
- VERIFY_RES(SEDNA_COMMIT_TRANSACTION_SUCCEEDED, res, conn);
314
+ int res;
315
+
316
+ if(SEtransactionStatus(conn) == SEDNA_TRANSACTION_ACTIVE) {
317
+ // Commit if a transaction was in progres.
318
+ res = SEcommit(conn);
319
+ VERIFY_RES(SEDNA_COMMIT_TRANSACTION_SUCCEEDED, res, conn);
320
+ } else {
321
+ // If there is no current transaction, raise an error.
322
+ rb_raise(cSednaTrnError, "No transaction in progress, cannot commit.");
323
+ }
324
+ }
325
+
326
+ // Commit a transaction and reset autocommit mode.
327
+ static void sedna_commit(SC *conn, VALUE self)
328
+ {
329
+ int status;
330
+
331
+ // Roll back.
332
+ rb_protect((void*)sedna_tr_commit, (VALUE)conn, &status);
333
+
334
+ // Turn autocommit back on if it was set.
335
+ SWITCH_SEDNA_AUTOCOMMIT(conn, rb_iv_get(self, IV_AUTOCOMMIT));
336
+
337
+ // Re-raise any exception.
338
+ if(status != 0) rb_jump_tag(status);
229
339
  }
230
340
 
231
341
  // Rollback a transaction.
232
342
  static void sedna_tr_rollback(SC *conn)
233
343
  {
234
- int res = SErollback(conn);
235
- VERIFY_RES(SEDNA_ROLLBACK_TRANSACTION_SUCCEEDED, res, conn);
344
+ int res;
345
+
346
+ // Roll back if a transaction was in progress.
347
+ if(SEtransactionStatus(conn) == SEDNA_TRANSACTION_ACTIVE) {
348
+ res = SErollback(conn);
349
+ VERIFY_RES(SEDNA_ROLLBACK_TRANSACTION_SUCCEEDED, res, conn);
350
+ }
351
+ }
352
+
353
+ // Rollback a transaction and reset autocommit mode.
354
+ static void sedna_rollback(SC *conn, VALUE self)
355
+ {
356
+ int status;
357
+
358
+ // Roll back.
359
+ rb_protect((void*)sedna_tr_rollback, (VALUE)conn, &status);
360
+
361
+ // Turn autocommit back on if it was set.
362
+ SWITCH_SEDNA_AUTOCOMMIT(conn, rb_iv_get(self, IV_AUTOCOMMIT));
363
+
364
+ // Re-raise any exception.
365
+ if(status != 0) rb_jump_tag(status);
236
366
  }
237
367
 
238
368
 
@@ -251,45 +381,47 @@ static VALUE cSedna_s_new(VALUE klass)
251
381
 
252
382
  /* :nodoc:
253
383
  *
254
- * Initialize a new instance of Sedna.
384
+ * Initialize a new instance of Sedna. Undocumented, because Sedna.connect should
385
+ * be used instead.
255
386
  */
256
387
  static VALUE cSedna_initialize(VALUE self, VALUE options)
257
388
  {
258
- int res;
259
389
  VALUE host_k, db_k, user_k, pw_k, host_v, db_v, user_v, pw_v;
260
390
  char *host, *db, *user, *pw;
261
- SC *conn = sedna_struct(self);
262
391
 
392
+ // Ensure the argument is a Hash.
263
393
  Check_Type(options, T_HASH);
394
+
395
+ // Store the symbols of the valid hash keys.
264
396
  host_k = ID2SYM(rb_intern("host"));
265
397
  db_k = ID2SYM(rb_intern("database"));
266
398
  user_k = ID2SYM(rb_intern("username"));
267
399
  pw_k = ID2SYM(rb_intern("password"));
268
400
 
401
+ // Get the connection details or set them to the default values if not given.
269
402
  if(NIL_P(host_v = rb_hash_aref(options, host_k))) host = DEFAULT_HOST; else host = STR2CSTR(host_v);
270
- if(NIL_P(db_v = rb_hash_aref(options, db_k))) db = DEFAULT_DATABASE; else db = STR2CSTR(db_v);
403
+ if(NIL_P(db_v = rb_hash_aref(options, db_k))) db = DEFAULT_DB; else db = STR2CSTR(db_v);
271
404
  if(NIL_P(user_v = rb_hash_aref(options, user_k))) user = DEFAULT_USER; else user = STR2CSTR(user_v);
272
- if(NIL_P(pw_v = rb_hash_aref(options, pw_k))) pw = DEFAULT_PASSWORD; else pw = STR2CSTR(pw_v);
273
-
274
- res = SEconnect(conn, host, db, user, pw);
275
- if(res != SEDNA_SESSION_OPEN) {
276
- // We have to set the connection status to closed explicitly here,
277
- // because the GC routine sedna_free() will test for this status, but
278
- // the socket is already closed by SEconnect(). If we do not change the
279
- // status, sedna_free() will attempt to close the connection again by
280
- // calling SEclose(), which will definitely lead to unpredictable
281
- // results.
282
- conn->isConnectionOk = SEDNA_CONNECTION_CLOSED;
283
- sedna_err(conn, res);
284
- }
285
-
286
- // Initialize @autocommit to true (default argument).
287
- rb_iv_set(self, "@autocommit", Qtrue);
405
+ if(NIL_P(pw_v = rb_hash_aref(options, pw_k))) pw = DEFAULT_PW; else pw = STR2CSTR(pw_v);
406
+
407
+ // Save all connection details to instance variables.
408
+ rb_iv_set(self, IV_HOST, rb_str_new2(host));
409
+ rb_iv_set(self, IV_DB, rb_str_new2(db));
410
+ rb_iv_set(self, IV_USER, rb_str_new2(user));
411
+ rb_iv_set(self, IV_PW, rb_str_new2(pw));
288
412
 
289
413
  #ifdef NON_BLOCKING
290
- rb_iv_set(self, "@mutex", rb_mutex_new());
414
+ // Create a mutex if this build supports non-blocking queries.
415
+ rb_iv_set(self, IV_MUTEX, rb_mutex_new());
291
416
  #endif
292
417
 
418
+ // Connect to the database.
419
+ SCA c = { sedna_struct(self), host, db, user, pw };
420
+ sedna_connect(self, &c);
421
+
422
+ // Initialize @autocommit to true.
423
+ rb_iv_set(self, IV_AUTOCOMMIT, Qtrue);
424
+
293
425
  return self;
294
426
  }
295
427
 
@@ -297,20 +429,48 @@ static VALUE cSedna_initialize(VALUE self, VALUE options)
297
429
  * call-seq:
298
430
  * sedna.close -> nil
299
431
  *
300
- * Closes an open Sedna connection. If the connection was already closed when
432
+ * Closes an open Sedna connection. If the connection is already closed when
301
433
  * this method is called, nothing happens. A Sedna::ConnectionError is raised
302
434
  * if the connection was open but could not be closed.
303
435
  */
304
436
  static VALUE cSedna_close(VALUE self)
305
437
  {
306
- int res;
307
438
  SC *conn = sedna_struct(self);
439
+
440
+ // Ensure the connection is closed.
441
+ sedna_close(conn);
442
+
443
+ // Always return nil if successful.
444
+ return Qnil;
445
+ }
446
+
447
+ /*
448
+ * call-seq:
449
+ * sedna.reset -> nil
450
+ *
451
+ * Closes an open Sedna connection and reconnects. If the connection is already
452
+ * closed when this method is called, the connection is just reestablished. When
453
+ * reconnecting, the same connection details are used that were given when initially
454
+ * connecting with the connect method.
455
+ *
456
+ * If the connection could not be closed or reopened, a Sedna::ConnectionError is
457
+ * raised. If the authentication fails when reconnecting, a
458
+ * Sedna::AuthenticationError is raised.
459
+ */
460
+ static VALUE cSedna_reset(VALUE self)
461
+ {
462
+ SC *conn = sedna_struct(self);
463
+
464
+ // First ensure the current connection is closed.
465
+ sedna_close(conn);
308
466
 
309
- if(SEconnectionStatus(conn) != SEDNA_CONNECTION_CLOSED) {
310
- res = SEclose(conn);
311
- VERIFY_RES(SEDNA_SESSION_CLOSED, res, conn);
312
- }
467
+ // Retrieve stored connection details.
468
+ SCA c = { conn, STR2CSTR(rb_iv_get(self, IV_HOST)), STR2CSTR(rb_iv_get(self, IV_DB)), STR2CSTR(rb_iv_get(self, IV_USER)), STR2CSTR(rb_iv_get(self, IV_PW)) };
469
+
470
+ // Connect to the database.
471
+ sedna_connect(self, &c);
313
472
 
473
+ // Always return nil if successful.
314
474
  return Qnil;
315
475
  }
316
476
 
@@ -320,32 +480,40 @@ static VALUE cSedna_close(VALUE self)
320
480
  * Sedna.connect(details) {|sedna| ... } -> nil
321
481
  *
322
482
  * Establishes a new connection to a \Sedna XML database. Accepts a hash that
323
- * describes which database to \connect to.
483
+ * describes which database to connect to.
324
484
  *
325
485
  * If a block is given, the block is executed if a connection was successfully
326
- * established. The connection is closed at the end of the block. If called
486
+ * established. The connection is closed at the end of the block or if the
487
+ * stack is unwinded (if an exception is raised, for example). If called
327
488
  * without a block, a Sedna object that represents the connection is returned.
328
489
  * The connection should be closed by calling Sedna#close.
329
490
  *
330
491
  * If a connection cannot be initiated, a Sedna::ConnectionError is raised.
331
492
  * If the authentication fails, a Sedna::AuthenticationError is raised.
332
493
  *
494
+ * This method does not block other threads in Ruby 1.9.1+ -- connections that
495
+ * are initiated in different threads will be created concurrently. You can
496
+ * use Sedna.blocking? to verify if the extension supports non-blocking
497
+ * behaviour.
498
+ *
333
499
  * ==== Valid connection details keys
334
500
  *
335
- * * <tt>:host</tt> - Host name or IP address to which to \connect to (defaults to +localhost+).
336
- * * <tt>:database</tt> - Name of the database to \connect to (defaults to +test+).
501
+ * * <tt>:host</tt> - Host name or IP address to which to connect to (defaults to +localhost+).
502
+ * * <tt>:database</tt> - Name of the database to connect to (defaults to +test+).
337
503
  * * <tt>:username</tt> - User name to authenticate with (defaults to +SYSTEM+).
338
504
  * * <tt>:password</tt> - Password to authenticate with (defaults to +MANAGER+).
339
505
  *
340
506
  * ==== Examples
341
507
  *
342
508
  * Call without a block and close the connection afterwards.
343
- * sedna = Sedna.connect(:database => "my_db", :host => "my_host")
509
+ *
510
+ * sedna = Sedna.connect :database => "my_db", :host => "my_host"
344
511
  * # Query the database and close afterwards.
345
512
  * sedna.close
346
513
  *
347
514
  * Call with a block. The connection is closed automatically.
348
- * Sedna.connect(:database => "my_db", :host => "my_host") do |sedna|
515
+ *
516
+ * Sedna.connect :database => "my_db", :host => "my_host" do |sedna|
349
517
  * # Query the database.
350
518
  * # The connection is closed automatically.
351
519
  * end
@@ -353,15 +521,24 @@ static VALUE cSedna_close(VALUE self)
353
521
  static VALUE cSedna_s_connect(VALUE klass, VALUE options)
354
522
  {
355
523
  int status;
524
+
525
+ // Create a new instance.
356
526
  VALUE obj = rb_funcall(klass, rb_intern("new"), 1, options);
357
527
 
358
528
  if(rb_block_given_p()) {
529
+ // If a block is given, yield the instance, and make sure we always return...
359
530
  rb_protect(rb_yield, obj, &status);
531
+
532
+ // ...to ensure that the connection is closed afterwards.
360
533
  cSedna_close(obj);
361
- if(status != 0) rb_jump_tag(status); // Re-raise any exception.
534
+
535
+ // Re-raise any exception.
536
+ if(status != 0) rb_jump_tag(status);
362
537
 
538
+ // Always return nil if successful.
363
539
  return Qnil;
364
540
  } else {
541
+ // If no block is given, simply return the instance.
365
542
  return obj;
366
543
  }
367
544
  }
@@ -381,16 +558,33 @@ static VALUE cSedna_s_version(VALUE klass)
381
558
  * call-seq:
382
559
  * Sedna.blocking? -> true or false
383
560
  *
384
- * Returns +true+ if querying the database with Sedna#execute will block other
385
- * threads. Returns +false+ if multiple queries can be run in different threads
386
- * simultaneously. \Sedna will not block other threads when compiled against
387
- * Ruby 1.9.
561
+ * Returns +true+ if connecting with Sedna.connect or querying the database
562
+ * with Sedna#execute will block other threads. Returns +false+ if multiple
563
+ * queries can be run or multiple connections can be made simultaneously in
564
+ * different threads. \Sedna will not block other threads (this method returns
565
+ * +false+) when compiled against Ruby 1.9.1+.
388
566
  */
389
567
  static VALUE cSedna_s_blocking(VALUE klass)
390
568
  {
391
569
  return SEDNA_BLOCKING;
392
570
  }
393
571
 
572
+ /*
573
+ * call-seq:
574
+ * sedna.connected? -> true or false
575
+ *
576
+ * Returns +true+ if the connection is connected and functioning properly. Returns
577
+ * +false+ if the connection has been closed.
578
+ */
579
+ static VALUE cSedna_connected(VALUE self)
580
+ {
581
+ SC *conn = sedna_struct(self);
582
+
583
+ // Return true if the connection status is OK. This only indicates that the
584
+ // client still thinks it is connected.
585
+ return (SEconnectionStatus(conn) == SEDNA_CONNECTION_OK) ? Qtrue : Qfalse;
586
+ }
587
+
394
588
  /*
395
589
  * call-seq:
396
590
  * sedna.execute(query) -> array or nil
@@ -399,11 +593,11 @@ static VALUE cSedna_s_blocking(VALUE klass)
399
593
  * Executes the given +query+ against a \Sedna database. Returns an array if the
400
594
  * given query is a select query. The elements of the array are strings that
401
595
  * correspond to each result in the result set. If the query is an update query
402
- * or a (bulk) load query, +nil+ is returned. When attempting to \execute a
596
+ * or a (bulk) load query, +nil+ is returned. When attempting to execute a
403
597
  * query on a closed connection, a Sedna::ConnectionError will be raised. A
404
598
  * Sedna::Exception is raised if the query fails or is invalid.
405
599
  *
406
- * This method does not block other threads in Ruby 1.9 -- database queries that
600
+ * This method does not block other threads in Ruby 1.9.1+ -- database queries that
407
601
  * are run in different threads with different connections will run concurrently.
408
602
  * You can use Sedna.blocking? to verify if the extension supports non-blocking
409
603
  * behaviour. Database queries run from different threads, but on the same
@@ -412,12 +606,17 @@ static VALUE cSedna_s_blocking(VALUE klass)
412
606
  * ==== Examples
413
607
  *
414
608
  * Create a new document.
609
+ *
415
610
  * sedna.execute "create document 'mydoc'"
416
611
  * #=> nil
612
+ *
417
613
  * Update the newly created document with a root node.
614
+ *
418
615
  * sedna.execute "update insert <message>Hello world!</message> into doc('mydoc')"
419
616
  * #=> nil
617
+ *
420
618
  * Select a node in a document using XPath.
619
+ *
421
620
  * sedna.execute "doc('mydoc')/message/text()"
422
621
  * #=> ["Hello world!"]
423
622
  *
@@ -429,27 +628,27 @@ static VALUE cSedna_s_blocking(VALUE klass)
429
628
  */
430
629
  static VALUE cSedna_execute(VALUE self, VALUE query)
431
630
  {
432
- int res;
433
631
  SC *conn = sedna_struct(self);
434
632
 
435
- if(SEconnectionStatus(conn) != SEDNA_CONNECTION_OK) rb_raise(cSednaConnError, "Connection is closed.");
436
-
437
- #ifdef NON_BLOCKING
438
- // Non-blocking variant for >= 1.9.
633
+ // Prepare query arguments.
439
634
  SQ q = { conn, STR2CSTR(query) };
440
- res = rb_mutex_synchronize(rb_iv_get(self, "@mutex"), (void*)sedna_execute, (VALUE)&q);
441
- #else
442
- // Blocking variant for < 1.9.
443
- res = SEexecute(conn, STR2CSTR(query));
444
- #endif
635
+
636
+ // Verify that the connection is OK.
637
+ if(SEconnectionStatus(conn) != SEDNA_CONNECTION_OK) rb_raise(cSednaConnError, "Connection is closed.");
638
+
639
+ // Execute query.
640
+ int res = SEDNA_EXECUTE(self, &q);
445
641
 
446
642
  switch(res) {
447
643
  case SEDNA_QUERY_SUCCEEDED:
644
+ // Return the results if this was a query.
448
645
  return sedna_get_results(conn);
449
646
  case SEDNA_UPDATE_SUCCEEDED:
450
647
  case SEDNA_BULK_LOAD_SUCCEEDED:
648
+ // Return nil if this was an update or bulk load.
451
649
  return Qnil;
452
650
  default:
651
+ // Raise an exception if something else happened.
453
652
  sedna_err(conn, res);
454
653
  return Qnil;
455
654
  }
@@ -470,7 +669,7 @@ static VALUE cSedna_execute(VALUE self, VALUE query)
470
669
  *
471
670
  * ==== Examples
472
671
  *
473
- * Create a new standalone document and retrieve it.
672
+ * Create a new document and retrieve its contents.
474
673
  *
475
674
  * sedna.load_document "<my_document>Hello world!</my_document>", "my_doc"
476
675
  * #=> nil
@@ -490,29 +689,41 @@ static VALUE cSedna_load_document(int argc, VALUE *argv, VALUE self)
490
689
  VALUE document, doc_name, col_name, buf;
491
690
  char *doc_name_c, *col_name_c;
492
691
 
692
+ // Verify that the connection is OK.
493
693
  if(SEconnectionStatus(conn) != SEDNA_CONNECTION_OK) rb_raise(cSednaConnError, "Connection is closed.");
494
694
 
495
- rb_scan_args(argc, argv, "21", &document, &doc_name, &col_name); // 2 mandatory args, 1 optional.
695
+ // 2 mandatory arguments, 1 optional.
696
+ rb_scan_args(argc, argv, "21", &document, &doc_name, &col_name);
496
697
  doc_name_c = STR2CSTR(doc_name);
497
698
  col_name_c = NIL_P(col_name) ? NULL : STR2CSTR(col_name);
498
699
 
499
700
  if(TYPE(document) == T_FILE) {
701
+ // If the document is an IO object...
500
702
  while(!NIL_P(buf = rb_funcall(document, rb_intern("read"), 1, INT2NUM(LOAD_BUF_LEN)))) {
703
+ // ...read from it until we reach EOF and load the data.
501
704
  res = SEloadData(conn, STR2CSTR(buf), RSTRING_LEN(buf), doc_name_c, col_name_c);
502
705
  VERIFY_RES(SEDNA_DATA_CHUNK_LOADED, res, conn);
503
706
  }
707
+
708
+ // If there is no data, raise an exception.
504
709
  if(res == 0) rb_raise(cSednaException, "Document is empty.");
505
710
  } else {
711
+ // If the document is not an IO object, verify it is a string instead.
506
712
  Check_Type(document, T_STRING);
713
+
714
+ // If there is no data, raise an exception.
507
715
  if(RSTRING_LEN(document) == 0) rb_raise(cSednaException, "Document is empty.");
508
716
 
717
+ // Load the data.
509
718
  res = SEloadData(conn, STR2CSTR(document), RSTRING_LEN(document), doc_name_c, col_name_c);
510
719
  VERIFY_RES(SEDNA_DATA_CHUNK_LOADED, res, conn);
511
720
  }
512
721
 
722
+ // Signal that we're finished.
513
723
  res = SEendLoadData(conn);
514
724
  VERIFY_RES(SEDNA_BULK_LOAD_SUCCEEDED, res, conn);
515
725
 
726
+ // Always return nil if successful.
516
727
  return Qnil;
517
728
  }
518
729
 
@@ -525,9 +736,13 @@ static VALUE cSedna_autocommit_set(VALUE self, VALUE auto_commit)
525
736
  int val = (RTEST(auto_commit) ? Qtrue : Qfalse);
526
737
  SC *conn = sedna_struct(self);
527
738
 
739
+ // Switch autocommit mode on or off.
528
740
  SWITCH_SEDNA_AUTOCOMMIT(conn, val);
529
- rb_iv_set(self, "@autocommit", val);
741
+
742
+ // Set the instance variable accordingly so it can be re-set after a transaction.
743
+ rb_iv_set(self, IV_AUTOCOMMIT, val);
530
744
 
745
+ // Always return nil if successful.
531
746
  return Qnil;
532
747
  }
533
748
 
@@ -537,74 +752,163 @@ static VALUE cSedna_autocommit_set(VALUE self, VALUE auto_commit)
537
752
  */
538
753
  static VALUE cSedna_autocommit_get(VALUE self)
539
754
  {
540
- return rb_iv_get(self, "@autocommit");
755
+ return rb_iv_get(self, IV_AUTOCOMMIT);
541
756
  }
542
757
 
543
758
  /*
544
759
  * call-seq:
545
760
  * sedna.transaction { ... } -> nil
761
+ * sedna.transaction -> nil
546
762
  *
547
- * Wraps the given block in a \transaction. If the block runs
548
- * completely, the \transaction is committed. If the stack is unwinded
549
- * prematurely, the \transaction is rolled back. This typically happens
550
- * when an \Exception is raised by calling +raise+ or a Symbol is thrown by
551
- * invoking +throw+. Note that Exceptions will not be rescued -- they will be
552
- * re-raised after rolling back the \transaction.
763
+ * Wraps the given block in a transaction. If the block runs completely, the
764
+ * transaction is committed. If the stack is unwinded prematurely, the
765
+ * transaction is rolled back. This typically happens when an exception is
766
+ * raised by calling +raise+ or a Symbol is thrown by invoking +throw+. Note
767
+ * that exceptions will not be rescued -- they will be re-raised after rolling
768
+ * back the transaction.
553
769
  *
554
- * This method returns +nil+ if the \transaction is successfully committed
770
+ * This method returns +nil+ if the transaction is successfully committed
555
771
  * to the database. If the given block completes successfully, but the
556
- * \transaction fails to be committed, a Sedna::TransactionError will
557
- * be raised.
772
+ * transaction fails to be committed, a Sedna::TransactionError will be raised.
773
+ *
774
+ * Transactions cannot be nested or executed simultaneously with the same
775
+ * connection. Calling this method inside a block that is passed to another
776
+ * transaction, or with the same connection in two concurrent threads will
777
+ * raise a Sedna::TransactionError on the second invocation.
558
778
  *
559
- * Transactions cannot be nested or executed simultaneously with the same connection.
560
- * Calling this method inside a block that is passed to another transaction, or
561
- * with the same connection in two concurrent threads will raise a
562
- * Sedna::TransactionError on the second invocation.
779
+ * If no block is given, this method only signals the beginning of a new
780
+ * transaction. A subsequent call to Sedna#commit or Sedna#rollback is required
781
+ * to end the transaction. Note that invoking this method with a block is the
782
+ * preferred way of executing transactions, because any exceptions that may be
783
+ * raised will automatically trigger a proper transaction rollback. Only call
784
+ * +commit+ and +rollback+ directly if you cannot use a block to wrap your
785
+ * transaction in.
563
786
  *
564
787
  * ==== Examples
565
788
  *
789
+ * Transactions are committed after the given block ends.
790
+ *
566
791
  * sedna.transaction do
567
- * count = sedna.execute "count(collection('my_col')/items)" #=> 0
568
- * sedna.execute "update insert <total>#{count}</total> into doc('my_doc')"
792
+ * amount = 100
793
+ * sedna.execute "update replace $balance in doc('my_account')/balance
794
+ * with <balance>{$balance - #{amount}}</balance>"
795
+ * sedna.execute "update replace $balance in doc('your_account')/balance
796
+ * with <balance>{$balance + #{amount}}</balance>"
569
797
  * # ...
570
798
  * end
571
799
  * # Transaction is committed.
572
800
  *
801
+ * Transactions are rolled back if something is thrown from inside the block.
802
+ *
803
+ * sedna.transaction do
804
+ * articles = sedna.execute "for $a in collection('articles')
805
+ * where $a/article/author = 'me' return $a"
806
+ * throw :no_articles if articles.empty?
807
+ * # ... never get here
808
+ * end
809
+ * # Transaction is rolled back.
810
+ *
811
+ * Transactions are also rolled back if an exception is raised inside the block.
812
+ *
573
813
  * sedna.transaction do
574
- * count = sedna.execute "count(collection('my_col')/items)" #=> 0
575
- * throw :no_items if count == 0
814
+ * amount = 100
815
+ * sedna.execute "update replace $balance in doc('my_account')/balance
816
+ * with <balance>{$balance - #{amount}}</balance>"
817
+ * new_balance = sedna.execute "doc('my_account')/balance"
818
+ * raise "Insufficient funds" if new_balance.to_i < 0
576
819
  * # ... never get here
577
820
  * end
578
821
  * # Transaction is rolled back.
822
+ *
823
+ * If you really have to, you can also use transactions declaratively. Make
824
+ * sure to roll back the transaction if something goes wrong!
825
+ *
826
+ * sedna.transaction
827
+ * begin
828
+ * amount = 100
829
+ * sedna.execute "update replace $balance in doc('my_account')/balance
830
+ * with <balance>{$balance - #{amount}}</balance>"
831
+ * sedna.execute "update replace $balance in doc('your_account')/balance
832
+ * with <balance>{$balance + #{amount}}</balance>"
833
+ * rescue Exception
834
+ * sedna.rollback
835
+ * else
836
+ * sedna.commit
837
+ * end
579
838
  */
580
839
  static VALUE cSedna_transaction(VALUE self)
581
840
  {
582
841
  int status;
583
842
  SC *conn = sedna_struct(self);
584
843
 
585
- SEDNA_AUTOCOMMIT_DISABLE(conn);
586
- sedna_tr_begin(conn);
844
+ // Begin the transaction.
845
+ sedna_begin(conn);
587
846
 
588
- rb_protect(rb_yield, Qnil, &status);
847
+ if(rb_block_given_p()) {
848
+ // Yield to the given block and protect it so we can always commit or rollback.
849
+ rb_protect(rb_yield, Qnil, &status);
589
850
 
590
- if(status == 0) {
591
- // Attempt to commit.
592
- if(SEtransactionStatus(conn) == SEDNA_TRANSACTION_ACTIVE) sedna_tr_commit(conn);
593
- else rb_raise(cSednaTrnError, "The transaction was prematurely ended, but no error was encountered. Did you rescue an exception inside the transaction?");
851
+ if(status == 0) {
852
+ // Attempt to commit if block completed successfully.
853
+ sedna_commit(conn, self);
854
+ } else {
855
+ // Stack has unwinded, attempt to roll back!
856
+ sedna_rollback(conn, self);
594
857
 
595
- SWITCH_SEDNA_AUTOCOMMIT(conn, rb_iv_get(self, "@autocommit"));
596
- } else {
597
- // Stack has unwinded, attempt to roll back.
598
- if(SEtransactionStatus(conn) == SEDNA_TRANSACTION_ACTIVE) sedna_tr_rollback(conn);
858
+ // Re-raise any exception or re-throw whatever was thrown.
859
+ rb_jump_tag(status);
860
+ }
861
+ }
599
862
 
600
- SWITCH_SEDNA_AUTOCOMMIT(conn, rb_iv_get(self, "@autocommit"));
863
+ // Always return nil if successful.
864
+ return Qnil;
865
+ }
601
866
 
602
- rb_jump_tag(status); // Re-raise exception.
603
- }
867
+ /*
868
+ * call-seq:
869
+ * sedna.commit -> nil
870
+ *
871
+ * Commits a currently active transaction. Only use this method if you are
872
+ * specifying a transaction declaratively. Invoking Sedna#transaction with a
873
+ * block will automatically commit the transaction if the block finishes
874
+ * successfully.
875
+ *
876
+ * This method will raise a Sedna::TransactionError if no transaction is in
877
+ * progress when it is called.
878
+ */
879
+ static VALUE cSedna_commit(VALUE self)
880
+ {
881
+ SC *conn = sedna_struct(self);
604
882
 
883
+ // Attempt to commit.
884
+ sedna_commit(conn, self);
885
+
886
+ // Always return nil if successful.
605
887
  return Qnil;
606
888
  }
607
889
 
890
+ /*
891
+ * call-seq:
892
+ * sedna.rollback -> nil
893
+ *
894
+ * Rolls back a currently active transaction. Only use this method if you are
895
+ * specifying a transaction declaratively. Invoking Sedna#transaction with a
896
+ * block will automatically roll back the transaction if an exception is raised
897
+ * or if the stack is unwinded for whatever reason.
898
+ *
899
+ * This method will do nothing if no transaction is in progress when it is
900
+ * called.
901
+ */
902
+ static VALUE cSedna_rollback(VALUE self)
903
+ {
904
+ SC *conn = sedna_struct(self);
905
+
906
+ // Attempt to roll back.
907
+ sedna_rollback(conn, self);
908
+
909
+ // Always return nil if successful.
910
+ return Qnil;
911
+ }
608
912
 
609
913
  // Initialize the extension ==============================================
610
914
 
@@ -621,7 +925,8 @@ void Init_sedna()
621
925
  * :username => "SYSTEM",
622
926
  * :password => "MANAGER",
623
927
  * }
624
- * Sedna.connect(connection_details) do |sedna|
928
+ *
929
+ * Sedna.connect connection_details do |sedna|
625
930
  * # Query the database.
626
931
  * # The connection is closed automatically.
627
932
  * end
@@ -636,19 +941,22 @@ void Init_sedna()
636
941
  rb_define_singleton_method(cSedna, "blocking?", cSedna_s_blocking, 0);
637
942
 
638
943
  rb_define_method(cSedna, "initialize", cSedna_initialize, 1);
639
- rb_define_method(cSedna, "execute", cSedna_execute, 1);
640
- rb_define_method(cSedna, "load_document", cSedna_load_document, -1);
944
+ rb_define_method(cSedna, "connected?", cSedna_connected, 0);
641
945
  rb_define_method(cSedna, "close", cSedna_close, 0);
946
+ rb_define_method(cSedna, "reset", cSedna_reset, 0);
642
947
  rb_define_method(cSedna, "transaction", cSedna_transaction, 0);
643
-
948
+ rb_define_method(cSedna, "commit", cSedna_commit, 0);
949
+ rb_define_method(cSedna, "rollback", cSedna_rollback, 0);
950
+ rb_define_method(cSedna, "execute", cSedna_execute, 1);
644
951
  rb_define_undocumented_alias(cSedna, "query", "execute");
952
+ rb_define_method(cSedna, "load_document", cSedna_load_document, -1);
645
953
 
646
954
  /*
647
955
  * Document-attr: autocommit
648
956
  *
649
957
  * When autocommit is set to +true+ (default), database queries can be run
650
958
  * without explicitly wrapping them in a transaction. Each query that is not
651
- * part of a \transaction is automatically committed to the database.
959
+ * part of a transaction is automatically committed to the database.
652
960
  * Explicit transactions in auto-commit mode will still be committed
653
961
  * atomically.
654
962
  *
@@ -664,9 +972,12 @@ void Init_sedna()
664
972
  rb_define_method(cSedna, "autocommit", cSedna_autocommit_get, 0);
665
973
 
666
974
  /*
667
- * The result set of a database query.
975
+ * The result of a database query is stored in a Sedna::Set object, which
976
+ * is a subclass of Array. Additional details about the executed query, such
977
+ * as timing and debug information, may be added to Sedna::Set objects in
978
+ * future versions of this library.
668
979
  */
669
- // Unused so far...
980
+ // Stick to Array for result sets.
670
981
  //cSednaSet = rb_define_class_under(cSedna, "Set", rb_cArray);
671
982
 
672
983
  /*