activerecord-jdbcsqlanywhere-adapter 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,5 @@ pkg/*
2
2
  *.gem
3
3
  .bundle
4
4
  *.jar
5
+ Gemfile.lock
6
+ .rvmrc
data/Gemfile CHANGED
@@ -1,4 +1,7 @@
1
1
  source :gemcutter
2
2
 
3
+ gem 'rails', ENV['AR_VERSION'] if ENV['AR_VERSION']
4
+ gem 'activerecord', ENV['AR_VERSION']
5
+
3
6
  # Specify your gem's dependencies in activerecord-sqlanywhere-adapter.gemspec
4
7
  gemspec
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
- activerecord-sqlanywhere-adapter
1
+ activerecord-jdbcsqlanywhere-adapter
2
2
  ===========================
3
3
 
4
- This is an ActiveRecord adapter for Sybase SQLAnywhere.
4
+ This is an ActiveRecord JDBC adapter for Sybase SQLAnywhere.
5
5
 
data/Rakefile CHANGED
@@ -23,6 +23,14 @@ end
23
23
  task :build => [:java_compile, :git_local_check]
24
24
 
25
25
  require 'rake/testtask'
26
+
27
+ # overriding the default rake tests loader
28
+ class Rake::TestTask
29
+ def rake_loader
30
+ 'test/my-minitest-loader.rb'
31
+ end
32
+ end
33
+
26
34
  Rake::TestTask.new(:test) do |test|
27
35
  test.libs << 'lib' << 'test'
28
36
  ar_jdbc = ENV['AR_JDBC'] ||
@@ -1,5 +1,5 @@
1
1
  module ArJdbc
2
2
  module SybaseSQLAnywhere
3
- VERSION = "1.0.0"
3
+ VERSION = "1.0.1"
4
4
  end
5
5
  end
@@ -12,6 +12,152 @@ module ::ArJdbc
12
12
  def init_column(name, default, *args)
13
13
  @name = name.downcase
14
14
  end
15
+
16
+ # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
17
+ def default_value(value)
18
+ # jdbc returns column default strings with actual single quotes around the value.
19
+ return $1 if value =~ /^'(.*)'$/
20
+
21
+ value
22
+ end
23
+
24
+ def simplified_type(field_type)
25
+ case field_type
26
+ when /int|bigint|smallint|tinyint/i then :integer
27
+ when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
28
+ when /float|double|decimal|money|real|smallmoney/i then :decimal
29
+ when /datetime|smalldatetime/i then :datetime
30
+ when /timestamp/i then :timestamp
31
+ when /time/i then :time
32
+ when /date/i then :date
33
+ when /text|ntext|xml/i then :text
34
+ when /binary|image|varbinary/i then :binary
35
+ when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
36
+ when /bit/i then :boolean
37
+ when /uniqueidentifier/i then :string
38
+ end
39
+ end
40
+
41
+ def type_cast(value)
42
+ return nil if value.nil? || value == "(null)" || value == "(NULL)"
43
+ case type
44
+ when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
45
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value=="1"
46
+ else
47
+ super
48
+ end
49
+ end
50
+ end
51
+
52
+ def quote_column_name(name)
53
+ "\"#{name}\""
54
+ end
55
+
56
+ def quoted_true
57
+ quote(1)
58
+ end
59
+
60
+ def quoted_false
61
+ quote(0)
62
+ end
63
+
64
+ def sybaseserver_version
65
+ @sybaseserver_version ||= select_value("select @@version").split('.').map(&:to_i)
66
+ end
67
+
68
+ def last_insert_id
69
+ Integer(select_value("SELECT @@IDENTITY"))
70
+ end
71
+
72
+ def _execute(sql, name = nil)
73
+ result = super
74
+ ActiveRecord::ConnectionAdapters::JdbcConnection::insert?(sql) ? last_insert_id : result
75
+ end
76
+
77
+ def modify_types(tp) #:nodoc:
78
+ tp[:primary_key] = "NUMERIC(22,0) DEFAULT AUTOINCREMENT PRIMARY KEY"
79
+ tp[:integer][:limit] = nil
80
+ tp[:boolean] = {:name => "bit"}
81
+ tp[:binary] = {:name => "image"}
82
+ tp
83
+ end
84
+
85
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
86
+ limit = nil if %w(text binary).include? type.to_s
87
+ return 'uniqueidentifier' if (type.to_s == 'uniqueidentifier')
88
+ return super unless type.to_s == 'integer'
89
+
90
+ if limit.nil? || limit == 4
91
+ 'int'
92
+ elsif limit == 2
93
+ 'smallint'
94
+ elsif limit == 1
95
+ 'tinyint'
96
+ else
97
+ 'bigint'
98
+ end
99
+ end
100
+
101
+ def add_limit_offset!(sql, options)
102
+ if options[:limit] and options[:offset] and options[:offset] > 0
103
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT\\1 TOP #{options[:limit]} START AT #{options[:offset]+1}")
104
+ elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
105
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
106
+ "SELECT#{$1} TOP #{options[:limit]}"
107
+ end unless options[:limit].nil?
108
+ end
109
+ end
110
+
111
+ def add_column_options!(sql, options) #:nodoc:
112
+ super
113
+ if options[:null] != false
114
+ sql << " NULL"
115
+ end
116
+ end
117
+
118
+ # Adds a new column to the named table.
119
+ # See TableDefinition#column for details of the options you can use.
120
+ def add_column(table_name, column_name, type, options = {})
121
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
122
+ fix_null = false
123
+ if options[:null] == false
124
+ fix_null = true
125
+ options.delete(:null)
126
+ end
127
+ add_column_options!(add_column_sql, options)
128
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
129
+ # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
130
+ execute(add_column_sql)
131
+ execute("ALTER TABLE #{table_name} MODIFY #{quote_column_name(column_name)} NOT NULL") if fix_null
132
+ end
133
+
134
+ def remove_column(table_name, column_name)
135
+ remove_from_primary_key(table_name, column_name)
136
+ remove_from_index(table_name, column_name)
137
+ remove_check_constraints(table_name, column_name)
138
+ execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
139
+ end
140
+
141
+ def remove_from_index(table_name, column_name)
142
+ end
143
+
144
+ def remove_check_constraints(table_name, column_name)
145
+ end
146
+
147
+ def remove_from_primary_key(table_name, column_name)
148
+ columns = select "select col.cname from SYS.syscolumns col WHERE col.tname = '#{table_name}' AND in_primary_key = 'Y'"
149
+ the_column, columns = columns.partition{|c| c['cname'].casecmp(column_name.to_s) == 0 }
150
+ if the_column.size > 0
151
+ execute "ALTER TABLE #{table_name} DROP PRIMARY KEY"
152
+ if columns.size > 0
153
+ columns.map! {|c| quote_column_name(c['cname'])}
154
+ execute "ALTER TABLE #{table_name} ADD PRIMARY KEY (#{columns.join(', ')})"
155
+ end
156
+ end
157
+ end
158
+
159
+ def remove_index(table_name, options = {})
160
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
15
161
  end
16
162
  end
17
163
  end
@@ -1,9 +1,15 @@
1
1
  class ActiveRecord::Base
2
2
  class << self
3
3
  def sqlanywhere_connection( config )
4
+ version4 = true
5
+ begin
6
+ Java::com.sybase.jdbc4.jdbc.SybDriver
7
+ rescue NameError
8
+ version4 = false
9
+ end
4
10
  config[:port] ||= 2638
5
- config[:url] ||= "jdbc:sybase:Tds:#{config[:server]}:#{config[:port]}?serviceName=#{config[:database]}"
6
- config[:driver] ||= "com.sybase.jdbc3.jdbc.SybDriver"
11
+ config[:url] ||= "jdbc:sybase:Tds:#{config[:host]}:#{config[:port]}?serviceName=#{config[:database]}"
12
+ config[:driver] ||= version4 ? "com.sybase.jdbc4.jdbc.SybDriver" : "com.sybase.jdbc3.jdbc.SybDriver"
7
13
  config[:dialect] = "sqlanywhere"
8
14
  jdbc_connection(config)
9
15
  end
@@ -31,6 +31,7 @@ import java.sql.DatabaseMetaData;
31
31
  import java.sql.ResultSet;
32
32
  import java.sql.SQLException;
33
33
  import java.sql.Statement;
34
+ import java.sql.Connection;
34
35
  import java.sql.Types;
35
36
 
36
37
  import arjdbc.jdbc.RubyJdbcConnection;
@@ -64,18 +65,32 @@ public class SQLAnywhereRubyJdbcConnection extends RubyJdbcConnection {
64
65
  }
65
66
  };
66
67
 
68
+ protected static IRubyObject booleanToRuby(Ruby runtime, ResultSet resultSet, boolean booleanValue)
69
+ throws SQLException {
70
+ if (booleanValue == false && resultSet.wasNull()) return runtime.getNil();
71
+ return runtime.newBoolean(booleanValue);
72
+ }
73
+
67
74
  /**
68
75
  * Treat LONGVARCHAR as CLOB on Informix for purposes of converting a JDBC value to Ruby.
69
76
  */
