activerecord-jdbc-adapter 1.2.0 → 1.2.1

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.
@@ -1,19 +1,27 @@
1
1
  namespace :db do
2
- task :load_arjdbc do
3
- $LOAD_PATH.unshift 'test'
4
- $LOAD_PATH.unshift 'lib'
5
- require 'abstract_db_create'
6
- end
7
-
8
2
  desc "Creates the test database for MySQL."
9
- task :mysql => :load_arjdbc do
3
+ task :mysql do
10
4
  load 'test/db/mysql.rb' rescue nil
11
- def db_config
12
- MYSQL_CONFIG
5
+ IO.popen("mysql -u root", "w") do |io|
6
+ io.puts <<-SQL
7
+ DROP DATABASE IF EXISTS `#{MYSQL_CONFIG[:database]}`;
8
+ CREATE DATABASE `#{MYSQL_CONFIG[:database]}` DEFAULT CHARACTER SET `utf8`;
9
+ GRANT ALL PRIVILEGES ON `#{MYSQL_CONFIG[:database]}`.* TO #{MYSQL_CONFIG[:username]}@localhost;
10
+ SET PASSWORD FOR #{MYSQL_CONFIG[:username]}@localhost = PASSWORD('#{MYSQL_CONFIG[:password]}');
11
+ SQL
12
+ end
13
+ end
14
+
15
+ desc "Creates the test database for PostgreSQL."
16
+ task :postgres do
17
+ load 'test/db/postgres.rb' rescue nil
18
+ IO.popen("psql", "w") do |io|
19
+ io.puts <<-SQL
20
+ DROP DATABASE IF EXISTS #{POSTGRES_CONFIG[:database]};
21
+ DROP USER IF EXISTS #{POSTGRES_CONFIG[:username]};
22
+ CREATE USER #{POSTGRES_CONFIG[:username]} CREATEDB SUPERUSER LOGIN PASSWORD '#{POSTGRES_CONFIG[:password]}';
23
+ CREATE DATABASE #{POSTGRES_CONFIG[:database]} OWNER #{POSTGRES_CONFIG[:username]};
24
+ SQL
13
25
  end
14
- extend AbstractDbCreate
15
- do_setup('arjdbc', nil)
16
- Rake::Task['db:drop'].invoke rescue nil
17
- Rake::Task['db:create'].invoke
18
26
  end
19
27
  end
@@ -26,7 +26,7 @@ namespace :rails do
26
26
  end
27
27
  end
28
28
 
29
- task :test => "java_compile" do
29
+ task :test => :jar do
30
30
  driver = ENV['DRIVER']
31
31
  raise "need a DRIVER" unless driver
32
32
  activerecord = ENV['RAILS']
