activerecord-jdbc-adapter 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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