sedna 0.1.0

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.
Files changed (6) hide show
  1. data/CHANGES +9 -0
  2. data/README +86 -0
  3. data/ext/extconf.rb +71 -0
  4. data/ext/sedna.c +515 -0
  5. data/test/test_sedna.rb +387 -0
  6. metadata +62 -0
data/CHANGES ADDED
@@ -0,0 +1,9 @@
1
+ === 0.1.0
2
+
3
+ * Released on the December 8th, 2008.
4
+ * Development preview (alpha) release.
5
+ * Simple, high-level API.
6
+ * Tested with Ruby 1.8.7 and Ruby 1.9.0.
7
+ * Support for version 3.1 of the \Sedna XML DBMS.
8
+ * Support for regular queries and transactions.
9
+ * Support for auto-commit mode.
data/README ADDED
@@ -0,0 +1,86 @@
1
+ = \Sedna XML database client library
2
+
3
+ This library provides a Ruby client for <i>Sedna</i>, an open-source, native XML
4
+ database system. The client is a Ruby extension that uses the official C
5
+ driver that is shipped as part of the S<i></i>edna distribution.
6
+
7
+ \Sedna provides a full range of core database services -- persistent storage,
8
+ ACID transactions, security, indices, hot backup. Flexible XML processing
9
+ facilities include W3C XQuery implementation, tight integration of XQuery with
10
+ full-text search facilities and a node-level update language.
11
+
12
+ For more information about the \Sedna XML database system, see the project page
13
+ at http://modis.ispras.ru/sedna
14
+
15
+ === About the client library
16
+
17
+ Author: Rolf Timmermans (r.timmermans <i>at</i> voormedia.com)
18
+
19
+ Copyright 2008 Voormedia B.V.
20
+
21
+ Licensed under the Apache License, Version 2.0 (the "License");
22
+ you may not use this file except in compliance with the License.
23
+ You may obtain a copy of the License at
24
+
25
+ http://www.apache.org/licenses/LICENSE-2.0
26
+
27
+ Unless required by applicable law or agreed to in writing, software
28
+ distributed under the License is distributed on an "AS IS" BASIS,
29
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30
+ See the License for the specific language governing permissions and
31
+ limitations under the License.
32
+
33
+ === Current version
34
+
35
+ The current version of this library is 0.1.0. This version is a <b>development
36
+ preview</b>. The API may change significantly between minor versions.
37
+ For a complete overview all recent and previous changes, see CHANGES.
38
+
39
+ === Requirements
40
+
41
+ Before installing the \Sedna client library, make sure you have the library and
42
+ header files of the C driver. They are shipped and installed as part of the binary
43
+ distribution of \Sedna. When installing, choose either <tt>/usr/local/sedna</tt> or
44
+ <tt>/opt/sedna</tt> as target locations.
45
+
46
+ === Installation
47
+
48
+ After installing the \Sedna C driver (see above), simply install the Ruby client
49
+ library as a rubygem.
50
+
51
+ % gem install sedna
52
+
53
+ If the library or header files of the S<i></i>edna C driver are located at non-default
54
+ locations, you can specify these locations by adding the <tt>--with-sedna-lib</tt>
55
+ or <tt>--with-sedna-include</tt> options when installing.
56
+
57
+ % gem install sedna -- --with-sedna-lib=/path/to/sedna/lib --with-sedna-include=/path/to/sedna/include
58
+
59
+ === Usage
60
+
61
+ After installation of the gem, +require+ the sedna library in order to start
62
+ using it.
63
+
64
+ require 'rubygems'
65
+ require 'sedna'
66
+
67
+ To start querying a database, create a new connection with the Sedna.connect
68
+ method. When a block is given, the Sedna connection object will be returned
69
+ which can be used to execute database statements.
70
+
71
+ connection_details = {
72
+ :database => "my_db",
73
+ :host => "localhost",
74
+ :username => "SYSTEM",
75
+ :password => "MANAGER",
76
+ }
77
+ Sedna.connect(connection_details) do |sedna|
78
+ # Start querying the database.
79
+ sedna.execute("create document 'mydoc'") #=> nil
80
+ sedna.execute("update insert <msg>Hello world!</msg> into doc('mydoc')") #=> nil
81
+ sedna.execute("doc('mydoc')/msg/text()") #=> ["Hello world!"]
82
+ # The connection is closed automatically for us.
83
+ end
84
+
85
+ See the documentation of the Sedna class -- the methods Sedna.connect and
86
+ Sedna#execute in particular -- for more details.
data/ext/extconf.rb ADDED
@@ -0,0 +1,71 @@
1
+ # Copyright 2008 Voormedia B.V.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Ruby extension library providing a client API to the Sedna native XML
16
+ # database management system, based on the official Sedna C driver.
17
+
18
+ # This file will generate a Makefile that can be used to build this extension.
19
+
20
+ require "mkmf"
21
+
22
+ if RUBY_VERSION < "1.8"
23
+ puts "This library requires ruby 1.8."
24
+ exit 1
25
+ end
26
+
27
+ driver = "/driver/c"
28
+ default_driver_dirs = ["/usr/local/sedna#{driver}", "/opt/sedna#{driver}"]
29
+ default_include_dirs = ["/usr/include", "/usr/include/sedna"] + default_driver_dirs
30
+ default_lib_dirs = ["/usr/lib", "/usr/lib/sedna"] + default_driver_dirs
31
+ idir, ldir = dir_config "sedna", nil, nil
32
+ idir ||= default_include_dirs
33
+ ldir ||= default_lib_dirs
34
+
35
+ if not find_library "sedna", "SEconnect", *ldir
36
+ $stderr.write %{
37
+ ==============================================================================
38
+ Could not find libsedna.
39
+ * Did you install Sedna somewhere else than in /usr/local/sedna or /opt/sedna?
40
+ Call extconf.rb with --with-sedna-lib=/path to override default location.
41
+ * Did you install a version of Sedna not compiled for your architecture?
42
+ ==============================================================================
43
+ }
44
+ exit 2
45
+ end
46
+
47
+ if not find_header "libsedna.h", *idir or not find_header "sp_defs.h", *idir
48
+ $stderr.write %{
49
+ ==============================================================================
50
+ Could not find header file(s) for libsedna.
51
+ * Did you install Sedna somewhere else than in /usr/local/sedna or /opt/sedna?
52
+ Call extconf.rb with --with-sedna-include=/path to override default location.
53
+ ==============================================================================
54
+ }
55
+ exit 3
56
+ end
57
+
58
+ if CONFIG["arch"] =~ /x86_64/i and File.exist?(f = $LIBPATH.first + File::Separator + "libsedna.a")
59
+ if system "/usr/bin/objdump --reloc \"#{f}\" 2>/dev/null | grep R_X86_64_32S >/dev/null && echo"
60
+ $stderr.write %{==============================================================================
61
+ Library libsedna.a was statically compiled for a 64-bit platform as position-
62
+ dependent code. It will not be possible to create a Ruby shared library with
63
+ this Sedna library. Recompile the library as position-independent code by
64
+ passing the -fPIC option to gcc.
65
+ ==============================================================================
66
+ }
67
+ exit 4
68
+ end
69
+ end
70
+
71
+ create_makefile "sedna"
data/ext/sedna.c ADDED
@@ -0,0 +1,515 @@
1
+ /*
2
+ * Copyright 2008 Voormedia B.V.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * Ruby extension library providing a client API to the Sedna native XML
17
+ * database management system, based on the official Sedna C driver.
18
+ *
19
+ * This file contains the Ruby C-extension.
20
+ */
21
+
22
+ #include <string.h>
23
+ #include "ruby.h"
24
+ #include "libsedna.h"
25
+
26
+ // Size of the read buffer.
27
+ #define BUF_LEN 8192
28
+
29
+ // Use macros to fool RDoc and hide some of our methods/aliases.
30
+ #define rb_define_undocumented_alias(kl, new, old) rb_define_alias(kl, new, old)
31
+
32
+ // Macro for verification of return values of the Sedna API + error handling.
33
+ #define verify_res(expected, real, conn) if(real != expected) sedna_err(conn, real)
34
+
35
+ // Macro for setting autocommit values on connection conn.
36
+ #define sedna_autocommit_on(conn) sedna_autocommit(conn, SEDNA_AUTOCOMMIT_ON)
37
+ #define sedna_autocommit_off(conn) sedna_autocommit(conn, SEDNA_AUTOCOMMIT_OFF)
38
+ #define switch_sedna_autocommit(conn, val) if(val) sedna_autocommit_on(conn); else sedna_autocommit_off(conn)
39
+
40
+
41
+ // Our classes.
42
+ static VALUE cSedna;
43
+ //static VALUE cSednaSet; //Unused so far.
44
+ static VALUE cSednaException;
45
+ static VALUE cSednaAuthError;
46
+ static VALUE cSednaConnError;
47
+ static VALUE cSednaTrnError;
48
+
49
+ // Define a shorthand for the common SednaConnection structure.
50
+ typedef struct SednaConnection SC;
51
+
52
+
53
+ // Test the last error message for conn, and raise an exception if there is one.
54
+ // The type of the exception is based on the result of the function that was
55
+ // called that generated the error and should be passed as res.
56
+ static void sedna_err(SC *conn, int res)
57
+ {
58
+ VALUE exception;
59
+ const char *msg;
60
+ char *err, *details, *p;
61
+ switch(res) {
62
+ case SEDNA_AUTHENTICATION_FAILED:
63
+ exception = cSednaAuthError; break;
64
+ case SEDNA_OPEN_SESSION_FAILED:
65
+ case SEDNA_CLOSE_SESSION_FAILED:
66
+ exception = cSednaConnError; break;
67
+ case SEDNA_BEGIN_TRANSACTION_FAILED:
68
+ case SEDNA_ROLLBACK_TRANSACTION_FAILED:
69
+ case SEDNA_COMMIT_TRANSACTION_FAILED:
70
+ exception = cSednaTrnError; break;
71
+ case SEDNA_ERROR:
72
+ default:
73
+ exception = cSednaException;
74
+ }
75
+ msg = SEgetLastErrorMsg(conn);
76
+ err = strstr(msg, "\n");
77
+ details = strstr(err, "\nDetails: ");
78
+ if(err != NULL) {
79
+ err++;
80
+ if((p = strstr(err, "\n")) != NULL) strncpy(p, "\0", 1);
81
+ } else {
82
+ err = "Unknown error.";
83
+ }
84
+ if(details != NULL) {
85
+ details += 10;
86
+ if((p = strstr(details, "\n")) != NULL) strncpy(p, "\0", 1);
87
+ rb_raise(exception, "%s (%s)", err, details);
88
+ } else {
89
+ rb_raise(exception, "%s", err);
90
+ }
91
+ }
92
+
93
+ // Retrieve the SednaConnection struct from the Ruby Sedna object obj.
94
+ static SC* sedna_struct(VALUE obj)
95
+ {
96
+ SC *conn;
97
+ Data_Get_Struct(obj, SC, conn);
98
+ return conn;
99
+ }
100
+
101
+ // Close the Sedna connection and free memory of the SednaConnection struct.
102
+ // Called at GC.
103
+ static void sedna_free(SC *conn)
104
+ {
105
+ if(SEconnectionStatus(conn) != SEDNA_CONNECTION_CLOSED) SEclose(conn);
106
+ free(conn);
107
+ }
108
+
109
+ // Mark any references to other objects for Ruby GC (if any).
110
+ static void sedna_mark(SC *conn)
111
+ { /* Unused. */ }
112
+
113
+ // Read one record completely and return it as a Ruby String object.
114
+ static VALUE sedna_read(SC *conn, int strip_n)
115
+ {
116
+ int bytes_read = 0;
117
+ char buffer[BUF_LEN];
118
+ VALUE str = rb_str_buf_new(0);
119
+ do {
120
+ bytes_read = SEgetData(conn, buffer, BUF_LEN - 1);
121
+ if(bytes_read == SEDNA_ERROR) {
122
+ sedna_err(conn, SEDNA_ERROR);
123
+ } else {
124
+ if(bytes_read > 0) {
125
+ if(strip_n) {
126
+ // Strange bug adds newlines to beginning of every result
127
+ // except the first. Strip them! This a known issue in the
128
+ // network protocol and serialization mechanism.
129
+ // See: http://sourceforge.net/mailarchive/forum.php?thread_name=3034886f0812030132v3bbd8e2erd86480d3dc640664%40mail.gmail.com&forum_name=sedna-discussion
130
+ rb_str_buf_cat(str, buffer + 1, bytes_read - 1);
131
+ // Do not strip newlines from subsequent buffer reads.
132
+ strip_n = 0;
133
+ } else {
134
+ rb_str_buf_cat(str, buffer, bytes_read);
135
+ }
136
+ }
137
+ }
138
+ } while(bytes_read > 0);
139
+ return str;
140
+ }
141
+
142
+ // Iterate over all records and add them to a Ruby Array.
143
+ static VALUE sedna_get_results(SC *conn)
144
+ {
145
+ int res, strip_n = 0;
146
+ VALUE set = rb_ary_new();
147
+ while((res = SEnext(conn)) != SEDNA_RESULT_END) {
148
+ if(res == SEDNA_ERROR) sedna_err(conn, res);
149
+ // Set strip_n to 1 for all results except the first. This will cause
150
+ // sedna_read() an incorrect newline that is prepended to these results.
151
+ rb_ary_push(set, sedna_read(conn, strip_n));
152
+ if(!strip_n) strip_n = 1;
153
+ };
154
+ return set;
155
+ }
156
+
157
+ // Enable or disable autocommit.
158
+ static void sedna_autocommit(SC *conn, int value)
159
+ {
160
+ int res = SEsetConnectionAttr(conn, SEDNA_ATTR_AUTOCOMMIT, (void *)&value, sizeof(int));
161
+ verify_res(SEDNA_SET_ATTRIBUTE_SUCCEEDED, res, conn);
162
+ }
163
+
164
+ // Begin a transaction.
165
+ static void sedna_tr_begin(SC *conn)
166
+ {
167
+ int res = SEbegin(conn);
168
+ verify_res(SEDNA_BEGIN_TRANSACTION_SUCCEEDED, res, conn);
169
+ }
170
+
171
+ // Commit a transaction.
172
+ static void sedna_tr_commit(SC *conn)
173
+ {
174
+ int res = SEcommit(conn);
175
+ verify_res(SEDNA_COMMIT_TRANSACTION_SUCCEEDED, res, conn);
176
+ }
177
+
178
+ // Rollback a transaction.
179
+ static void sedna_tr_rollback(SC *conn)
180
+ {
181
+ int res = SErollback(conn);
182
+ verify_res(SEDNA_ROLLBACK_TRANSACTION_SUCCEEDED, res, conn);
183
+ }
184
+
185
+ // Alocates memory for a SednaConnection struct.
186
+ static VALUE cSedna_s_new(VALUE klass)
187
+ {
188
+ SC *conn, init = SEDNA_CONNECTION_INITIALIZER;
189
+ conn = (SC *)malloc(sizeof(SC));
190
+ if(conn == NULL) rb_raise(rb_eNoMemError, "Could not allocate memory.");
191
+ memcpy(conn, &init, sizeof(init));
192
+ return Data_Wrap_Struct(klass, sedna_mark, sedna_free, conn);
193
+ }
194
+
195
+ /* :nodoc:
196
+ *
197
+ * Initialize a new instance of Sedna.
198
+ */
199
+ static VALUE cSedna_initialize(VALUE self, VALUE options)
200
+ {
201
+ int res;
202
+ VALUE host_k, db_k, user_k, pw_k, host_v, db_v, user_v, pw_v;
203
+ char *host, *db, *user, *pw;
204
+ SC *conn = sedna_struct(self);
205
+
206
+ Check_Type(options, T_HASH);
207
+ host_k = ID2SYM(rb_intern("host"));
208
+ db_k = ID2SYM(rb_intern("database"));
209
+ user_k = ID2SYM(rb_intern("username"));
210
+ pw_k = ID2SYM(rb_intern("password"));
211
+
212
+ if(NIL_P(host_v = rb_hash_aref(options, host_k))) host = "localhost"; else host = STR2CSTR(host_v);
213
+ if(NIL_P(db_v = rb_hash_aref(options, db_k))) db = "test"; else db = STR2CSTR(db_v);
214
+ if(NIL_P(user_v = rb_hash_aref(options, user_k))) user = "SYSTEM"; else user = STR2CSTR(user_v);
215
+ if(NIL_P(pw_v = rb_hash_aref(options, pw_k))) pw = "MANAGER"; else pw = STR2CSTR(pw_v);
216
+
217
+ res = SEconnect(conn, host, db, user, pw);
218
+ verify_res(SEDNA_SESSION_OPEN, res, conn);
219
+
220
+ // Initialize @autocommit to true (default argument).
221
+ rb_iv_set(self, "@autocommit", Qtrue);
222
+
223
+ return self;
224
+ }
225
+
226
+ /*
227
+ * call-seq:
228
+ * sedna.close -> nil
229
+ *
230
+ * Closes an open Sedna connection. If the connection was already closed when
231
+ * this method is called, nothing happens. A Sedna::ConnectionError is raised
232
+ * if the connection was open but could not be closed.
233
+ */
234
+ static VALUE cSedna_close(VALUE self)
235
+ {
236
+ int res;
237
+ SC *conn = sedna_struct(self);
238
+ if(SEconnectionStatus(conn) != SEDNA_CONNECTION_CLOSED) {
239
+ res = SEclose(conn);
240
+ verify_res(SEDNA_SESSION_CLOSED, res, conn);
241
+ }
242
+ return Qnil;
243
+ }
244
+
245
+ /*
246
+ * call-seq:
247
+ * Sedna.connect(details) -> Sedna instance
248
+ * Sedna.connect(details) {|sedna| ... } -> nil
249
+ *
250
+ * Establishes a new connection to a \Sedna XML database. Accepts a hash that
251
+ * describes which database to \connect to.
252
+ *
253
+ * If a block is given, the block is executed if a connection was successfully
254
+ * established. The connection is closed at the end of the block. If called
255
+ * without a block, a Sedna object that represents the connection is returned.
256
+ * The connection should be closed by calling Sedna#close.
257
+ *
258
+ * If a connection cannot be initiated, a Sedna::ConnectionError is raised.
259
+ * If the authentication fails, a Sedna::AuthenticationError is raised.
260
+ *
261
+ * ==== Valid connection details keys
262
+ *
263
+ * * <tt>:host</tt> - Host name or IP address to which to \connect to (defaults to +localhost+).
264
+ * * <tt>:database</tt> - Name of the database to \connect to (defaults to +test+).
265
+ * * <tt>:username</tt> - User name to authenticate with (defaults to +SYSTEM+).
266
+ * * <tt>:password</tt> - Password to authenticate with (defaults to +MANAGER+).
267
+ *
268
+ * ==== Examples
269
+ *
270
+ * Call without a block and close the connection afterwards.
271
+ * sedna = Sedna.connect(:database => "my_db", :host => "my_host")
272
+ * # Query the database and close afterwards.
273
+ * sedna.close
274
+ *
275
+ * Call with a block. The connection is closed automatically.
276
+ * Sedna.connect(:database => "my_db", :host => "my_host") do |sedna|
277
+ * # Query the database.
278
+ * # The connection is closed automatically.
279
+ * end
280
+ */
281
+ static VALUE cSedna_s_connect(VALUE klass, VALUE options)
282
+ {
283
+ int status;
284
+ VALUE obj = rb_funcall(klass, rb_intern("new"), 1, options);
285
+ if(rb_block_given_p()) {
286
+ rb_protect(rb_yield, obj, &status);
287
+ cSedna_close(obj);
288
+ if(status != 0) rb_jump_tag(status); // Re-raise any exception.
289
+ return Qnil;
290
+ } else {
291
+ return obj;
292
+ }
293
+ }
294
+
295
+ /*
296
+ * call-seq:
297
+ * sedna.execute(query) -> array or nil
298
+ * sedna.query(query) -> array or nil
299
+ *
300
+ * Execute the given +query+ against a \Sedna database. Returns an array if the
301
+ * given query is a select query. The elements of the array are strings that
302
+ * correspond to each result in the result set. If the query is an update query
303
+ * or a (bulk) load query, +nil+ is returned. When attempting to \execute a
304
+ * query on a closed connection, a Sedna::ConnectionError will be raised. A
305
+ * Sedna::Exception is raised if the query fails or is invalid.
306
+ *
307
+ * ==== Examples
308
+ *
309
+ * Create a new document.
310
+ * sedna.execute "create document 'mydoc'"
311
+ * #=> nil
312
+ * Update the newly created document with a root node.
313
+ * sedna.execute "update insert <message>Hello world!</message> into doc('mydoc')"
314
+ * #=> nil
315
+ * Select a node in a document using XPath.
316
+ * sedna.execute "doc('mydoc')/message/text()"
317
+ * #=> ["Hello world!"]
318
+ *
319
+ * ==== Further reading
320
+ *
321
+ * For more information about \Sedna's database query syntax and support, see the
322
+ * <i>Database language</i> section of the official documentation of the
323
+ * \Sedna project at http://modis.ispras.ru/sedna/progguide/ProgGuidese2.html
324
+ */
325
+ static VALUE cSedna_execute(VALUE self, VALUE query)
326
+ {
327
+ int res;
328
+ SC *conn = sedna_struct(self);
329
+ if(SEconnectionStatus(conn) != SEDNA_CONNECTION_OK) rb_raise(cSednaConnError, "Connection is closed.");
330
+ res = SEexecute(conn, STR2CSTR(query));
331
+ switch(res) {
332
+ case SEDNA_QUERY_SUCCEEDED:
333
+ return sedna_get_results(conn);
334
+ case SEDNA_UPDATE_SUCCEEDED:
335
+ case SEDNA_BULK_LOAD_SUCCEEDED:
336
+ return Qnil;
337
+ default:
338
+ sedna_err(conn, res);
339
+ return Qnil;
340
+ }
341
+ }
342
+
343
+ /* :nodoc:
344
+ *
345
+ * Turn autocommit on or off.
346
+ */
347
+ static VALUE cSedna_autocommit_set(VALUE self, VALUE auto_commit)
348
+ {
349
+ int val = (RTEST(auto_commit) ? Qtrue : Qfalse);
350
+ SC *conn = sedna_struct(self);
351
+ switch_sedna_autocommit(conn, val);
352
+ rb_iv_set(self, "@autocommit", val);
353
+ return Qnil;
354
+ }
355
+
356
+ /* :nodoc:
357
+ *
358
+ * Get the current autocommit value.
359
+ */
360
+ static VALUE cSedna_autocommit_get(VALUE self)
361
+ {
362
+ return rb_iv_get(self, "@autocommit");
363
+ }
364
+
365
+ /*
366
+ * call-seq:
367
+ * sedna.transaction { ... } -> true
368
+ *
369
+ * Wraps the given block in a \transaction. If the block runs
370
+ * completely, the \transaction is committed. If the stack is unwinded
371
+ * prematurely, the \transaction is rolled back. This typically happens
372
+ * when an \Exception is raised by calling +raise+ or a Symbol is thrown by
373
+ * invoking +throw+. Note that Exceptions will not be rescued -- they will be
374
+ * re-raised after rolling back the \transaction.
375
+ *
376
+ * This method returns +true+ if the \transaction is successfully committed
377
+ * to the database. If the given block completes successfully, but the
378
+ * \transaction fails to be committed, a Sedna::TransactionError will
379
+ * be raised.
380
+ *
381
+ * ==== Examples
382
+ *
383
+ * sedna.transaction do
384
+ * count = sedna.execute "count(collection('mycol')/items)" #=> 0
385
+ * sedna.execute "update insert <total>#{count}</total> into doc('mydoc')"
386
+ * # ...
387
+ * end
388
+ * # Transaction is committed.
389
+ *
390
+ * sedna.transaction do
391
+ * count = sedna.execute "count(collection('mycol')/items)" #=> 0
392
+ * throw :no_items if count == 0
393
+ * # ... never get here
394
+ * end
395
+ * # Transaction is rolled back.
396
+ */
397
+ static VALUE cSedna_transaction(VALUE self)
398
+ {
399
+ int status;
400
+ SC *conn = sedna_struct(self);
401
+ sedna_autocommit_off(conn);
402
+ sedna_tr_begin(conn);
403
+ rb_protect(rb_yield, Qnil, &status);
404
+ if(status == 0) {
405
+ // Attempt to commit.
406
+ if(SEtransactionStatus(conn) == SEDNA_TRANSACTION_ACTIVE) sedna_tr_commit(conn);
407
+ else rb_raise(cSednaTrnError, "The transaction was prematurely ended, but no error was encountered. Did you rescue an exception inside the transaction?");
408
+ switch_sedna_autocommit(conn, rb_iv_get(self, "@autocommit"));
409
+ } else {
410
+ // Stack has unwinded, attempt to roll back.
411
+ if(SEtransactionStatus(conn) == SEDNA_TRANSACTION_ACTIVE) sedna_tr_rollback(conn);
412
+ switch_sedna_autocommit(conn, rb_iv_get(self, "@autocommit"));
413
+ rb_jump_tag(status); // Re-raise exception.
414
+ }
415
+ return Qtrue;
416
+ }
417
+
418
+ void Init_sedna()
419
+ {
420
+ /*
421
+ * Objects of class Sedna represent a connection to a \Sedna XML
422
+ * database. Establish a new connection by invoking the Sedna.connect
423
+ * class method.
424
+ *
425
+ * connection_details = {
426
+ * :database => "my_db",
427
+ * :host => "localhost",
428
+ * :username => "SYSTEM",
429
+ * :password => "MANAGER",
430
+ * }
431
+ * Sedna.connect(connection_details) do |sedna|
432
+ * # Query the database.
433
+ * # The connection is closed automatically.
434
+ * end
435
+ *
436
+ * See the README for a high-level overview of how to use this library.
437
+ */
438
+ cSedna = rb_define_class("Sedna", rb_cObject);
439
+ rb_define_alloc_func(cSedna, cSedna_s_new);
440
+ rb_define_singleton_method(cSedna, "connect", cSedna_s_connect, 1);
441
+ rb_define_method(cSedna, "initialize", cSedna_initialize, 1);
442
+ rb_define_method(cSedna, "execute", cSedna_execute, 1);
443
+ rb_define_undocumented_alias(cSedna, "query", "execute");
444
+ rb_define_method(cSedna, "close", cSedna_close, 0);
445
+
446
+ /*
447
+ * Document-attr: autocommit
448
+ *
449
+ * When autocommit is set to +true+ (default), database queries can be run
450
+ * without explicitly wrapping them in a transaction. Each query that is not
451
+ * part of a \transaction is automatically committed to the database.
452
+ * Explicit transactions in auto-commit mode will still be committed
453
+ * atomically.
454
+ *
455
+ * When autocommit is set to +false+, queries can only be run inside an
456
+ * explicit transaction. Queries run outside transactions will fail with a
457
+ * Sedna::Exception.
458
+ */
459
+ /* Trick RDoc into thinking this is a regular attribute. We documented the
460
+ * attribute above.
461
+ rb_define_attr(cSedna, "autocommit", 1, 1);
462
+ */
463
+ rb_define_method(cSedna, "autocommit=", cSedna_autocommit_set, 1);
464
+ rb_define_method(cSedna, "autocommit", cSedna_autocommit_get, 0);
465
+ rb_define_method(cSedna, "transaction", cSedna_transaction, 0);
466
+
467
+ /*
468
+ * The result set of a database query.
469
+ */
470
+ // Unused so far...
471
+ //cSednaSet = rb_define_class_under(cSedna, "Set", rb_cArray);
472
+
473
+ /*
474
+ * Generic exception class for errors. All errors raised by the \Sedna
475
+ * client library are of type Sedna::Exception.
476
+ *
477
+ * === Subclasses
478
+ *
479
+ * For some specific errors, an exception of a particular subtype is raised.
480
+ *
481
+ * [Sedna::AuthenticationError]
482
+ * Raised when a database connection was successfully established, but
483
+ * the supplied credentials were incorrect. Can only be raised when
484
+ * invoking Sedna.connect.
485
+ * [Sedna::ConnectionError]
486
+ * Raised when a connection to a database could not be established or when
487
+ * a connection could not be closed. Can be raised when invoking Sedna.connect
488
+ * or Sedna#close.
489
+ * [Sedna::TransactionError]
490
+ * Raised when a transaction could not be committed.
491
+ */
492
+ cSednaException = rb_define_class_under(cSedna, "Exception", rb_eStandardError);
493
+
494
+ /*
495
+ * Sedna::AuthenticationError is a subclass of Sedna::Exception, and is
496
+ * raised when a database connection was successfully established, but the
497
+ * supplied credentials were incorrect. Can only be raised when invoking
498
+ * Sedna.connect.
499
+ */
500
+ cSednaAuthError = rb_define_class_under(cSedna, "AuthenticationError", cSednaException);
501
+
502
+ /*
503
+ * Sedna::ConnectionError is a subclass of Sedna::Exception, and is
504
+ * raised when a connection to a database could not be established or when
505
+ * a connection could not be closed. Can be raised when invoking Sedna.connect
506
+ * or Sedna#close.
507
+ */
508
+ cSednaConnError = rb_define_class_under(cSedna, "ConnectionError", cSednaException);
509
+
510
+ /*
511
+ * Sedna::TransactionError is a subclass of Sedna::Exception, and is
512
+ * raised when a transaction could not be committed.
513
+ */
514
+ cSednaTrnError = rb_define_class_under(cSedna, "TransactionError", cSednaException);
515
+ }
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright 2008 Voormedia B.V.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ # Ruby extension library providing a client API to the Sedna native XML
18
+ # database management system, based on the official Sedna C driver.
19
+
20
+ # This file contains the test suite to verify the client library is working
21
+ # correctly.
22
+
23
+ require 'test/unit'
24
+ require '../ext/sedna'
25
+ require 'socket'
26
+
27
+ class TestSedna < Test::Unit::TestCase
28
+ def setup
29
+ @connection = {
30
+ :database => "test",
31
+ :host => "localhost",
32
+ :username => "SYSTEM",
33
+ :password => "MANAGER",
34
+ }
35
+ end
36
+
37
+ def teardown
38
+ end
39
+
40
+ def test_aaa_connection
41
+ port = 5050
42
+ begin
43
+ s = TCPSocket.new @connection[:host], port
44
+ rescue Errno::ECONNREFUSED, SocketError
45
+ # No DB appears to be running; fail fatally. Do not run the other tests and just exit.
46
+ puts "Connection to port #{port} on #{@connection[:host]} could not be established. Check if the Sedna XML database is running before running this test suite."
47
+ exit 1
48
+ end
49
+ assert s
50
+ s.close
51
+ end
52
+
53
+ # Test Sedna.connect.
54
+ def test_connect_should_return_sedna_object
55
+ sedna = Sedna.connect @connection
56
+ assert_kind_of Sedna, sedna
57
+ sedna.close
58
+ end
59
+
60
+ def test_connect_should_raise_exception_when_host_not_found
61
+ assert_raises Sedna::ConnectionError do
62
+ Sedna.connect @connection.merge(:host => "non-existant-host")
63
+ end
64
+ end
65
+
66
+ def test_connect_should_raise_exception_when_credentials_are_incorrect
67
+ assert_raises Sedna::AuthenticationError do
68
+ Sedna.connect @connection.merge(:username => "non-existant-user")
69
+ end
70
+ end
71
+
72
+ def test_connect_should_return_nil_on_error
73
+ begin
74
+ sedna = Sedna.connect @connection.merge(:username => "non-existant-user")
75
+ rescue
76
+ end
77
+ assert_nil sedna
78
+ end
79
+
80
+ def test_connect_should_return_nil_if_block_given
81
+ sedna = Sedna.connect @connection do |s| end
82
+ assert_nil sedna
83
+ end
84
+
85
+ def test_connect_should_close_connection_after_block
86
+ sedna = nil
87
+ Sedna.connect @connection do |s|
88
+ sedna = s
89
+ end
90
+ assert_raises Sedna::ConnectionError do
91
+ sedna.execute "<test/>"
92
+ end
93
+ end
94
+
95
+ def test_connect_should_close_connection_if_exception_is_raised_inside_block
96
+ sedna = nil
97
+ begin
98
+ Sedna.connect @connection do |s|
99
+ sedna = s
100
+ raise Exception
101
+ end
102
+ rescue Exception
103
+ end
104
+ assert_raises Sedna::ConnectionError do
105
+ sedna.execute "<test/>"
106
+ end
107
+ end
108
+
109
+ def test_connect_should_close_connection_if_something_is_thrown_inside_block
110
+ sedna = nil
111
+ catch :ball do
112
+ Sedna.connect @connection do |s|
113
+ sedna = s
114
+ throw :ball
115
+ end
116
+ end
117
+ assert_raises Sedna::ConnectionError do
118
+ sedna.execute "<test/>"
119
+ end
120
+ end
121
+
122
+ def test_connect_should_reraise_exceptions_from_inside_block
123
+ assert_raises Exception do
124
+ Sedna.connect @connection do
125
+ raise Exception
126
+ end
127
+ end
128
+ end
129
+
130
+ # TODO: Fix the following strangely-failing test case.
131
+ #def test_zzz_connect_should_not_fail_to_close_connection_when_require_called_inside_block
132
+ # # Squash a strange bug -- only appears to work if this test is run last and nothing else fails.
133
+ # assert_nothing_raised do
134
+ # Sedna.connect @connection do |sedna|
135
+ # require 'pp'
136
+ # end
137
+ # end
138
+ #end
139
+
140
+ # Test sedna.close.
141
+ def test_close_should_return_nil
142
+ sedna = Sedna.connect @connection
143
+ assert_nil sedna.close
144
+ end
145
+
146
+ def test_close_should_fail_silently_if_connection_is_already_closed
147
+ sedna = Sedna.connect @connection
148
+ assert_nothing_raised do
149
+ sedna.close
150
+ sedna.close
151
+ end
152
+ end
153
+
154
+ # Test sedna.execute / sedna.query.
155
+ def test_execute_should_return_nil_for_data_structure_query
156
+ Sedna.connect @connection do |sedna|
157
+ name = "test_execute_should_return_nil_for_create_document_query"
158
+ sedna.execute("drop document '#{name}'") rescue Sedna::Exception
159
+ assert_nil sedna.execute("create document '#{name}'")
160
+ sedna.execute("drop document '#{name}'") rescue Sedna::Exception
161
+ end
162
+ end
163
+
164
+ def test_execute_should_return_array_for_select_query
165
+ Sedna.connect @connection do |sedna|
166
+ assert_kind_of Array, sedna.execute("<test/>")
167
+ end
168
+ end
169
+
170
+ def test_execute_should_return_array_with_single_string_for_single_select_query
171
+ Sedna.connect @connection do |sedna|
172
+ assert_equal ["<test/>"], sedna.execute("<test/>")
173
+ end
174
+ end
175
+
176
+ def test_execute_should_return_array_with_strings_for_select_query
177
+ Sedna.connect @connection do |sedna|
178
+ assert_equal ["<test/>", "<test/>", "<test/>"], sedna.execute("<test/>, <test/>, <test/>")
179
+ end
180
+ end
181
+
182
+ def test_execute_should_fail_if_autocommit_is_false
183
+ Sedna.connect @connection do |sedna|
184
+ sedna.autocommit = false
185
+ assert_raises Sedna::Exception do
186
+ sedna.execute "<test/>"
187
+ end
188
+ end
189
+ end
190
+
191
+ def test_execute_should_fail_with_sedna_exception_for_invalid_statments
192
+ Sedna.connect @connection do |sedna|
193
+ assert_raises Sedna::Exception do
194
+ sedna.execute "INVALID"
195
+ end
196
+ end
197
+ end
198
+
199
+ def test_execute_should_fail_with_sedna_connection_error_if_connection_is_closed
200
+ Sedna.connect @connection do |sedna|
201
+ sedna.close
202
+ assert_raises Sedna::ConnectionError do
203
+ sedna.execute "<test/>"
204
+ end
205
+ end
206
+ end
207
+
208
+ def test_execute_should_strip_first_newline_of_all_but_first_results
209
+ Sedna.connect @connection do |sedna|
210
+ name = "test_execute_should_strip_first_newline_of_all_but_first_results"
211
+ sedna.execute("drop document '#{name}'") rescue Sedna::Exception
212
+ sedna.execute("create document '#{name}'")
213
+ sedna.execute("update insert <test><a>\n\nt</a><a>\n\nt</a><a>\n\nt</a></test> into doc('#{name}')")
214
+ assert_equal ["\n\nt", "\n\nt", "\n\nt"], sedna.execute("doc('#{name}')/test/a/text()")
215
+ sedna.execute("drop document '#{name}'") rescue Sedna::Exception
216
+ end
217
+ end
218
+
219
+ def test_query_should_be_alias_of_execute
220
+ Sedna.connect @connection do |sedna|
221
+ assert_equal ["<test/>"], sedna.query("<test/>")
222
+ end
223
+ end
224
+
225
+ # Test sedna.autocommit= / sedna.autocommit.
226
+ def test_autocommit_should_return_true_by_default
227
+ Sedna.connect @connection do |sedna|
228
+ assert_equal true, sedna.autocommit
229
+ end
230
+ end
231
+
232
+ def test_autocommit_should_return_true_if_set_to_true
233
+ Sedna.connect @connection do |sedna|
234
+ sedna.autocommit = true
235
+ assert_equal true, sedna.autocommit
236
+ end
237
+ end
238
+
239
+ def test_autocommit_should_return_false_if_set_to_false
240
+ Sedna.connect @connection do |sedna|
241
+ sedna.autocommit = false
242
+ assert_equal false, sedna.autocommit
243
+ end
244
+ end
245
+
246
+ def test_autocommit_should_return_true_if_set_to_true_after_being_set_to_false
247
+ Sedna.connect @connection do |sedna|
248
+ sedna.autocommit = false
249
+ sedna.autocommit = true
250
+ assert_equal true, sedna.autocommit
251
+ end
252
+ end
253
+
254
+ def test_autocommit_should_return_true_if_argument_evaluates_to_true
255
+ Sedna.connect @connection do |sedna|
256
+ sedna.autocommit = "string evaluates to true"
257
+ assert_equal true, sedna.autocommit
258
+ end
259
+ end
260
+
261
+ def test_autocommit_should_return_false_if_argument_evaluates_to_false
262
+ Sedna.connect @connection do |sedna|
263
+ sedna.autocommit = nil
264
+ assert_equal false, sedna.autocommit
265
+ end
266
+ end
267
+
268
+ def test_autocommit_should_be_reenabled_after_transactions
269
+ Sedna.connect @connection do |sedna|
270
+ sedna.autocommit = true
271
+ sedna.transaction do end
272
+ assert_nothing_raised do
273
+ sedna.execute "<test/>"
274
+ end
275
+ end
276
+ end
277
+
278
+ # Test sedna.transaction.
279
+ def test_transaction_should_return_true_if_committed
280
+ Sedna.connect @connection do |sedna|
281
+ assert_equal true, sedna.transaction(){}
282
+ end
283
+ end
284
+
285
+ def test_transaction_should_raise_localjumperror_if_no_block_is_given
286
+ assert_raises LocalJumpError do
287
+ Sedna.connect @connection do |sedna|
288
+ sedna.transaction
289
+ end
290
+ end
291
+ end
292
+
293
+ def test_transaction_should_be_possible_with_autocommit
294
+ Sedna.connect @connection do |sedna|
295
+ sedna.autocommit = true
296
+ assert_nothing_raised do
297
+ sedna.transaction do end
298
+ end
299
+ end
300
+ end
301
+
302
+ def test_transaction_should_fail_with_transaction_error_if_another_transaction_is_started_inside_it
303
+ assert_raises Sedna::TransactionError do
304
+ Sedna.connect @connection do |sedna|
305
+ sedna.transaction do
306
+ sedna.transaction do end
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ def test_transaction_should_commit_if_block_given
313
+ Sedna.connect @connection do |sedna|
314
+ sedna.execute "drop document '#{name}'" rescue Sedna::Exception
315
+ sedna.execute "create document '#{name}'"
316
+ sedna.transaction do
317
+ sedna.execute "update insert <test>test</test> into doc('#{name}')"
318
+ end
319
+ assert_equal 1, sedna.execute("count(doc('#{name}')/test)").first.to_i
320
+ sedna.execute "drop document '#{name}'" rescue Sedna::Exception
321
+ end
322
+ end
323
+
324
+ def test_transaction_should_rollback_if_exception_is_raised_inside_block
325
+ Sedna.connect @connection do |sedna|
326
+ sedna.execute "drop document '#{name}'" rescue Sedna::Exception
327
+ sedna.execute "create document '#{name}'"
328
+ begin
329
+ sedna.transaction do
330
+ sedna.execute "update insert <test>test</test> into doc('#{name}')"
331
+ raise Exception
332
+ end
333
+ rescue Exception
334
+ end
335
+ assert_equal 0, sedna.execute("count(doc('#{name}')/test)").first.to_i
336
+ sedna.execute "drop document '#{name}'" rescue Sedna::Exception
337
+ end
338
+ end
339
+
340
+ def test_transaction_should_rollback_if_something_is_thrown_inside_block
341
+ Sedna.connect @connection do |sedna|
342
+ sedna.execute "drop document '#{name}'" rescue Sedna::Exception
343
+ sedna.execute "create document '#{name}'"
344
+ catch :ball do
345
+ sedna.transaction do
346
+ sedna.execute "update insert <test>test</test> into doc('#{name}')"
347
+ throw :ball
348
+ end
349
+ end
350
+ assert_equal 0, sedna.execute("count(doc('#{name}')/test)").first.to_i
351
+ sedna.execute "drop document '#{name}'" rescue Sedna::Exception
352
+ end
353
+ end
354
+
355
+ def test_transaction_should_raise_transaction_error_if_invalid_statement_caused_exception_but_it_was_rescued
356
+ assert_raises Sedna::TransactionError do
357
+ Sedna.connect @connection do |sedna|
358
+ sedna.transaction do
359
+ sedna.execute "FAILS" rescue Sedna::Exception
360
+ end
361
+ end
362
+ end
363
+ end
364
+
365
+ def test_transaction_should_reraise_exceptions_from_inside_block
366
+ Sedna.connect @connection do |sedna|
367
+ assert_raises Exception do
368
+ sedna.transaction do
369
+ raise Exception
370
+ end
371
+ end
372
+ end
373
+ end
374
+
375
+ def test_transaction_with_invalid_statements_should_cause_transaction_to_roll_back_once
376
+ exc = nil
377
+ begin
378
+ Sedna.connect @connection do |sedna|
379
+ sedna.transaction do
380
+ sedna.execute "FAILS"
381
+ end
382
+ end
383
+ rescue Sedna::Exception => exc
384
+ end
385
+ assert_equal "It is a dynamic error if evaluation of an expression relies on some part of the dynamic context that has not been assigned a value.", exc.message
386
+ end
387
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sedna
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rolf Timmermans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-08 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Ruby extension that provides a client library for the Sedna XML DBMS, making use of the official C driver of the Sedna project.
17
+ email: r.timmermans@voormedia.com
18
+ executables: []
19
+
20
+ extensions:
21
+ - ext/extconf.rb
22
+ extra_rdoc_files:
23
+ - CHANGES
24
+ - README
25
+ - ext/sedna.c
26
+ files:
27
+ - ext/extconf.rb
28
+ - ext/sedna.c
29
+ - test/test_sedna.rb
30
+ - CHANGES
31
+ - README
32
+ has_rdoc: true
33
+ homepage: http://sedna.rubyforge.org/
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --title
37
+ - Sedna XML DBMS client library for Ruby
38
+ - --main
39
+ - README
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements:
55
+ - Sedna XML DBMS C driver (library and headers).
56
+ rubyforge_project: sedna
57
+ rubygems_version: 1.3.1
58
+ signing_key:
59
+ specification_version: 2
60
+ summary: Sedna XML DBMS client library.
61
+ test_files:
62
+ - test/test_sedna.rb