@@ -1,5 +1,5 @@
1
1
  /***** BEGIN LICENSE BLOCK *****
2
- * Copyright (c) 2006-2007, 2010 Nick Sieger <nick@nicksieger.com>
2
+ * Copyright (c) 2006-2011 Nick Sieger <nick@nicksieger.com>
3
3
  * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
4
4
  *
5
5
  * Permission is hereby granted, free of charge, to any person obtaining
@@ -29,13 +29,8 @@ import java.sql.SQLException;
29
29
  import arjdbc.jdbc.RubyJdbcConnection;
30
30
 
31
31
  import org.jruby.Ruby;
32
- import org.jruby.RubyBigDecimal;
33
- import org.jruby.RubyBignum;
34
32
  import org.jruby.RubyBoolean;
35
- import org.jruby.RubyFixnum;
36
- import org.jruby.RubyFloat;
37
33
  import org.jruby.RubyModule;
38
- import org.jruby.RubyNumeric;
39
34
  import org.jruby.RubyObjectAdapter;
40
35
  import org.jruby.RubyRange;
41
36
  import org.jruby.RubyString;
@@ -66,39 +61,39 @@ public class DerbyModule {
66
61
  String type = rubyApi.getInstanceVariable(recv, "@type").toString();
67
62
 
68
63
  switch (type.charAt(0)) {
69
- case 's': //string
64
+ case 's': //string
65
+ return value;
66
+ case 't': //text, timestamp, time
67
+ if (type.equals("text")) {
70
68
  return value;
71
- case 't': //text, timestamp, time
72
- if (type.equals("text")) {
73
- return value;
74
- } else if (type.equals("timestamp")) {
75
- return rubyApi.callMethod(recv.getMetaClass(), "string_to_time", value);
76
- } else { //time
77
- return rubyApi.callMethod(recv.getMetaClass(), "string_to_dummy_time", value);
78
- }
79
- case 'i': //integer
80
- case 'p': //primary key
81
- if (value.respondsTo("to_i")) {
82
- return rubyApi.callMethod(value, "to_i");
83
- } else {
84
- return runtime.newFixnum(value.isTrue() ? 1 : 0);
85
- }
86
- case 'd': //decimal, datetime, date
87
- if (type.equals("datetime")) {
88
- return rubyApi.callMethod(recv.getMetaClass(), "string_to_time", value);
89
- } else if (type.equals("date")) {
90
- return rubyApi.callMethod(recv.getMetaClass(), "string_to_date", value);
91
- } else {
92
- return rubyApi.callMethod(recv.getMetaClass(), "value_to_decimal", value);
93
- }
94
- case 'f': //float
95
- return rubyApi.callMethod(value, "to_f");
96
- case 'b': //binary, boolean
97
- if (type.equals("binary")) {
98
- return rubyApi.callMethod(recv.getMetaClass(), "binary_to_string", value);
99
- } else {
100
- return rubyApi.callMethod(recv.getMetaClass(), "value_to_boolean", value);
101
- }
69
+ } else if (type.equals("timestamp")) {
70
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_time", value);
71
+ } else { //time
72
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_dummy_time", value);
73
+ }
74
+ case 'i': //integer
75
+ case 'p': //primary key
76
+ if (value.respondsTo("to_i")) {
77
+ return rubyApi.callMethod(value, "to_i");
78
+ } else {
79
+ return runtime.newFixnum(value.isTrue() ? 1 : 0);
80
+ }
81
+ case 'd': //decimal, datetime, date
82
+ if (type.equals("datetime")) {
83
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_time", value);
84
+ } else if (type.equals("date")) {
85
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_date", value);
86
+ } else {
87
+ return rubyApi.callMethod(recv.getMetaClass(), "value_to_decimal", value);
88
+ }
89
+ case 'f': //float
90
+ return rubyApi.callMethod(value, "to_f");
91
+ case 'b': //binary, boolean
92
+ if (type.equals("binary")) {
93
+ return rubyApi.callMethod(recv.getMetaClass(), "binary_to_string", value);
94
+ } else {
95
+ return rubyApi.callMethod(recv.getMetaClass(), "value_to_boolean", value);
96
+ }
102
97
  }
103
98
  return value;
104
99
  }
@@ -115,6 +110,8 @@ public class DerbyModule {
115
110
  if (type.equals("text") || type.equals("string")) {
116
111
  value = make_ruby_string_for_text_column(context, recv, runtime, value);
117
112
  }
113
+ String metaClass = value.getMetaClass().getName();
114
+
118
115
  if (value instanceof RubyString) {
119
116
  if (type.equals("string")) {
120
117
  return quote_string_with_surround(runtime, "'", (RubyString)value, "'");
@@ -130,7 +127,7 @@ public class DerbyModule {
130
127
  return super_quote(context, recv, runtime, value, col);
131
128
  }
132
129
  }
133
- } else if ((value instanceof RubyFloat) || (value instanceof RubyFixnum) || (value instanceof RubyBignum)) {
130
+ } else if (metaClass.equals("Float") || metaClass.equals("Fixnum") || metaClass.equals("Bignum")) {
134
131
  if (type.equals("string")) {
135
132
  return quote_string_with_surround(runtime, "'", RubyString.objAsString(context, value), "'");
136
133
  }
@@ -139,49 +136,54 @@ public class DerbyModule {
139
136
  return super_quote(context, recv, runtime, value, runtime.getNil());
140
137
  }
141
138
 
142
- /*
139
+ /*
143
140
  * Derby is not permissive like MySql. Try and send an Integer to a CLOB or VARCHAR column and Derby will vomit.
144
141
  * This method turns non stringy things into strings.
145
142
  */
