fb 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,218 @@
1
+ # Ruby Firebird Extension Library
2
+ #
3
+ # Please see the RDoc documentation for API details.
4
+ #
5
+ # What follows is a brief overview of how to use this library.
6
+ # This file is executable. However, you may have to adjust the database connection parameters.
7
+
8
+ # Load the library
9
+
10
+ require 'fb'
11
+
12
+ # The library contains on module, Fb.
13
+ # Within Fb are four primary classes, Fb::Database, Fb::Connection, Fb::Cursor and Fb::Error.
14
+ # For convenience, we'll include these classes into the current context.
15
+
16
+ include Fb
17
+
18
+ # The Database class acts as a factory for Connections.
19
+ # It can also create and drop databases.
20
+
21
+ db = Database.new(
22
+ :database => "localhost:c:/var/fbdata/readme.fdb",
23
+ :username => 'sysdba',
24
+ :password => 'masterkey')
25
+
26
+ # :database is the only parameter without a default.
27
+
28
+ # Let's connect to the database, creating it if it doesn't already exist.
29
+
30
+ conn = db.connect rescue db.create.connect
31
+
32
+ # We'll need to create the database schema if this is a new database.
33
+
34
+ conn.execute("CREATE TABLE TEST (ID INT NOT NULL PRIMARY KEY, NAME VARCHAR(20))") if !conn.table_names.include?("TEST")
35
+
36
+ # Let's insert some test data using a parameterized query. Note the use of question marks for place holders.
37
+
38
+ 10.times {|id| conn.execute("INSERT INTO TEST VALUES (?, ?)", id, "John #{id}") }
39
+
40
+ # Here we'll conduct a spot check of the data we have just inserted.
41
+
42
+ ary = conn.query("SELECT * FROM TEST WHERE ID = 0 OR ID = 9")
43
+ ary.each {|row| puts "ID: #{row[0]}, Name: #{row[1]}" }
44
+
45
+ # Don't like tying yourself down to column offsets?
46
+
47
+ ary = conn.query(:hash, "SELECT * FROM TEST WHERE ID = 0 OR ID = 9")
48
+ ary.each {|row| puts "ID: #{row['ID']}, Name: #{row['NAME']}" }
49
+
50
+ # Let's change all the names.
51
+
52
+ total_updated = 0
53
+ conn.execute("SELECT ID FROM TEST") do |cursor|
54
+ cursor.each do |row|
55
+ updated = conn.execute("UPDATE TEST SET NAME = ? WHERE ID = ?", "John Doe #{row[0]}", row[0])
56
+ total_updated += updated
57
+ end
58
+ end
59
+ puts "We updated a total of #{total_updated} rows."
60
+
61
+ # Actually, I only need the first and last rows.
62
+
63
+ deleted = conn.execute("DELETE FROM TEST WHERE ID > ? AND ID < ?", 0, 9)
64
+ puts "Expecting to delete 8 rows, we have deleted #{deleted}."
65
+
66
+ # Using a simple, per-connection transaction strategy, we'll demonstrate rollback and commit.
67
+
68
+ conn.transaction
69
+
70
+ for i in 10..1000
71
+ conn.execute("INSERT INTO TEST VALUES (?, ?)", i, "Jane #{i}")
72
+ end
73
+
74
+ # What was I thinking? Let's roll that back.
75
+
76
+ conn.rollback
77
+
78
+ # Are they still there?
79
+
80
+ janes = conn.query("SELECT * FROM TEST WHERE ID >= 10")
81
+ puts "Expecting zero rows, we find #{janes.size} Janes."
82
+
83
+ # Let's try again.
84
+
85
+ conn.transaction
86
+
87
+ 10.upto(19) do |i|
88
+ conn.execute("INSERT INTO TEST (ID, NAME) VALUES (?, ?)", i, "Sue #{i}")
89
+ end
90
+
91
+ # That's more like it.
92
+
93
+ conn.commit
94
+
95
+ # It's important to close your cursor when you're done with it.
96
+
97
+ cursor = conn.execute("SELECT * FROM TEST")
98
+ while row = cursor.fetch(:hash)
99
+ break if row['NAME'] =~ /e 13/
100
+ end
101
+ cursor.close
102
+
103
+ # You may find it easier to use a block.
104
+
105
+ conn.execute("SELECT * FROM TEST") do |cursor|
106
+ while row = cursor.fetch(:hash)
107
+ break if row['NAME'] =~ /e 13/
108
+ end
109
+ end
110
+
111
+ # That way the cursor always gets closed, even if an exception is raised.
112
+ # Transactions work the same way. Here's one that should work.
113
+
114
+ conn.transaction do
115
+ 20.upto(25) do |i|
116
+ conn.execute("INSERT INTO TEST VALUES (?, ?)", i, "George #{i}")
117
+ end
118
+ end
119
+
120
+ # The transaction is automatically committed if no exception is raised in the block.
121
+ # We expect trouble in this next example, on account of our primary key.
122
+
123
+ begin
124
+ conn.transaction do
125
+ execute("INSERT INTO TEST VALUES (0, 'Trouble')")
126
+ puts "This line should never be executed."
127
+ end
128
+ rescue
129
+ puts "Rescued."
130
+ end
131
+
132
+ # Is it there?
133
+
134
+ trouble = conn.query("SELECT * FROM TEST WHERE NAME = 'Trouble'")
135
+ puts "Expecting zero rows, we find #{trouble.size} 'Trouble' rows."
136
+
137
+ # How about demonstrating a more advanced transaction?
138
+ # First, we'll start a snapshot transaction.
139
+ # This should give us a consistent view of the database.
140
+
141
+ conn.transaction("SNAPSHOT") do
142
+
143
+ # Then, we'll open another connection to the database and insert some rows.
144
+
145
+ Database.connect(:database => "localhost:c:/var/fbdata/readme.fdb") do |conn2|
146
+ for i in 100...110
147
+ conn2.execute("INSERT INTO TEST VALUES (?, ?)", i, "Hi #{i}")
148
+ end
149
+ end
150
+
151
+ # Now, let's see if we see them.
152
+
153
+ hi = conn.query("SELECT * FROM TEST WHERE ID >= ?", 100)
154
+ puts "Expecting zero rows, we find #{hi.size} Hi rows."
155
+ end
156
+
157
+ # Now we will try our example again, only with a READ COMMITTED transaction.
158
+
159
+ conn.transaction("READ COMMITTED") do
160
+
161
+ # Then, we'll open another connection to the database and insert some rows.
162
+
163
+ Database.connect(:database => "localhost:c:/var/fbdata/readme.fdb") do |conn2|
164
+ for i in 200...210
165
+ conn2.execute("INSERT INTO TEST VALUES (?, ?)", i, "Hello #{i}")
166
+ end
167
+ end
168
+
169
+ # Now, let's see if we see them.
170
+
171
+ hello = conn.query("SELECT * FROM TEST WHERE ID >= ?", 200)
172
+ puts "Expecting ten rows, we find #{hello.size}."
173
+ end
174
+
175
+ # Don't forget to close up shop.
176
+
177
+ conn.close
178
+
179
+ # We could have called conn.drop.
180
+ # We could still call db.drop
181
+
182
+ __END__
183
+ 5. Changes
184
+
185
+ 0.01 - 1999-06-11 - baseline
186
+ 0.02 - 1999-07-16 - memory bug in ibconn_cursor, English documents.
187
+ 0.03 - ???
188
+ 0.04 - 2001-08-17 - insert, fetch blobs as strings
189
+ 0.05 - 2001-09-05 - add block syntax for Connection#connect and Connection#execute
190
+ 0.06 - 2004-11-02 - ???
191
+ 0.07 - 2004-11-03 - ???
192
+
193
+ Change Log
194
+
195
+ 2006-05-18 - 2006-06-04
196
+
197
+ * Forked to create Fb extension.
198
+ * Converted from K&R to ANSI C.
199
+ * Added ability to create and drop databases.
200
+ * Created near-comprehensive unit tests.
201
+ * Renamed functions for consistency and improved style.
202
+ * Improved error message reporting.
203
+ * Added the option of returning rows as Hashes as well as tuples.
204
+ * Fixed bug inserting blobs larger than 64K.
205
+ * Added methods to retrieve lists of tables, generators, views, stored procedures and roles.
206
+ * Added .NET-style connection strings.
207
+ * Improved reporting on column types.
208
+ * Added the option to report column names in lowercase where they were not already in mixed case (this feature is for Rails).
209
+ * Changed automatic transaction semantics so automatically-started transactions are also committed automatically.
210
+ * Converted from using a single, global transaction to a transaction per connection.
211
+ * Converted from using global buffers to per-cursor input and output buffers.
212
+ * Added RDoc documentation.
213
+ * Insert, update and delete statements may be given an array of tuples for a parameter and will be executed once per tuple.
214
+ * Rewrote README to match new API.
215
+
216
+ 2007-12-11
217
+
218
+ * Properly scaled NUMERIC and DECIMAL types when inserting with parameters.
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ # = Windows
3
+ # === Sample of Makefile creation:
4
+ # <tt>ruby extconf.rb --with-opt-dir=c:/Firebird --with-dotnet-dir=C:\PROGRA~1\MICROS~2.NET\Vc7 --with-win32-dir=C:\PROGRA~1\MI9547~1</tt>
5
+ # === Notes
6
+ # * Windows build currently only compiles using VC7 (Visual Studio .NET 2003).
7
+ # * mkmf doesn't like directories with spaces, hence the 8.3 notation in the example above.
8
+ # = Linux (Intel)
9
+ # === Notes
10
+ # * Build seems to "just work."
11
+ # * Unit tests take about 10 times as long to complete using Firebird Classic. Default xinetd.conf settings may not allow the tests to complete due to the frequency with which new attachments are made.
12
+ # = Linux (Other)
13
+ # * Volunteers?
14
+ # = Mac OS X (PowerPC)
15
+ # * Not currently tested.
16
+ # = Mac OS X (Intel)
17
+ # * Works
18
+ require 'mkmf'
19
+
20
+ libs = %w/ fbclient gds /
21
+
22
+ case RUBY_PLATFORM
23
+ when /bccwin32/
24
+ libs.push "fbclient_bor"
25
+ when /mswin32/
26
+ $CFLAGS = $CFLAGS + " -DOS_WIN32"
27
+ libs.push "fbclient_ms"
28
+ when /darwin/
29
+ #$CFLAGS = $CFLAGS + " -DOS_UNIX"
30
+ $CPPFLAGS += " -I/Library/Frameworks/Firebird.framework/Headers"
31
+ $LDFLAGS += " -framework Firebird"
32
+ when /linux/
33
+ $CFLAGS = $CFLAGS + " -DOS_UNIX"
34
+ end
35
+
36
+ dir_config("firebird")
37
+
38
+ test_func = "isc_attach_database"
39
+
40
+ case RUBY_PLATFORM
41
+ when /mswin32/
42
+ libs.find {|lib| have_library(lib) } and
43
+ have_func(test_func, ["ibase.h"])
44
+ else
45
+ libs.find {|lib| have_library(lib, test_func) }
46
+ end
47
+
48
+ create_makefile("fb")
data/fb.c ADDED
@@ -0,0 +1,3008 @@
1
+ /*
2
+ * fb.c
3
+ * A module to access the Firebird database from Ruby.
4
+ * Fork of interbase.c to fb.c by Brent Rowland.
5
+ * All changes, improvements and associated bugs Copyright (C) 2006 Brent Rowland and Target Training International.
6
+ * License to all changes, improvements and bugs is granted under the same terms as the original and/or
7
+ * the Ruby license, whichever is most applicable.
8
+ * Based on interbase.c
9
+ *
10
+ * Copyright (C) 1999 by NaCl inc.
11
+ * Copyright (C) 1997,1998 by RIOS Corporation
12
+ *
13
+ * Permission to use, copy, modify, and distribute this software and its
14
+ * documentation for any purpose and without fee is hereby granted, provided
15
+ * that the above copyright notice appear in all copies.
16
+ * RIOS Corporation makes no representations about the suitability of
17
+ * this software for any purpose. It is provided "as is" without express
18
+ * or implied warranty. By use of this software the user agrees to
19
+ * indemnify and hold harmless RIOS Corporation from any claims or
20
+ * liability for loss arising out of such use.
21
+ */
22
+
23
+ #include "ruby.h"
24
+ #include <stdio.h>
25
+ #include <string.h>
26
+ #include <limits.h>
27
+ #include <ibase.h>
28
+ #include <float.h>
29
+ #include <time.h>
30
+
31
+ #define SQLDA_COLSINIT 50
32
+ #define SQLCODE_NOMORE 100
33
+ #define TPBBUFF_ALLOC 64
34
+ #define CMND_DELIMIT " \t\n\r\f"
35
+ #define LIST_DELIMIT ", \t\n\r\f"
36
+ #define META_NAME_MAX 31
37
+
38
+ /* Statement type */
39
+ #define STATEMENT_DDL 1
40
+ #define STATEMENT_DML 0
41
+
42
+ /* Execute process flag */
43
+ #define EXECF_EXECDML 0
44
+ #define EXECF_SETPARM 1
45
+
46
+ static VALUE rb_mFb;
47
+ static VALUE rb_cFbDatabase;
48
+ static VALUE rb_cFbConnection;
49
+ static VALUE rb_cFbCursor;
50
+ static VALUE rb_cFbSqlType;
51
+ static VALUE rb_cFbGlobal;
52
+ static VALUE rb_eFbError;
53
+ static VALUE rb_sFbField;
54
+ static VALUE rb_sFbIndex;
55
+
56
+ static VALUE rb_cDate;
57
+
58
+ static char isc_info_stmt[] = { isc_info_sql_stmt_type };
59
+ static char isc_info_buff[16];
60
+ static char isc_tpb_0[] = {
61
+ isc_tpb_version1, isc_tpb_write,
62
+ isc_tpb_concurrency, isc_tpb_nowait
63
+ };
64
+
65
+ /* structs */
66
+
67
+ /* DB handle and TR parameter block list structure */
68
+ typedef struct
69
+ {
70
+ isc_db_handle *dbb_ptr ;
71
+ long tpb_len ;
72
+ char *tpb_ptr ;
73
+ } ISC_TEB ; /* transaction existence block */
74
+
75
+ /* InterBase varchar structure */
76
+ typedef struct
77
+ {
78
+ short vary_length;
79
+ char vary_string[1];
80
+ } VARY;
81
+
82
+ struct FbConnection {
83
+ isc_db_handle db; /* DB handle */
84
+ isc_tr_handle transact; /* transaction handle */
85
+ VALUE cursor;
86
+ unsigned short dialect;
87
+ unsigned short db_dialect;
88
+ short downcase_names;
89
+ int dropped;
90
+ struct FbConnection *next;
91
+ };
92
+
93
+ static struct FbConnection *fb_connection_list;
94
+
95
+ struct FbCursor {
96
+ int open;
97
+ isc_tr_handle auto_transact;
98
+ isc_stmt_handle stmt;
99
+ XSQLDA *i_sqlda;
100
+ XSQLDA *o_sqlda;
101
+ char *i_buffer;
102
+ long i_buffer_size;
103
+ char *o_buffer;
104
+ long o_buffer_size;
105
+ VALUE fields_ary;
106
+ VALUE fields_hash;
107
+ VALUE connection;
108
+ };
109
+
110
+ typedef struct trans_opts
111
+ {
112
+ char *option1;
113
+ char *option2;
114
+ char optval;
115
+ short position;
116
+ struct trans_opts *sub_opts;
117
+ } trans_opts;
118
+
119
+ /* global data */
120
+ static isc_tr_handle global_transact = 0; /* transaction handle */
121
+ static int connection_count = 0;
122
+
123
+ /* global utilities */
124
+
125
+ #define ALIGN(n, b) ((n + b - 1) & ~(b - 1))
126
+ #define UPPER(c) (((c) >= 'a' && (c)<= 'z') ? (c) - 'a' + 'A' : (c))
127
+ #define FREE(p) if (p) { xfree(p); p = 0; }
128
+ #define SETNULL(p) if (p && strlen(p) == 0) { p = 0; }
129
+
130
+ static long calculate_buffsize(XSQLDA *sqlda)
131
+ {
132
+ XSQLVAR *var;
133
+ long cols;
134
+ short dtp;
135
+ long offset;
136
+ long alignment;
137
+ long length;
138
+ long count;
139
+
140
+ cols = sqlda->sqld;
141
+ for (var = sqlda->sqlvar, offset = 0,count = 0; count < cols; var++,count++) {
142
+ length = alignment = var->sqllen;
143
+ dtp = var->sqltype & ~1;
144
+
145
+ if (dtp == SQL_TEXT) {
146
+ alignment = 1;
147
+ } else if (dtp == SQL_VARYING) {
148
+ length += sizeof(short);
149
+ alignment = sizeof(short);
150
+ }
151
+
152
+ offset = ALIGN(offset, alignment);
153
+ offset += length;
154
+ offset = ALIGN(offset, sizeof(short));
155
+ offset += sizeof(short);
156
+ }
157
+
158
+ return offset;
159
+ }
160
+
161
+ static VALUE fb_error_msg(const ISC_STATUS *isc_status)
162
+ {
163
+ char msg[512];
164
+ VALUE result = rb_str_new(NULL, 0);
165
+ while (fb_interpret(msg, 512, &isc_status))
166
+ {
167
+ result = rb_str_cat(result, msg, strlen(msg));
168
+ result = rb_str_cat(result, "\n", strlen("\n"));
169
+ }
170
+ return result;
171
+ }
172
+
173
+ struct time_object {
174
+ struct timeval tv;
175
+ struct tm tm;
176
+ int gmt;
177
+ int tm_got;
178
+ };
179
+
180
+ #define GetTimeval(obj, tobj) \
181
+ Data_Get_Struct(obj, struct time_object, tobj)
182
+
183
+ static VALUE fb_mktime(struct tm *tm)
184
+ {
185
+ return rb_funcall(
186
+ rb_cTime, rb_intern("utc"), 6,
187
+ INT2FIX(tm->tm_year), INT2FIX(tm->tm_mon), INT2FIX(tm->tm_mday),
188
+ INT2FIX(tm->tm_hour), INT2FIX(tm->tm_min), INT2FIX(tm->tm_sec));
189
+ }
190
+
191
+ static VALUE fb_mkdate(struct tm *tm)
192
+ {
193
+ return rb_funcall(
194
+ rb_cDate, rb_intern("civil"), 3,
195
+ INT2FIX(1900 + tm->tm_year), INT2FIX(tm->tm_mon + 1), INT2FIX(tm->tm_mday));
196
+ }
197
+
198
+ static int responds_like_date(VALUE obj)
199
+ {
200
+ return rb_respond_to(obj, rb_intern("year")) &&
201
+ rb_respond_to(obj, rb_intern("month")) &&
202
+ rb_respond_to(obj, rb_intern("day"));
203
+ }
204
+ static void tm_from_date(struct tm *tm, VALUE date)
205
+ {
206
+ VALUE year, month, day;
207
+
208
+ if (!responds_like_date(date)) {
209
+ VALUE s = rb_funcall(date, rb_intern("to_s"), 0);
210
+ date = rb_funcall(rb_cDate, rb_intern("parse"), 1, s);
211
+ }
212
+ year = rb_funcall(date, rb_intern("year"), 0);
213
+ month = rb_funcall(date, rb_intern("month"), 0);
214
+ day = rb_funcall(date, rb_intern("day"), 0);
215
+ memset(tm, 0, sizeof(struct tm));
216
+ tm->tm_year = FIX2INT(year) - 1900;
217
+ tm->tm_mon = FIX2INT(month) - 1;
218
+ tm->tm_mday = FIX2INT(day);
219
+ }
220
+
221
+ static void tm_from_timestamp(struct tm *tm, VALUE obj)
222
+ {
223
+ struct time_object *tobj;
224
+
225
+ if (!rb_obj_is_kind_of(obj, rb_cTime) && rb_respond_to(obj, rb_intern("to_str")))
226
+ {
227
+ VALUE s = rb_funcall(obj, rb_intern("to_str"), 0);
228
+ obj = rb_funcall(rb_cTime, rb_intern("parse"), 1, s);
229
+ }
230
+
231
+ GetTimeval(obj, tobj);
232
+ *tm = tobj->tm;
233
+ }
234
+
235
+ static VALUE long_from_obj(VALUE obj)
236
+ {
237
+ ID id_to_str = rb_intern("to_str");
238
+ if (TYPE(obj) != T_FIXNUM && rb_respond_to(obj, id_to_str))
239
+ {
240
+ VALUE s = rb_funcall(obj, id_to_str, 0);
241
+ obj = rb_funcall(s, rb_intern("to_i"), 0);
242
+ }
243
+ return obj;
244
+ }
245
+
246
+ static VALUE ll_from_obj(VALUE obj)
247
+ {
248
+ if (TYPE(obj) == T_STRING)
249
+ {
250
+ obj = rb_funcall(obj, rb_intern("to_i"), 0);
251
+ }
252
+ return obj;
253
+ }
254
+
255
+ static VALUE double_from_obj(VALUE obj)
256
+ {
257
+ if (TYPE(obj) == T_STRING)
258
+ {
259
+ obj = rb_funcall(obj, rb_intern("to_f"), 0);
260
+ }
261
+ return obj;
262
+ }
263
+
264
+ static VALUE fb_sql_type_from_code(int code, int subtype)
265
+ {
266
+ char *sql_type = NULL;
267
+ switch(code) {
268
+ case SQL_TEXT:
269
+ case blr_text:
270
+ sql_type = "CHAR";
271
+ break;
272
+ case SQL_VARYING:
273
+ case blr_varying:
274
+ sql_type = "VARCHAR";
275
+ break;
276
+ case SQL_SHORT:
277
+ case blr_short:
278
+ switch (subtype) {
279
+ case 0: sql_type = "SMALLINT"; break;
280
+ case 1: sql_type = "NUMERIC"; break;
281
+ case 2: sql_type = "DECIMAL"; break;
282
+ }
283
+ break;
284
+ case SQL_LONG:
285
+ case blr_long:
286
+ switch (subtype) {
287
+ case 0: sql_type = "INTEGER"; break;
288
+ case 1: sql_type = "NUMERIC"; break;
289
+ case 2: sql_type = "DECIMAL"; break;
290
+ }
291
+ break;
292
+ case SQL_FLOAT:
293
+ case blr_float:
294
+ sql_type = "FLOAT";
295
+ break;
296
+ case SQL_DOUBLE:
297
+ case blr_double:
298
+ switch (subtype) {
299
+ case 0: sql_type = "DOUBLE PRECISION"; break;
300
+ case 1: sql_type = "NUMERIC"; break;
301
+ case 2: sql_type = "DECIMAL"; break;
302
+ }
303
+ break;
304
+ case SQL_D_FLOAT:
305
+ case blr_d_float:
306
+ sql_type = "DOUBLE PRECISION";
307
+ break;
308
+ case SQL_TIMESTAMP:
309
+ case blr_timestamp:
310
+ sql_type = "TIMESTAMP";
311
+ break;
312
+ case SQL_BLOB:
313
+ case blr_blob:
314
+ sql_type = "BLOB";
315
+ break;
316
+ case SQL_ARRAY:
317
+ sql_type = "ARRAY";
318
+ break;
319
+ case SQL_QUAD:
320
+ case blr_quad:
321
+ sql_type = "DECIMAL";
322
+ break;
323
+ case SQL_TYPE_TIME:
324
+ case blr_sql_time:
325
+ sql_type = "TIME";
326
+ break;
327
+ case SQL_TYPE_DATE:
328
+ case blr_sql_date:
329
+ sql_type = "DATE";
330
+ break;
331
+ case SQL_INT64:
332
+ case blr_int64:
333
+ switch (subtype) {
334
+ case 0: sql_type = "BIGINT"; break;
335
+ case 1: sql_type = "NUMERIC"; break;
336
+ case 2: sql_type = "DECIMAL"; break;
337
+ }
338
+ break;
339
+ default:
340
+ printf("Unknown: %d, %d\n", code, subtype);
341
+ sql_type = "UNKNOWN";
342
+ break;
343
+ }
344
+ return rb_str_new2(sql_type);
345
+ }
346
+
347
+ static void fb_error_check(long *isc_status)
348
+ {
349
+ short code = isc_sqlcode(isc_status);
350
+
351
+ if (code != 0) {
352
+ char buf[1024];
353
+ VALUE exc, msg, msg1, msg2;
354
+
355
+ isc_sql_interprete(code, buf, 1024);
356
+ msg1 = rb_str_new2(buf);
357
+ msg2 = fb_error_msg(isc_status);
358
+ msg = rb_str_cat(msg1, "\n", strlen("\n"));
359
+ msg = rb_str_concat(msg, msg2);
360
+
361
+ exc = rb_exc_new3(rb_eFbError, msg);
362
+ rb_iv_set(exc, "error_code", INT2FIX(code));
363
+ rb_exc_raise(exc);
364
+ }
365
+ }
366
+
367
+ static void fb_error_check_warn(long *isc_status)
368
+ {
369
+ short code = isc_sqlcode(isc_status);
370
+ if (code != 0) {
371
+ char buf[1024];
372
+ isc_sql_interprete(code, buf, 1024);
373
+ rb_warning("%s(%d)", buf, code);
374
+ }
375
+ }
376
+
377
+ static XSQLDA* sqlda_alloc(long cols)
378
+ {
379
+ XSQLDA *sqlda;
380
+
381
+ sqlda = (XSQLDA*)xmalloc(XSQLDA_LENGTH(cols));
382
+ #ifdef SQLDA_CURRENT_VERSION
383
+ sqlda->version = SQLDA_CURRENT_VERSION;
384
+ #else
385
+ sqlda->version = SQLDA_VERSION1;
386
+ #endif
387
+ sqlda->sqln = cols;
388
+ sqlda->sqld = 0;
389
+ return sqlda;
390
+ }
391
+
392
+ static VALUE cursor_close _((VALUE));
393
+ static VALUE cursor_drop _((VALUE));
394
+ static VALUE cursor_execute _((int, VALUE*, VALUE));
395
+ static VALUE cursor_fetchall _((int, VALUE*, VALUE));
396
+
397
+ static void fb_cursor_mark();
398
+ static void fb_cursor_free();
399
+
400
+ /* connection utilities */
401
+ static void fb_connection_check(struct FbConnection *fb_connection)
402
+ {
403
+ if (fb_connection->db == 0) {
404
+ rb_raise(rb_eFbError, "closed db connection");
405
+ }
406
+ }
407
+
408
+ static void global_close_cursors()
409
+ {
410
+ struct FbConnection *list = fb_connection_list;
411
+ int i;
412
+
413
+ while (list) {
414
+ for (i = 0; i < RARRAY(list->cursor)->len; i++) {
415
+ cursor_close(RARRAY(list->cursor)->ptr[i]);
416
+ }
417
+ list = list->next;
418
+ }
419
+ }
420
+
421
+ static void fb_connection_close_cursors(struct FbConnection *fb_connection)
422
+ {
423
+ int i;
424
+
425
+ for (i = 0; i < RARRAY(fb_connection->cursor)->len; i++) {
426
+ cursor_close(RARRAY(fb_connection->cursor)->ptr[i]);
427
+ }
428
+ }
429
+
430
+ static void fb_connection_drop_cursors(struct FbConnection *fb_connection)
431
+ {
432
+ int i;
433
+
434
+ for (i = 0; i < RARRAY(fb_connection->cursor)->len; i++) {
435
+ cursor_drop(RARRAY(fb_connection->cursor)->ptr[i]);
436
+ }
437
+ RARRAY(fb_connection->cursor)->len = 0;
438
+ }
439
+
440
+ static void fb_connection_remove(struct FbConnection *fb_connection)
441
+ {
442
+ if (fb_connection_list != NULL) {
443
+ if (fb_connection_list == fb_connection) {
444
+ fb_connection_list = fb_connection_list->next;
445
+ } else {
446
+ struct FbConnection *list = fb_connection_list;
447
+ while (list->next) {
448
+ if (list->next == fb_connection) {
449
+ list->next = fb_connection->next;
450
+ break;
451
+ }
452
+ list = list->next;
453
+ }
454
+ }
455
+ fb_connection->db = 0;
456
+ connection_count--;
457
+ }
458
+ }
459
+
460
+ static void fb_connection_disconnect(struct FbConnection *fb_connection)
461
+ {
462
+ long isc_status[20];
463
+
464
+ if (fb_connection->transact) {
465
+ isc_commit_transaction(isc_status, &fb_connection->transact);
466
+ fb_error_check(isc_status);
467
+ }
468
+ if (fb_connection->dropped) {
469
+ isc_drop_database(isc_status, &fb_connection->db);
470
+ } else {
471
+ isc_detach_database(isc_status, &fb_connection->db);
472
+ }
473
+ fb_error_check(isc_status);
474
+ fb_connection_remove(fb_connection);
475
+ }
476
+
477
+ static void fb_connection_disconnect_warn(struct FbConnection *fb_connection)
478
+ {
479
+ long isc_status[20];
480
+
481
+ if (fb_connection->transact) {
482
+ isc_commit_transaction(isc_status, &fb_connection->transact);
483
+ fb_error_check_warn(isc_status);
484
+ }
485
+ isc_detach_database(isc_status, &fb_connection->db);
486
+ fb_error_check_warn(isc_status);
487
+ fb_connection_remove(fb_connection);
488
+ }
489
+
490
+ static void fb_connection_mark(struct FbConnection *fb_connection)
491
+ {
492
+ rb_gc_mark(fb_connection->cursor);
493
+ }
494
+
495
+ static void fb_connection_free(struct FbConnection *fb_connection)
496
+ {
497
+ if (fb_connection->db) {
498
+ fb_connection_disconnect_warn(fb_connection);
499
+ }
500
+ xfree(fb_connection);
501
+ }
502
+
503
+ static struct FbConnection* fb_connection_check_retrieve(VALUE data)
504
+ {
505
+ if (TYPE(data) != T_DATA || RDATA(data)->dfree != (void *)fb_connection_free) {
506
+ rb_raise(rb_eTypeError,
507
+ "Wrong argument type %s (expected Fb::Connection)",
508
+ rb_class2name(CLASS_OF(data)));
509
+ }
510
+ return (struct FbConnection*)RDATA(data)->data;
511
+ }
512
+
513
+ static unsigned short fb_connection_db_SQL_Dialect(struct FbConnection *fb_connection)
514
+ {
515
+ long isc_status[20];
516
+ long dialect;
517
+ long length;
518
+ char db_info_command = isc_info_db_SQL_dialect;
519
+
520
+ /* Get the db SQL Dialect */
521
+ isc_database_info(isc_status, &fb_connection->db,
522
+ 1, &db_info_command,
523
+ sizeof(isc_info_buff), isc_info_buff);
524
+ fb_error_check(isc_status);
525
+
526
+ if (isc_info_buff[0] == isc_info_db_SQL_dialect) {
527
+ length = isc_vax_integer(&isc_info_buff[1], 2);
528
+ dialect = isc_vax_integer(&isc_info_buff[3], (short)length);
529
+ } else {
530
+ dialect = 1;
531
+ }
532
+ return dialect;
533
+ }
534
+
535
+ static unsigned short fb_connection_dialect(struct FbConnection *fb_connection)
536
+ {
537
+ return fb_connection->dialect;
538
+ }
539
+
540
+ static unsigned short fb_connection_db_dialect(struct FbConnection *fb_connection)
541
+ {
542
+ return fb_connection->db_dialect;
543
+ }
544
+
545
+ /* Transaction option list */
546
+
547
+ static trans_opts rcom_opt_S[] =
548
+ {
549
+ "NO", "RECORD_VERSION", isc_tpb_no_rec_version, -1, 0,
550
+ "RECORD_VERSION", 0, isc_tpb_rec_version, -1, 0,
551
+ "*", 0, isc_tpb_no_rec_version, -1, 0,
552
+ 0, 0, 0, 0, 0
553
+ };
554
+
555
+
556
+ static trans_opts read_opt_S[] =
557
+ {
558
+ "WRITE", 0, isc_tpb_write, 1, 0,
559
+ "ONLY", 0, isc_tpb_read, 1, 0,
560
+ "COMMITTED", 0, isc_tpb_read_committed, 2, rcom_opt_S,
561
+ 0, 0, 0, 0, 0
562
+ };
563
+
564
+
565
+ static trans_opts snap_opt_S[] =
566
+ {
567
+ "TABLE", "STABILITY", isc_tpb_consistency, 2, 0,
568
+ "*", 0, isc_tpb_concurrency, 2, 0,
569
+ 0, 0, 0, 0, 0
570
+ };
571
+
572
+
573
+ static trans_opts isol_opt_S[] =
574
+ {
575
+ "SNAPSHOT", 0, 0, 0, snap_opt_S,
576
+ "READ", "COMMITTED", isc_tpb_read_committed, 2, rcom_opt_S,
577
+ 0, 0, 0, 0, 0
578
+ };
579
+
580
+
581
+ static trans_opts trans_opt_S[] =
582
+ {
583
+ "READ", 0, 0, 0, read_opt_S,
584
+ "WAIT", 0, isc_tpb_wait, 3, 0,
585
+ "NO", "WAIT", isc_tpb_nowait, 3, 0,
586
+ "ISOLATION", "LEVEL", 0, 0, isol_opt_S,
587
+ "SNAPSHOT", 0, 0, 0, snap_opt_S,
588
+ "RESERVING", 0, -1, 0, 0,
589
+ 0, 0, 0, 0, 0
590
+ };
591
+
592
+ /* Name1 Name2 Option value Position Sub-option */
593
+
594
+ #define RESV_TABLEEND "FOR"
595
+ #define RESV_SHARED "SHARED"
596
+ #define RESV_PROTECTD "PROTECTED"
597
+ #define RESV_READ "READ"
598
+ #define RESV_WRITE "WRITE"
599
+ #define RESV_CONTINUE ','
600
+
601
+ static char* trans_parseopts(VALUE opt, int *tpb_len)
602
+ {
603
+ char *s, *trans;
604
+ long used;
605
+ long size;
606
+ char *tpb;
607
+ trans_opts *curr_p;
608
+ trans_opts *target_p;
609
+ char *check1_p;
610
+ char *check2_p;
611
+ int count;
612
+ int next_c;
613
+ char check_f[4];
614
+ char *resv_p;
615
+ char *resend_p;
616
+ char *tblend_p;
617
+ int tbl_len;
618
+ int res_first;
619
+ int res_count;
620
+ int ofs;
621
+ char sp_prm;
622
+ char rw_prm;
623
+ int cont_f;
624
+ char *desc = 0;
625
+
626
+ /* Initialize */
627
+ s = STR2CSTR(opt);
628
+ trans = ALLOCA_N(char, strlen(s)+1);
629
+ strcpy(trans, s);
630
+ s = trans;
631
+ while (*s) {
632
+ *s = UPPER(*s);
633
+ s++;
634
+ }
635
+
636
+ used = 0;
637
+ size = 0;
638
+ tpb = NULL;
639
+ memset((void *)check_f, 0, sizeof(check_f));
640
+
641
+ /* Set the default transaction option */
642
+ tpb = (char*)xmalloc(TPBBUFF_ALLOC);
643
+ size = TPBBUFF_ALLOC;
644
+ memcpy((void*)tpb, (void*)isc_tpb_0, sizeof(isc_tpb_0));
645
+ used = sizeof(isc_tpb_0);
646
+
647
+ /* Analize the transaction option strings */
648
+ curr_p = trans_opt_S;
649
+ check1_p = strtok(trans, CMND_DELIMIT);
650
+ if (check1_p) {
651
+ check2_p = strtok(0, CMND_DELIMIT);
652
+ } else {
653
+ check2_p = 0;
654
+ }
655
+ while (curr_p) {
656
+ target_p = 0;
657
+ next_c = 0;
658
+ for (count = 0; curr_p[count].option1; count++) {
659
+ if (!strcmp(curr_p[count].option1, "*")) {
660
+ target_p = &curr_p[count];
661
+ break;
662
+ } else if (check1_p && !strcmp(check1_p, curr_p[count].option1)) {
663
+ if (!curr_p[count].option2) {
664
+ next_c = 1;
665
+ target_p = &curr_p[count];
666
+ break;
667
+ } else if (check2_p && !strcmp(check2_p, curr_p[count].option2)) {
668
+ next_c = 2;
669
+ target_p = &curr_p[count];
670
+ break;
671
+ }
672
+ }
673
+ }
674
+
675
+ if (!target_p) {
676
+ desc = "Illegal transaction option was specified";
677
+ goto error;
678
+ }
679
+
680
+ /* Set the transaction option */
681
+ if (target_p->optval > '\0') {
682
+ if (target_p->position > 0) {
683
+ if (check_f[target_p->position]) {
684
+ desc = "Duplicate transaction option was specified";
685
+ goto error;
686
+ }
687
+ tpb[target_p->position] = target_p->optval;
688
+ check_f[target_p->position] = 1;
689
+ } else {
690
+ if (used + 1 > size) {
691
+ tpb = (char *)realloc(tpb, size + TPBBUFF_ALLOC);
692
+ size += TPBBUFF_ALLOC;
693
+ }
694
+ tpb[used] = target_p->optval;
695
+ used++;
696
+ }
697
+ } else if (target_p->optval) { /* RESERVING ... FOR */
698
+ if (check_f[0]) {
699
+ desc = "Duplicate transaction option was specified";
700
+ goto error;
701
+ }
702
+ resv_p = check2_p;
703
+ if (!resv_p || !strcmp(resv_p, RESV_TABLEEND)) {
704
+ desc = "RESERVING needs table name list";
705
+ goto error;
706
+ }
707
+ while (resv_p) {
708
+ res_first = used;
709
+ res_count = 0;
710
+ resend_p = strtok(0, CMND_DELIMIT);
711
+ while (resend_p) {
712
+ if (!strcmp(resend_p, RESV_TABLEEND)) {
713
+ break;
714
+ }
715
+ resend_p = strtok(0, CMND_DELIMIT);
716
+ }
717
+
718
+ if (!resend_p) {
719
+ desc = "Illegal transaction option was specified";
720
+ goto error;
721
+ }
722
+
723
+ while (resv_p < resend_p) {
724
+ if (*resv_p == '\0' || (ofs = strspn(resv_p, LIST_DELIMIT)) < 0) {
725
+ resv_p++;
726
+ } else {
727
+ resv_p = &resv_p[ofs];
728
+ tblend_p = strpbrk(resv_p, LIST_DELIMIT);
729
+ if (tblend_p) {
730
+ tbl_len = tblend_p - resv_p;
731
+ } else {
732
+ tbl_len = strlen(resv_p);
733
+ }
734
+ if (tbl_len > META_NAME_MAX) {
735
+ desc = "Illegal table name was specified";
736
+ goto error;
737
+ }
738
+
739
+ if (tbl_len > 0) {
740
+ if (used + tbl_len + 3 > size) {
741
+ tpb = (char*)xrealloc(tpb, size+TPBBUFF_ALLOC);
742
+ size += TPBBUFF_ALLOC;
743
+ }
744
+ tpb[used+1] = (char)tbl_len;
745
+ memcpy((void *)&tpb[used+2],resv_p, tbl_len);
746
+ used += tbl_len + 3;
747
+ res_count++;
748
+ }
749
+ resv_p += tbl_len;
750
+ }
751
+ }
752
+
753
+ resv_p = strtok(0, CMND_DELIMIT);
754
+ if (resv_p && !strcmp(resv_p, RESV_SHARED)) {
755
+ sp_prm = isc_tpb_shared;
756
+ } else if (resv_p && !strcmp(resv_p, RESV_PROTECTD)) {
757
+ sp_prm = isc_tpb_protected;
758
+ } else {
759
+ desc = "RESERVING needs {SHARED|PROTECTED} {READ|WRITE}";
760
+ goto error;
761
+ }
762
+
763
+ cont_f = 0;
764
+ resv_p = strtok(0, CMND_DELIMIT);
765
+ if (resv_p) {
766
+ if (resv_p[strlen(resv_p)-1] == RESV_CONTINUE) {
767
+ cont_f = 1;
768
+ resv_p[strlen(resv_p)-1] = '\0';
769
+ } else {
770
+ tblend_p = strpbrk(resv_p, LIST_DELIMIT);
771
+ if (tblend_p) {
772
+ cont_f = 2;
773
+ *tblend_p = '\0';
774
+ }
775
+ }
776
+ }
777
+
778
+ if (resv_p && !strcmp(resv_p, RESV_READ)) {
779
+ rw_prm = isc_tpb_lock_read;
780
+ } else if (resv_p && !strcmp(resv_p, RESV_WRITE)) {
781
+ rw_prm = isc_tpb_lock_write;
782
+ } else {
783
+ desc = "RESERVING needs {SHARED|PROTECTED} {READ|WRITE}";
784
+ goto error;
785
+ }
786
+
787
+ ofs = res_first;
788
+ for (count = 0; count < res_count; count++) {
789
+ tpb[ofs++] = rw_prm;
790
+ ofs += tpb[ofs] + 1;
791
+ tpb[ofs++] = sp_prm;
792
+ }
793
+
794
+ if (cont_f == 1) {
795
+ resv_p = strtok(0, CMND_DELIMIT);
796
+ if (!resv_p) {
797
+ desc = "Unexpected end of command";
798
+ goto error;
799
+ }
800
+ }
801
+ if (cont_f == 2) {
802
+ resv_p = tblend_p + 1;
803
+ } else {
804
+ resv_p = strtok(0, CMND_DELIMIT);
805
+ if (resv_p) {
806
+ if ((int)strlen(resv_p) == 1 && resv_p[0] == RESV_CONTINUE) {
807
+ resv_p = strtok(0, CMND_DELIMIT);
808
+ if (!resv_p) {
809
+ desc = "Unexpected end of command";
810
+ goto error;
811
+ }
812
+ } else if (resv_p[0] == RESV_CONTINUE) {
813
+ resv_p++;
814
+ } else {
815
+ next_c = 1;
816
+ check2_p = resv_p;
817
+ resv_p = 0;
818
+ }
819
+ } else {
820
+ next_c = 0;
821
+ check1_p = check2_p = 0;
822
+ }
823
+ }
824
+ }
825
+
826
+ check_f[0] = 1;
827
+ }
828
+
829
+
830
+ /* Set the next check list */
831
+ curr_p = target_p->sub_opts;
832
+
833
+ for (count = 0; count < next_c; count++) {
834
+ check1_p = check2_p;
835
+ if (check2_p) {
836
+ check2_p = strtok(0, CMND_DELIMIT);
837
+ }
838
+ }
839
+
840
+ if (check1_p && !curr_p) {
841
+ curr_p = trans_opt_S;
842
+ }
843
+ }
844
+
845
+ /* Set the results */
846
+ *tpb_len = used;
847
+ return tpb;
848
+
849
+ error:
850
+ xfree(tpb);
851
+ rb_raise(rb_eFbError, desc);
852
+ }
853
+
854
+ static void set_teb_vec(ISC_TEB *vec, struct FbConnection *fb_connection, char *tpb, int len)
855
+ {
856
+ vec->dbb_ptr = &fb_connection->db;
857
+ if (tpb) {
858
+ vec->tpb_ptr = tpb;
859
+ vec->tpb_len = len;
860
+ } else {
861
+ vec->tpb_ptr = 0;
862
+ vec->tpb_len = 0;
863
+ }
864
+ }
865
+
866
+ static void global_transaction_start(VALUE opt, int argc, VALUE *argv)
867
+ {
868
+ long isc_status[20];
869
+ struct FbConnection *fb_connection;
870
+ ISC_TEB *teb_vec = ALLOCA_N(ISC_TEB, connection_count);
871
+ ISC_TEB *vec = teb_vec;
872
+ char *tpb = 0;
873
+ short n;
874
+ int tpb_len;
875
+
876
+ if (global_transact) {
877
+ rb_raise(rb_eFbError, "The transaction has been already started");
878
+ }
879
+
880
+ if (!NIL_P(opt)) {
881
+ tpb = trans_parseopts(opt, &tpb_len);
882
+ }
883
+
884
+ if (argc > connection_count) {
885
+ rb_raise(rb_eFbError, "Too many databases specified for the transaction");
886
+ }
887
+ if (argc == 0) {
888
+ n = connection_count;
889
+ for (fb_connection = fb_connection_list; fb_connection; fb_connection = fb_connection->next) {
890
+ set_teb_vec(vec, fb_connection, tpb, tpb_len);
891
+ vec++;
892
+ }
893
+ } else {
894
+ for (n = 0; n < argc; n++) {
895
+ fb_connection = fb_connection_check_retrieve(argv[n]);
896
+ set_teb_vec(vec, fb_connection, tpb, tpb_len);
897
+ vec++;
898
+ }
899
+ }
900
+
901
+ isc_start_multiple(isc_status, &global_transact, n, teb_vec);
902
+ xfree(tpb);
903
+ fb_error_check(isc_status);
904
+ }
905
+
906
+ /* call-seq:
907
+ * commit() -> nil
908
+ *
909
+ * Commit the current (global) transaction.
910
+ */
911
+ static VALUE global_commit()
912
+ {
913
+ long isc_status[20];
914
+
915
+ if (global_transact) {
916
+ global_close_cursors();
917
+ isc_commit_transaction(isc_status, &global_transact);
918
+ fb_error_check(isc_status);
919
+ }
920
+ return Qnil;
921
+ }
922
+
923
+ /* call-seq:
924
+ * rollback() -> nil
925
+ *
926
+ * Rollback the current (global) transaction.
927
+ */
928
+ static VALUE global_rollback()
929
+ {
930
+ long isc_status[20];
931
+
932
+ if (global_transact) {
933
+ global_close_cursors();
934
+ isc_rollback_transaction(isc_status, &global_transact);
935
+ fb_error_check(isc_status);
936
+ }
937
+ return Qnil;
938
+ }
939
+
940
+ /* call-seq:
941
+ * transaction(options, *connections) -> true
942
+ * transaction(options, *connections) { } -> block result
943
+ *
944
+ * Start a (global) transaction.
945
+ */
946
+ static VALUE global_transaction(int argc, VALUE *argv, VALUE self)
947
+ {
948
+ VALUE opt = Qnil;
949
+
950
+ if (argc > 0) {
951
+ opt = *argv++;
952
+ argc--;
953
+ }
954
+ global_transaction_start(opt, argc, argv);
955
+
956
+ if (rb_block_given_p()) {
957
+ int state;
958
+ VALUE result = rb_protect(rb_yield, 0, &state);
959
+ if (state) {
960
+ global_rollback();
961
+ return rb_funcall(rb_mKernel, rb_intern("raise"), 0);
962
+ } else {
963
+ global_commit();
964
+ return result;
965
+ }
966
+ } else {
967
+ return Qtrue;
968
+ }
969
+ }
970
+
971
+ /* call-seq:
972
+ * transaction_started()? -> true or false
973
+ *
974
+ * Returns true if a transaction is currently active.
975
+ */
976
+ static VALUE global_transaction_started()
977
+ {
978
+ return global_transact ? Qtrue : Qfalse;
979
+ }
980
+
981
+ static void fb_connection_transaction_start(struct FbConnection *fb_connection, VALUE opt)
982
+ {
983
+ long isc_status[20];
984
+ char *tpb = 0;
985
+ int tpb_len;
986
+
987
+ if (fb_connection->transact) {
988
+ rb_raise(rb_eFbError, "A transaction has been already started");
989
+ }
990
+
991
+ if (!NIL_P(opt)) {
992
+ tpb = trans_parseopts(opt, &tpb_len);
993
+ } else {
994
+ tpb_len = 0;
995
+ tpb = NULL;
996
+ }
997
+
998
+ isc_start_transaction(isc_status, &fb_connection->transact, 1, &fb_connection->db, tpb_len, tpb);
999
+ xfree(tpb);
1000
+ fb_error_check(isc_status);
1001
+ }
1002
+
1003
+ static void fb_connection_commit(struct FbConnection *fb_connection)
1004
+ {
1005
+ long isc_status[20];
1006
+
1007
+ if (fb_connection->transact) {
1008
+ fb_connection_close_cursors(fb_connection);
1009
+ isc_commit_transaction(isc_status, &fb_connection->transact);
1010
+ fb_error_check(isc_status);
1011
+ }
1012
+ }
1013
+
1014
+ static void fb_connection_rollback(struct FbConnection *fb_connection)
1015
+ {
1016
+ long isc_status[20];
1017
+
1018
+ if (fb_connection->transact) {
1019
+ fb_connection_close_cursors(fb_connection);
1020
+ isc_rollback_transaction(isc_status, &fb_connection->transact);
1021
+ fb_error_check(isc_status);
1022
+ }
1023
+ }
1024
+
1025
+ /* call-seq:
1026
+ * transaction(options) -> true
1027
+ * transaction(options) { } -> block result
1028
+ *
1029
+ * Start a transaction for this connection.
1030
+ */
1031
+ static VALUE connection_transaction(int argc, VALUE *argv, VALUE self)
1032
+ {
1033
+ struct FbConnection *fb_connection;
1034
+ VALUE opt = Qnil;
1035
+
1036
+ rb_scan_args(argc, argv, "01", &opt);
1037
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1038
+
1039
+ fb_connection_transaction_start(fb_connection, opt);
1040
+
1041
+ if (rb_block_given_p()) {
1042
+ int state;
1043
+ VALUE result = rb_protect(rb_yield, 0, &state);
1044
+ if (state) {
1045
+ fb_connection_rollback(fb_connection);
1046
+ return rb_funcall(rb_mKernel, rb_intern("raise"), 0);
1047
+ } else {
1048
+ fb_connection_commit(fb_connection);
1049
+ return result;
1050
+ }
1051
+ } else {
1052
+ return Qtrue;
1053
+ }
1054
+ }
1055
+
1056
+ /* call-seq:
1057
+ * transaction_started()? -> true or false
1058
+ *
1059
+ * Returns true if a transaction is currently active.
1060
+ */
1061
+ static VALUE connection_transaction_started(VALUE self)
1062
+ {
1063
+ struct FbConnection *fb_connection;
1064
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1065
+
1066
+ return fb_connection->transact ? Qtrue : Qfalse;
1067
+ }
1068
+
1069
+ /* call-seq:
1070
+ * commit() -> nil
1071
+ *
1072
+ * Commit the current transaction.
1073
+ */
1074
+ static VALUE connection_commit(VALUE self)
1075
+ {
1076
+ struct FbConnection *fb_connection;
1077
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1078
+
1079
+ fb_connection_commit(fb_connection);
1080
+ return Qnil;
1081
+ }
1082
+
1083
+ /* call-seq:
1084
+ * rollback() -> nil
1085
+ *
1086
+ * Rollback the current transaction.
1087
+ */
1088
+ static VALUE connection_rollback(VALUE self)
1089
+ {
1090
+ struct FbConnection *fb_connection;
1091
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1092
+
1093
+ fb_connection_rollback(fb_connection);
1094
+ return Qnil;
1095
+ }
1096
+
1097
+ /*
1098
+ * call-seq:
1099
+ * open?() -> true or false
1100
+ *
1101
+ * Current connection status.
1102
+ */
1103
+
1104
+ static VALUE connection_is_open(VALUE self)
1105
+ {
1106
+ struct FbConnection *fb_connection;
1107
+
1108
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1109
+ return (fb_connection->db == 0) ? Qfalse : Qtrue;
1110
+ }
1111
+
1112
+ /* call-seq:
1113
+ * to_s() -> String
1114
+ *
1115
+ * Return current database connection string and either (OPEN) or (CLOSED).
1116
+ */
1117
+ static VALUE connection_to_s(VALUE self)
1118
+ {
1119
+ VALUE is_open = connection_is_open(self);
1120
+ VALUE status = (is_open == Qtrue) ? rb_str_new2(" (OPEN)") : rb_str_new2(" (CLOSED)");
1121
+ VALUE database = rb_iv_get(self, "@database");
1122
+ VALUE s = rb_str_dup(database);
1123
+ return rb_str_concat(s, status);
1124
+ }
1125
+
1126
+ /* call-seq:
1127
+ * cursor() -> Cursor
1128
+ *
1129
+ * Creates a +Cursor+ for the +Connection+ and allocates a statement.
1130
+ * This function is no longer published.
1131
+ */
1132
+ static VALUE connection_cursor(VALUE self)
1133
+ {
1134
+ long isc_status[20];
1135
+ VALUE c;
1136
+ struct FbConnection *fb_connection;
1137
+ struct FbCursor *fb_cursor;
1138
+
1139
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1140
+ fb_connection_check(fb_connection);
1141
+
1142
+ c = Data_Make_Struct(rb_cFbCursor, struct FbCursor, fb_cursor_mark, fb_cursor_free, fb_cursor);
1143
+ fb_cursor->connection = self;
1144
+ fb_cursor->fields_ary = Qnil;
1145
+ fb_cursor->fields_hash = Qnil;
1146
+ fb_cursor->open = Qfalse;
1147
+ fb_cursor->stmt = 0;
1148
+ fb_cursor->i_sqlda = sqlda_alloc(SQLDA_COLSINIT);
1149
+ fb_cursor->o_sqlda = sqlda_alloc(SQLDA_COLSINIT);
1150
+ fb_cursor->i_buffer = NULL;
1151
+ fb_cursor->i_buffer_size = 0;
1152
+ fb_cursor->o_buffer = NULL;
1153
+ fb_cursor->o_buffer_size = 0;
1154
+ isc_dsql_alloc_statement2(isc_status, &fb_connection->db, &fb_cursor->stmt);
1155
+ fb_error_check(isc_status);
1156
+
1157
+ return c;
1158
+ }
1159
+
1160
+ /* call-seq:
1161
+ * execute(sql, *args) -> Cursor or rows affected
1162
+ * execute(sql, *args) {|cursor| } -> block result
1163
+ *
1164
+ * Allocates a +Cursor+ and executes the +sql+ statement, matching up the
1165
+ * parameters in +args+ with the place holders in the statement, represented by ?'s.
1166
+ *
1167
+ * If the sql statement returns a result set and a block is provided, the cursor is
1168
+ * yielded to the block before being automatically closed.
1169
+ *
1170
+ * If the sql statement returns a result set and a block is not provided, a +Cursor+
1171
+ * object is returned.
1172
+ *
1173
+ * If the sql statement performs an INSERT, UPDATE or DELETE, the number of rows
1174
+ * affected is returned. Other statements, such as schema updates, return -1.
1175
+ *
1176
+ * +*args+ can be one or more tuples of parameters or an array of tuples (arrays),
1177
+ * in which case the statement will be executed once for each tuple of parameters.
1178
+ *
1179
+ * If no transaction is currently active, a transaction is automatically started
1180
+ * and is closed when the cursor is closed. Note that if additional statements
1181
+ * yielding cursors are started before the first cursor is closed, these cursors will
1182
+ * also be closed when the first one is closed and its transaction committed.
1183
+ */
1184
+ static VALUE connection_execute(int argc, VALUE *argv, VALUE self)
1185
+ {
1186
+ VALUE cursor = connection_cursor(self);
1187
+ VALUE val = cursor_execute(argc, argv, cursor);
1188
+
1189
+ if (NIL_P(val)) {
1190
+ if (rb_block_given_p()) {
1191
+ return rb_ensure(rb_yield,cursor,cursor_close,cursor);
1192
+ } else {
1193
+ return cursor;
1194
+ }
1195
+ } else {
1196
+ cursor_drop(cursor);
1197
+ }
1198
+ return val;
1199
+ }
1200
+
1201
+ /* call-seq:
1202
+ * query(:array, sql, *arg) -> Array of Arrays or nil
1203
+ * query(:hash, sql, *arg) -> Array of Hashes or nil
1204
+ * query(sql, *args) -> Array of Arrays or nil
1205
+ *
1206
+ * For queries returning a result set, an array is returned, containing
1207
+ * either a list of Arrays or Hashes, one for each row.
1208
+ *
1209
+ * If the sql statement performs an INSERT, UPDATE or DELETE, the number of rows
1210
+ * affected is returned. Other statements, such as schema updates, return -1.
1211
+ *
1212
+ * If no transaction is currently active, a transaction is automatically started
1213
+ * and committed. Otherwise, the statement executes within the context of the
1214
+ * current transaction.
1215
+ */
1216
+ static VALUE connection_query(int argc, VALUE *argv, VALUE self)
1217
+ {
1218
+ VALUE format;
1219
+ VALUE cursor;
1220
+ VALUE result;
1221
+
1222
+ if (argc >= 1 && TYPE(argv[0]) == T_SYMBOL) {
1223
+ format = argv[0];
1224
+ argc--; argv++;
1225
+ } else {
1226
+ format = ID2SYM(rb_intern("array"));
1227
+ }
1228
+ cursor = connection_cursor(self);
1229
+ result = cursor_execute(argc, argv, cursor);
1230
+ if (NIL_P(result)) {
1231
+ result = cursor_fetchall(1, &format, cursor);
1232
+ cursor_close(cursor);
1233
+ }
1234
+
1235
+ return result;
1236
+ }
1237
+
1238
+ /* call-seq:
1239
+ * close() -> nil
1240
+ *
1241
+ * Closes connection to database. All open cursors are dropped.
1242
+ */
1243
+ static VALUE connection_close(VALUE self)
1244
+ {
1245
+ struct FbConnection *fb_connection;
1246
+
1247
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1248
+
1249
+ if (fb_connection->dropped) return Qnil;
1250
+
1251
+ fb_connection_check(fb_connection);
1252
+ fb_connection_disconnect(fb_connection);
1253
+ fb_connection_drop_cursors(fb_connection);
1254
+
1255
+ return Qnil;
1256
+ }
1257
+
1258
+ /* call-seq:
1259
+ * drop() -> nil
1260
+ *
1261
+ * Drops connected database. All open cursors are dropped.
1262
+ */
1263
+ static VALUE connection_drop(VALUE self)
1264
+ {
1265
+ struct FbConnection *fb_connection;
1266
+
1267
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1268
+ fb_connection->dropped = 1;
1269
+ fb_connection_disconnect(fb_connection);
1270
+ fb_connection_drop_cursors(fb_connection);
1271
+
1272
+ return Qnil;
1273
+ }
1274
+
1275
+ /* call-seq:
1276
+ * dialect() -> int
1277
+ *
1278
+ * Returns dialect of connection.
1279
+ */
1280
+ static VALUE connection_dialect(VALUE self)
1281
+ {
1282
+ struct FbConnection *fb_connection;
1283
+
1284
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1285
+ fb_connection_check(fb_connection);
1286
+
1287
+ return INT2FIX(fb_connection->dialect);
1288
+ }
1289
+
1290
+ /* call-seq:
1291
+ * db_dialect() -> int
1292
+ *
1293
+ * Returns database dialect.
1294
+ */
1295
+ static VALUE connection_db_dialect(VALUE self)
1296
+ {
1297
+ struct FbConnection *fb_connection;
1298
+
1299
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
1300
+ fb_connection_check(fb_connection);
1301
+
1302
+ return INT2FIX(fb_connection->db_dialect);
1303
+ }
1304
+
1305
+ static void fb_cursor_check(struct FbCursor *fb_cursor)
1306
+ {
1307
+ if (fb_cursor->stmt == 0) {
1308
+ rb_raise(rb_eFbError, "dropped db cursor");
1309
+ }
1310
+ if (!fb_cursor->open) {
1311
+ rb_raise(rb_eFbError, "closed db cursor");
1312
+ }
1313
+ }
1314
+
1315
+ static void fb_cursor_drop(struct FbCursor *fb_cursor)
1316
+ {
1317
+ long isc_status[20];
1318
+ if (fb_cursor->open) {
1319
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_close);
1320
+ fb_error_check(isc_status);
1321
+ }
1322
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_drop);
1323
+ fb_error_check(isc_status);
1324
+ }
1325
+
1326
+ static void fb_cursor_drop_warn(struct FbCursor *fb_cursor)
1327
+ {
1328
+ long isc_status[20];
1329
+ if (fb_cursor->open) {
1330
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_close);
1331
+ fb_error_check_warn(isc_status);
1332
+ }
1333
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_drop);
1334
+ fb_error_check_warn(isc_status);
1335
+ }
1336
+
1337
+ static void fb_cursor_mark(struct FbCursor *fb_cursor)
1338
+ {
1339
+ rb_gc_mark(fb_cursor->connection);
1340
+ rb_gc_mark(fb_cursor->fields_ary);
1341
+ rb_gc_mark(fb_cursor->fields_hash);
1342
+ }
1343
+
1344
+ static void fb_cursor_free(struct FbCursor *fb_cursor)
1345
+ {
1346
+ if (fb_cursor->stmt) {
1347
+ fb_cursor_drop_warn(fb_cursor);
1348
+ }
1349
+ xfree(fb_cursor->i_sqlda);
1350
+ xfree(fb_cursor->o_sqlda);
1351
+ xfree(fb_cursor->i_buffer);
1352
+ xfree(fb_cursor->o_buffer);
1353
+ xfree(fb_cursor);
1354
+ }
1355
+
1356
+ static void fb_cursor_set_inputparams(struct FbCursor *fb_cursor, int argc, VALUE *argv)
1357
+ {
1358
+ struct FbConnection *fb_connection;
1359
+ long count;
1360
+ long offset;
1361
+ long type;
1362
+ short dtp;
1363
+ VALUE obj;
1364
+ long lvalue;
1365
+ long alignment;
1366
+ double ratio;
1367
+ double dvalue;
1368
+ long scnt;
1369
+ double dcheck;
1370
+ VARY *vary;
1371
+ XSQLVAR *var;
1372
+
1373
+ isc_blob_handle blob_handle;
1374
+ ISC_QUAD blob_id;
1375
+ static char blob_items[] = { isc_info_blob_max_segment };
1376
+ char blob_info[16];
1377
+ char *p;
1378
+ long length;
1379
+ struct time_object *tobj;
1380
+ struct tm tms;
1381
+
1382
+ long isc_status[20];
1383
+
1384
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
1385
+
1386
+ /* Check the number of parameters */
1387
+ if (fb_cursor->i_sqlda->sqld != argc) {
1388
+ rb_raise(rb_eFbError, "statement requires %d items; %d given", fb_cursor->i_sqlda->sqld, argc);
1389
+ }
1390
+
1391
+ /* Get the parameters */
1392
+ for (count = 0,offset = 0; count < argc; count++) {
1393
+ obj = argv[count];
1394
+
1395
+ type = TYPE(obj);
1396
+
1397
+ /* Convert the data type for InterBase */
1398
+ var = &fb_cursor->i_sqlda->sqlvar[count];
1399
+ if (!NIL_P(obj)) {
1400
+ dtp = var->sqltype & ~1; /* Erase null flag */
1401
+ alignment = var->sqllen;
1402
+
1403
+ switch (dtp) {
1404
+ case SQL_TEXT :
1405
+ alignment = 1;
1406
+ offset = ALIGN(offset, alignment);
1407
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1408
+ obj = rb_obj_as_string(obj);
1409
+ if (RSTRING(obj)->len > var->sqllen) {
1410
+ rb_raise(rb_eRangeError, "CHAR overflow: %d bytes exceeds %d byte(s) allowed.",
1411
+ RSTRING(obj)->len, var->sqllen);
1412
+ }
1413
+ memcpy(var->sqldata, RSTRING(obj)->ptr, RSTRING(obj)->len);
1414
+ var->sqllen = RSTRING(obj)->len;
1415
+ offset += var->sqllen + 1;
1416
+ break;
1417
+
1418
+ case SQL_VARYING :
1419
+ alignment = sizeof(short);
1420
+ offset = ALIGN(offset, alignment);
1421
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1422
+ vary = (VARY *)var->sqldata;
1423
+ obj = rb_obj_as_string(obj);
1424
+ if (RSTRING(obj)->len > var->sqllen) {
1425
+ rb_raise(rb_eRangeError, "VARCHAR overflow: %d bytes exceeds %d byte(s) allowed.",
1426
+ RSTRING(obj)->len, var->sqllen);
1427
+ }
1428
+ memcpy(vary->vary_string, RSTRING(obj)->ptr, RSTRING(obj)->len);
1429
+ vary->vary_length = RSTRING(obj)->len;
1430
+ offset += vary->vary_length + sizeof(short);
1431
+ break;
1432
+
1433
+ case SQL_SHORT :
1434
+ offset = ALIGN(offset, alignment);
1435
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1436
+ if (var->sqlscale < 0) {
1437
+ ratio = 1;
1438
+ for (scnt = 0; scnt > var->sqlscale; scnt--)
1439
+ ratio *= 10;
1440
+ obj = double_from_obj(obj);
1441
+ dvalue = NUM2DBL(obj) * ratio;
1442
+ lvalue = (long)(dvalue + 0.5);
1443
+ } else {
1444
+ obj = long_from_obj(obj);
1445
+ lvalue = NUM2LONG(obj);
1446
+ }
1447
+ if (lvalue < SHRT_MIN || lvalue > SHRT_MAX) {
1448
+ rb_raise(rb_eRangeError, "short integer overflow");
1449
+ }
1450
+ *(short *)var->sqldata = lvalue;
1451
+ offset += alignment;
1452
+ break;
1453
+
1454
+ case SQL_LONG :
1455
+ offset = ALIGN(offset, alignment);
1456
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1457
+ if (var->sqlscale < 0) {
1458
+ ratio = 1;
1459
+ for (scnt = 0; scnt > var->sqlscale; scnt--)
1460
+ ratio *= 10;
1461
+ obj = double_from_obj(obj);
1462
+ dvalue = NUM2DBL(obj) * ratio;
1463
+ lvalue = (long)(dvalue + 0.5);
1464
+ } else {
1465
+ obj = long_from_obj(obj);
1466
+ lvalue = NUM2LONG(obj);
1467
+ }
1468
+ *(long *)var->sqldata = lvalue;
1469
+ offset += alignment;
1470
+ break;
1471
+
1472
+ case SQL_FLOAT :
1473
+ offset = ALIGN(offset, alignment);
1474
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1475
+ obj = double_from_obj(obj);
1476
+ dvalue = NUM2DBL(obj);
1477
+ if (dvalue >= 0.0) {
1478
+ dcheck = dvalue;
1479
+ } else {
1480
+ dcheck = dvalue * -1;
1481
+ }
1482
+ if (dcheck != 0.0 && (dcheck < FLT_MIN || dcheck > FLT_MAX)) {
1483
+ rb_raise(rb_eRangeError, "float overflow");
1484
+ }
1485
+ *(float *)var->sqldata = dvalue;
1486
+ offset += alignment;
1487
+ break;
1488
+
1489
+ case SQL_DOUBLE :
1490
+ offset = ALIGN(offset, alignment);
1491
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1492
+ obj = double_from_obj(obj);
1493
+ dvalue = NUM2DBL(obj);
1494
+ *(double *)var->sqldata = dvalue;
1495
+ offset += alignment;
1496
+ break;
1497
+ #if HAVE_LONG_LONG
1498
+ case SQL_INT64 :
1499
+ offset = ALIGN(offset, alignment);
1500
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1501
+ obj = ll_from_obj(obj);
1502
+ *(ISC_INT64 *)var->sqldata = NUM2LL(obj);
1503
+ offset += alignment;
1504
+ break;
1505
+ #endif
1506
+ case SQL_BLOB :
1507
+ offset = ALIGN(offset, alignment);
1508
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1509
+ obj = rb_obj_as_string(obj);
1510
+
1511
+ blob_handle = NULL;
1512
+ isc_create_blob2(
1513
+ isc_status,&fb_connection->db,&fb_connection->transact,
1514
+ &blob_handle,&blob_id,0,NULL);
1515
+ fb_error_check(isc_status);
1516
+ length = RSTRING(obj)->len;
1517
+ p = RSTRING(obj)->ptr;
1518
+ while (length >= 4096) {
1519
+ isc_put_segment(isc_status,&blob_handle,4096,p);
1520
+ fb_error_check(isc_status);
1521
+ p += 4096;
1522
+ length -= 4096;
1523
+ }
1524
+ if (length) {
1525
+ isc_put_segment(isc_status,&blob_handle,length,p);
1526
+ fb_error_check(isc_status);
1527
+ }
1528
+ isc_close_blob(isc_status,&blob_handle);
1529
+ fb_error_check(isc_status);
1530
+
1531
+ *(ISC_QUAD *)var->sqldata = blob_id;
1532
+ offset += alignment;
1533
+ break;
1534
+
1535
+ case SQL_TIMESTAMP :
1536
+ offset = ALIGN(offset, alignment);
1537
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1538
+ tm_from_timestamp(&tms, obj);
1539
+ isc_encode_timestamp(&tms, (ISC_TIMESTAMP *)var->sqldata);
1540
+ offset += alignment;
1541
+ break;
1542
+
1543
+ case SQL_TYPE_TIME :
1544
+ offset = ALIGN(offset, alignment);
1545
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1546
+ tm_from_timestamp(&tms, obj);
1547
+ isc_encode_sql_time(&tms, (ISC_TIME *)var->sqldata);
1548
+ offset += alignment;
1549
+ break;
1550
+
1551
+ case SQL_TYPE_DATE :
1552
+ offset = ALIGN(offset, alignment);
1553
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1554
+ tm_from_date(&tms, obj);
1555
+ isc_encode_sql_date(&tms, (ISC_DATE *)var->sqldata);
1556
+ offset += alignment;
1557
+ break;
1558
+
1559
+ #if 0
1560
+ case SQL_ARRAY :
1561
+ /* Not supported now
1562
+ offset = ALIGN(offset, alignment);
1563
+ var->sqldata = (char *)(fb_cursor->i_buffer + offset);
1564
+ if (get_arrayvalue(self, type, obj, var))
1565
+ return(STATUS_ABNORMAL);
1566
+ offset += alignment;
1567
+ break;
1568
+ */
1569
+ rb_raise(rb_eArgError, "Arrays not supported");
1570
+ break;
1571
+ #endif
1572
+
1573
+ default :
1574
+ rb_raise(rb_eFbError, "Specified table includes unsupported datatype (%d)", dtp);
1575
+ }
1576
+
1577
+ if (var->sqltype & 1) {
1578
+ offset = ALIGN(offset, sizeof(short));
1579
+ var->sqlind = (short *)(fb_cursor->i_buffer + offset);
1580
+ *var->sqlind = 0;
1581
+ offset += sizeof(short);
1582
+ }
1583
+ } else if (var->sqltype & 1) {
1584
+ var->sqldata = 0;
1585
+ offset = ALIGN(offset, sizeof(short));
1586
+ var->sqlind = (short *)(fb_cursor->i_buffer + offset);
1587
+ *var->sqlind = -1;
1588
+ offset += sizeof(short);
1589
+ } else {
1590
+ rb_raise(rb_eFbError, "specified column is not permitted to be null");
1591
+ }
1592
+ }
1593
+ }
1594
+
1595
+ static void fb_cursor_execute_withparams(struct FbCursor *fb_cursor, int argc, VALUE *argv)
1596
+ {
1597
+ struct FbConnection *fb_connection;
1598
+ long isc_status[20];
1599
+
1600
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
1601
+ /* Check the first object type of the parameters */
1602
+ if (argc >= 1 && TYPE(argv[0]) == T_ARRAY) {
1603
+ int i;
1604
+ VALUE obj;
1605
+ VALUE ary = argv[0];
1606
+ if (RARRAY(ary)->len > 0 && TYPE(RARRAY(ary)->ptr[0]) == T_ARRAY) {
1607
+ for (i = 0; i < RARRAY(ary)->len; i++) {
1608
+ obj = rb_ary_entry(ary, i);
1609
+ fb_cursor_execute_withparams(fb_cursor, 1, &obj);
1610
+ }
1611
+ } else {
1612
+ for (i = 0; i < argc; i++) {
1613
+ obj = argv[i];
1614
+
1615
+ /* Set the input parameters */
1616
+ Check_Type(obj, T_ARRAY);
1617
+ fb_cursor_set_inputparams(fb_cursor, RARRAY(obj)->len, RARRAY(obj)->ptr);
1618
+
1619
+ /* Execute SQL statement */
1620
+ isc_dsql_execute2(isc_status, &fb_connection->transact, &fb_cursor->stmt, SQLDA_VERSION1, fb_cursor->i_sqlda, NULL);
1621
+ fb_error_check(isc_status);
1622
+ }
1623
+ }
1624
+ } else {
1625
+ /* Set the input parameters */
1626
+ fb_cursor_set_inputparams(fb_cursor, argc, argv);
1627
+
1628
+ /* Execute SQL statement */
1629
+ isc_dsql_execute2(isc_status, &fb_connection->transact, &fb_cursor->stmt, SQLDA_VERSION1, fb_cursor->i_sqlda, NULL);
1630
+ fb_error_check(isc_status);
1631
+ }
1632
+ }
1633
+
1634
+ static VALUE precision_from_sqlvar(XSQLVAR *sqlvar)
1635
+ {
1636
+ switch(sqlvar->sqltype & ~1) {
1637
+ case SQL_TEXT: return Qnil;
1638
+ case SQL_VARYING: return Qnil;
1639
+ case SQL_SHORT:
1640
+ switch (sqlvar->sqlsubtype) {
1641
+ case 0: return INT2FIX(0);
1642
+ case 1: return INT2FIX(4);
1643
+ case 2: return INT2FIX(4);
1644
+ }
1645
+ break;
1646
+ case SQL_LONG:
1647
+ switch (sqlvar->sqlsubtype) {
1648
+ case 0: return INT2FIX(0);
1649
+ case 1: return INT2FIX(9);
1650
+ case 2: return INT2FIX(9);
1651
+ }
1652
+ break;
1653
+ case SQL_FLOAT: return Qnil;
1654
+ case SQL_DOUBLE:
1655
+ case SQL_D_FLOAT:
1656
+ switch (sqlvar->sqlsubtype) {
1657
+ case 0: return Qnil;
1658
+ case 1: return INT2FIX(15);
1659
+ case 2: return INT2FIX(15);
1660
+ }
1661
+ break;
1662
+ case SQL_TIMESTAMP: return Qnil;
1663
+ case SQL_BLOB: return Qnil;
1664
+ case SQL_ARRAY: return Qnil;
1665
+ case SQL_QUAD: return Qnil;
1666
+ case SQL_TYPE_TIME: return Qnil;
1667
+ case SQL_TYPE_DATE: return Qnil;
1668
+ case SQL_INT64:
1669
+ switch (sqlvar->sqlsubtype) {
1670
+ case 0: return INT2FIX(0);
1671
+ case 1: return INT2FIX(18);
1672
+ case 2: return INT2FIX(18);
1673
+ }
1674
+ break;
1675
+ }
1676
+ return Qnil;
1677
+ }
1678
+
1679
+ static VALUE fb_cursor_fields_ary(XSQLDA *sqlda, short downcase_names)
1680
+ {
1681
+ long cols;
1682
+ long count;
1683
+ XSQLVAR *var;
1684
+ short dtp;
1685
+ VALUE ary;
1686
+ VALUE re_lowercase;
1687
+ ID id_matches, id_downcase_bang;
1688
+
1689
+ cols = sqlda->sqld;
1690
+ if (cols == 0) {
1691
+ return Qnil;
1692
+ }
1693
+
1694
+ if (downcase_names) {
1695
+ re_lowercase = rb_reg_regcomp(rb_str_new2("[[:lower:]]"));
1696
+ id_matches = rb_intern("=~");
1697
+ id_downcase_bang = rb_intern("downcase!");
1698
+ }
1699
+
1700
+ ary = rb_ary_new();
1701
+ for (count = 0; count < cols; count++) {
1702
+ VALUE field;
1703
+ VALUE name, type_code, sql_type, sql_subtype, display_size, internal_size, precision, scale, nullable;
1704
+
1705
+ var = &sqlda->sqlvar[count];
1706
+ dtp = var->sqltype & ~1;
1707
+
1708
+ if (var->aliasname_length) { /* aliasname always present? */
1709
+ name = rb_tainted_str_new(var->aliasname, var->aliasname_length);
1710
+ } else {
1711
+ name = rb_tainted_str_new(var->sqlname, var->sqlname_length);
1712
+ }
1713
+ if (downcase_names && rb_funcall(re_lowercase, id_matches, 1, name) == Qnil) {
1714
+ rb_funcall(name, id_downcase_bang, 0);
1715
+ }
1716
+ rb_str_freeze(name);
1717
+ type_code = INT2NUM((long)(var->sqltype & ~1));
1718
+ sql_type = fb_sql_type_from_code(dtp, var->sqlsubtype);
1719
+ sql_subtype = INT2FIX(var->sqlsubtype);
1720
+ display_size = INT2NUM((long)var->sqllen);
1721
+ if (dtp == SQL_VARYING) {
1722
+ internal_size = INT2NUM((long)var->sqllen + sizeof(short));
1723
+ } else {
1724
+ internal_size = INT2NUM((long)var->sqllen);
1725
+ }
1726
+ precision = precision_from_sqlvar(var);
1727
+ scale = INT2NUM((long)var->sqlscale);
1728
+ nullable = (var->sqltype & 1) ? Qtrue : Qfalse;
1729
+
1730
+ field = rb_struct_new(rb_sFbField, name, sql_type, sql_subtype, display_size, internal_size, precision, scale, nullable, type_code);
1731
+ rb_ary_push(ary, field);
1732
+ }
1733
+ rb_ary_freeze(ary);
1734
+ return ary;
1735
+ }
1736
+
1737
+ static VALUE fb_cursor_fields_hash(VALUE fields_ary)
1738
+ {
1739
+ int i;
1740
+ VALUE hash = rb_hash_new();
1741
+
1742
+ for (i = 0; i < RARRAY(fields_ary)->len; i++) {
1743
+ VALUE field = rb_ary_entry(fields_ary, i);
1744
+ VALUE name = rb_struct_aref(field, LONG2NUM(0));
1745
+ rb_hash_aset(hash, name, field);
1746
+ }
1747
+
1748
+ return hash;
1749
+ }
1750
+
1751
+ static void fb_cursor_fetch_prep(struct FbCursor *fb_cursor)
1752
+ {
1753
+ struct FbConnection *fb_connection;
1754
+ long cols;
1755
+ long count;
1756
+ XSQLVAR *var;
1757
+ short dtp;
1758
+ long length;
1759
+ long alignment;
1760
+ long offset;
1761
+ long isc_status[20];
1762
+
1763
+ fb_cursor_check(fb_cursor);
1764
+
1765
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
1766
+ fb_connection_check(fb_connection);
1767
+
1768
+ /* Check if open cursor */
1769
+ if (!fb_cursor->open) {
1770
+ rb_raise(rb_eFbError, "The cursor has not been open. Use execute(query)");
1771
+ }
1772
+ /* Describe output SQLDA */
1773
+ isc_dsql_describe(isc_status, &fb_cursor->stmt, 1, fb_cursor->o_sqlda);
1774
+ fb_error_check(isc_status);
1775
+
1776
+ /* Set the output SQLDA */
1777
+ cols = fb_cursor->o_sqlda->sqld;
1778
+ for (var = fb_cursor->o_sqlda->sqlvar, offset = 0, count = 0; count < cols; var++, count++) {
1779
+ length = alignment = var->sqllen;
1780
+ dtp = var->sqltype & ~1;
1781
+
1782
+ if (dtp == SQL_TEXT) {
1783
+ alignment = 1;
1784
+ } else if (dtp == SQL_VARYING) {
1785
+ length += sizeof(short);
1786
+ alignment = sizeof(short);
1787
+ }
1788
+ offset = ALIGN(offset, alignment);
1789
+ var->sqldata = (char*)(fb_cursor->o_buffer + offset);
1790
+ offset += length;
1791
+ offset = ALIGN(offset, sizeof(short));
1792
+ var->sqlind = (short*)(fb_cursor->o_buffer + offset);
1793
+ offset += sizeof(short);
1794
+ }
1795
+ }
1796
+
1797
+ static VALUE fb_cursor_fetch(struct FbCursor *fb_cursor)
1798
+ {
1799
+ struct FbConnection *fb_connection;
1800
+ long cols;
1801
+ VALUE ary;
1802
+ long count;
1803
+ XSQLVAR *var;
1804
+ long dtp;
1805
+ VALUE val;
1806
+ VARY *vary;
1807
+ double ratio;
1808
+ double dval;
1809
+ long scnt;
1810
+ struct tm tms;
1811
+
1812
+ isc_blob_handle blob_handle;
1813
+ ISC_QUAD blob_id;
1814
+ unsigned short actual_seg_len;
1815
+ time_t t;
1816
+ static char blob_items[] = {
1817
+ isc_info_blob_max_segment,
1818
+ isc_info_blob_num_segments,
1819
+ isc_info_blob_total_length
1820
+ };
1821
+ char blob_info[32];
1822
+ char *p, item;
1823
+ short length;
1824
+ unsigned short max_segment;
1825
+ ISC_LONG num_segments;
1826
+ ISC_LONG total_length;
1827
+
1828
+ long isc_status[20];
1829
+
1830
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
1831
+ fb_connection_check(fb_connection);
1832
+
1833
+ /* Fetch one row */
1834
+ if (isc_dsql_fetch(isc_status, &fb_cursor->stmt, 1, fb_cursor->o_sqlda) == SQLCODE_NOMORE) {
1835
+ return Qnil;
1836
+ }
1837
+ fb_error_check(isc_status);
1838
+
1839
+ /* Create the result tuple object */
1840
+ cols = fb_cursor->o_sqlda->sqld;
1841
+ ary = rb_ary_new2(cols);
1842
+
1843
+ /* Create the result objects for each columns */
1844
+ for (count = 0; count < cols; count++) {
1845
+ var = &fb_cursor->o_sqlda->sqlvar[count];
1846
+ dtp = var->sqltype & ~1;
1847
+
1848
+ /* Check if column is null */
1849
+
1850
+ if ((var->sqltype & 1) && (*var->sqlind < 0)) {
1851
+ val = Qnil;
1852
+ } else {
1853
+ /* Set the column value to the result tuple */
1854
+
1855
+ switch (dtp) {
1856
+ case SQL_TEXT:
1857
+ val = rb_tainted_str_new(var->sqldata, var->sqllen);
1858
+ break;
1859
+
1860
+ case SQL_VARYING:
1861
+ vary = (VARY*)var->sqldata;
1862
+ val = rb_tainted_str_new(vary->vary_string, vary->vary_length);
1863
+ break;
1864
+
1865
+ case SQL_SHORT:
1866
+ if (var->sqlscale < 0) {
1867
+ ratio = 1;
1868
+ for (scnt = 0; scnt > var->sqlscale; scnt--)
1869
+ ratio *= 10;
1870
+ dval = (double)*(short*)var->sqldata/ratio;
1871
+ val = rb_float_new(dval);
1872
+ } else {
1873
+ val = INT2NUM((long)*(short*)var->sqldata);
1874
+ }
1875
+ break;
1876
+
1877
+ case SQL_LONG:
1878
+ if (var->sqlscale < 0) {
1879
+ ratio = 1;
1880
+ for (scnt = 0; scnt > var->sqlscale; scnt--)
1881
+ ratio *= 10;
1882
+ dval = (double)*(long*)var->sqldata/ratio;
1883
+ val = rb_float_new(dval);
1884
+ } else {
1885
+ val = INT2NUM(*(long*)var->sqldata);
1886
+ }
1887
+ break;
1888
+
1889
+ case SQL_FLOAT:
1890
+ val = rb_float_new((double)*(float*)var->sqldata);
1891
+ break;
1892
+
1893
+ case SQL_DOUBLE:
1894
+ val = rb_float_new(*(double*)var->sqldata);
1895
+ break;
1896
+ #if HAVE_LONG_LONG
1897
+ case SQL_INT64:
1898
+ val = LL2NUM(*(LONG_LONG*)var->sqldata);
1899
+ break;
1900
+ #endif
1901
+ case SQL_TIMESTAMP:
1902
+ isc_decode_timestamp((ISC_TIMESTAMP *)var->sqldata, &tms);
1903
+ t = mktime(&tms);
1904
+ if (t < 0) t = 0;
1905
+ val = rb_time_new(t, 0);
1906
+ break;
1907
+
1908
+ case SQL_TYPE_TIME:
1909
+ isc_decode_sql_time((ISC_TIME *)var->sqldata, &tms);
1910
+ /*
1911
+ t = mktime(&tms);
1912
+ if (t < 0) t = t + (24 * 60 * 60);
1913
+ val = rb_time_new(t, 0);
1914
+ */
1915
+ tms.tm_year = 2000;
1916
+ tms.tm_mon = 1;
1917
+ tms.tm_mday = 1;
1918
+ val = fb_mktime(&tms);
1919
+ break;
1920
+
1921
+ case SQL_TYPE_DATE:
1922
+ isc_decode_sql_date((ISC_DATE *)var->sqldata, &tms);
1923
+ val = fb_mkdate(&tms);
1924
+ break;
1925
+
1926
+ case SQL_BLOB:
1927
+ blob_handle = NULL;
1928
+ blob_id = *(ISC_QUAD *)var->sqldata;
1929
+ isc_open_blob2(isc_status, &fb_connection->db, &fb_connection->transact, &blob_handle, &blob_id, 0, NULL);
1930
+ fb_error_check(isc_status);
1931
+ isc_blob_info(
1932
+ isc_status, &blob_handle,
1933
+ sizeof(blob_items), blob_items,
1934
+ sizeof(blob_info), blob_info);
1935
+ fb_error_check(isc_status);
1936
+ for (p = blob_info; *p != isc_info_end; p += length) {
1937
+ item = *p++;
1938
+ length = (short) isc_vax_integer(p,2);
1939
+ p += 2;
1940
+ switch (item) {
1941
+ case isc_info_blob_max_segment:
1942
+ max_segment = isc_vax_integer(p,length);
1943
+ break;
1944
+ case isc_info_blob_num_segments:
1945
+ num_segments = isc_vax_integer(p,length);
1946
+ break;
1947
+ case isc_info_blob_total_length:
1948
+ total_length = isc_vax_integer(p,length);
1949
+ break;
1950
+ }
1951
+ }
1952
+ val = rb_tainted_str_new(NULL,total_length);
1953
+ for (p = RSTRING(val)->ptr; num_segments > 0; num_segments--, p += actual_seg_len) {
1954
+ isc_get_segment(isc_status, &blob_handle, &actual_seg_len, max_segment, p);
1955
+ fb_error_check(isc_status);
1956
+ }
1957
+ isc_close_blob(isc_status, &blob_handle);
1958
+ fb_error_check(isc_status);
1959
+ break;
1960
+
1961
+ case SQL_ARRAY:
1962
+ rb_warn("ARRAY not supported (yet)");
1963
+ val = Qnil;
1964
+ break;
1965
+
1966
+ default:
1967
+ rb_raise(rb_eFbError, "Specified table includes unsupported datatype (%d)", dtp);
1968
+ break;
1969
+ }
1970
+ }
1971
+ rb_ary_push(ary, val);
1972
+ }
1973
+
1974
+ return ary;
1975
+ }
1976
+
1977
+ static long cursor_rows_affected(struct FbCursor *fb_cursor, long statement_type)
1978
+ {
1979
+ long inserted = 0, selected = 0, updated = 0, deleted = 0;
1980
+ char request[] = { isc_info_sql_records };
1981
+ char response[64], *r;
1982
+ long isc_status[20];
1983
+
1984
+ isc_dsql_sql_info(isc_status, &fb_cursor->stmt, sizeof(request), request, sizeof(response), response);
1985
+ fb_error_check(isc_status);
1986
+ if (response[0] != isc_info_sql_records) { return -1; }
1987
+
1988
+ r = response + 3; /* skip past first cluster */
1989
+ while (*r != isc_info_end) {
1990
+ char count_type = *r++;
1991
+ short len = isc_vax_integer(r, sizeof(short));
1992
+ r += sizeof(short);
1993
+ switch (count_type) {
1994
+ case isc_info_req_insert_count:
1995
+ inserted = isc_vax_integer(r, len);
1996
+ break;
1997
+ case isc_info_req_select_count:
1998
+ selected = isc_vax_integer(r, len);
1999
+ break;
2000
+ case isc_info_req_update_count:
2001
+ updated = isc_vax_integer(r, len);
2002
+ break;
2003
+ case isc_info_req_delete_count:
2004
+ deleted = isc_vax_integer(r, len);
2005
+ break;
2006
+ }
2007
+ r += len;
2008
+ }
2009
+ switch (statement_type) {
2010
+ case isc_info_sql_stmt_select: return selected;
2011
+ case isc_info_sql_stmt_insert: return inserted;
2012
+ case isc_info_sql_stmt_update: return updated;
2013
+ case isc_info_sql_stmt_delete: return deleted;
2014
+ default: return inserted + selected + updated + deleted;
2015
+ }
2016
+ }
2017
+
2018
+ /* call-seq:
2019
+ * execute2(sql, *args) -> nil or rows affected
2020
+ *
2021
+ * This function is not published.
2022
+ */
2023
+ static VALUE cursor_execute2(VALUE args)
2024
+ {
2025
+ struct FbCursor *fb_cursor;
2026
+ struct FbConnection *fb_connection;
2027
+ char *sql;
2028
+ long statement;
2029
+ long length;
2030
+ long in_params;
2031
+ long cols;
2032
+ long rows_affected;
2033
+ VALUE result = Qnil;
2034
+ long isc_status[20];
2035
+
2036
+ VALUE self = rb_ary_pop(args);
2037
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2038
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
2039
+
2040
+ sql = STR2CSTR(rb_ary_shift(args));
2041
+
2042
+ /* Prepare query */
2043
+ isc_dsql_prepare(isc_status, &fb_connection->transact, &fb_cursor->stmt, 0, sql, fb_connection_dialect(fb_connection), fb_cursor->o_sqlda);
2044
+ fb_error_check(isc_status);
2045
+
2046
+ /* Get the statement type */
2047
+ isc_dsql_sql_info(isc_status, &fb_cursor->stmt,
2048
+ sizeof(isc_info_stmt), isc_info_stmt,
2049
+ sizeof(isc_info_buff), isc_info_buff);
2050
+ fb_error_check(isc_status);
2051
+
2052
+ if (isc_info_buff[0] == isc_info_sql_stmt_type) {
2053
+ length = isc_vax_integer(&isc_info_buff[1], 2);
2054
+ statement = isc_vax_integer(&isc_info_buff[3], (short)length);
2055
+ } else {
2056
+ statement = 0;
2057
+ }
2058
+ /* Describe the parameters */
2059
+ isc_dsql_describe_bind(isc_status, &fb_cursor->stmt, 1, fb_cursor->i_sqlda);
2060
+ fb_error_check(isc_status);
2061
+
2062
+ isc_dsql_describe(isc_status, &fb_cursor->stmt, 1, fb_cursor->o_sqlda);
2063
+ fb_error_check(isc_status);
2064
+
2065
+ /* Get the number of parameters and reallocate the SQLDA */
2066
+ in_params = fb_cursor->i_sqlda->sqld;
2067
+ if (fb_cursor->i_sqlda->sqln < in_params) {
2068
+ xfree(fb_cursor->i_sqlda);
2069
+ fb_cursor->i_sqlda = sqlda_alloc(in_params);
2070
+ /* Describe again */
2071
+ isc_dsql_describe_bind(isc_status, &fb_cursor->stmt, 1, fb_cursor->i_sqlda);
2072
+ fb_error_check(isc_status);
2073
+ }
2074
+
2075
+ /* Get the size of parameters buffer and reallocate it */
2076
+ if (in_params) {
2077
+ length = calculate_buffsize(fb_cursor->i_sqlda);
2078
+ if (length > fb_cursor->i_buffer_size) {
2079
+ fb_cursor->i_buffer = xrealloc(fb_cursor->i_buffer, length);
2080
+ fb_cursor->i_buffer_size = length;
2081
+ }
2082
+ }
2083
+
2084
+ /* Execute the SQL statement if it is not query */
2085
+ if (!fb_cursor->o_sqlda->sqld) {
2086
+ if (statement == isc_info_sql_stmt_start_trans) {
2087
+ rb_raise(rb_eFbError, "use Fb::Connection#transaction()");
2088
+ } else if (statement == isc_info_sql_stmt_commit) {
2089
+ rb_raise(rb_eFbError, "use Fb::Connection#commit()");
2090
+ } else if (statement == isc_info_sql_stmt_rollback) {
2091
+ rb_raise(rb_eFbError, "use Fb::Connection#rollback()");
2092
+ } else if (in_params) {
2093
+ fb_cursor_execute_withparams(fb_cursor, RARRAY(args)->len, RARRAY(args)->ptr);
2094
+ } else {
2095
+ isc_dsql_execute2(isc_status, &fb_connection->transact, &fb_cursor->stmt, SQLDA_VERSION1, NULL, NULL);
2096
+ fb_error_check(isc_status);
2097
+ }
2098
+ rows_affected = cursor_rows_affected(fb_cursor, statement);
2099
+ result = INT2NUM(rows_affected);
2100
+ } else {
2101
+ /* Open cursor if the SQL statement is query */
2102
+ /* Get the number of columns and reallocate the SQLDA */
2103
+ cols = fb_cursor->o_sqlda->sqld;
2104
+ if (fb_cursor->o_sqlda->sqln < cols) {
2105
+ xfree(fb_cursor->o_sqlda);
2106
+ fb_cursor->o_sqlda = sqlda_alloc(cols);
2107
+ /* Describe again */
2108
+ isc_dsql_describe(isc_status, &fb_cursor->stmt, 1, fb_cursor->o_sqlda);
2109
+ fb_error_check(isc_status);
2110
+ }
2111
+
2112
+ if (in_params) {
2113
+ fb_cursor_set_inputparams(fb_cursor, RARRAY(args)->len, RARRAY(args)->ptr);
2114
+ }
2115
+
2116
+ /* Open cursor */
2117
+ isc_dsql_execute2(isc_status, &fb_connection->transact, &fb_cursor->stmt, SQLDA_VERSION1, in_params ? fb_cursor->i_sqlda : NULL, NULL);
2118
+ fb_error_check(isc_status);
2119
+ fb_cursor->open = Qtrue;
2120
+
2121
+ /* Get the size of results buffer and reallocate it */
2122
+ length = calculate_buffsize(fb_cursor->o_sqlda);
2123
+ if (length > fb_cursor->o_buffer_size) {
2124
+ fb_cursor->o_buffer = xrealloc(fb_cursor->o_buffer, length);
2125
+ fb_cursor->o_buffer_size = length;
2126
+ }
2127
+
2128
+ /* Set the description attributes */
2129
+ fb_cursor->fields_ary = fb_cursor_fields_ary(fb_cursor->o_sqlda, fb_connection->downcase_names);
2130
+ fb_cursor->fields_hash = fb_cursor_fields_hash(fb_cursor->fields_ary);
2131
+ }
2132
+ return result;
2133
+ }
2134
+
2135
+ /* call-seq:
2136
+ * execute(sql, *args) -> nil or rows affected
2137
+ *
2138
+ * This function is no longer published.
2139
+ */
2140
+ static VALUE cursor_execute(int argc, VALUE* argv, VALUE self)
2141
+ {
2142
+ struct FbCursor *fb_cursor;
2143
+ struct FbConnection *fb_connection;
2144
+ long isc_status[20];
2145
+ VALUE args;
2146
+
2147
+ if (argc < 1) {
2148
+ rb_raise(rb_eArgError, "At least 1 argument required.");
2149
+ }
2150
+
2151
+ args = rb_ary_new4(argc, argv);
2152
+ rb_ary_push(args, self);
2153
+
2154
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2155
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
2156
+ fb_connection_check(fb_connection);
2157
+
2158
+ if (fb_cursor->open) {
2159
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_close);
2160
+ fb_error_check(isc_status);
2161
+ fb_cursor->open = Qfalse;
2162
+ }
2163
+
2164
+ if (!fb_connection->transact) {
2165
+ VALUE result;
2166
+ int state;
2167
+
2168
+ fb_connection_transaction_start(fb_connection, Qnil);
2169
+ fb_cursor->auto_transact = fb_connection->transact;
2170
+
2171
+ result = rb_protect(cursor_execute2, args, &state);
2172
+ if (state) {
2173
+ fb_connection_rollback(fb_connection);
2174
+ return rb_funcall(rb_mKernel, rb_intern("raise"), 0);
2175
+ } else if (result != Qnil) {
2176
+ fb_connection_commit(fb_connection);
2177
+ return result;
2178
+ } else {
2179
+ return result;
2180
+ }
2181
+ } else {
2182
+ return cursor_execute2(args);
2183
+ }
2184
+ }
2185
+
2186
+ static VALUE fb_hash_from_ary(VALUE fields, VALUE row)
2187
+ {
2188
+ VALUE hash = rb_hash_new();
2189
+ int i;
2190
+ for (i = 0; i < RARRAY(fields)->len; i++) {
2191
+ VALUE field = rb_ary_entry(fields, i);
2192
+ VALUE name = rb_struct_aref(field, LONG2NUM(0));
2193
+ VALUE v = rb_ary_entry(row, i);
2194
+ rb_hash_aset(hash, name, v);
2195
+ }
2196
+ return hash;
2197
+ }
2198
+
2199
+ static int hash_format(int argc, VALUE *argv)
2200
+ {
2201
+ if (argc == 0 || argv[0] == ID2SYM(rb_intern("array"))) {
2202
+ return 0;
2203
+ } else if (argv[0] == ID2SYM(rb_intern("hash"))) {
2204
+ return 1;
2205
+ } else {
2206
+ rb_raise(rb_eFbError, "Unknown format");
2207
+ }
2208
+ }
2209
+
2210
+ /* call-seq:
2211
+ * fetch() -> Array
2212
+ * fetch(:array) -> Array
2213
+ * fetch(:hash) -> Hash
2214
+ *
2215
+ * Reads and returns a single row from the open cursor in either an Array or a Hash,
2216
+ * where the column names or aliases from the query form the keys.
2217
+ * If the +downcase_names+ attribute of the associated connection evaluates to true,
2218
+ * the keys are lower case, except where the column name was mixed case to begin with.
2219
+ */
2220
+ static VALUE cursor_fetch(int argc, VALUE* argv, VALUE self)
2221
+ {
2222
+ VALUE ary;
2223
+ struct FbCursor *fb_cursor;
2224
+
2225
+ int hash_row = hash_format(argc, argv);
2226
+
2227
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2228
+ fb_cursor_fetch_prep(fb_cursor);
2229
+
2230
+ ary = fb_cursor_fetch(fb_cursor);
2231
+ return hash_row ? fb_hash_from_ary(fb_cursor->fields_ary, ary) : ary;
2232
+ }
2233
+
2234
+ /* call-seq:
2235
+ * fetchall() -> Array of Arrays
2236
+ * fetchall(:array) -> Array of Arrays
2237
+ * fetchall(:hash) -> Array of Hashes
2238
+ *
2239
+ * Returns the remainder of the rows from the open cursor, with each row represented
2240
+ * by either an Array or a Hash, where the column names or aliases from the query form the keys.
2241
+ * If the +downcase_names+ attribute of the associated connection evaluates to true,
2242
+ * the keys are lower case, except where the column name was mixed case to begin with.
2243
+ */
2244
+ static VALUE cursor_fetchall(int argc, VALUE* argv, VALUE self)
2245
+ {
2246
+ VALUE ary, row;
2247
+ struct FbCursor *fb_cursor;
2248
+
2249
+ int hash_rows = hash_format(argc, argv);
2250
+
2251
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2252
+ fb_cursor_fetch_prep(fb_cursor);
2253
+
2254
+ ary = rb_ary_new();
2255
+ for (;;) {
2256
+ row = fb_cursor_fetch(fb_cursor);
2257
+ if (NIL_P(row)) break;
2258
+ if (hash_rows) {
2259
+ rb_ary_push(ary, fb_hash_from_ary(fb_cursor->fields_ary, row));
2260
+ } else {
2261
+ rb_ary_push(ary, row);
2262
+ }
2263
+ }
2264
+
2265
+ return ary;
2266
+ }
2267
+
2268
+ /* call-seq:
2269
+ * each() {|Array| } -> nil
2270
+ * each(:array) {|Array| } -> nil
2271
+ * each(:hash) {|Hash| } -> nil
2272
+ *
2273
+ * Iterates the rows from the open cursor, passing each one to a block in either
2274
+ * an Array or a Hash, where the column names or aliases from the query form the keys.
2275
+ * If the +downcase_names+ attribute of the associated connection evaluates to true,
2276
+ * the keys are lower case, except where the column name was mixed case to begin with.
2277
+ */
2278
+ static VALUE cursor_each(int argc, VALUE* argv, VALUE self)
2279
+ {
2280
+ VALUE ary, row;
2281
+ struct FbCursor *fb_cursor;
2282
+
2283
+ int hash_rows = hash_format(argc, argv);
2284
+
2285
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2286
+ fb_cursor_fetch_prep(fb_cursor);
2287
+
2288
+ for (;;) {
2289
+ row = fb_cursor_fetch(fb_cursor);
2290
+ if (NIL_P(row)) break;
2291
+ if (hash_rows) {
2292
+ rb_yield(fb_hash_from_ary(fb_cursor->fields_ary, row));
2293
+ } else {
2294
+ rb_yield(row);
2295
+ }
2296
+ }
2297
+
2298
+ return Qnil;
2299
+ }
2300
+
2301
+ /* call-seq:
2302
+ * close(sql, *args) -> nil
2303
+ *
2304
+ * Closes the cursor. If a transaction was automatically started for this cursor, the transaction is commited.
2305
+ */
2306
+ static VALUE cursor_close(VALUE self)
2307
+ {
2308
+ struct FbCursor *fb_cursor;
2309
+ struct FbConnection *fb_connection;
2310
+ long isc_status[20];
2311
+
2312
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2313
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
2314
+ fb_cursor_check(fb_cursor);
2315
+
2316
+ /* Close the cursor */
2317
+ if (fb_cursor->stmt) {
2318
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_close);
2319
+ fb_error_check_warn(isc_status);
2320
+ isc_dsql_free_statement(isc_status, &fb_cursor->stmt, DSQL_drop);
2321
+ fb_error_check(isc_status);
2322
+ fb_cursor->open = Qfalse;
2323
+ if (fb_connection->transact == fb_cursor->auto_transact) {
2324
+ isc_commit_transaction(isc_status, &fb_connection->transact);
2325
+ fb_error_check(isc_status);
2326
+ fb_cursor->auto_transact = fb_connection->transact;
2327
+ }
2328
+ }
2329
+ fb_cursor->fields_ary = Qnil;
2330
+ fb_cursor->fields_hash = Qnil;
2331
+ return Qnil;
2332
+ }
2333
+
2334
+ /* call-seq:
2335
+ * drop() -> nil
2336
+ *
2337
+ * Drops the cursor.
2338
+ *
2339
+ * TODO: How is this different from close()?
2340
+ */
2341
+ static VALUE cursor_drop(VALUE self)
2342
+ {
2343
+ struct FbCursor *fb_cursor;
2344
+ struct FbConnection *fb_connection;
2345
+ int i;
2346
+
2347
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2348
+ fb_cursor_drop(fb_cursor);
2349
+ fb_cursor->fields_ary = Qnil;
2350
+ fb_cursor->fields_hash = Qnil;
2351
+
2352
+ /* reset the reference from connection */
2353
+ Data_Get_Struct(fb_cursor->connection, struct FbConnection, fb_connection);
2354
+ for (i = 0; i < RARRAY(fb_connection->cursor)->len; i++) {
2355
+ if (RARRAY(fb_connection->cursor)->ptr[i] == self) {
2356
+ RARRAY(fb_connection->cursor)->ptr[i] = Qnil;
2357
+ }
2358
+ }
2359
+
2360
+ return Qnil;
2361
+ }
2362
+
2363
+ /* call-seq:
2364
+ * fields() -> Array
2365
+ * fields(:array) -> Array
2366
+ * fields(:hash) -> Hash
2367
+ *
2368
+ * Return an array of Field Structs or a hash indexed by field name.
2369
+ * If the +downcase_names+ attribute of the associated connection evaluates to true,
2370
+ * the keys are lower case, except where the column name was mixed case to begin with.
2371
+ */
2372
+ static VALUE cursor_fields(int argc, VALUE* argv, VALUE self)
2373
+ {
2374
+ struct FbCursor *fb_cursor;
2375
+
2376
+ Data_Get_Struct(self, struct FbCursor, fb_cursor);
2377
+ if (argc == 0 || argv[0] == ID2SYM(rb_intern("array"))) {
2378
+ return fb_cursor->fields_ary;
2379
+ } else if (argv[0] == ID2SYM(rb_intern("hash"))) {
2380
+ return fb_cursor->fields_hash;
2381
+ } else {
2382
+ rb_raise(rb_eFbError, "Unknown format");
2383
+ }
2384
+ }
2385
+
2386
+ /* call-seq:
2387
+ * error_code -> int
2388
+ *
2389
+ * Returns the sqlcode associated with the error.
2390
+ */
2391
+ static VALUE error_error_code(VALUE error)
2392
+ {
2393
+ rb_p(error);
2394
+ return rb_iv_get(error, "error_code");
2395
+ }
2396
+
2397
+ static char* dbp_create(int *length)
2398
+ {
2399
+ char *dbp = ALLOC_N(char, 1);
2400
+ *dbp = isc_dpb_version1;
2401
+ *length = 1;
2402
+ return dbp;
2403
+ }
2404
+
2405
+ static char* dbp_add_string(char *dbp, char isc_dbp_code, char *s, int *length)
2406
+ {
2407
+ char *buf;
2408
+ int old_length = *length;
2409
+ int s_len = strlen(s);
2410
+ *length += 2 + s_len;
2411
+ REALLOC_N(dbp, char, *length);
2412
+ buf = dbp + old_length;
2413
+ *buf++ = isc_dbp_code;
2414
+ *buf++ = (char)s_len;
2415
+ memcpy(buf, s, s_len);
2416
+ return dbp;
2417
+ }
2418
+
2419
+ static char* connection_create_dbp(VALUE self, int *length)
2420
+ {
2421
+ char *dbp;
2422
+ VALUE username, password, charset, role;
2423
+
2424
+ username = rb_iv_get(self, "@username");
2425
+ Check_Type(username, T_STRING);
2426
+ password = rb_iv_get(self, "@password");
2427
+ Check_Type(password, T_STRING);
2428
+ role = rb_iv_get(self, "@role");
2429
+ charset = rb_iv_get(self, "@charset");
2430
+
2431
+ dbp = dbp_create(length);
2432
+ dbp = dbp_add_string(dbp, isc_dpb_user_name, STR2CSTR(username), length);
2433
+ dbp = dbp_add_string(dbp, isc_dpb_password, STR2CSTR(password), length);
2434
+ if (!NIL_P(charset)) {
2435
+ dbp = dbp_add_string(dbp, isc_dpb_lc_ctype, STR2CSTR(charset), length);
2436
+ }
2437
+ if (!NIL_P(role)) {
2438
+ dbp = dbp_add_string(dbp, isc_dpb_sql_role_name, STR2CSTR(role), length);
2439
+ }
2440
+ return dbp;
2441
+ }
2442
+
2443
+ static char* CONNECTION_PARMS[] = {
2444
+ "@database",
2445
+ "@username",
2446
+ "@password",
2447
+ "@charset",
2448
+ "@role",
2449
+ "@downcase_names",
2450
+ (char *)0
2451
+ };
2452
+
2453
+ static VALUE connection_create(isc_db_handle handle, VALUE db)
2454
+ {
2455
+ unsigned short dialect;
2456
+ unsigned short db_dialect;
2457
+ VALUE downcase_names;
2458
+ char *parm;
2459
+ int i;
2460
+ struct FbConnection *fb_connection;
2461
+ VALUE connection = Data_Make_Struct(rb_cFbConnection, struct FbConnection, fb_connection_mark, fb_connection_free, fb_connection);
2462
+ fb_connection->db = handle;
2463
+ fb_connection->transact = 0;
2464
+ fb_connection->cursor = rb_ary_new();
2465
+ connection_count++;
2466
+ fb_connection->next = fb_connection_list;
2467
+ fb_connection_list = fb_connection;
2468
+
2469
+ dialect = SQL_DIALECT_CURRENT;
2470
+ db_dialect = fb_connection_db_SQL_Dialect(fb_connection);
2471
+
2472
+ if (db_dialect < dialect) {
2473
+ dialect = db_dialect;
2474
+ /* TODO: downgrade warning */
2475
+ }
2476
+
2477
+ fb_connection->dialect = dialect;
2478
+ fb_connection->db_dialect = db_dialect;
2479
+ downcase_names = rb_iv_get(db, "@downcase_names");
2480
+ fb_connection->downcase_names = RTEST(downcase_names);
2481
+
2482
+ for (i = 0; parm = CONNECTION_PARMS[i]; i++) {
2483
+ rb_iv_set(connection, parm, rb_iv_get(db, parm));
2484
+ }
2485
+
2486
+ return connection;
2487
+ }
2488
+
2489
+ static VALUE connection_names(VALUE self, char *sql)
2490
+ {
2491
+ VALUE row;
2492
+ VALUE query = rb_str_new2(sql);
2493
+ VALUE cursor = connection_execute(1, &query, self);
2494
+ VALUE names = rb_ary_new();
2495
+ ID id_rstrip_bang = rb_intern("rstrip!");
2496
+ struct FbConnection *fb_connection;
2497
+ VALUE re_lowercase;
2498
+ ID id_matches, id_downcase_bang;
2499
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
2500
+
2501
+ if (fb_connection->downcase_names) {
2502
+ re_lowercase = rb_reg_regcomp(rb_str_new2("[[:lower:]]"));
2503
+ id_matches = rb_intern("=~");
2504
+ id_downcase_bang = rb_intern("downcase!");
2505
+ }
2506
+
2507
+ while ((row = cursor_fetch(0, NULL, cursor)) != Qnil) {
2508
+ VALUE name = rb_ary_entry(row, 0);
2509
+ if (fb_connection->downcase_names && rb_funcall(re_lowercase, id_matches, 1, name) == Qnil) {
2510
+ rb_funcall(name, id_downcase_bang, 0);
2511
+ }
2512
+ rb_funcall(name, id_rstrip_bang, 0);
2513
+ rb_ary_push(names, name);
2514
+ }
2515
+
2516
+ cursor_close(cursor);
2517
+ return names;
2518
+ }
2519
+
2520
+ /* call-seq:
2521
+ * table_names() -> array
2522
+ *
2523
+ * Returns sorted array of table names in connected database.
2524
+ */
2525
+ static VALUE connection_table_names(VALUE self)
2526
+ {
2527
+ char *sql = "SELECT RDB$RELATION_NAME FROM RDB$RELATIONS "
2528
+ "WHERE (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND RDB$VIEW_BLR IS NULL "
2529
+ "ORDER BY RDB$RELATION_NAME";
2530
+ return connection_names(self, sql);
2531
+ }
2532
+
2533
+ /* call-seq:
2534
+ * generator_names() -> array
2535
+ *
2536
+ * Returns sorted array of generator names in connected database.
2537
+ */
2538
+ static VALUE connection_generator_names(VALUE self)
2539
+ {
2540
+ char *sql = "SELECT RDB$GENERATOR_NAME FROM RDB$GENERATORS "
2541
+ "WHERE (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG <> 1) "
2542
+ "ORDER BY RDB$GENERATOR_NAME";
2543
+ return connection_names(self, sql);
2544
+ }
2545
+
2546
+ /* call-seq:
2547
+ * view_names() -> array
2548
+ *
2549
+ * Returns sorted array of view names in connected database.
2550
+ */
2551
+ static VALUE connection_view_names(VALUE self)
2552
+ {
2553
+ char *sql = "SELECT RDB$RELATION_NAME, RDB$OWNER_NAME, RDB$VIEW_SOURCE FROM RDB$RELATIONS "
2554
+ "WHERE (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND NOT RDB$VIEW_BLR IS NULL AND RDB$FLAGS = 1 "
2555
+ "ORDER BY RDB$RELATION_ID";
2556
+ return connection_names(self, sql);
2557
+ }
2558
+
2559
+ /* call-seq:
2560
+ * role_names() -> array
2561
+ *
2562
+ * Returns sorted array of role names in connected database.
2563
+ */
2564
+ static VALUE connection_role_names(VALUE self)
2565
+ {
2566
+ char *sql = "SELECT * FROM RDB$ROLES ORDER BY RDB$ROLE_NAME";
2567
+ return connection_names(self, sql);
2568
+ }
2569
+
2570
+ /* call-seq:
2571
+ * procedure_names() -> array
2572
+ *
2573
+ * Returns sorted array of stored procedure names in connected database.
2574
+ */
2575
+ static VALUE connection_procedure_names(VALUE self)
2576
+ {
2577
+ char *sql = "SELECT RDB$PROCEDURE_NAME FROM RDB$PROCEDURES "
2578
+ "ORDER BY RDB$PROCEDURE_NAME";
2579
+ return connection_names(self, sql);
2580
+ }
2581
+
2582
+ char *p(char *prompt, VALUE s)
2583
+ {
2584
+ char *sz;
2585
+ if (TYPE(s) != T_STRING) {
2586
+ s = rb_funcall(s, rb_intern("to_s"), 0);
2587
+ }
2588
+ sz = StringValuePtr(s);
2589
+ printf("%s: %s\n", prompt, sz);
2590
+ return sz;
2591
+ }
2592
+
2593
+ static VALUE connection_index_columns(VALUE self, VALUE index_name)
2594
+ {
2595
+ char *sql_columns = "SELECT * "
2596
+ "FROM RDB$INDEX_SEGMENTS "
2597
+ "WHERE RDB$INDEX_SEGMENTS.RDB$INDEX_NAME = ? "
2598
+ "ORDER BY RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION";
2599
+ ID id_rstrip_bang = rb_intern("rstrip!");
2600
+ VALUE query_columns = rb_str_new2(sql_columns);
2601
+ VALUE query_parms[] = { query_columns, index_name };
2602
+ VALUE result = connection_query(2, query_parms, self);
2603
+ VALUE columns = rb_ary_new();
2604
+ VALUE re_lowercase;
2605
+ ID id_matches, id_downcase_bang;
2606
+ int i;
2607
+ struct FbConnection *fb_connection;
2608
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
2609
+
2610
+ if (fb_connection->downcase_names) {
2611
+ re_lowercase = rb_reg_regcomp(rb_str_new2("[[:lower:]]"));
2612
+ id_matches = rb_intern("=~");
2613
+ id_downcase_bang = rb_intern("downcase!");
2614
+ }
2615
+
2616
+ for (i = 0; i < RARRAY(result)->len; i++) {
2617
+ VALUE row = rb_ary_entry(result, i);
2618
+ VALUE name = rb_ary_entry(row, 1);
2619
+ rb_funcall(name, id_rstrip_bang, 0);
2620
+ if (fb_connection->downcase_names && rb_funcall(re_lowercase, id_matches, 1, name) == Qnil) {
2621
+ rb_funcall(name, id_downcase_bang, 0);
2622
+ }
2623
+ rb_ary_push(columns, name);
2624
+ }
2625
+ return columns;
2626
+ }
2627
+
2628
+ /* call-seq:
2629
+ * indexes() -> Hash
2630
+ *
2631
+ * Returns a hash of indexes, keyed by index name.
2632
+ */
2633
+ static VALUE connection_indexes(VALUE self)
2634
+ {
2635
+ char *sql_indexes = "SELECT RDB$INDICES.RDB$RELATION_NAME, RDB$INDICES.RDB$INDEX_NAME, RDB$INDICES.RDB$UNIQUE_FLAG, RDB$INDICES.RDB$INDEX_TYPE "
2636
+ "FROM RDB$INDICES "
2637
+ " JOIN RDB$RELATIONS ON RDB$INDICES.RDB$RELATION_NAME = RDB$RELATIONS.RDB$RELATION_NAME "
2638
+ "WHERE (RDB$RELATIONS.RDB$SYSTEM_FLAG <> 1 OR RDB$RELATIONS.RDB$SYSTEM_FLAG IS NULL) ";
2639
+ ID id_rstrip_bang = rb_intern("rstrip!");
2640
+ VALUE query_indexes = rb_str_new2(sql_indexes);
2641
+ VALUE ary_indexes = connection_query(1, &query_indexes, self);
2642
+ VALUE indexes = rb_hash_new();
2643
+ VALUE re_lowercase;
2644
+ ID id_matches, id_downcase_bang;
2645
+ int i;
2646
+ struct FbConnection *fb_connection;
2647
+ Data_Get_Struct(self, struct FbConnection, fb_connection);
2648
+
2649
+ if (fb_connection->downcase_names) {
2650
+ re_lowercase = rb_reg_regcomp(rb_str_new2("[[:lower:]]"));
2651
+ id_matches = rb_intern("=~");
2652
+ id_downcase_bang = rb_intern("downcase!");
2653
+ }
2654
+
2655
+ for (i = 0; i < RARRAY(ary_indexes)->len; i++) {
2656
+ VALUE index_struct;
2657
+ VALUE row = rb_ary_entry(ary_indexes, i);
2658
+ VALUE table_name = rb_ary_entry(row, 0);
2659
+ VALUE index_name = rb_ary_entry(row, 1);
2660
+ VALUE unique = rb_ary_entry(row, 2);
2661
+ VALUE descending = rb_ary_entry(row, 3);
2662
+ VALUE columns = connection_index_columns(self, index_name);
2663
+
2664
+ rb_funcall(table_name, id_rstrip_bang, 0);
2665
+ rb_funcall(index_name, id_rstrip_bang, 0);
2666
+
2667
+ if (fb_connection->downcase_names) {
2668
+ if (rb_funcall(re_lowercase, id_matches, 1, table_name) == Qnil) {
2669
+ rb_funcall(table_name, id_downcase_bang, 0);
2670
+ }
2671
+ if (rb_funcall(re_lowercase, id_matches, 1, index_name) == Qnil) {
2672
+ rb_funcall(index_name, id_downcase_bang, 0);
2673
+ }
2674
+ }
2675
+
2676
+ rb_str_freeze(table_name);
2677
+ rb_str_freeze(index_name);
2678
+
2679
+ unique = RTEST(unique) ? Qtrue : Qfalse;
2680
+ descending = RTEST(descending) ? Qtrue : Qfalse;
2681
+
2682
+ index_struct = rb_struct_new(rb_sFbIndex, table_name, index_name, unique, descending, columns);
2683
+ rb_hash_aset(indexes, index_name, index_struct);
2684
+ }
2685
+ return indexes;
2686
+ }
2687
+
2688
+ /* call-seq:
2689
+ * from_code(code, subtype) -> String
2690
+ *
2691
+ * Returns the SQL type, such as VARCHAR or INTEGER for a given type code and subtype.
2692
+ */
2693
+ static VALUE sql_type_from_code(VALUE self, VALUE code, VALUE subtype)
2694
+ {
2695
+ return fb_sql_type_from_code(NUM2INT(code), NUM2INT(subtype));
2696
+ }
2697
+
2698
+ static void define_attrs(VALUE klass, char **attrs)
2699
+ {
2700
+ char *parm;
2701
+ while (parm = *attrs)
2702
+ {
2703
+ rb_define_attr(klass, parm+1, 1, 1);
2704
+ attrs++;
2705
+ }
2706
+ }
2707
+
2708
+ static VALUE default_string(VALUE hash, char *key, char *def)
2709
+ {
2710
+ VALUE sym = ID2SYM(rb_intern(key));
2711
+ VALUE val = rb_hash_aref(hash, sym);
2712
+ return NIL_P(val) ? rb_str_new2(def) : StringValue(val);
2713
+ }
2714
+
2715
+ static VALUE default_int(VALUE hash, char *key, int def)
2716
+ {
2717
+ VALUE sym = ID2SYM(rb_intern(key));
2718
+ VALUE val = rb_hash_aref(hash, sym);
2719
+ return NIL_P(val) ? INT2NUM(def) : val;
2720
+ }
2721
+
2722
+ static VALUE database_allocate_instance(VALUE klass)
2723
+ {
2724
+ NEWOBJ(obj, struct RObject);
2725
+ OBJSETUP(obj, klass, T_OBJECT);
2726
+ return (VALUE)obj;
2727
+ }
2728
+
2729
+ static VALUE hash_from_connection_string(VALUE cs)
2730
+ {
2731
+ VALUE hash = rb_hash_new();
2732
+ VALUE re_SemiColon = rb_reg_regcomp(rb_str_new2("\\s*;\\s*"));
2733
+ VALUE re_Equal = rb_reg_regcomp(rb_str_new2("\\s*=\\s*"));
2734
+ ID id_split = rb_intern("split");
2735
+ VALUE pairs = rb_funcall(cs, id_split, 1, re_SemiColon);
2736
+ int i;
2737
+ for (i = 0; i < RARRAY(pairs)->len; i++) {
2738
+ VALUE pair = rb_ary_entry(pairs, i);
2739
+ VALUE keyValue = rb_funcall(pair, id_split, 1, re_Equal);
2740
+ if (RARRAY(keyValue)->len == 2) {
2741
+ VALUE key = rb_ary_entry(keyValue, 0);
2742
+ VALUE val = rb_ary_entry(keyValue, 1);
2743
+ rb_hash_aset(hash, rb_str_intern(key), val);
2744
+ }
2745
+ }
2746
+ return hash;
2747
+ }
2748
+
2749
+ static void check_page_size(int page_size)
2750
+ {
2751
+ if (page_size != 1024 && page_size != 2048 && page_size != 4096 && page_size != 8192) {
2752
+ rb_raise(rb_eFbError, "Invalid page size: %d", page_size);
2753
+ }
2754
+ }
2755
+
2756
+ /* call-seq:
2757
+ * Database.new(options) -> Database
2758
+ *
2759
+ * Initialize Database with Hash of values:
2760
+ * :database:: Full Firebird connection string, e.g. 'localhost:/var/fbdata/drivertest.fdb' (required)
2761
+ * :username:: database username (default: 'sysdba')
2762
+ * :password:: database password (default: 'masterkey')
2763
+ * :charset:: character set to be used with the connection (default: 'NONE')
2764
+ * :role:: database role to connect using (default: nil)
2765
+ * :downcase_names:: Column names are reported in lowercase, unless they were originally mixed case (default: nil).
2766
+ * :page_size:: page size to use when creating a database (default: 1024)
2767
+ */
2768
+ static VALUE database_initialize(int argc, VALUE *argv, VALUE self)
2769
+ {
2770
+ VALUE parms, database;
2771
+
2772
+ if (argc >= 1) {
2773
+ parms = argv[0];
2774
+ if (TYPE(parms) == T_STRING) {
2775
+ parms = hash_from_connection_string(parms);
2776
+ } else {
2777
+ Check_Type(parms, T_HASH);
2778
+ }
2779
+ database = rb_hash_aref(parms, ID2SYM(rb_intern("database")));
2780
+ if (NIL_P(database)) rb_raise(rb_eFbError, "Database must be specified.");
2781
+ rb_iv_set(self, "@database", database);
2782
+ rb_iv_set(self, "@username", default_string(parms, "username", "sysdba"));
2783
+ rb_iv_set(self, "@password", default_string(parms, "password", "masterkey"));
2784
+ rb_iv_set(self, "@charset", default_string(parms, "charset", "NONE"));
2785
+ rb_iv_set(self, "@role", rb_hash_aref(parms, ID2SYM(rb_intern("role"))));
2786
+ rb_iv_set(self, "@downcase_names", rb_hash_aref(parms, ID2SYM(rb_intern("downcase_names"))));
2787
+ rb_iv_set(self, "@page_size", default_int(parms, "page_size", 1024));
2788
+ }
2789
+ return self;
2790
+ }
2791
+
2792
+ /* call-seq:
2793
+ * create() -> Database
2794
+ * create() {|connection| } -> Database
2795
+ *
2796
+ * Create a database using the current database options.
2797
+ * If a block is provided, an open connection to the new database is passed to it
2798
+ * before being automatically closed.
2799
+ */
2800
+ static VALUE database_create(VALUE self)
2801
+ {
2802
+ long isc_status[20];
2803
+ isc_db_handle handle = 0;
2804
+ isc_tr_handle local_transact = 0;
2805
+ VALUE parms, fmt, stmt;
2806
+ char *sql;
2807
+
2808
+ VALUE database = rb_iv_get(self, "@database");
2809
+ VALUE username = rb_iv_get(self, "@username");
2810
+ VALUE password = rb_iv_get(self, "@password");
2811
+ VALUE page_size = rb_iv_get(self, "@page_size");
2812
+ VALUE charset = rb_iv_get(self, "@charset");
2813
+
2814
+ check_page_size(NUM2INT(page_size));
2815
+
2816
+ parms = rb_ary_new3(5, database, username, password, page_size, charset);
2817
+
2818
+ fmt = rb_str_new2("CREATE DATABASE '%s' USER '%s' PASSWORD '%s' PAGE_SIZE = %d DEFAULT CHARACTER SET %s;");
2819
+ stmt = rb_funcall(fmt, rb_intern("%"), 1, parms);
2820
+ sql = StringValuePtr(stmt);
2821
+
2822
+ if (isc_dsql_execute_immediate(isc_status, &handle, &local_transact, 0, sql, 3, NULL) != 0) {
2823
+ fb_error_check(isc_status);
2824
+ }
2825
+ if (handle) {
2826
+ if (rb_block_given_p()) {
2827
+ VALUE connection = connection_create(handle, self);
2828
+ rb_ensure(rb_yield,connection,connection_close,connection);
2829
+ } else {
2830
+ isc_detach_database(isc_status, &handle);
2831
+ fb_error_check(isc_status);
2832
+ }
2833
+ }
2834
+
2835
+ return self;
2836
+ }
2837
+
2838
+ /* call-seq:
2839
+ * Database.create(options) -> Database
2840
+ * Database.create(options) {|connection| } -> Database
2841
+ *
2842
+ * Create a database using the specified options (see: Database.new for details of options Hash).
2843
+ * If a block is provided, an open connection to the new database is passed to it
2844
+ * before being automatically closed.
2845
+ */
2846
+ static VALUE database_s_create(int argc, VALUE *argv, VALUE klass)
2847
+ {
2848
+ VALUE obj = database_allocate_instance(klass);
2849
+ database_initialize(argc, argv, obj);
2850
+ return database_create(obj);
2851
+ }
2852
+
2853
+ /* call-seq:
2854
+ * connect() -> Connection
2855
+ * connect() {|connection| } -> nil
2856
+ *
2857
+ * Connect to the database specified by the current database options.
2858
+ *
2859
+ * If a block is provided, the open connection is passed to it before being
2860
+ * automatically closed.
2861
+ */
2862
+ static VALUE database_connect(VALUE self)
2863
+ {
2864
+ long isc_status[20];
2865
+ char *dbp;
2866
+ int length;
2867
+ isc_db_handle handle = NULL;
2868
+ VALUE database = rb_iv_get(self, "@database");
2869
+ Check_Type(database, T_STRING);
2870
+ dbp = connection_create_dbp(self, &length);
2871
+ isc_attach_database(isc_status, 0, STR2CSTR(database), &handle, length, dbp);
2872
+ xfree(dbp);
2873
+ fb_error_check(isc_status);
2874
+ {
2875
+ VALUE connection = connection_create(handle, self);
2876
+ if (rb_block_given_p()) {
2877
+ return rb_ensure(rb_yield, connection, connection_close, connection);
2878
+ return Qnil;
2879
+ } else {
2880
+ return connection;
2881
+ }
2882
+ }
2883
+ }
2884
+
2885
+ /* call-seq:
2886
+ * Database.connect(options) -> Connection
2887
+ * Database.connect(options) {|connection| } -> nil
2888
+ *
2889
+ * Connect to a database using the options given (see: Database.new for details of options Hash).
2890
+ *
2891
+ * If a block is provided, the open connection is passed to it before being
2892
+ * automatically closed.
2893
+ */
2894
+ static VALUE database_s_connect(int argc, VALUE *argv, VALUE klass)
2895
+ {
2896
+ VALUE obj = database_allocate_instance(klass);
2897
+ database_initialize(argc, argv, obj);
2898
+ return database_connect(obj);
2899
+ }
2900
+
2901
+ /* call-seq:
2902
+ * drop() -> nil
2903
+ *
2904
+ * Drop the database specified by the current database options.
2905
+ */
2906
+ static VALUE database_drop(VALUE self)
2907
+ {
2908
+ long isc_status[20];
2909
+ struct FbConnection *fb_connection;
2910
+
2911
+ VALUE connection = database_connect(self);
2912
+ Data_Get_Struct(connection, struct FbConnection, fb_connection);
2913
+ isc_drop_database(isc_status, &fb_connection->db);
2914
+ fb_error_check(isc_status);
2915
+ fb_connection_remove(fb_connection);
2916
+ return Qnil;
2917
+ }
2918
+
2919
+ /* call-seq:
2920
+ * Database.drop(options) -> nil
2921
+ *
2922
+ * Drop the database specified by the options given (see: Database.new for details of options Hash).
2923
+ */
2924
+ static VALUE database_s_drop(int argc, VALUE *argv, VALUE klass)
2925
+ {
2926
+ VALUE obj = database_allocate_instance(klass);
2927
+ database_initialize(argc, argv, obj);
2928
+ return database_drop(obj);
2929
+ }
2930
+
2931
+ void Init_fb()
2932
+ {
2933
+ rb_mFb = rb_define_module("Fb");
2934
+
2935
+ rb_cFbDatabase = rb_define_class_under(rb_mFb, "Database", rb_cData);
2936
+ rb_define_alloc_func(rb_cFbDatabase, database_allocate_instance);
2937
+ rb_define_method(rb_cFbDatabase, "initialize", database_initialize, -1);
2938
+ rb_define_attr(rb_cFbDatabase, "database", 1, 1);
2939
+ rb_define_attr(rb_cFbDatabase, "username", 1, 1);
2940
+ rb_define_attr(rb_cFbDatabase, "password", 1, 1);
2941
+ rb_define_attr(rb_cFbDatabase, "charset", 1, 1);
2942
+ rb_define_attr(rb_cFbDatabase, "role", 1, 1);
2943
+ rb_define_attr(rb_cFbDatabase, "downcase_names", 1, 1);
2944
+ rb_define_attr(rb_cFbDatabase, "page_size", 1, 1);
2945
+ rb_define_method(rb_cFbDatabase, "create", database_create, 0);
2946
+ rb_define_singleton_method(rb_cFbDatabase, "create", database_s_create, -1);
2947
+ rb_define_method(rb_cFbDatabase, "connect", database_connect, 0);
2948
+ rb_define_singleton_method(rb_cFbDatabase, "connect", database_s_connect, -1);
2949
+ rb_define_method(rb_cFbDatabase, "drop", database_drop, 0);
2950
+ rb_define_singleton_method(rb_cFbDatabase, "drop", database_s_drop, -1);
2951
+
2952
+ rb_cFbConnection = rb_define_class_under(rb_mFb, "Connection", rb_cData);
2953
+ rb_define_attr(rb_cFbConnection, "database", 1, 1);
2954
+ rb_define_attr(rb_cFbConnection, "username", 1, 1);
2955
+ rb_define_attr(rb_cFbConnection, "password", 1, 1);
2956
+ rb_define_attr(rb_cFbConnection, "charset", 1, 1);
2957
+ rb_define_attr(rb_cFbConnection, "role", 1, 1);
2958
+ rb_define_attr(rb_cFbConnection, "downcase_names", 1, 1);
2959
+ rb_define_method(rb_cFbConnection, "to_s", connection_to_s, 0);
2960
+ rb_define_method(rb_cFbConnection, "execute", connection_execute, -1);
2961
+ rb_define_method(rb_cFbConnection, "query", connection_query, -1);
2962
+ rb_define_method(rb_cFbConnection, "transaction", connection_transaction, -1);
2963
+ rb_define_method(rb_cFbConnection, "transaction_started", connection_transaction_started, 0);
2964
+ rb_define_method(rb_cFbConnection, "commit", connection_commit, 0);
2965
+ rb_define_method(rb_cFbConnection, "rollback", connection_rollback, 0);
2966
+ rb_define_method(rb_cFbConnection, "close", connection_close, 0);
2967
+ rb_define_method(rb_cFbConnection, "drop", connection_drop, 0);
2968
+ rb_define_method(rb_cFbConnection, "open?", connection_is_open, 0);
2969
+ rb_define_method(rb_cFbConnection, "dialect", connection_dialect, 0);
2970
+ rb_define_method(rb_cFbConnection, "db_dialect", connection_db_dialect, 0);
2971
+ rb_define_method(rb_cFbConnection, "table_names", connection_table_names, 0);
2972
+ rb_define_method(rb_cFbConnection, "generator_names", connection_generator_names, 0);
2973
+ rb_define_method(rb_cFbConnection, "view_names", connection_view_names, 0);
2974
+ rb_define_method(rb_cFbConnection, "role_names", connection_role_names, 0);
2975
+ rb_define_method(rb_cFbConnection, "procedure_names", connection_procedure_names, 0);
2976
+ rb_define_method(rb_cFbConnection, "indexes", connection_indexes, 0);
2977
+ //rb_define_method(rb_cFbConnection, "cursor", connection_cursor, 0);
2978
+
2979
+ rb_cFbCursor = rb_define_class_under(rb_mFb, "Cursor", rb_cData);
2980
+ //rb_define_method(rb_cFbCursor, "execute", cursor_execute, -1);
2981
+ rb_define_method(rb_cFbCursor, "fields", cursor_fields, -1);
2982
+ rb_define_method(rb_cFbCursor, "fetch", cursor_fetch, -1);
2983
+ rb_define_method(rb_cFbCursor, "fetchall", cursor_fetchall, -1);
2984
+ rb_define_method(rb_cFbCursor, "each", cursor_each, -1);
2985
+ rb_define_method(rb_cFbCursor, "close", cursor_close, 0);
2986
+ rb_define_method(rb_cFbCursor, "drop", cursor_drop, 0);
2987
+
2988
+ rb_cFbSqlType = rb_define_class_under(rb_mFb, "SqlType", rb_cData);
2989
+ rb_define_singleton_method(rb_cFbSqlType, "from_code", sql_type_from_code, 2);
2990
+
2991
+ /*
2992
+ // rb_cFbGlobal = rb_define_class_under(rb_mFb, "Global", rb_cData);
2993
+ // rb_define_singleton_method(rb_cFbGlobal, "transaction", global_transaction, -1);
2994
+ // rb_define_singleton_method(rb_cFbGlobal, "transaction_started", global_transaction_started, 0);
2995
+ // rb_define_singleton_method(rb_cFbGlobal, "commit", global_commit, 0);
2996
+ // rb_define_singleton_method(rb_cFbGlobal, "rollback", global_rollback, 0);
2997
+ */
2998
+
2999
+ rb_eFbError = rb_define_class_under(rb_mFb, "Error", rb_eStandardError);
3000
+ rb_define_method(rb_eFbError, "error_code", error_error_code, 0);
3001
+
3002
+ rb_sFbField = rb_struct_define("FbField", "name", "sql_type", "sql_subtype", "display_size", "internal_size", "precision", "scale", "nullable", "type_code", NULL);
3003
+ rb_sFbIndex = rb_struct_define("FbIndex", "table_name", "index_name", "unique", "descending", "columns", NULL);
3004
+
3005
+ rb_require("date");
3006
+ rb_require("time"); /* Needed as of Ruby 1.8.5 */
3007
+ rb_cDate = rb_const_get(rb_cObject, rb_intern("Date"));
3008
+ }