ar-sybase-jdbc-adapter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 arkadiy kraportov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,92 @@
1
+ = ar-sybase-jdbc-adapter
2
+
3
+ ar-sybase-jdbc-adapter enhances the Arel 2 activerecord-jdbc-adapter to support "limit" and "offset" for Sybase ASE DB.
4
+
5
+ To use this gem, set the "dialect" configuration parameter to "sybase_jtds".
6
+
7
+ e.g.
8
+ development:
9
+ adapter: jdbc
10
+ username: user_name
11
+ password: password
12
+ database: your_database
13
+ dialect: sybase_jtds
14
+ driver: net.sourceforge.jtds.jdbc.Driver
15
+ url: jdbc:jtds:sybase://host:port/db_name
16
+
17
+ == Offset in Sybase ASE
18
+
19
+ Sybase is an awful DB in term of support for "offset". I found 2 ways to do it for generic queries:
20
+
21
+ === 1. Use TempTables:
22
+
23
+ select top <limit + offset> * into #tt from (<original query with NO order by>) t ORDER BY <original order> ASC
24
+ select top <limit> * from #tt into #tt_sorted ORDER BY <original order> DESC
25
+ select * from #tt_sorted ORDER BY <original order>
26
+
27
+ There are 2 major drawbacks here.
28
+ 1. If the offset is a large number, the space taken by the temptable can fill up the tempdb.
29
+ 2. The original query should be parsed to strip out "ORDER BY"
30
+
31
+ === 2. Use a scrollable cursor:
32
+
33
+ declare crsr insensitive scroll cursor for
34
+ select * from <original query>
35
+ go
36
+
37
+ declare @counter int
38
+ set @counter = 1
39
+ open crsr
40
+ fetch absolute <offset> from crsr
41
+
42
+ while @counter < <limit>
43
+ begin
44
+ set @counter = @counter + 1
45
+ fetch next from crsr
46
+ end
47
+
48
+ close crsr
49
+ deallocate crsr
50
+
51
+ The problems here are:
52
+ 1. Scrollable cursor works for Sybase ASE starting from version 15.
53
+ 2. Cursors are not very efficient in Sybase ASE, and very inefficient in Sybase IQ.
54
+
55
+ I am not a Sybase expert, so *Please let me know if you are aware of more efficient ways to do limit and offset.*
56
+
57
+ == Known issues
58
+
59
+ I am aware of a very strange issue where the driver does not work when the very first query uses "limit()".
60
+
61
+ e.g.
62
+ $ rails c
63
+ Loading development environment (Rails 3.0.3)
64
+ irb(main):001:0> Client.limit(10).to_sql
65
+ => "SELECT clients.* FROM clients LIMIT 10"
66
+
67
+ Otherwise, the driver works fine by adding the "TOP" keyword to your SQL query:
68
+
69
+ e.g.
70
+ $ rails c
71
+ Loading development environment (Rails 3.0.3)
72
+ irb(main):001:0> Client.scoped.to_sql
73
+ => "SELECT clients.* FROM clients"
74
+ irb(main):002:0> Client.limit(10).to_sql
75
+ => "SELECT TOP 10 clients.* FROM clients"
76
+
77
+
78
+ == Contributing to ar-sybase-jdbc-adapter
79
+
80
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
81
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
82
+ * Fork the project
83
+ * Start a feature/bugfix branch
84
+ * Commit and push until you are happy with your contribution
85
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
86
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
87
+
88
+ == Copyright
89
+
90
+ Copyright (c) 2010 arkadiy kraportov. See LICENSE.txt for
91
+ further details.
92
+
@@ -0,0 +1,2 @@
1
+ require 'arjdbc'
2
+
@@ -0,0 +1,26 @@
1
+ module Arel
2
+ module Visitors
3
+ class SybaseJtds < Arel::Visitors::ToSql
4
+ def visit_Arel_Nodes_SelectStatement o
5
+ limit = o.limit
6
+ offset = o.offset
7
+ o.limit = o.offset = nil
8
+ sql = super
9
+
10
+ if limit && !offset
11
+ limit_and_no_offset sql, limit
12
+ end
13
+
14
+ sql
15
+ end
16
+
17
+ def limit_and_no_offset sql, limit
18
+ if sql =~ /DISTINCT /
19
+ sql.gsub!(/DISTINCT /, "DISTINCT TOP #{limit} ")
20
+ else
21
+ sql.gsub!(/SELECT /, "SELECT TOP #{limit} ")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ module ::ArJdbc
2
+ extension :SybaseJtds do |name|
3
+ if name =~ /sybase_jtds/i
4
+ require 'arjdbc/sybase_jtds'
5
+ true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module ::ArJdbc
2
+ module SybaseJtds
3
+ def arel2_visitors
4
+ {'sybase_jtds' => ::Arel::Visitors::SybaseJtds}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,31 @@
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
@@ -0,0 +1,91 @@
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
@@ -0,0 +1,42 @@
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
@@ -0,0 +1,60 @@
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
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar-sybase-jdbc-adapter
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - arkadiy kraportov
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-27 00:00:00 +09:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 1
28
+ - 0
29
+ - 0
30
+ version: 1.0.0
31
+ requirement: *id001
32
+ prerelease: false
33
+ type: :development
34
+ - !ruby/object:Gem::Dependency
35
+ name: jeweler
36
+ version_requirements: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 1
42
+ - 5
43
+ - 1
44
+ version: 1.5.1
45
+ requirement: *id002
46
+ prerelease: false
47
+ type: :development
48
+ - !ruby/object:Gem::Dependency
49
+ name: rcov
50
+ version_requirements: &id003 !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ requirement: *id003
58
+ prerelease: false
59
+ type: :development
60
+ - !ruby/object:Gem::Dependency
61
+ name: minitest
62
+ version_requirements: &id004 !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirement: *id004
70
+ prerelease: false
71
+ type: :development
72
+ - !ruby/object:Gem::Dependency
73
+ name: arel
74
+ version_requirements: &id005 !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">"
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 2
80
+ - 0
81
+ - 0
82
+ version: 2.0.0
83
+ requirement: *id005
84
+ prerelease: false
85
+ type: :development
86
+ - !ruby/object:Gem::Dependency
87
+ name: activerecord-jdbc-adapter
88
+ version_requirements: &id006 !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ segments:
93
+ - 1
94
+ - 1
95
+ - 0
96
+ version: 1.1.0
97
+ requirement: *id006
98
+ prerelease: false
99
+ type: :development
100
+ - !ruby/object:Gem::Dependency
101
+ name: activerecord
102
+ version_requirements: &id007 !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 3
108
+ - 0
109
+ - 3
110
+ version: 3.0.3
111
+ requirement: *id007
112
+ prerelease: false
113
+ type: :development
114
+ - !ruby/object:Gem::Dependency
115
+ name: activerecord-jdbc-adapter
116
+ version_requirements: &id008 !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ segments:
121
+ - 1
122
+ - 1
123
+ - 0
124
+ version: 1.1.0
125
+ requirement: *id008
126
+ prerelease: false
127
+ type: :runtime
128
+ - !ruby/object:Gem::Dependency
129
+ name: arel
130
+ version_requirements: &id009 !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ segments:
135
+ - 2
136
+ - 0
137
+ - 6
138
+ version: 2.0.6
139
+ requirement: *id009
140
+ prerelease: false
141
+ type: :runtime
142
+ - !ruby/object:Gem::Dependency
143
+ name: jdbc-jtds
144
+ version_requirements: &id010 !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirement: *id010
152
+ prerelease: false
153
+ type: :runtime
154
+ - !ruby/object:Gem::Dependency
155
+ name: minitest
156
+ version_requirements: &id011 !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ segments:
161
+ - 2
162
+ - 0
163
+ - 0
164
+ version: 2.0.0
165
+ requirement: *id011
166
+ prerelease: false
167
+ type: :development
168
+ description: Adds support for limit and offset for Sybase ASE DB to activerecord-jdbc-adapter for Arel 2
169
+ email: arkadiyk@gmail.com
170
+ executables: []
171
+
172
+ extensions: []
173
+
174
+ extra_rdoc_files:
175
+ - LICENSE.txt
176
+ - README.rdoc
177
+ files:
178
+ - lib/ar-sybase-jdbc-adapter.rb
179
+ - lib/arel/visitors/sybase_jtds.rb
180
+ - lib/arjdbc/discover.rb
181
+ - lib/arjdbc/sybase_jtds.rb
182
+ - LICENSE.txt
183
+ - README.rdoc
184
+ has_rdoc: true
185
+ homepage: http://github.com/arkadiyk/ar-sybase-jdbc-adapter
186
+ licenses:
187
+ - MIT
188
+ post_install_message:
189
+ rdoc_options: []
190
+
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ segments:
198
+ - 0
199
+ version: "0"
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - ">="
203
+ - !ruby/object:Gem::Version
204
+ segments:
205
+ - 0
206
+ version: "0"
207
+ requirements: []
208
+
209
+ rubyforge_project:
210
+ rubygems_version: 1.3.6
211
+ signing_key:
212
+ specification_version: 3
213
+ summary: Adds support for limit and offset for Sybase JDBC driver
214
+ test_files:
215
+ - test/helper.rb
216
+ - test/support/fake_record.rb
217
+ - test/test_connection.rb
218
+ - test/test_visitor.rb