146
143
  private static IRubyObject make_ruby_string_for_text_column(ThreadContext context, IRubyObject recv, Ruby runtime, IRubyObject value) {
147
- RubyModule multibyteChars = (RubyModule)
148
- ((RubyModule) ((RubyModule) runtime.getModule("ActiveSupport")).getConstant("Multibyte")).getConstantAt("Chars");
149
- if (value instanceof RubyString || rubyApi.isKindOf(value, multibyteChars) || value.isNil()) {
150
- return value;
151
- }
152
- if (value instanceof RubyBoolean) {
144
+ RubyModule multibyteChars = (RubyModule)
145
+ ((RubyModule) ((RubyModule) runtime.getModule("ActiveSupport")).getConstant("Multibyte")).getConstantAt("Chars");
146
+ if (value instanceof RubyString || rubyApi.isKindOf(value, multibyteChars) || value.isNil()) {
147
+ return value;
148
+ }
149
+
150
+ String metaClass = value.getMetaClass().getName();
151
+
152
+ if (value instanceof RubyBoolean) {
153
153
  return value.isTrue() ? runtime.newString("1") : runtime.newString("0");
154
- } else if (value instanceof RubyFloat || value instanceof RubyFixnum || value instanceof RubyBignum) {
155
- return RubyString.objAsString(context, value);
156
- } else if ( value instanceof RubyBigDecimal) {
157
- return rubyApi.callMethod(value, "to_s", runtime.newString("F"));
158
- } else {
159
- if (rubyApi.callMethod(value, "acts_like?", runtime.newString("date")).isTrue() || rubyApi.callMethod(value, "acts_like?", runtime.newString("time")).isTrue()) {
160
- return (RubyString)rubyApi.callMethod(recv, "quoted_date", value);
161
- } else {
162
- return (RubyString)rubyApi.callMethod(value, "to_yaml");
163
- }
164
- }
165
- }
166
-
167
- private final static ByteList NULL = new ByteList("NULL".getBytes());
154
+ } else if (metaClass.equals("Float") || metaClass.equals("Fixnum") || metaClass.equals("Bignum")) {
155
+ return RubyString.objAsString(context, value);
156
+ } else if (metaClass.equals("BigDecimal")) {
157
+ return rubyApi.callMethod(value, "to_s", runtime.newString("F"));
158
+ } else {
159
+ if (rubyApi.callMethod(value, "acts_like?", runtime.newString("date")).isTrue() || rubyApi.callMethod(value, "acts_like?", runtime.newString("time")).isTrue()) {
160
+ return (RubyString)rubyApi.callMethod(recv, "quoted_date", value);
161
+ } else {
162
+ return (RubyString)rubyApi.callMethod(value, "to_yaml");
163
+ }
164
+ }
165
+ }
166
+
167
+ private final static ByteList NULL = new ByteList("NULL".getBytes());
168
168
 
