sedna 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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