fb 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +218 -0
- data/extconf.rb +48 -0
- data/fb.c +3008 -0
- data/test/ConnectionTestCases.rb +462 -0
- data/test/CursorTestCases.rb +235 -0
- data/test/DataTypesTestCases.rb +428 -0
- data/test/DatabaseTestCases.rb +180 -0
- data/test/FbTestCases.rb +33 -0
- data/test/FbTestSuite.rb +8 -0
- data/test/TransactionTestCases.rb +259 -0
- metadata +67 -0
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.
|
data/extconf.rb
ADDED
@@ -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
|
+
}
|