169
169
  private static IRubyObject super_quote(ThreadContext context, IRubyObject recv, Ruby runtime, IRubyObject value, IRubyObject col) {
170
170
  if (value.respondsTo("quoted_id")) {
171
171
  return rubyApi.callMethod(value, "quoted_id");
172
172
  }
173
173
 
174
+ String metaClass = value.getMetaClass().getName();
175
+
174
176
  IRubyObject type = (col.isNil()) ? col : rubyApi.callMethod(col, "type");
175
177
  RubyModule multibyteChars = (RubyModule)
176
- ((RubyModule) ((RubyModule) runtime.getModule("ActiveSupport")).getConstant("Multibyte")).getConstantAt("Chars");
178
+ ((RubyModule) ((RubyModule) runtime.getModule("ActiveSupport")).getConstant("Multibyte")).getConstantAt("Chars");
177
179
  if (value instanceof RubyString || rubyApi.isKindOf(value, multibyteChars)) {
178
180
  RubyString svalue = RubyString.objAsString(context, value);
179
181
  if (type == runtime.newSymbol("binary") && col.getType().respondsTo("string_to_binary")) {
180
182
  return quote_string_with_surround(runtime, "'", (RubyString)(rubyApi.callMethod(col.getType(), "string_to_binary", svalue)), "'");
181
183
  } else if (type == runtime.newSymbol("integer") || type == runtime.newSymbol("float")) {
182
184
  return RubyString.objAsString(context, ((type == runtime.newSymbol("integer")) ?
183
- rubyApi.callMethod(svalue, "to_i") :
184
- rubyApi.callMethod(svalue, "to_f")));
185
+ rubyApi.callMethod(svalue, "to_i") :
186
+ rubyApi.callMethod(svalue, "to_f")));
185
187
  } else {
186
188
  return quote_string_with_surround(runtime, "'", svalue, "'");
187
189
  }
@@ -191,9 +193,9 @@ public class DerbyModule {
191
193
  return (value.isTrue() ?
192
194
  (type == runtime.newSymbol(":integer")) ? runtime.newString("1") : rubyApi.callMethod(recv, "quoted_true") :
193
195
  (type == runtime.newSymbol(":integer")) ? runtime.newString("0") : rubyApi.callMethod(recv, "quoted_false"));
194
- } else if((value instanceof RubyFloat) || (value instanceof RubyFixnum) || (value instanceof RubyBignum)) {
196
+ } else if (metaClass.equals("Float") || metaClass.equals("Fixnum") || metaClass.equals("Bignum")) {
195
197
  return RubyString.objAsString(context, value);
196
- } else if(value instanceof RubyBigDecimal) {
198
+ } else if (metaClass.equals("BigDecimal")) {
197
199
  return rubyApi.callMethod(value, "to_s", runtime.newString("F"));
198
200
  } else if (rubyApi.callMethod(value, "acts_like?", runtime.newString("date")).isTrue() || rubyApi.callMethod(value, "acts_like?", runtime.newString("time")).isTrue()) {
199
201
  return quote_string_with_surround(runtime, "'", (RubyString)(rubyApi.callMethod(recv, "quoted_date", value)), "'");
@@ -237,8 +239,8 @@ public class DerbyModule {
237
239
  output.append(lower);
238
240
  written += 2;
239
241
  if(written >= 16334) { // max hex length = 16334
240
- output.append("'||X'".getBytes());
241
- written = 0;
242
+ output.append("'||X'".getBytes());
243
+ written = 0;
242
244
  }
243
245
  }
244
246
 
@@ -26,10 +26,21 @@
26
26
 
27
27
  package arjdbc.sqlite3;
28
28
 
29
+ import java.io.ByteArrayInputStream;
30
+ import java.io.IOException;
31
+ import java.sql.Connection;
32
+ import java.sql.ResultSet;
33
+ import java.sql.ResultSetMetaData;
34
+ import java.sql.SQLException;
35
+ import java.sql.Statement;
36
+ import java.sql.Types;
37
+
29
38
  import arjdbc.jdbc.RubyJdbcConnection;
39
+ import arjdbc.jdbc.SQLBlock;
30
40
 
31
41
  import org.jruby.Ruby;
32
42
  import org.jruby.RubyClass;
43
+ import org.jruby.anno.JRubyMethod;
33
44
  import org.jruby.runtime.ObjectAllocator;
34
45
  import org.jruby.runtime.ThreadContext;
35
46
  import org.jruby.runtime.builtin.IRubyObject;
@@ -57,8 +68,59 @@ public class Sqlite3RubyJdbcConnection extends RubyJdbcConnection {
57
68
  }
58
69
  };
59
70
 
