activerecord-jdbc-adapter 0.6 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +46 -27
- data/Manifest.txt +7 -4
- data/Rakefile +6 -5
- data/lib/active_record/connection_adapters/jdbc_adapter.rb +93 -108
- data/lib/jdbc_adapter/jdbc_adapter_internal.jar +0 -0
- data/lib/jdbc_adapter/jdbc_derby.rb +1 -0
- data/lib/jdbc_adapter/jdbc_hsqldb.rb +1 -1
- data/lib/jdbc_adapter/jdbc_mysql.rb +55 -13
- data/lib/jdbc_adapter/version.rb +1 -1
- data/src/java/jdbc_adapter/JdbcAdapterInternalService.java +1113 -0
- data/src/java/jdbc_adapter/JdbcConnectionFactory.java +36 -0
- data/src/java/{JDBCDerbySpec.java → jdbc_adapter/JdbcDerbySpec.java} +95 -91
- data/src/java/{JDBCMySQLSpec.java → jdbc_adapter/JdbcMySQLSpec.java} +24 -27
- data/src/java/jdbc_adapter/SQLBlock.java +18 -0
- data/test/has_many_through.rb +72 -0
- data/test/jdbc_common.rb +1 -0
- data/test/mysql_simple_test.rb +4 -0
- data/test/simple.rb +36 -1
- metadata +9 -6
- data/lib/jdbc_adapter_internal.jar +0 -0
- data/src/java/JdbcAdapterInternalService.java +0 -953
Binary file
|
@@ -337,6 +337,7 @@ module ::JdbcSpec
|
|
337
337
|
|
338
338
|
# For DDL it appears you can quote "" column names, but in queries (like insert it errors out?)
|
339
339
|
def quote_column_name(name) #:nodoc:
|
340
|
+
name = name.to_s
|
340
341
|
if /^references$/i =~ name
|
341
342
|
%Q{"#{name.upcase}"}
|
342
343
|
elsif /[A-Z]/ =~ name && /[a-z]/ =~ name
|
@@ -7,13 +7,14 @@ module ::JdbcSpec
|
|
7
7
|
warn "AR-JDBC MySQL on JRuby does not support sockets"
|
8
8
|
end
|
9
9
|
config[:port] ||= 3306
|
10
|
-
config[:url] ||= "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}
|
10
|
+
config[:url] ||= "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}?#{MySQL::URL_OPTIONS}"
|
11
11
|
config[:driver] = "com.mysql.jdbc.Driver"
|
12
12
|
jdbc_connection(config)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
module MySQL
|
17
|
+
URL_OPTIONS = "zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&useUnicode=true&characterEncoding=utf8"
|
17
18
|
def self.column_selector
|
18
19
|
[/mysql/i, lambda {|cfg,col| col.extend(::JdbcSpec::MySQL::Column)}]
|
19
20
|
end
|
@@ -80,6 +81,10 @@ module ::JdbcSpec
|
|
80
81
|
def quote_column_name(name) #:nodoc:
|
81
82
|
"`#{name}`"
|
82
83
|
end
|
84
|
+
|
85
|
+
def quote_table_name(name) #:nodoc:
|
86
|
+
quote_column_name(name).gsub('.', '`.`')
|
87
|
+
end
|
83
88
|
|
84
89
|
def quoted_true
|
85
90
|
"1"
|
@@ -89,6 +94,25 @@ module ::JdbcSpec
|
|
89
94
|
"0"
|
90
95
|
end
|
91
96
|
|
97
|
+
def begin_db_transaction #:nodoc:
|
98
|
+
@connection.begin
|
99
|
+
rescue Exception
|
100
|
+
# Transactions aren't supported
|
101
|
+
end
|
102
|
+
|
103
|
+
def commit_db_transaction #:nodoc:
|
104
|
+
@connection.commit
|
105
|
+
rescue Exception
|
106
|
+
# Transactions aren't supported
|
107
|
+
end
|
108
|
+
|
109
|
+
def rollback_db_transaction #:nodoc:
|
110
|
+
@connection.rollback
|
111
|
+
rescue Exception
|
112
|
+
# Transactions aren't supported
|
113
|
+
end
|
114
|
+
|
115
|
+
|
92
116
|
# SCHEMA STATEMENTS ========================================
|
93
117
|
|
94
118
|
def structure_dump #:nodoc:
|
@@ -100,7 +124,7 @@ module ::JdbcSpec
|
|
100
124
|
|
101
125
|
select_all(sql).inject("") do |structure, table|
|
102
126
|
table.delete('Table_type')
|
103
|
-
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
|
127
|
+
structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
|
104
128
|
end
|
105
129
|
end
|
106
130
|
|
@@ -126,28 +150,32 @@ module ::JdbcSpec
|
|
126
150
|
end
|
127
151
|
|
128
152
|
def rename_table(name, new_name)
|
129
|
-
execute "RENAME TABLE #{name} TO #{new_name}"
|
153
|
+
execute "RENAME TABLE #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
|
130
154
|
end
|
131
155
|
|
132
156
|
def change_column_default(table_name, column_name, default) #:nodoc:
|
133
|
-
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
157
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
134
158
|
|
135
|
-
execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
|
159
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
|
136
160
|
end
|
137
161
|
|
138
162
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
139
|
-
unless
|
140
|
-
|
163
|
+
unless options_include_default?(options)
|
164
|
+
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
165
|
+
options[:default] = column.default
|
166
|
+
else
|
167
|
+
raise "No such column: #{table_name}.#{column_name}"
|
168
|
+
end
|
141
169
|
end
|
142
|
-
|
143
|
-
change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
170
|
+
|
171
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
144
172
|
add_column_options!(change_column_sql, options)
|
145
173
|
execute(change_column_sql)
|
146
174
|
end
|
147
|
-
|
175
|
+
|
148
176
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
149
|
-
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
150
|
-
execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
|
177
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
178
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_table_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
151
179
|
end
|
152
180
|
|
153
181
|
def add_limit_offset!(sql, options) #:nodoc:
|
@@ -160,9 +188,23 @@ module ::JdbcSpec
|
|
160
188
|
end
|
161
189
|
end
|
162
190
|
|
191
|
+
def show_variable(var)
|
192
|
+
res = execute("show variables like '#{var}'")
|
193
|
+
row = res.detect {|row| row["Variable_name"] == var }
|
194
|
+
row && row["Value"]
|
195
|
+
end
|
196
|
+
|
197
|
+
def charset
|
198
|
+
show_variable("character_set_database")
|
199
|
+
end
|
200
|
+
|
201
|
+
def collation
|
202
|
+
show_variable("collation_database")
|
203
|
+
end
|
204
|
+
|
163
205
|
private
|
164
206
|
def supports_views?
|
165
207
|
false
|
166
208
|
end
|
167
209
|
end
|
168
|
-
end
|
210
|
+
end
|
data/lib/jdbc_adapter/version.rb
CHANGED
@@ -0,0 +1,1113 @@
|
|
1
|
+
/***** BEGIN LICENSE BLOCK *****
|
2
|
+
* Copyright (c) 2006-2007 Nick Sieger <nick@nicksieger.com>
|
3
|
+
* Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
|
4
|
+
*
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
* a copy of this software and associated documentation files (the
|
7
|
+
* "Software"), to deal in the Software without restriction, including
|
8
|
+
* without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
* permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
* the following conditions:
|
12
|
+
*
|
13
|
+
* The above copyright notice and this permission notice shall be
|
14
|
+
* included in all copies or substantial portions of the Software.
|
15
|
+
*
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
***** END LICENSE BLOCK *****/
|
24
|
+
|
25
|
+
package jdbc_adapter;
|
26
|
+
|
27
|
+
import java.io.IOException;
|
28
|
+
import java.io.Reader;
|
29
|
+
import java.io.InputStream;
|
30
|
+
import java.io.ByteArrayInputStream;
|
31
|
+
import java.io.StringReader;
|
32
|
+
|
33
|
+
import java.sql.Connection;
|
34
|
+
import java.sql.DatabaseMetaData;
|
35
|
+
import java.sql.PreparedStatement;
|
36
|
+
import java.sql.ResultSetMetaData;
|
37
|
+
import java.sql.ResultSet;
|
38
|
+
import java.sql.SQLException;
|
39
|
+
import java.sql.Statement;
|
40
|
+
import java.sql.PreparedStatement;
|
41
|
+
import java.sql.Timestamp;
|
42
|
+
import java.sql.Types;
|
43
|
+
|
44
|
+
import java.text.DateFormat;
|
45
|
+
import java.text.SimpleDateFormat;
|
46
|
+
|
47
|
+
import java.util.ArrayList;
|
48
|
+
import java.util.Calendar;
|
49
|
+
import java.util.Date;
|
50
|
+
import java.util.List;
|
51
|
+
|
52
|
+
import org.jruby.Ruby;
|
53
|
+
import org.jruby.RubyArray;
|
54
|
+
import org.jruby.RubyClass;
|
55
|
+
import org.jruby.RubyHash;
|
56
|
+
import org.jruby.RubyModule;
|
57
|
+
import org.jruby.RubyNumeric;
|
58
|
+
import org.jruby.RubyObjectAdapter;
|
59
|
+
import org.jruby.RubyString;
|
60
|
+
import org.jruby.RubySymbol;
|
61
|
+
import org.jruby.RubyTime;
|
62
|
+
import org.jruby.
|
63
|
+
exceptions.RaiseException;
|
64
|
+
import org.jruby.javasupport.Java;
|
65
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
66
|
+
import org.jruby.javasupport.JavaObject;
|
67
|
+
import org.jruby.runtime.Arity;
|
68
|
+
import org.jruby.runtime.Block;
|
69
|
+
import org.jruby.runtime.CallbackFactory;
|
70
|
+
import org.jruby.runtime.ThreadContext;
|
71
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
72
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
73
|
+
import org.jruby.util.ByteList;
|
74
|
+
|
75
|
+
public class JdbcAdapterInternalService implements BasicLibraryService {
|
76
|
+
private static RubyObjectAdapter rubyApi;
|
77
|
+
|
78
|
+
public boolean basicLoad(final Ruby runtime) throws IOException {
|
79
|
+
RubyClass cJdbcConn = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).
|
80
|
+
defineClassUnder("JdbcConnection",runtime.getObject(),runtime.getObject().getAllocator());
|
81
|
+
|
82
|
+
CallbackFactory cf = runtime.callbackFactory(JdbcAdapterInternalService.class);
|
83
|
+
cJdbcConn.defineMethod("unmarshal_result",cf.getSingletonMethod("unmarshal_result", IRubyObject.class));
|
84
|
+
cJdbcConn.defineMethod("with_connection_retry_guard",cf.getSingletonMethod("with_connection_retry_guard"));
|
85
|
+
cJdbcConn.defineFastMethod("connection",cf.getFastSingletonMethod("connection"));
|
86
|
+
cJdbcConn.defineFastMethod("reconnect!",cf.getFastSingletonMethod("reconnect"));
|
87
|
+
cJdbcConn.defineFastMethod("disconnect!",cf.getFastSingletonMethod("disconnect"));
|
88
|
+
cJdbcConn.defineFastMethod("execute_update",cf.getFastSingletonMethod("execute_update", IRubyObject.class));
|
89
|
+
cJdbcConn.defineFastMethod("execute_query",cf.getFastOptSingletonMethod("execute_query"));
|
90
|
+
cJdbcConn.defineFastMethod("execute_insert",cf.getFastSingletonMethod("execute_insert", IRubyObject.class));
|
91
|
+
cJdbcConn.defineFastMethod("execute_id_insert",cf.getFastSingletonMethod("execute_id_insert", IRubyObject.class, IRubyObject.class));
|
92
|
+
cJdbcConn.defineFastMethod("primary_keys",cf.getFastSingletonMethod("primary_keys", IRubyObject.class));
|
93
|
+
cJdbcConn.defineFastMethod("set_native_database_types",cf.getFastSingletonMethod("set_native_database_types"));
|
94
|
+
cJdbcConn.defineFastMethod("native_database_types",cf.getFastSingletonMethod("native_database_types"));
|
95
|
+
cJdbcConn.defineFastMethod("begin",cf.getFastSingletonMethod("begin"));
|
96
|
+
cJdbcConn.defineFastMethod("commit",cf.getFastSingletonMethod("commit"));
|
97
|
+
cJdbcConn.defineFastMethod("rollback",cf.getFastSingletonMethod("rollback"));
|
98
|
+
cJdbcConn.defineFastMethod("database_name",cf.getFastSingletonMethod("database_name"));
|
99
|
+
cJdbcConn.defineFastMethod("columns",cf.getFastOptSingletonMethod("columns_internal"));
|
100
|
+
cJdbcConn.defineFastMethod("columns_internal",cf.getFastOptSingletonMethod("columns_internal"));
|
101
|
+
cJdbcConn.defineFastMethod("tables",cf.getFastOptSingletonMethod("tables"));
|
102
|
+
|
103
|
+
cJdbcConn.defineFastMethod("insert_bind",cf.getFastOptSingletonMethod("insert_bind"));
|
104
|
+
cJdbcConn.defineFastMethod("update_bind",cf.getFastOptSingletonMethod("update_bind"));
|
105
|
+
|
106
|
+
cJdbcConn.defineFastMethod("write_large_object",cf.getFastOptSingletonMethod("write_large_object"));
|
107
|
+
|
108
|
+
cJdbcConn.getMetaClass().defineFastMethod("insert?",cf.getFastSingletonMethod("insert_p", IRubyObject.class));
|
109
|
+
cJdbcConn.getMetaClass().defineFastMethod("select?",cf.getFastSingletonMethod("select_p", IRubyObject.class));
|
110
|
+
|
111
|
+
RubyModule jdbcSpec = runtime.getOrCreateModule("JdbcSpec");
|
112
|
+
|
113
|
+
rubyApi = JavaEmbedUtils.newObjectAdapter();
|
114
|
+
JdbcMySQLSpec.load(runtime, jdbcSpec);
|
115
|
+
JdbcDerbySpec.load(runtime, jdbcSpec, rubyApi);
|
116
|
+
return true;
|
117
|
+
}
|
118
|
+
|
119
|
+
private static int whitespace(int p, final int pend, ByteList bl) {
|
120
|
+
while(p < pend) {
|
121
|
+
switch(bl.bytes[p]) {
|
122
|
+
case ' ':
|
123
|
+
case '\n':
|
124
|
+
case '\r':
|
125
|
+
case '\t':
|
126
|
+
p++;
|
127
|
+
break;
|
128
|
+
default:
|
129
|
+
return p;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
return p;
|
133
|
+
}
|
134
|
+
|
135
|
+
public static IRubyObject insert_p(IRubyObject recv, IRubyObject _sql) {
|
136
|
+
ByteList bl = rubyApi.convertToRubyString(_sql).getByteList();
|
137
|
+
|
138
|
+
int p = bl.begin;
|
139
|
+
int pend = p + bl.realSize;
|
140
|
+
|
141
|
+
p = whitespace(p, pend, bl);
|
142
|
+
|
143
|
+
if(pend - p >= 6) {
|
144
|
+
switch(bl.bytes[p++]) {
|
145
|
+
case 'i':
|
146
|
+
case 'I':
|
147
|
+
switch(bl.bytes[p++]) {
|
148
|
+
case 'n':
|
149
|
+
case 'N':
|
150
|
+
switch(bl.bytes[p++]) {
|
151
|
+
case 's':
|
152
|
+
case 'S':
|
153
|
+
switch(bl.bytes[p++]) {
|
154
|
+
case 'e':
|
155
|
+
case 'E':
|
156
|
+
switch(bl.bytes[p++]) {
|
157
|
+
case 'r':
|
158
|
+
case 'R':
|
159
|
+
switch(bl.bytes[p++]) {
|
160
|
+
case 't':
|
161
|
+
case 'T':
|
162
|
+
return recv.getRuntime().getTrue();
|
163
|
+
}
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
return recv.getRuntime().getFalse();
|
171
|
+
}
|
172
|
+
|
173
|
+
public static IRubyObject select_p(IRubyObject recv, IRubyObject _sql) {
|
174
|
+
ByteList bl = rubyApi.convertToRubyString(_sql).getByteList();
|
175
|
+
|
176
|
+
int p = bl.begin;
|
177
|
+
int pend = p + bl.realSize;
|
178
|
+
|
179
|
+
p = whitespace(p, pend, bl);
|
180
|
+
|
181
|
+
if(pend - p >= 6) {
|
182
|
+
if(bl.bytes[p] == '(') {
|
183
|
+
p++;
|
184
|
+
p = whitespace(p, pend, bl);
|
185
|
+
}
|
186
|
+
if(pend - p >= 6) {
|
187
|
+
switch(bl.bytes[p++]) {
|
188
|
+
case 's':
|
189
|
+
case 'S':
|
190
|
+
switch(bl.bytes[p++]) {
|
191
|
+
case 'e':
|
192
|
+
case 'E':
|
193
|
+
switch(bl.bytes[p++]) {
|
194
|
+
case 'l':
|
195
|
+
case 'L':
|
196
|
+
switch(bl.bytes[p++]) {
|
197
|
+
case 'e':
|
198
|
+
case 'E':
|
199
|
+
switch(bl.bytes[p++]) {
|
200
|
+
case 'c':
|
201
|
+
case 'C':
|
202
|
+
switch(bl.bytes[p++]) {
|
203
|
+
case 't':
|
204
|
+
case 'T':
|
205
|
+
return recv.getRuntime().getTrue();
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
case 'h':
|
211
|
+
case 'H':
|
212
|
+
switch(bl.bytes[p++]) {
|
213
|
+
case 'o':
|
214
|
+
case 'O':
|
215
|
+
switch(bl.bytes[p++]) {
|
216
|
+
case 'w':
|
217
|
+
case 'W':
|
218
|
+
return recv.getRuntime().getTrue();
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
}
|
224
|
+
}
|
225
|
+
return recv.getRuntime().getFalse();
|
226
|
+
}
|
227
|
+
|
228
|
+
public static IRubyObject connection(IRubyObject recv) {
|
229
|
+
Connection c = getConnection(recv);
|
230
|
+
if (c == null) {
|
231
|
+
reconnect(recv);
|
232
|
+
}
|
233
|
+
return rubyApi.getInstanceVariable(recv, "@connection");
|
234
|
+
}
|
235
|
+
|
236
|
+
public static IRubyObject disconnect(IRubyObject recv) {
|
237
|
+
setConnection(recv, null);
|
238
|
+
return recv;
|
239
|
+
}
|
240
|
+
|
241
|
+
public static IRubyObject reconnect(IRubyObject recv) {
|
242
|
+
setConnection(recv, getConnectionFactory(recv).newConnection());
|
243
|
+
return recv;
|
244
|
+
}
|
245
|
+
|
246
|
+
public static IRubyObject with_connection_retry_guard(final IRubyObject recv, final Block block) {
|
247
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
248
|
+
public IRubyObject call(Connection c) throws SQLException {
|
249
|
+
return block.call(recv.getRuntime().getCurrentContext(), new IRubyObject[] {
|
250
|
+
wrappedConnection(recv, c)
|
251
|
+
});
|
252
|
+
}
|
253
|
+
});
|
254
|
+
}
|
255
|
+
|
256
|
+
private static IRubyObject withConnectionAndRetry(IRubyObject recv, SQLBlock block) {
|
257
|
+
int tries = 1;
|
258
|
+
int i = 0;
|
259
|
+
Throwable toWrap = null;
|
260
|
+
while (i < tries) {
|
261
|
+
Connection c = getConnection(recv);
|
262
|
+
try {
|
263
|
+
return block.call(c);
|
264
|
+
} catch (Exception e) {
|
265
|
+
toWrap = e;
|
266
|
+
while (toWrap.getCause() != null && toWrap.getCause() != toWrap) {
|
267
|
+
toWrap = toWrap.getCause();
|
268
|
+
}
|
269
|
+
if (i == 0) {
|
270
|
+
tries = (int) rubyApi.convertToRubyInteger(config_value(recv, "retry_count")).getLongValue();
|
271
|
+
if (tries <= 0) {
|
272
|
+
tries = 1;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
i++;
|
276
|
+
if (isConnectionBroken(recv, c)) {
|
277
|
+
reconnect(recv);
|
278
|
+
} else {
|
279
|
+
throw wrap(recv, toWrap);
|
280
|
+
}
|
281
|
+
}
|
282
|
+
}
|
283
|
+
throw wrap(recv, toWrap);
|
284
|
+
}
|
285
|
+
|
286
|
+
private static SQLBlock tableLookupBlock(final Ruby runtime,
|
287
|
+
final String catalog, final String schemapat,
|
288
|
+
final String tablepat, final String[] types) {
|
289
|
+
return new SQLBlock() {
|
290
|
+
public IRubyObject call(Connection c) throws SQLException {
|
291
|
+
ResultSet rs = null;
|
292
|
+
try {
|
293
|
+
DatabaseMetaData metadata = c.getMetaData();
|
294
|
+
String clzName = metadata.getClass().getName().toLowerCase();
|
295
|
+
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
|
296
|
+
|
297
|
+
String realschema = schemapat;
|
298
|
+
if (realschema == null && isOracle) {
|
299
|
+
ResultSet schemas = metadata.getSchemas();
|
300
|
+
String username = metadata.getUserName();
|
301
|
+
while (schemas.next()) {
|
302
|
+
if (schemas.getString(1).equalsIgnoreCase(username)) {
|
303
|
+
realschema = schemas.getString(1);
|
304
|
+
break;
|
305
|
+
}
|
306
|
+
}
|
307
|
+
schemas.close();
|
308
|
+
}
|
309
|
+
rs = metadata.getTables(catalog, realschema, tablepat, types);
|
310
|
+
List arr = new ArrayList();
|
311
|
+
while (rs.next()) {
|
312
|
+
String name = rs.getString(3).toLowerCase();
|
313
|
+
// Handle stupid Oracle 10g RecycleBin feature
|
314
|
+
if (!isOracle || !name.startsWith("bin$")) {
|
315
|
+
arr.add(runtime.newString(name));
|
316
|
+
}
|
317
|
+
}
|
318
|
+
return runtime.newArray(arr);
|
319
|
+
} finally {
|
320
|
+
try { rs.close(); } catch (Exception e) { }
|
321
|
+
}
|
322
|
+
}
|
323
|
+
};
|
324
|
+
}
|
325
|
+
|
326
|
+
public static IRubyObject tables(final IRubyObject recv, IRubyObject[] args) {
|
327
|
+
final Ruby runtime = recv.getRuntime();
|
328
|
+
final String catalog = getCatalog(args);
|
329
|
+
final String schemapat = getSchemaPattern(args);
|
330
|
+
final String tablepat = getTablePattern(args);
|
331
|
+
final String[] types = getTypes(args);
|
332
|
+
return withConnectionAndRetry(recv, tableLookupBlock(runtime, catalog,
|
333
|
+
schemapat, tablepat, types));
|
334
|
+
}
|
335
|
+
|
336
|
+
private static String getCatalog(IRubyObject[] args) {
|
337
|
+
if (args != null && args.length > 0) {
|
338
|
+
return convertToStringOrNull(args[0]);
|
339
|
+
}
|
340
|
+
return null;
|
341
|
+
}
|
342
|
+
|
343
|
+
private static String getSchemaPattern(IRubyObject[] args) {
|
344
|
+
if (args != null && args.length > 1) {
|
345
|
+
return convertToStringOrNull(args[1]);
|
346
|
+
}
|
347
|
+
return null;
|
348
|
+
}
|
349
|
+
|
350
|
+
private static String getTablePattern(IRubyObject[] args) {
|
351
|
+
if (args != null && args.length > 2) {
|
352
|
+
return convertToStringOrNull(args[2]);
|
353
|
+
}
|
354
|
+
return null;
|
355
|
+
}
|
356
|
+
|
357
|
+
private static String[] getTypes(IRubyObject[] args) {
|
358
|
+
String[] types = new String[]{"TABLE"};
|
359
|
+
if (args != null && args.length > 3) {
|
360
|
+
IRubyObject typearr = args[3];
|
361
|
+
if (typearr instanceof RubyArray) {
|
362
|
+
IRubyObject[] arr = rubyApi.convertToJavaArray(typearr);
|
363
|
+
types = new String[arr.length];
|
364
|
+
for (int i = 0; i < types.length; i++) {
|
365
|
+
types[i] = arr[i].toString();
|
366
|
+
}
|
367
|
+
} else {
|
368
|
+
types = new String[]{types.toString()};
|
369
|
+
}
|
370
|
+
}
|
371
|
+
return types;
|
372
|
+
}
|
373
|
+
|
374
|
+
public static IRubyObject native_database_types(IRubyObject recv) {
|
375
|
+
return rubyApi.getInstanceVariable(recv, "@tps");
|
376
|
+
}
|
377
|
+
|
378
|
+
public static IRubyObject set_native_database_types(IRubyObject recv) throws SQLException, IOException {
|
379
|
+
Ruby runtime = recv.getRuntime();
|
380
|
+
IRubyObject types = unmarshal_result_downcase(recv, getConnection(recv).getMetaData().getTypeInfo());
|
381
|
+
IRubyObject typeConverter = ((RubyModule) (runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).getConstant("JdbcTypeConverter");
|
382
|
+
IRubyObject value = rubyApi.callMethod(rubyApi.callMethod(typeConverter, "new", types), "choose_best_types");
|
383
|
+
rubyApi.setInstanceVariable(recv, "@native_types", value);
|
384
|
+
return runtime.getNil();
|
385
|
+
}
|
386
|
+
|
387
|
+
public static IRubyObject database_name(IRubyObject recv) throws SQLException {
|
388
|
+
String name = getConnection(recv).getCatalog();
|
389
|
+
if(null == name) {
|
390
|
+
name = getConnection(recv).getMetaData().getUserName();
|
391
|
+
if(null == name) {
|
392
|
+
name = "db1";
|
393
|
+
}
|
394
|
+
}
|
395
|
+
return recv.getRuntime().newString(name);
|
396
|
+
}
|
397
|
+
|
398
|
+
public static IRubyObject begin(IRubyObject recv) throws SQLException {
|
399
|
+
getConnection(recv).setAutoCommit(false);
|
400
|
+
return recv.getRuntime().getNil();
|
401
|
+
}
|
402
|
+
|
403
|
+
public static IRubyObject commit(IRubyObject recv) throws SQLException {
|
404
|
+
try {
|
405
|
+
getConnection(recv).commit();
|
406
|
+
return recv.getRuntime().getNil();
|
407
|
+
} finally {
|
408
|
+
getConnection(recv).setAutoCommit(true);
|
409
|
+
}
|
410
|
+
}
|
411
|
+
|
412
|
+
public static IRubyObject rollback(IRubyObject recv) throws SQLException {
|
413
|
+
try {
|
414
|
+
getConnection(recv).rollback();
|
415
|
+
return recv.getRuntime().getNil();
|
416
|
+
} finally {
|
417
|
+
getConnection(recv).setAutoCommit(true);
|
418
|
+
}
|
419
|
+
}
|
420
|
+
|
421
|
+
public static IRubyObject columns_internal(final IRubyObject recv, final IRubyObject[] args) throws SQLException, IOException {
|
422
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
423
|
+
public IRubyObject call(Connection c) throws SQLException {
|
424
|
+
ResultSet results = null;
|
425
|
+
try {
|
426
|
+
String table_name = rubyApi.convertToRubyString(args[0]).getUnicodeValue();
|
427
|
+
DatabaseMetaData metadata = c.getMetaData();
|
428
|
+
String clzName = metadata.getClass().getName().toLowerCase();
|
429
|
+
boolean isDerby = clzName.indexOf("derby") != -1;
|
430
|
+
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
|
431
|
+
String schemaName = null;
|
432
|
+
|
433
|
+
if(args.length>2) {
|
434
|
+
schemaName = args[2].toString();
|
435
|
+
}
|
436
|
+
|
437
|
+
if(metadata.storesUpperCaseIdentifiers()) {
|
438
|
+
table_name = table_name.toUpperCase();
|
439
|
+
} else if(metadata.storesLowerCaseIdentifiers()) {
|
440
|
+
table_name = table_name.toLowerCase();
|
441
|
+
}
|
442
|
+
|
443
|
+
if(schemaName == null && (isDerby || isOracle)) {
|
444
|
+
ResultSet schemas = metadata.getSchemas();
|
445
|
+
String username = metadata.getUserName();
|
446
|
+
while(schemas.next()) {
|
447
|
+
if(schemas.getString(1).equalsIgnoreCase(username)) {
|
448
|
+
schemaName = schemas.getString(1);
|
449
|
+
break;
|
450
|
+
}
|
451
|
+
}
|
452
|
+
schemas.close();
|
453
|
+
}
|
454
|
+
|
455
|
+
RubyArray matchingTables = (RubyArray) tableLookupBlock(recv.getRuntime(),
|
456
|
+
c.getCatalog(), schemaName, table_name, getTypes(null)).call(c);
|
457
|
+
if (matchingTables.isEmpty()) {
|
458
|
+
throw new SQLException("Table " + table_name + " does not exist");
|
459
|
+
}
|
460
|
+
|
461
|
+
results = metadata.getColumns(c.getCatalog(),schemaName,table_name,null);
|
462
|
+
return unmarshal_columns(recv, metadata, results);
|
463
|
+
} finally {
|
464
|
+
try { if (results != null) results.close(); } catch (SQLException sqx) {}
|
465
|
+
}
|
466
|
+
}
|
467
|
+
});
|
468
|
+
}
|
469
|
+
|
470
|
+
private static final java.util.regex.Pattern HAS_SMALL = java.util.regex.Pattern.compile("[a-z]");
|
471
|
+
private static IRubyObject unmarshal_columns(IRubyObject recv, DatabaseMetaData metadata, ResultSet rs) throws SQLException {
|
472
|
+
try {
|
473
|
+
List columns = new ArrayList();
|
474
|
+
String clzName = metadata.getClass().getName().toLowerCase();
|
475
|
+
boolean isDerby = clzName.indexOf("derby") != -1;
|
476
|
+
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
|
477
|
+
Ruby runtime = recv.getRuntime();
|
478
|
+
|
479
|
+
IRubyObject adapter = rubyApi.callMethod(recv, "adapter");
|
480
|
+
RubyHash tps = (RubyHash) rubyApi.callMethod(adapter, "native_database_types");
|
481
|
+
|
482
|
+
IRubyObject jdbcCol = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).getConstant("JdbcColumn");
|
483
|
+
|
484
|
+
while(rs.next()) {
|
485
|
+
String column_name = rs.getString(4);
|
486
|
+
if(metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(column_name).find()) {
|
487
|
+
column_name = column_name.toLowerCase();
|
488
|
+
}
|
489
|
+
|
490
|
+
String prec = rs.getString(7);
|
491
|
+
String scal = rs.getString(9);
|
492
|
+
int precision = -1;
|
493
|
+
int scale = -1;
|
494
|
+
if(prec != null) {
|
495
|
+
precision = Integer.parseInt(prec);
|
496
|
+
if(scal != null) {
|
497
|
+
scale = Integer.parseInt(scal);
|
498
|
+
}
|
499
|
+
}
|
500
|
+
String type = rs.getString(6);
|
501
|
+
if(prec != null && precision > 0) {
|
502
|
+
type += "(" + precision;
|
503
|
+
if(scal != null && scale > 0) {
|
504
|
+
type += "," + scale;
|
505
|
+
}
|
506
|
+
type += ")";
|
507
|
+
}
|
508
|
+
String def = rs.getString(13);
|
509
|
+
IRubyObject _def;
|
510
|
+
if(def == null || (isOracle && def.toLowerCase().trim().equals("null"))) {
|
511
|
+
_def = runtime.getNil();
|
512
|
+
} else {
|
513
|
+
if(isOracle) {
|
514
|
+
def = def.trim();
|
515
|
+
}
|
516
|
+
if((isDerby || isOracle) && def.length() > 0 && def.charAt(0) == '\'') {
|
517
|
+
def = def.substring(1, def.length()-1);
|
518
|
+
}
|
519
|
+
_def = runtime.newString(def);
|
520
|
+
}
|
521
|
+
IRubyObject config = rubyApi.getInstanceVariable(recv, "@config");
|
522
|
+
IRubyObject c = rubyApi.callMethod(jdbcCol, "new",
|
523
|
+
new IRubyObject[]{
|
524
|
+
config, runtime.newString(column_name),
|
525
|
+
_def, runtime.newString(type),
|
526
|
+
runtime.newBoolean(!rs.getString(18).trim().equals("NO"))
|
527
|
+
});
|
528
|
+
columns.add(c);
|
529
|
+
|
530
|
+
IRubyObject tp = (IRubyObject)tps.fastARef(rubyApi.callMethod(c,"type"));
|
531
|
+
if(tp != null && !tp.isNil() && rubyApi.callMethod(tp, "[]", runtime.newSymbol("limit")).isNil()) {
|
532
|
+
rubyApi.callMethod(c, "limit=", runtime.getNil());
|
533
|
+
if(!rubyApi.callMethod(c, "type").equals(runtime.newSymbol("decimal"))) {
|
534
|
+
rubyApi.callMethod(c, "precision=", runtime.getNil());
|
535
|
+
}
|
536
|
+
}
|
537
|
+
}
|
538
|
+
return runtime.newArray(columns);
|
539
|
+
} finally {
|
540
|
+
try {
|
541
|
+
rs.close();
|
542
|
+
} catch(Exception e) {}
|
543
|
+
}
|
544
|
+
}
|
545
|
+
|
546
|
+
public static IRubyObject primary_keys(final IRubyObject recv, final IRubyObject _table_name) throws SQLException {
|
547
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
548
|
+
public IRubyObject call(Connection c) throws SQLException {
|
549
|
+
DatabaseMetaData metadata = c.getMetaData();
|
550
|
+
String table_name = _table_name.toString();
|
551
|
+
if (metadata.storesUpperCaseIdentifiers()) {
|
552
|
+
table_name = table_name.toUpperCase();
|
553
|
+
} else if (metadata.storesLowerCaseIdentifiers()) {
|
554
|
+
table_name = table_name.toLowerCase();
|
555
|
+
}
|
556
|
+
ResultSet result_set = metadata.getPrimaryKeys(null, null, table_name);
|
557
|
+
List keyNames = new ArrayList();
|
558
|
+
Ruby runtime = recv.getRuntime();
|
559
|
+
while (result_set.next()) {
|
560
|
+
String s1 = result_set.getString(4);
|
561
|
+
if (metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(s1).find()) {
|
562
|
+
s1 = s1.toLowerCase();
|
563
|
+
}
|
564
|
+
keyNames.add(runtime.newString(s1));
|
565
|
+
}
|
566
|
+
|
567
|
+
try {
|
568
|
+
result_set.close();
|
569
|
+
} catch (Exception e) {
|
570
|
+
}
|
571
|
+
|
572
|
+
return runtime.newArray(keyNames);
|
573
|
+
}
|
574
|
+
});
|
575
|
+
}
|
576
|
+
|
577
|
+
public static IRubyObject execute_id_insert(IRubyObject recv, final IRubyObject sql, final IRubyObject id) throws SQLException {
|
578
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
579
|
+
public IRubyObject call(Connection c) throws SQLException {
|
580
|
+
PreparedStatement ps = c.prepareStatement(rubyApi.convertToRubyString(sql).getUnicodeValue());
|
581
|
+
try {
|
582
|
+
ps.setLong(1, RubyNumeric.fix2long(id));
|
583
|
+
ps.executeUpdate();
|
584
|
+
} finally {
|
585
|
+
ps.close();
|
586
|
+
}
|
587
|
+
return id;
|
588
|
+
}
|
589
|
+
});
|
590
|
+
}
|
591
|
+
|
592
|
+
public static IRubyObject execute_update(final IRubyObject recv, final IRubyObject sql) throws SQLException {
|
593
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
594
|
+
public IRubyObject call(Connection c) throws SQLException {
|
595
|
+
Statement stmt = null;
|
596
|
+
try {
|
597
|
+
stmt = c.createStatement();
|
598
|
+
return recv.getRuntime().newFixnum(stmt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue()));
|
599
|
+
} finally {
|
600
|
+
if (null != stmt) {
|
601
|
+
try {
|
602
|
+
stmt.close();
|
603
|
+
} catch (Exception e) {
|
604
|
+
}
|
605
|
+
}
|
606
|
+
}
|
607
|
+
}
|
608
|
+
});
|
609
|
+
}
|
610
|
+
|
611
|
+
public static IRubyObject execute_query(final IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
|
612
|
+
final IRubyObject sql = args[0];
|
613
|
+
final int maxrows;
|
614
|
+
|
615
|
+
if (args.length > 1) {
|
616
|
+
maxrows = RubyNumeric.fix2int(args[1]);
|
617
|
+
} else {
|
618
|
+
maxrows = 0;
|
619
|
+
}
|
620
|
+
|
621
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
622
|
+
public IRubyObject call(Connection c) throws SQLException {
|
623
|
+
Statement stmt = null;
|
624
|
+
try {
|
625
|
+
stmt = c.createStatement();
|
626
|
+
stmt.setMaxRows(maxrows);
|
627
|
+
return unmarshal_result(recv, stmt.executeQuery(rubyApi.convertToRubyString(sql).getUnicodeValue()));
|
628
|
+
} finally {
|
629
|
+
if (null != stmt) {
|
630
|
+
try {
|
631
|
+
stmt.close();
|
632
|
+
} catch (Exception e) {
|
633
|
+
}
|
634
|
+
}
|
635
|
+
}
|
636
|
+
}
|
637
|
+
});
|
638
|
+
}
|
639
|
+
|
640
|
+
public static IRubyObject execute_insert(final IRubyObject recv, final IRubyObject sql) throws SQLException {
|
641
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
642
|
+
public IRubyObject call(Connection c) throws SQLException {
|
643
|
+
Statement stmt = null;
|
644
|
+
try {
|
645
|
+
stmt = c.createStatement();
|
646
|
+
stmt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);
|
647
|
+
return unmarshal_id_result(recv.getRuntime(), stmt.getGeneratedKeys());
|
648
|
+
} finally {
|
649
|
+
if (null != stmt) {
|
650
|
+
try {
|
651
|
+
stmt.close();
|
652
|
+
} catch (Exception e) {
|
653
|
+
}
|
654
|
+
}
|
655
|
+
}
|
656
|
+
}
|
657
|
+
});
|
658
|
+
}
|
659
|
+
|
660
|
+
public static IRubyObject unmarshal_result_downcase(IRubyObject recv, ResultSet rs) throws SQLException, IOException {
|
661
|
+
List results = new ArrayList();
|
662
|
+
Ruby runtime = recv.getRuntime();
|
663
|
+
try {
|
664
|
+
ResultSetMetaData metadata = rs.getMetaData();
|
665
|
+
int col_count = metadata.getColumnCount();
|
666
|
+
IRubyObject[] col_names = new IRubyObject[col_count];
|
667
|
+
int[] col_types = new int[col_count];
|
668
|
+
int[] col_scale = new int[col_count];
|
669
|
+
|
670
|
+
for(int i=0;i<col_count;i++) {
|
671
|
+
col_names[i] = runtime.newString(metadata.getColumnName(i+1).toLowerCase());
|
672
|
+
col_types[i] = metadata.getColumnType(i+1);
|
673
|
+
col_scale[i] = metadata.getScale(i+1);
|
674
|
+
}
|
675
|
+
|
676
|
+
while(rs.next()) {
|
677
|
+
RubyHash row = RubyHash.newHash(runtime);
|
678
|
+
for(int i=0;i<col_count;i++) {
|
679
|
+
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
|
680
|
+
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
|
681
|
+
});
|
682
|
+
}
|
683
|
+
results.add(row);
|
684
|
+
}
|
685
|
+
} finally {
|
686
|
+
try {
|
687
|
+
rs.close();
|
688
|
+
} catch(Exception e) {}
|
689
|
+
}
|
690
|
+
|
691
|
+
return runtime.newArray(results);
|
692
|
+
}
|
693
|
+
|
694
|
+
public static IRubyObject unmarshal_result(IRubyObject recv, ResultSet rs) throws SQLException {
|
695
|
+
Ruby runtime = recv.getRuntime();
|
696
|
+
List results = new ArrayList();
|
697
|
+
try {
|
698
|
+
ResultSetMetaData metadata = rs.getMetaData();
|
699
|
+
boolean storesUpper = rs.getStatement().getConnection().getMetaData().storesUpperCaseIdentifiers();
|
700
|
+
int col_count = metadata.getColumnCount();
|
701
|
+
IRubyObject[] col_names = new IRubyObject[col_count];
|
702
|
+
int[] col_types = new int[col_count];
|
703
|
+
int[] col_scale = new int[col_count];
|
704
|
+
|
705
|
+
for(int i=0;i<col_count;i++) {
|
706
|
+
String s1 = metadata.getColumnName(i+1);
|
707
|
+
if(storesUpper && !HAS_SMALL.matcher(s1).find()) {
|
708
|
+
s1 = s1.toLowerCase();
|
709
|
+
}
|
710
|
+
col_names[i] = runtime.newString(s1);
|
711
|
+
col_types[i] = metadata.getColumnType(i+1);
|
712
|
+
col_scale[i] = metadata.getScale(i+1);
|
713
|
+
}
|
714
|
+
|
715
|
+
while(rs.next()) {
|
716
|
+
RubyHash row = RubyHash.newHash(runtime);
|
717
|
+
for(int i=0;i<col_count;i++) {
|
718
|
+
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
|
719
|
+
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
|
720
|
+
});
|
721
|
+
}
|
722
|
+
results.add(row);
|
723
|
+
}
|
724
|
+
} finally {
|
725
|
+
try {
|
726
|
+
rs.close();
|
727
|
+
} catch(Exception e) {}
|
728
|
+
}
|
729
|
+
return runtime.newArray(results);
|
730
|
+
}
|
731
|
+
|
732
|
+
public static IRubyObject unmarshal_result(IRubyObject recv, IRubyObject resultset, Block row_filter) throws SQLException, IOException {
|
733
|
+
Ruby runtime = recv.getRuntime();
|
734
|
+
ResultSet rs = intoResultSet(resultset);
|
735
|
+
List results = new ArrayList();
|
736
|
+
try {
|
737
|
+
ResultSetMetaData metadata = rs.getMetaData();
|
738
|
+
int col_count = metadata.getColumnCount();
|
739
|
+
IRubyObject[] col_names = new IRubyObject[col_count];
|
740
|
+
int[] col_types = new int[col_count];
|
741
|
+
int[] col_scale = new int[col_count];
|
742
|
+
|
743
|
+
for (int i=0;i<col_count;i++) {
|
744
|
+
col_names[i] = runtime.newString(metadata.getColumnName(i+1));
|
745
|
+
col_types[i] = metadata.getColumnType(i+1);
|
746
|
+
col_scale[i] = metadata.getScale(i+1);
|
747
|
+
}
|
748
|
+
|
749
|
+
if (row_filter.isGiven()) {
|
750
|
+
while (rs.next()) {
|
751
|
+
if (row_filter.yield(runtime.getCurrentContext(),resultset).isTrue()) {
|
752
|
+
RubyHash row = RubyHash.newHash(runtime);
|
753
|
+
for (int i=0;i<col_count;i++) {
|
754
|
+
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
|
755
|
+
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
|
756
|
+
});
|
757
|
+
}
|
758
|
+
results.add(row);
|
759
|
+
}
|
760
|
+
}
|
761
|
+
} else {
|
762
|
+
while (rs.next()) {
|
763
|
+
RubyHash row = RubyHash.newHash(runtime);
|
764
|
+
for (int i=0;i<col_count;i++) {
|
765
|
+
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
|
766
|
+
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
|
767
|
+
});
|
768
|
+
}
|
769
|
+
results.add(row);
|
770
|
+
}
|
771
|
+
}
|
772
|
+
|
773
|
+
} finally {
|
774
|
+
try {
|
775
|
+
rs.close();
|
776
|
+
} catch(Exception e) {}
|
777
|
+
}
|
778
|
+
|
779
|
+
return runtime.newArray(results);
|
780
|
+
}
|
781
|
+
|
782
|
+
private static IRubyObject jdbc_to_ruby(Ruby runtime, int row, int type, int scale, ResultSet rs) throws SQLException {
|
783
|
+
try {
|
784
|
+
int n;
|
785
|
+
switch (type) {
|
786
|
+
case Types.BINARY:
|
787
|
+
case Types.BLOB:
|
788
|
+
case Types.LONGVARBINARY:
|
789
|
+
case Types.VARBINARY:
|
790
|
+
InputStream is = rs.getBinaryStream(row);
|
791
|
+
if (is == null || rs.wasNull()) {
|
792
|
+
return runtime.getNil();
|
793
|
+
}
|
794
|
+
ByteList str = new ByteList(2048);
|
795
|
+
byte[] buf = new byte[2048];
|
796
|
+
|
797
|
+
while ((n = is.read(buf)) != -1) {
|
798
|
+
str.append(buf, 0, n);
|
799
|
+
}
|
800
|
+
is.close();
|
801
|
+
|
802
|
+
return runtime.newString(str);
|
803
|
+
case Types.LONGVARCHAR:
|
804
|
+
case Types.CLOB:
|
805
|
+
Reader rss = rs.getCharacterStream(row);
|
806
|
+
if (rss == null || rs.wasNull()) {
|
807
|
+
return runtime.getNil();
|
808
|
+
}
|
809
|
+
StringBuffer str2 = new StringBuffer(2048);
|
810
|
+
char[] cuf = new char[2048];
|
811
|
+
while ((n = rss.read(cuf)) != -1) {
|
812
|
+
str2.append(cuf, 0, n);
|
813
|
+
}
|
814
|
+
rss.close();
|
815
|
+
return RubyString.newUnicodeString(runtime, str2.toString());
|
816
|
+
case Types.TIMESTAMP:
|
817
|
+
Timestamp time = rs.getTimestamp(row);
|
818
|
+
if (time == null || rs.wasNull()) {
|
819
|
+
return runtime.getNil();
|
820
|
+
}
|
821
|
+
String sttr = time.toString();
|
822
|
+
if (sttr.endsWith(" 00:00:00.0")) {
|
823
|
+
sttr = sttr.substring(0, sttr.length() - (" 00:00:00.0".length()));
|
824
|
+
}
|
825
|
+
return RubyString.newUnicodeString(runtime, sttr);
|
826
|
+
default:
|
827
|
+
String vs = rs.getString(row);
|
828
|
+
if (vs == null || rs.wasNull()) {
|
829
|
+
return runtime.getNil();
|
830
|
+
}
|
831
|
+
|
832
|
+
return RubyString.newUnicodeString(runtime, vs);
|
833
|
+
}
|
834
|
+
} catch (IOException ioe) {
|
835
|
+
throw (SQLException) new SQLException(ioe.getMessage()).initCause(ioe);
|
836
|
+
}
|
837
|
+
}
|
838
|
+
|
839
|
+
public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
|
840
|
+
try {
|
841
|
+
if(rs.next()) {
|
842
|
+
if(rs.getMetaData().getColumnCount() > 0) {
|
843
|
+
return runtime.newFixnum(rs.getLong(1));
|
844
|
+
}
|
845
|
+
}
|
846
|
+
return runtime.getNil();
|
847
|
+
} finally {
|
848
|
+
try {
|
849
|
+
rs.close();
|
850
|
+
} catch(Exception e) {}
|
851
|
+
}
|
852
|
+
}
|
853
|
+
|
854
|
+
private static String convertToStringOrNull(IRubyObject obj) {
|
855
|
+
if (obj.isNil()) {
|
856
|
+
return null;
|
857
|
+
}
|
858
|
+
return obj.toString();
|
859
|
+
}
|
860
|
+
|
861
|
+
private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
|
862
|
+
if(!(type instanceof RubySymbol)) {
|
863
|
+
type = rubyApi.callMethod(type, "type");
|
864
|
+
}
|
865
|
+
if(type == runtime.newSymbol("string")) {
|
866
|
+
return Types.VARCHAR;
|
867
|
+
} else if(type == runtime.newSymbol("text")) {
|
868
|
+
return Types.CLOB;
|
869
|
+
} else if(type == runtime.newSymbol("integer")) {
|
870
|
+
return Types.INTEGER;
|
871
|
+
} else if(type == runtime.newSymbol("decimal")) {
|
872
|
+
return Types.DECIMAL;
|
873
|
+
} else if(type == runtime.newSymbol("float")) {
|
874
|
+
return Types.FLOAT;
|
875
|
+
} else if(type == runtime.newSymbol("datetime")) {
|
876
|
+
return Types.TIMESTAMP;
|
877
|
+
} else if(type == runtime.newSymbol("timestamp")) {
|
878
|
+
return Types.TIMESTAMP;
|
879
|
+
} else if(type == runtime.newSymbol("time")) {
|
880
|
+
return Types.TIME;
|
881
|
+
} else if(type == runtime.newSymbol("date")) {
|
882
|
+
return Types.DATE;
|
883
|
+
} else if(type == runtime.newSymbol("binary")) {
|
884
|
+
return Types.BLOB;
|
885
|
+
} else if(type == runtime.newSymbol("boolean")) {
|
886
|
+
return Types.BOOLEAN;
|
887
|
+
} else {
|
888
|
+
return -1;
|
889
|
+
}
|
890
|
+
}
|
891
|
+
|
892
|
+
private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
|
893
|
+
|
894
|
+
private static void setValue(PreparedStatement ps, int index, Ruby runtime, IRubyObject value, IRubyObject type) throws SQLException {
|
895
|
+
final int tp = getTypeValueFor(runtime, type);
|
896
|
+
if(value.isNil()) {
|
897
|
+
ps.setNull(index, tp);
|
898
|
+
return;
|
899
|
+
}
|
900
|
+
|
901
|
+
switch(tp) {
|
902
|
+
case Types.VARCHAR:
|
903
|
+
case Types.CLOB:
|
904
|
+
ps.setString(index, RubyString.objAsString(value).toString());
|
905
|
+
break;
|
906
|
+
case Types.INTEGER:
|
907
|
+
ps.setLong(index, RubyNumeric.fix2long(value));
|
908
|
+
break;
|
909
|
+
case Types.FLOAT:
|
910
|
+
ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
|
911
|
+
break;
|
912
|
+
case Types.TIMESTAMP:
|
913
|
+
case Types.TIME:
|
914
|
+
case Types.DATE:
|
915
|
+
if(!(value instanceof RubyTime)) {
|
916
|
+
try {
|
917
|
+
Date dd = FORMAT.parse(RubyString.objAsString(value).toString());
|
918
|
+
ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
|
919
|
+
} catch(Exception e) {
|
920
|
+
ps.setString(index, RubyString.objAsString(value).toString());
|
921
|
+
}
|
922
|
+
} else {
|
923
|
+
RubyTime rubyTime = (RubyTime) value;
|
924
|
+
java.util.Date date = rubyTime.getJavaDate();
|
925
|
+
long millis = date.getTime();
|
926
|
+
long micros = rubyTime.microseconds() - millis / 1000;
|
927
|
+
java.sql.Timestamp ts = new java.sql.Timestamp(millis);
|
928
|
+
java.util.Calendar cal = Calendar.getInstance();
|
929
|
+
cal.setTime(date);
|
930
|
+
ts.setNanos((int)(micros * 1000));
|
931
|
+
ps.setTimestamp(index, ts, cal);
|
932
|
+
}
|
933
|
+
break;
|
934
|
+
case Types.BOOLEAN:
|
935
|
+
ps.setBoolean(index, value.isTrue());
|
936
|
+
break;
|
937
|
+
default: throw new RuntimeException("type " + type + " not supported in _bind yet");
|
938
|
+
}
|
939
|
+
}
|
940
|
+
|
941
|
+
private static void setValuesOnPS(PreparedStatement ps, Ruby runtime, IRubyObject values, IRubyObject types) throws SQLException {
|
942
|
+
RubyArray vals = (RubyArray)values;
|
943
|
+
RubyArray tps = (RubyArray)types;
|
944
|
+
|
945
|
+
for(int i=0, j=vals.getLength(); i<j; i++) {
|
946
|
+
setValue(ps, i+1, runtime, vals.eltInternal(i), tps.eltInternal(i));
|
947
|
+
}
|
948
|
+
}
|
949
|
+
|
950
|
+
/*
|
951
|
+
* sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
|
952
|
+
*/
|
953
|
+
public static IRubyObject insert_bind(IRubyObject recv, final IRubyObject[] args) throws SQLException {
|
954
|
+
final Ruby runtime = recv.getRuntime();
|
955
|
+
Arity.checkArgumentCount(runtime, args, 3, 7);
|
956
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
957
|
+
public IRubyObject call(Connection c) throws SQLException {
|
958
|
+
PreparedStatement ps = null;
|
959
|
+
try {
|
960
|
+
ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
|
961
|
+
setValuesOnPS(ps, runtime, args[1], args[2]);
|
962
|
+
ps.executeUpdate();
|
963
|
+
return unmarshal_id_result(runtime, ps.getGeneratedKeys());
|
964
|
+
} finally {
|
965
|
+
try {
|
966
|
+
ps.close();
|
967
|
+
} catch (Exception e) {
|
968
|
+
}
|
969
|
+
}
|
970
|
+
}
|
971
|
+
});
|
972
|
+
}
|
973
|
+
|
974
|
+
/*
|
975
|
+
* sql, values, types, name = nil
|
976
|
+
*/
|
977
|
+
public static IRubyObject update_bind(IRubyObject recv, final IRubyObject[] args) throws SQLException {
|
978
|
+
final Ruby runtime = recv.getRuntime();
|
979
|
+
Arity.checkArgumentCount(runtime, args, 3, 4);
|
980
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
981
|
+
public IRubyObject call(Connection c) throws SQLException {
|
982
|
+
PreparedStatement ps = null;
|
983
|
+
try {
|
984
|
+
ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString());
|
985
|
+
setValuesOnPS(ps, runtime, args[1], args[2]);
|
986
|
+
ps.executeUpdate();
|
987
|
+
} finally {
|
988
|
+
try {
|
989
|
+
ps.close();
|
990
|
+
} catch (Exception e) {
|
991
|
+
}
|
992
|
+
}
|
993
|
+
return runtime.getNil();
|
994
|
+
}
|
995
|
+
});
|
996
|
+
}
|
997
|
+
|
998
|
+
/*
|
999
|
+
* (is binary?, colname, tablename, primary key, id, value)
|
1000
|
+
*/
|
1001
|
+
public static IRubyObject write_large_object(IRubyObject recv, final IRubyObject[] args)
|
1002
|
+
throws SQLException, IOException {
|
1003
|
+
final Ruby runtime = recv.getRuntime();
|
1004
|
+
Arity.checkArgumentCount(runtime, args, 6, 6);
|
1005
|
+
return withConnectionAndRetry(recv, new SQLBlock() {
|
1006
|
+
public IRubyObject call(Connection c) throws SQLException {
|
1007
|
+
String sql = "UPDATE " + rubyApi.convertToRubyString(args[2])
|
1008
|
+
+ " SET " + rubyApi.convertToRubyString(args[1])
|
1009
|
+
+ " = ? WHERE " + rubyApi.convertToRubyString(args[3])
|
1010
|
+
+ "=" + rubyApi.convertToRubyString(args[4]);
|
1011
|
+
PreparedStatement ps = null;
|
1012
|
+
try {
|
1013
|
+
ps = c.prepareStatement(sql);
|
1014
|
+
if (args[0].isTrue()) { // binary
|
1015
|
+
ByteList outp = rubyApi.convertToRubyString(args[5]).getByteList();
|
1016
|
+
ps.setBinaryStream(1, new ByteArrayInputStream(outp.bytes,
|
1017
|
+
outp.begin, outp.realSize), outp.realSize);
|
1018
|
+
} else { // clob
|
1019
|
+
String ss = rubyApi.convertToRubyString(args[5]).getUnicodeValue();
|
1020
|
+
ps.setCharacterStream(1, new StringReader(ss), ss.length());
|
1021
|
+
}
|
1022
|
+
ps.executeUpdate();
|
1023
|
+
} finally {
|
1024
|
+
try {
|
1025
|
+
ps.close();
|
1026
|
+
} catch (Exception e) {
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
return runtime.getNil();
|
1030
|
+
}
|
1031
|
+
});
|
1032
|
+
}
|
1033
|
+
|
1034
|
+
private static Connection getConnection(IRubyObject recv) {
|
1035
|
+
Connection conn = (Connection) recv.dataGetStruct();
|
1036
|
+
return conn;
|
1037
|
+
}
|
1038
|
+
|
1039
|
+
private static RuntimeException wrap(IRubyObject recv, Throwable exception) {
|
1040
|
+
RubyClass err = recv.getRuntime().getModule("ActiveRecord").getClass("ActiveRecordError");
|
1041
|
+
return (RuntimeException) new RaiseException(recv.getRuntime(), err, exception.getMessage(), false).initCause(exception);
|
1042
|
+
}
|
1043
|
+
|
1044
|
+
private static ResultSet intoResultSet(IRubyObject inp) {
|
1045
|
+
JavaObject jo;
|
1046
|
+
if (inp instanceof JavaObject) {
|
1047
|
+
jo = (JavaObject) inp;
|
1048
|
+
} else {
|
1049
|
+
jo = (JavaObject) rubyApi.getInstanceVariable(inp, "@java_object");
|
1050
|
+
}
|
1051
|
+
return (ResultSet) jo.getValue();
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
private static boolean isConnectionBroken(IRubyObject recv, Connection c) {
|
1055
|
+
try {
|
1056
|
+
IRubyObject alive = config_value(recv, "connection_alive_sql");
|
1057
|
+
if (select_p(recv, alive).isTrue()) {
|
1058
|
+
String connectionSQL = rubyApi.convertToRubyString(alive).toString();
|
1059
|
+
Statement s = c.createStatement();
|
1060
|
+
try {
|
1061
|
+
s.execute(connectionSQL);
|
1062
|
+
} finally {
|
1063
|
+
try { s.close(); } catch (SQLException ignored) {}
|
1064
|
+
}
|
1065
|
+
return true;
|
1066
|
+
} else {
|
1067
|
+
return !c.isClosed();
|
1068
|
+
}
|
1069
|
+
} catch (SQLException sx) {
|
1070
|
+
return true;
|
1071
|
+
}
|
1072
|
+
}
|
1073
|
+
|
1074
|
+
private static IRubyObject setConnection(IRubyObject recv, Connection c) {
|
1075
|
+
Connection prev = getConnection(recv);
|
1076
|
+
if (prev != null) {
|
1077
|
+
try {
|
1078
|
+
prev.close();
|
1079
|
+
} catch(Exception e) {}
|
1080
|
+
}
|
1081
|
+
IRubyObject rubyconn = recv.getRuntime().getNil();
|
1082
|
+
if (c != null) {
|
1083
|
+
rubyconn = wrappedConnection(recv,c);
|
1084
|
+
}
|
1085
|
+
rubyApi.setInstanceVariable(recv, "@connection", rubyconn);
|
1086
|
+
recv.dataWrapStruct(c);
|
1087
|
+
return recv;
|
1088
|
+
}
|
1089
|
+
|
1090
|
+
private static IRubyObject wrappedConnection(IRubyObject recv, Connection c) {
|
1091
|
+
return Java.java_to_ruby(recv, JavaObject.wrap(recv.getRuntime(), c), Block.NULL_BLOCK);
|
1092
|
+
}
|
1093
|
+
|
1094
|
+
private static JdbcConnectionFactory getConnectionFactory(IRubyObject recv) throws RaiseException {
|
1095
|
+
IRubyObject connection_factory = rubyApi.getInstanceVariable(recv, "@connection_factory");
|
1096
|
+
JdbcConnectionFactory factory = null;
|
1097
|
+
try {
|
1098
|
+
factory = (JdbcConnectionFactory) ((JavaObject) rubyApi.getInstanceVariable(connection_factory, "@java_object")).getValue();
|
1099
|
+
} catch (Exception e) {
|
1100
|
+
factory = null;
|
1101
|
+
}
|
1102
|
+
if (factory == null) {
|
1103
|
+
throw recv.getRuntime().newRuntimeError("@connection_factory not set properly");
|
1104
|
+
}
|
1105
|
+
return factory;
|
1106
|
+
}
|
1107
|
+
|
1108
|
+
private static IRubyObject config_value(IRubyObject recv, String key) {
|
1109
|
+
Ruby runtime = recv.getRuntime();
|
1110
|
+
IRubyObject config_hash = rubyApi.getInstanceVariable(recv, "@config");
|
1111
|
+
return rubyApi.callMethod(config_hash, "[]", runtime.newSymbol(key));
|
1112
|
+
}
|
1113
|
+
}
|