activerecord-jdbc-adapter 0.6 → 0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|