71
+ @JRubyMethod(name = "last_insert_row_id")
72
+ public IRubyObject getLastInsertRowId(final ThreadContext context)
73
+ throws SQLException {
74
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
75
+ public Object call(Connection c) throws SQLException {
76
+ Statement stmt = null;
77
+ try {
78
+ stmt = c.createStatement();
79
+ return unmarshal_id_result(context.getRuntime(),
80
+ stmt.getGeneratedKeys());
81
+ } catch (SQLException sqe) {
82
+ if (context.getRuntime().isDebug()) {
83
+ System.out.println("Error SQL:" + sqe.getMessage());
84
+ }
85
+ throw sqe;
86
+ } finally {
87
+ close(stmt);
88
+ }
89
+ }
90
+ });
91
+ }
92
+
60
93
  @Override
61
94
  protected IRubyObject tables(ThreadContext context, String catalog, String schemaPattern, String tablePattern, String[] types) {
62
95
  return (IRubyObject) withConnectionAndRetry(context, tableLookupBlock(context.getRuntime(), catalog, schemaPattern, tablePattern, types, true));
63
96
  }
97
+
98
+ @Override
99
+ protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
100
+ throws SQLException {
101
+ try {
102
+ // This is rather gross, and only needed because the resultset metadata for SQLite tries to be overly
103
+ // clever, and returns a type for the column of the "current" row, so an integer value stored in a
104
+ // decimal column is returned as Types.INTEGER. Therefore, if the first row of a resultset was an
105
+ // integer value, all rows of that result set would get truncated.
106
+ if( resultSet instanceof ResultSetMetaData ) {
107
+ type = ((ResultSetMetaData)resultSet).getColumnType(column);
108
+ }
109
+ switch (type) {
110
+ case Types.BINARY:
111
+ case Types.BLOB:
112
+ case Types.LONGVARBINARY:
113
+ case Types.VARBINARY:
114
+ return streamToRuby(runtime, resultSet, new ByteArrayInputStream(resultSet.getBytes(column)));
115
+ case Types.LONGVARCHAR:
116
+ return runtime.is1_9() ?
117
+ readerToRuby(runtime, resultSet, resultSet.getCharacterStream(column)) :
118
+ streamToRuby(runtime, resultSet, new ByteArrayInputStream(resultSet.getBytes(column)));
119
+ default:
120
+ return super.jdbcToRuby(runtime, column, type, resultSet);
121
+ }
122
+ } catch (IOException ioe) {
123
+ throw (SQLException) new SQLException(ioe.getMessage()).initCause(ioe);
124
+ }
125
+ }
64
126
  }
@@ -1,8 +1,8 @@
1
1
  MYSQL_CONFIG = {
2
- :username => 'blog',
3
- :password => '',
2
+ :username => 'arjdbc',
3
+ :password => 'arjdbc',
4
4
  :adapter => 'mysql',
5
- :database => 'weblog_development',
5
+ :database => 'arjdbc_test',
6
6
  :host => 'localhost'
7
7
  }
8
8
 
@@ -1,9 +1,9 @@
1
1
  POSTGRES_CONFIG = {
2
2
  :adapter => 'postgresql',
3
- :database => 'weblog_development',
3
+ :database => 'arjdbc_test',
4
4
  :host => 'localhost',
5
- :username => 'blog',
6
- :password => ''
5
+ :username => 'arjdbc',
6
+ :password => 'arjdbc'
7
7
  }
8
8
 
9
9
  ActiveRecord::Base.establish_connection(POSTGRES_CONFIG)
