ar-sybase-jdbc-adapter 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,16 @@
1
1
  = ar-sybase-jdbc-adapter
2
2
 
3
- ar-sybase-jdbc-adapter enhances activerecord-jdbc-adapter (Rails 3) to support "limit" and "offset" for Sybase ASE DB.
3
+ ar-sybase-jdbc-adapter enhances activerecord-jdbc-adapter (Rails 3) to support <tt>.limit</tt> and <tt>offset</tt> for Sybase ASE DB.
4
4
 
5
- **This project is a proof of concept that Sybase ASE can work nicely with Rails**
6
- Once the project reaches "close to production" functionality I will try to merge it with activerecord-jdbc-adapter
5
+ At the moment Sybase ASE version 15 or grater is required. If you need it work for ASE version 12, please open an Issue[https://github.com/arkadiyk/ar-sybase-jdbc-adapter/issues]
7
6
 
7
+ <em>This project is a proof of concept that Sybase ASE can work nicely with Rails</em>. Once the project reaches "close to production" functionality I will try to merge it with activerecord-jdbc-adapter
8
+
9
+ If you have any issues with the adapter please add an Issue[https://github.com/arkadiyk/ar-sybase-jdbc-adapter/issues] or fork the project and send a pull request.
8
10
 
9
11
  == Usage
10
12
  1. Install
11
-
12
- gem install ar-sybase-jdbc-adapter
13
+ gem install ar-sybase-jdbc-adapter
13
14
 
14
15
  2. Configuration
15
16
  To use this gem, set the "dialect" configuration parameter to "sybase_jtds".
@@ -24,22 +25,19 @@ Example:
24
25
  driver: net.sourceforge.jtds.jdbc.Driver
25
26
  url: jdbc:jtds:sybase://host:port/db_name
26
27
 
28
+ == Implementation notes
29
+ If <tt>.limit</tt> with no <tt>.offset</tt> or <tt>.count</tt> methods is used, the adapter simply adds "TOP" keyword to SQL and sends it to the Sybase server:
30
+ User.limit(10)
31
+ produces:
32
+ SELECT TOP 10 users.* FROM users
27
33
 
28
- == Offset in Sybase ASE
29
-
30
- Sybase is an awful DB in term of support for "offset". I found 2 ways to do it for generic queries:
31
-
32
- === 1. Use TempTables:
33
-
34
- select top <limit + offset> * into #tt from (<original query with NO order by>) t ORDER BY <original order> ASC
35
- select top <limit> * from #tt into #tt_sorted ORDER BY <original order> DESC
36
- select * from #tt_sorted ORDER BY <original order>
37
-
38
- There are 2 major drawbacks here.
39
- 1. If the offset is a large number, the space taken by the temptable can fill up the tempdb.
40
- 2. The original query should be parsed to strip out "ORDER BY"
34
+ The adapter has to rely on Java code to implement <tt>.offset</tt> or when <tt>.count</tt> is used together with <tt>.offset</tt> or <tt>.limit</tt>. In this case adapter will generate SQL like it was MySQL query:
35
+ User.limit(10).offset(20)
36
+ produces
37
+ SELECT users.* FROM users LIMIT 10 OFFSET 21
38
+ This can be confusing if you are looking at the log file.
41
39
 
42
- === 2. Use a scrollable cursor:
40
+ Java layer parses the SQL and executes it as multistep scrollable cursor query:
43
41
 
44
42
  declare crsr insensitive scroll cursor for
45
43
  select * from <original query>
@@ -52,40 +50,15 @@ There are 2 major drawbacks here.
52
50
  close crsr
53
51
  deallocate crsr
54
52
 
55
- The problems here are:
56
- 1. Scrollable cursor works for Sybase ASE starting from version 15.
57
- 2. Cursors are not very efficient in Sybase ASE, and very inefficient in Sybase IQ.
58
-
59
- I am not a Sybase expert, so *Please let me know if you are aware of more efficient ways to do limit and offset.*
60
-
61
- == Limit and Count in Sybase ASE
62
-
63
- There is another interesting issue with Sybase DB I have just discovered. To implement "limit" I add "TOP <limit>" to the
64
- query and it works fine. To check how many records this query returns the obvios thing is to run something like
65
-
66
- select count(*) from (select top 10 * from table_name) t -- DOES NOT WORK!
67
-
68
- But it does not work! The result of this query will be total number of rows in the table. So the only solution will be to
69
- fall back to `cursor` and get `@@rowcount` to get the number of rows.
70
53
 
71
- declare crsr insensitive scroll cursor for
72
- select * from <original query>
73
- go
74
- open crsr
75
-
76
- set cursor rows <limit> for crsr
77
- fetch absolute <offset> from crsr
78
-
79
- select @@rowcount
80
-
81
- close crsr
82
- deallocate crsr
54
+ Unfortunately this approach is not very efficient for very large OFFSET values. Also scrollable cursor works for Sybase ASE starting from version 15.
83
55
 
56
+ I am not a Sybase expert, so <em>Please let me know if you are aware of more efficient ways to do limit and offset.</em>
84
57
 
85
58
 
86
59
  == Known issues
87
60
 
88
- I am aware of a very strange issue where the driver does not work when the very first query uses "limit()".
61
+ I am aware of a very strange issue where the adapter does not work when the very first query uses "limit()".
89
62
 
90
63
  e.g.
91
64
  $ rails c
@@ -93,7 +66,7 @@ e.g.
93
66
  irb(main):001:0> Client.limit(10).to_sql
94
67
  => "SELECT clients.* FROM clients LIMIT 10"
95
68
 
96
- Otherwise, the driver works fine by adding the "TOP" keyword to your SQL query:
69
+ Otherwise, the adapter works fine by adding the "TOP" keyword to your SQL query:
97
70
 
98
71
  e.g.
99
72
  $ rails c
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar-sybase-jdbc-adapter
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 0
9
- version: 0.2.0
4
+ prerelease:
5
+ version: 0.2.2
10
6
  platform: ruby
11
7
  authors:
12
8
  - arkadiy kraportov
@@ -14,45 +10,38 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-01-17 00:00:00 +09:00
13
+ date: 2011-06-15 00:00:00 +09:00
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
21
17
  name: bundler
22
18
  version_requirements: &id001 !ruby/object:Gem::Requirement
19
+ none: false
23
20
  requirements:
24
21
  - - ~>
25
22
  - !ruby/object:Gem::Version
26
- segments:
27
- - 1
28
- - 0
29
- - 0
30
- version: 1.0.0
23
+ version: 1.0.15
31
24
  requirement: *id001
32
25
  prerelease: false
33
26
  type: :development
34
27
  - !ruby/object:Gem::Dependency
35
28
  name: jeweler
36
29
  version_requirements: &id002 !ruby/object:Gem::Requirement
30
+ none: false
37
31
  requirements:
38
32
  - - ~>
39
33
  - !ruby/object:Gem::Version
40
- segments:
41
- - 1
42
- - 5
43
- - 1
44
- version: 1.5.1
34
+ version: 1.6.2
45
35
  requirement: *id002
46
36
  prerelease: false
47
37
  type: :development
48
38
  - !ruby/object:Gem::Dependency
49
39
  name: rcov
50
40
  version_requirements: &id003 !ruby/object:Gem::Requirement
41
+ none: false
51
42
  requirements:
52
43
  - - ">="
53
44
  - !ruby/object:Gem::Version
54
- segments:
55
- - 0
56
45
  version: "0"
57
46
  requirement: *id003
58
47
  prerelease: false
@@ -60,11 +49,10 @@ dependencies:
60
49
  - !ruby/object:Gem::Dependency
61
50
  name: minitest
62
51
  version_requirements: &id004 !ruby/object:Gem::Requirement
52
+ none: false
63
53
  requirements:
64
54
  - - ">="
65
55
  - !ruby/object:Gem::Version
66
- segments:
67
- - 0
68
56
  version: "0"
69
57
  requirement: *id004
70
58
  prerelease: false
@@ -72,27 +60,21 @@ dependencies:
72
60
  - !ruby/object:Gem::Dependency
73
61
  name: arel
74
62
  version_requirements: &id005 !ruby/object:Gem::Requirement
63
+ none: false
75
64
  requirements:
76
- - - "="
65
+ - - ~>
77
66
  - !ruby/object:Gem::Version
78
- segments:
79
- - 2
80
- - 0
81
- - 7
82
- version: 2.0.7
67
+ version: 2.0.10
83
68
  requirement: *id005
84
69
  prerelease: false
85
70
  type: :development
86
71
  - !ruby/object:Gem::Dependency
87
72
  name: activerecord-jdbc-adapter
88
73
  version_requirements: &id006 !ruby/object:Gem::Requirement
74
+ none: false
89
75
  requirements:
90
76
  - - "="
91
77
  - !ruby/object:Gem::Version
92
- segments:
93
- - 1
94
- - 1
95
- - 1
96
78
  version: 1.1.1
97
79
  requirement: *id006
98
80
  prerelease: false
@@ -100,27 +82,21 @@ dependencies:
100
82
  - !ruby/object:Gem::Dependency
101
83
  name: activerecord
102
84
  version_requirements: &id007 !ruby/object:Gem::Requirement
85
+ none: false
103
86
  requirements:
104
- - - ">="
87
+ - - ~>
105
88
  - !ruby/object:Gem::Version
106
- segments:
107
- - 3
108
- - 0
109
- - 3
110
- version: 3.0.3
89
+ version: 3.0.7
111
90
  requirement: *id007
112
91
  prerelease: false
113
92
  type: :development
114
93
  - !ruby/object:Gem::Dependency
115
94
  name: activerecord-jdbc-adapter
116
95
  version_requirements: &id008 !ruby/object:Gem::Requirement
96
+ none: false
117
97
  requirements:
118
- - - ">="
98
+ - - ~>
119
99
  - !ruby/object:Gem::Version
120
- segments:
121
- - 1
122
- - 1
123
- - 1
124
100
  version: 1.1.1
125
101
  requirement: *id008
126
102
  prerelease: false
@@ -128,13 +104,10 @@ dependencies:
128
104
  - !ruby/object:Gem::Dependency
129
105
  name: arel
130
106
  version_requirements: &id009 !ruby/object:Gem::Requirement
107
+ none: false
131
108
  requirements:
132
- - - ">="
109
+ - - ~>
133
110
  - !ruby/object:Gem::Version
134
- segments:
135
- - 2
136
- - 0
137
- - 7
138
111
  version: 2.0.7
139
112
  requirement: *id009
140
113
  prerelease: false
@@ -142,11 +115,10 @@ dependencies:
142
115
  - !ruby/object:Gem::Dependency
143
116
  name: jdbc-jtds
144
117
  version_requirements: &id010 !ruby/object:Gem::Requirement
118
+ none: false
145
119
  requirements:
146
120
  - - ">="
147
121
  - !ruby/object:Gem::Version
148
- segments:
149
- - 0
150
122
  version: "0"
151
123
  requirement: *id010
152
124
  prerelease: false
@@ -154,14 +126,11 @@ dependencies:
154
126
  - !ruby/object:Gem::Dependency
155
127
  name: minitest
156
128
  version_requirements: &id011 !ruby/object:Gem::Requirement
129
+ none: false
157
130
  requirements:
158
- - - ">="
131
+ - - ~>
159
132
  - !ruby/object:Gem::Version
160
- segments:
161
- - 2
162
- - 0
163
- - 0
164
- version: 2.0.0
133
+ version: 2.2.2
165
134
  requirement: *id011
166
135
  prerelease: false
167
136
  type: :development
@@ -192,28 +161,26 @@ rdoc_options: []
192
161
  require_paths:
193
162
  - lib
194
163
  required_ruby_version: !ruby/object:Gem::Requirement
164
+ none: false
195
165
  requirements:
196
166
  - - ">="
197
167
  - !ruby/object:Gem::Version
168
+ hash: 2
198
169
  segments:
199
170
  - 0
200
171
  version: "0"
201
172
  required_rubygems_version: !ruby/object:Gem::Requirement
173
+ none: false
202
174
  requirements:
203
175
  - - ">="
204
176
  - !ruby/object:Gem::Version
205
- segments:
206
- - 0
207
177
  version: "0"
208
178
  requirements: []
209
179
 
210
180
  rubyforge_project:
211
- rubygems_version: 1.3.6
181
+ rubygems_version: 1.5.1
212
182
  signing_key:
213
183
  specification_version: 3
214
184
  summary: Adds support for limit and offset for Rails 3 and Sybase JDBC driver
215
- test_files:
216
- - test/helper.rb
217
- - test/support/fake_record.rb
218
- - test/test_connection.rb
219
- - test/test_visitor.rb
185
+ test_files: []
186
+
@@ -1,31 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
-
4
- begin
5
- Bundler.setup(:default, :development)
6
- rescue Bundler::BundlerError => e
7
- $stderr.puts e.message
8
- $stderr.puts "Run `bundle install` to install missing gems"
9
- exit e.status_code
10
- end
11
-
12
-
13
- require 'minitest/autorun'
14
- require 'fileutils'
15
- require 'arel'
16
-
17
-
18
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
19
- $LOAD_PATH.unshift(File.dirname(__FILE__))
20
-
21
- require 'ar-sybase-jdbc-adapter'
22
- require 'support/fake_record'
23
-
24
-
25
- Arel::Table.engine = Arel::Sql::Engine.new(FakeRecord::Base.new)
26
-
27
- class Object
28
- def must_be_like other
29
- gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip
30
- end
31
- end
@@ -1,91 +0,0 @@
1
- module FakeRecord
2
- class Column < Struct.new(:name, :type)
3
- end
4
-
5
- class Connection
6
- attr_reader :tables
7
-
8
- def initialize
9
- @tables = %w{ users photos developers }
10
- @columns = {
11
- 'users' => [
12
- Column.new('id', :integer),
13
- Column.new('name', :string),
14
- Column.new('bool', :boolean),
15
- Column.new('created_at', :date),
16
- ]
17
- }
18
- @primary_keys = {
19
- 'users' => 'id'
20
- }
21
- end
22
-
23
- def primary_key name
24
- @primary_keys[name.to_s]
25
- end
26
-
27
- def table_exists? name
28
- @tables.include? name.to_s
29
- end
30
-
31
- def columns name, message = nil
32
- @columns[name.to_s]
33
- end
34
-
35
- def quote_table_name name
36
- "\"#{name.to_s}\""
37
- end
38
-
39
- def quote_column_name name
40
- "\"#{name.to_s}\""
41
- end
42
-
43
- def quote thing, column = nil
44
- if column && column.type == :integer
45
- return 'NULL' if thing.nil?
46
- return thing.to_i
47
- end
48
-
49
- case thing
50
- when true
51
- "'t'"
52
- when false
53
- "'f'"
54
- when nil
55
- 'NULL'
56
- when Numeric
57
- thing
58
- else
59
- "'#{thing}'"
60
- end
61
- end
62
- end
63
-
64
- class ConnectionPool
65
- class Spec < Struct.new(:config)
66
- end
67
-
68
- attr_reader :spec, :connection
69
-
70
- def initialize
71
- @spec = Spec.new(:adapter => 'america')
72
- @connection = Connection.new
73
- end
74
-
75
- def with_connection
76
- yield connection
77
- end
78
- end
79
-
80
- class Base
81
- attr_accessor :connection_pool
82
-
83
- def initialize
84
- @connection_pool = ConnectionPool.new
85
- end
86
-
87
- def connection
88
- connection_pool.connection
89
- end
90
- end
91
- end
@@ -1,42 +0,0 @@
1
- require 'arjdbc'
2
- require 'helper'
3
-
4
- #module ActiveRecord
5
- # module ConnectionAdapters
6
- # class JdbcAdapter < AbstractAdapter
7
- # def initialize
8
- # end
9
- # end
10
- # end
11
- #end
12
-
13
- module ConnectionTests
14
- class MockConnection
15
- def adapter=(adapt)
16
- end
17
- def jndi_connection?
18
- false
19
- end
20
- end
21
-
22
- describe 'the sybase jtds connection' do
23
- before do
24
- @config = {
25
- :driver => 'net.sourceforge.jtds.Driver',
26
- :url => "jdbc:jtds:sybase://test:1234/database",
27
- :dialect => 'sybase_jtds'
28
- }
29
- @adapter = ActiveRecord::ConnectionAdapters::JdbcAdapter.new MockConnection.new, nil, @config
30
- end
31
-
32
- it "instantiate correct adapter when using 'sybase_jtds' dialect" do
33
- @adapter.must_be_kind_of(::ArJdbc::SybaseJtds)
34
- end
35
-
36
- it "should configure arel2 visitors for SybaseJtds" do
37
- ::Arel::Visitors::VISITORS.must_include('sybase_jtds')
38
- visitor = ::Arel::Visitors::VISITORS['sybase_jtds']
39
- visitor.must_equal(::Arel::Visitors::SybaseJtds)
40
- end
41
- end
42
- end
@@ -1,60 +0,0 @@
1
- require 'helper'
2
- require 'arel/visitors/sybase_jtds'
3
-
4
- module Arel
5
- module Visitors
6
- describe 'the sybase jtds visitor' do
7
- before do
8
- @visitor = SybaseJtds.new Table.engine
9
- end
10
-
11
-
12
- describe Nodes::SelectStatement do
13
- it "should not have 'LIMIT' keyword" do
14
- stmt = Nodes::SelectStatement.new
15
- stmt.cores.first.projections << 'first_field'
16
- stmt.limit = 10
17
- sql = @visitor.accept stmt
18
- sql.wont_match /LIMIT 10/
19
- end
20
-
21
- describe 'limit with no offset and no "DISTINCT"' do
22
- it 'adds a TOP keyword after "SELECT"' do
23
- stmt = Nodes::SelectStatement.new
24
- stmt.cores.first.projections << 'first_field'
25
- stmt.limit = 10
26
- sql = @visitor.accept stmt
27
- sql.must_be_like %{ SELECT TOP 10 'first_field' }
28
- end
29
- end
30
-
31
- describe 'limit with no offset and "DISTINCT"' do
32
- it 'adds a TOP keyword after "DISTINCT"' do
33
- stmt = Nodes::SelectStatement.new
34
- stmt.cores.first.projections << Nodes::SqlLiteral.new('DISTINCT id')
35
- stmt.limit = 10
36
- sql = @visitor.accept stmt
37
- sql.must_be_like %{ SELECT DISTINCT TOP 10 id }
38
- end
39
- end
40
-
41
- #
42
- # describe 'only offset' do
43
- # it 'creates a select from subquery with rownum condition' do
44
- # stmt = Nodes::SelectStatement.new
45
- # stmt.offset = Nodes::Offset.new(10)
46
- # sql = @visitor.accept stmt
47
- # sql.must_be_like %{
48
- # SELECT * FROM (
49
- # SELECT raw_sql_.*, rownum raw_rnum_
50
- # FROM (SELECT ) raw_sql_
51
- # )
52
- # WHERE raw_rnum_ > 10
53
- # }
54
- # end
55
- # end
56
-
57
- end
58
- end
59
- end
60
- end