70
77
  @Override
71
78
  protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
72
79
  throws SQLException {
80
+ if ( Types.BOOLEAN == type || Types.BIT == type ) {
81
+ return booleanToRuby(runtime, resultSet, resultSet.getBoolean(column));
82
+ }
73
83
  if (type == Types.LONGVARCHAR) {
74
84
  type = Types.CLOB;
75
85
  }
76
86
  return super.jdbcToRuby(runtime, column, type, resultSet);
77
87
  }
78
88
 
89
+ @Override
90
+ protected IRubyObject unmarshalKeysOrUpdateCount(ThreadContext context, Connection c, Statement stmt) throws SQLException {
91
+ return context.getRuntime().newFixnum(stmt.getUpdateCount());
92
+ }
93
+
79
94
  @Override
80
95
  protected IRubyObject unmarshalResults(ThreadContext context, DatabaseMetaData metadata,
81
96
  Statement stmt, boolean downCase) throws SQLException {
@@ -1,9 +1,10 @@
1
1
  config = {
2
2
  :username => 'dba',
3
- :password => 'weblog',
3
+ :password => 'sql',
4
4
  :adapter => 'sqlanywhere',
5
5
  :host => ENV[ "SQLANYWHERE_HOST" ] || 'localhost',
6
- :database => ENV[ "SQLANYWHERE_NAMESPACE" ] || 'weblog_development'
6
+ :port => ENV[ "SQLANYWHERE_PORT" ] || nil,
7
+ :database => ENV[ "SQLANYWHERE_NAMESPACE" ] || 'ARTest'
7
8
  }
8
9
 
9
10
  ActiveRecord::Base.establish_connection( config )
@@ -0,0 +1,13 @@
1
+ require 'rake'
2
+
3
+ # Load the test files from the command line.
4
+
5
+ ARGV.each do |f|
6
+ break if f =~ /^-/
7
+
8
+ if f =~ /\*/
9
+ FileList[f].to_a.each { |fn| require File.expand_path(fn) }
10
+ else
11
+ require File.expand_path(f)
12
+ end
13
+ end
@@ -0,0 +1,159 @@
1
+ require 'jdbc_common'
2
+ require 'db/sqlanywhere'
3
+
4
+ ActiveRecord::Schema.verbose = false
5
+
6
+ class CreateLongShips < ActiveRecord::Migration
7
+
8
+ def self.up
9
+ create_table "long_ships", :force => true do |t|
10
+ t.string "name", :limit => 50, :null => false
11
+ t.integer "width", :default => 123
12
+ t.integer "length", :default => 456
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table "long_ships"
18
+ end
19
+
20
+ end
21
+
22
+ class LongShip < ActiveRecord::Base
23
+ has_many :vikings
24
+ end
25
+
26
+ class CreateVikings < ActiveRecord::Migration
27
+
28
+ def self.up
29
+ create_table "vikings", :force => true do |t|
30
+ t.integer "long_ship_id", :null => false
31
+ t.string "name", :limit => 50, :default => "Sven"
32
+ end
33
+ end
34
+
35
+ def self.down
36
+ drop_table "vikings"
37
+ end
38
+
39
+ end
40
+
41
+ class Viking < ActiveRecord::Base
42
+ belongs_to :long_ship
43
+ end
44
+
45
+
46
+ class CreateNoIdVikings < ActiveRecord::Migration
47
+ def self.up
48
+ create_table "no_id_vikings", :force => true do |t|
49
+ t.string "name", :limit => 50, :default => "Sven"
50
+ end
51
+ remove_column "no_id_vikings", "id"
52
+ end
53
+
54
+ def self.down
55
+ drop_table "no_id_vikings"
56
+ end
57
+ end
58
+
59
+ class NoIdViking < ActiveRecord::Base
60
+ end
61
+
62
+
63
+
64
+ class SQLAnywhereLimitOffsetTest < Test::Unit::TestCase
65
+
66
+ def setup
67
+ CreateLongShips.up
68
+ CreateVikings.up
69
+ CreateNoIdVikings.up
70
+ @connection = ActiveRecord::Base.connection
71
+ end
72
+
73
+ def teardown
74
+ CreateVikings.down
75
+ CreateLongShips.down
76
+ CreateNoIdVikings.down
77
+ ActiveRecord::Base.clear_active_connections!
78
+ end
79
+
80
+ def test_limit_with_no_id_column_available
81
+ NoIdViking.create!(:name => 'Erik')
82
+ assert_nothing_raised(ActiveRecord::StatementInvalid) do
83
+ NoIdViking.find(:first)
84
+ end
85
+ end
86
+
87
+ def test_limit
88
+ %w(one two three four five six seven eight).each do |name|
89
+ LongShip.create!(:name => name)
90
+ end
91
+ ship_names = LongShip.find(:all, :limit => 3).map(&:name)
92
+ assert_equal(%w(one two three), ship_names)
93
+ end
94
+
95
+ def test_limit_and_offset
96
+ return if @connection.sybaseserver_version[0] < 10
97
+
98
+ %w(one two three four five six seven eight).each do |name|
99
+ LongShip.create!(:name => name)
100
+ end
101
+ ship_names = LongShip.find(:all, :offset => 2, :limit => 3).map(&:name)
102
+ assert_equal(%w(three four five), ship_names)
103
+ end
104
+
105
+ def test_limit_with_order
106
+ %w(one two three four five six seven eight).each do |name|
107
+ LongShip.create!(:name => name)
108
+ end
109
+ ship_names = LongShip.find(:all, :order => "name", :limit => 2).map(&:name)
110
+ assert_equal(%w(eight five), ship_names)
111
+ end
112
+
113
+ def test_limit_and_offset_with_order
114
+ return if @connection.sybaseserver_version[0] < 10
115
+
116
+ %w(one two three four five six seven eight).each do |name|
117
+ LongShip.create!(:name => name)
118
+ end
119
+ ship_names = LongShip.find(:all, :order => "name", :offset => 4, :limit => 2).map(&:name)
120
+ assert_equal(%w(seven six), ship_names)
121
+ end
122
+
123
+ # TODO: work out how to fix DISTINCT support without breaking :include
124
+ # def test_limit_and_offset_with_distinct
125
+ # %w(c a b a b a c d c d).each do |name|
126
+ # LongShip.create!(:name => name)
127
+ # end
128
+ # ship_names = LongShip.find(:all, :select => "DISTINCT name", :order => "name", :offset => 1, :limit => 2).map(&:name)
129
+ # assert_equal(%w(b c), ship_names)
130
+ # end
131
+
132
+ def test_limit_and_offset_with_include
133
+ return if @connection.sybaseserver_version[0] < 10
134
+
135
+ skei = LongShip.create!(:name => "Skei")
136
+ skei.vikings.create!(:name => "Bob")
137
+ skei.vikings.create!(:name => "Ben")
138
+ skei.vikings.create!(:name => "Basil")
139
+ ships = Viking.find(:all, :include => :long_ship, :offset => 1, :limit => 2)
140
+ assert_equal(2, ships.size)
141
+ end
142
+
143
+ def test_limit_and_offset_with_include_and_order
144
+ return if @connection.sybaseserver_version[0] < 10
145
+
146
+ boat1 = LongShip.create!(:name => "1-Skei")
147
+ boat2 = LongShip.create!(:name => "2-Skei")
148
+
149
+ boat1.vikings.create!(:name => "Adam")
150
+ boat2.vikings.create!(:name => "Ben")
151
+ boat1.vikings.create!(:name => "Carl")
152
+ boat2.vikings.create!(:name => "Donald")
153
+
154
+ vikings = Viking.find(:all, :include => :long_ship, :order => "long_ships.name, vikings.name", :offset => 0, :limit => 3)
155
+ assert_equal(["Adam", "Carl", "Ben"], vikings.map(&:name))
156
+
157
+ end
158
+
159
+ end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-jdbcsqlanywhere-adapter
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 1
7
- - 0
8
- - 0
9
- version: 1.0.0
4
+ prerelease:
5
+ version: 1.0.1
10
6
  platform: ruby
11
7
  authors:
12
8
  - Brian Olsen
@@ -14,20 +10,16 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-05-09 00:00:00 +02:00
18
- default_executable:
13
+ date: 2011-10-17 00:00:00 Z
19
14
  dependencies:
20
15
  - !ruby/object:Gem::Dependency
21
16
  name: activerecord-jdbc-adapter
22
17
  prerelease: false
23
18
  requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
24
20
  requirements:
25
21
  - - ~>
26
22
  - !ruby/object:Gem::Version
27
- segments:
28
- - 1
29
- - 1
30
- - 1
31
23
  version: 1.1.1
32
24
  type: :runtime
33
25
  version_requirements: *id001
@@ -35,13 +27,10 @@ dependencies:
35
27
  name: bundler
36
28
  prerelease: false
37
29
  requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
38
31
  requirements:
39
32
  - - ">="
40
33
  - !ruby/object:Gem::Version
41
- segments:
42
- - 1
43
- - 0
44
- - 0
45
34
  version: 1.0.0
46
35
  type: :development
47
36
  version_requirements: *id002
@@ -70,9 +59,10 @@ files:
70
59
  - src/java/arjdbc/sqlanywhere/AdapterJavaService.java
71
60
  - src/java/arjdbc/sqlanywhere/SQLAnywhereRubyJdbcConnection.java
72
61
  - test/db/sqlanywhere.rb
62
+ - test/my-minitest-loader.rb
63
+ - test/sqlanywhere_limit_offset_test.rb
73
64
  - test/sqlanywhere_simple_test.rb
74
65
  - lib/arjdbc/sqlanywhere/adapter_java.jar
75
- has_rdoc: true
76
66
  homepage: http://github.com/griff/activerecord-jdbcsqlanywhere-adapter
77
67
  licenses: []
78
68
 
@@ -82,25 +72,21 @@ rdoc_options: []
82
72
  require_paths:
83
73
  - lib
84
74
  required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
85
76
  requirements:
86
77
  - - ">="
87
78
  - !ruby/object:Gem::Version
88
- segments:
89
- - 0
90
79
  version: "0"
91
80
  required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
92
82
  requirements:
93
83
  - - ">="
94
84
  - !ruby/object:Gem::Version
95
- segments:
96
- - 1
97
- - 3
98
- - 6
99
85
  version: 1.3.6
100
86
  requirements: []
101
87
 
102
88
  rubyforge_project:
103
- rubygems_version: 1.3.6
89
+ rubygems_version: 1.8.10
104
90
  signing_key:
105
91
  specification_version: 3
106
92
  summary: Sybase SQLAnywhere JDBC adapter for JRuby on Rails