@@ -0,0 +1,68 @@
1
+ require 'jdbc_common'
2
+ require 'db/h2'
3
+
4
+ class H2ChangeColumnTest < Test::Unit::TestCase
5
+
6
+ class Person < ActiveRecord::Base; end
7
+
8
+ class CreatePeopleTable < ActiveRecord::Migration
9
+ def self.up
10
+ create_table :people do |t|
11
+ t.integer :phone
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ drop_table :people
17
+ end
18
+ end
19
+
20
+ def setup
21
+ CreatePeopleTable.up
22
+ end
23
+
24
+ def teardown
25
+ CreatePeopleTable.down
26
+ end
27
+
28
+ def test_should_change_column_type
29
+ ActiveRecord::Migration.change_column :people, :phone, :string
30
+ Person.reset_column_information
31
+
32
+ p = Person.create!(:phone => 'ABC')
33
+ assert_equal 'ABC', p.phone
34
+ end
35
+
36
+ def test_sets_defaults_on_column
37
+ ActiveRecord::Migration.change_column :people, :phone, :string, :default => '123456'
38
+ Person.reset_column_information
39
+
40
+ p = Person.create!
41
+ assert_equal '123456', p.phone
42
+ end
43
+
44
+ def test_should_change_column_default_value
45
+ ActiveRecord::Migration.add_column :people, :email, :string, :default => 'foo@example.com'
46
+ ActiveRecord::Migration.change_column :people, :email, :string, :default => 'bar@example.com'
47
+ Person.reset_column_information
48
+
49
+ p = Person.create!
50
+ assert_equal 'bar@example.com', p.email
51
+ end
52
+
53
+ def test_should_set_non_null_restriction
54
+ ActiveRecord::Migration.change_column :people, :phone, :string, :null => false
55
+ Person.reset_column_information
56
+ assert_raises(ActiveRecord::StatementInvalid) { Person.create! }
57
+ end
58
+
59
+ def test_should_set_null_restriction_with_default
60
+ p = Person.create!
61
+ ActiveRecord::Migration.change_column :people, :phone, :string, :null => true, :default => '123456'
62
+ Person.reset_column_information
63
+
64
+ assert_nil p.reload.phone
65
+ assert_equal '123456', Person.create!.phone
66
+ end
67
+ end
68
+
@@ -3,3 +3,72 @@ module Kernel
3
3
  ENV['PATH'].split(File::PATH_SEPARATOR).detect {|p| File.executable?(File.join(p, name))}
4
4
  end
5
5
  end
6
+
7
+ # assert_queries and SQLCounter taken from rails active_record tests
8
+ require 'test/unit'
9
+ class Test::Unit::TestCase
10
+ def assert_queries(num = 1)
11
+ ActiveRecord::SQLCounter.log = []
12
+ yield
13
+ ensure
14
+ assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
15
+ end
16
+ end
17
+
18
+ require 'active_support/notifications'
19
+ module ActiveRecord
20
+ class SQLCounter
21
+ def self.ignored_sql
22
+ @@ignored_sql
23
+ end
24
+
25
+ def self.ignored_sql=(value)
26
+ @@ignored_sql = value
27
+ end
28
+
29
+ self.ignored_sql = [
30
+ /^PRAGMA (?!(table_info))/,
31
+ /^SELECT currval/,
32
+ /^SELECT CAST/,
33
+ /^SELECT @@IDENTITY/,
34
+ /^SELECT @@ROWCOUNT/,
35
+ /^SAVEPOINT/,
36
+ /^ROLLBACK TO SAVEPOINT/,
37
+ /^RELEASE SAVEPOINT/,
38
+ /^SHOW max_identifier_length/,
39
+ /^BEGIN/,
40
+ /^COMMIT/
41
+ ]
42
+
43
+ # FIXME: this needs to be refactored so specific database can add their own
44
+ # ignored SQL. This ignored SQL is for Oracle.
45
+ ignored_sql.concat [/^select .*nextval/i,
46
+ /^SAVEPOINT/,
47
+ /^ROLLBACK TO/,
48
+ /^\s*select .* from all_triggers/im
49
+ ]
50
+
51
+ def self.log=(v)
52
+ @@log = v
53
+ end
54
+
55
+ def self.log
56
+ @@log
57
+ end
58
+
59
+ self.log = []
60
+
61
+ def call(name, start, finish, message_id, values)
62
+ sql = values[:sql]
63
+
64
+ # FIXME: this seems bad. we should probably have a better way to indicate
65
+ # the query was cached
66
+ unless 'CACHE' == values[:name]
67
+ self.class.log << sql unless self.class.ignored_sql.
68
+ any? { |r| sql =~ r }
69
+ end
70
+ end
71
+ end
72
+
73
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
74
+ end