do_sqlserver 0.10.1-java

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.
@@ -0,0 +1,41 @@
1
+ CONNECTING
2
+ ==========
3
+
4
+ Various notes (needs to be cleaned-up).
5
+
6
+ See:
7
+ * http://blogs.msdn.com/sqlexpress/archive/2004/07/23/192044.aspx
8
+ * http://blogs.msdn.com/bethmassi/archive/2008/09/17/enabling-remote-sql-express-2008-network-connections-on-vista.aspx
9
+
10
+
11
+ Example Setup
12
+ -------------
13
+
14
+ * In Visual Studio, click Server Explorer
15
+ * Right click Data Connections
16
+ * Create New SQL Server Database
17
+ * Server Name: YOURPCNAME\SQLEXPRESS
18
+ * Use Windows Authentication
19
+ * #Use SQL Server Authentication
20
+ * #User name: do_test
21
+ * #Password: do_test
22
+ * New database name: do_test
23
+
24
+ See:
25
+ http://social.msdn.microsoft.com/Forums/en-US/Vsexpressinstall/thread/aaf2f68c-4a40-44c8-b7ee-b2f5d94e23c3
26
+
27
+
28
+ ---
29
+
30
+
31
+ Tips
32
+ ----
33
+
34
+ * Check the password is not required to be set on first connect.
35
+ * Test you can connect locally. Either through Visual Studio SQL Server tools,
36
+ SQL Server Management Studio, or simply using telnet: `telnet localhost 4322`.
37
+ * Configure Firewall correctly: http://support.microsoft.com/kb/287932
38
+ * Specify an instance: in the DO URL append `INSTANCE=SQLEXPRESS`.
39
+ * You can not add the instance to the hostname, i.e. `192.168.2.110\SQLEXPRESS`
40
+ as the underlying jTDS driver needs a URL that looks like this:
41
+ `jdbc:jtds:sqlserver://192.168.2.110:1433/do_test;instance=SQLEXPRESS`.
@@ -0,0 +1,25 @@
1
+ ## 0.10.1 (unreleased, in git)
2
+
3
+ Initial release as part of mainline DataObjects project.
4
+
5
+ * Switch to Jeweler for Gem building tasks (this change may be temporary).
6
+ * Switch to using Bacon for running specs: This should make specs friendlier to
7
+ new Ruby implementations that are not yet 100% MRI-compatible, and in turn,
8
+ prepared the road for our own IronRuby and MacRuby support.
9
+ * Switch to the newly added rake-compiler `JavaExtensionTask` for compiling
10
+ JRuby extensions, instead of our (broken) home-grown solution.
11
+
12
+ * Known Issues:
13
+ * Writing Extlib::ByteArray is not currently supported.
14
+
15
+ ## 0.10.0 2009-09-15
16
+
17
+ (NOT RELEASED)
18
+
19
+ * Improvements
20
+ * JRuby Support (using *do_jdbc*)
21
+
22
+ ## 0.0.1 2009-05-11
23
+
24
+ * 1 major enhancement:
25
+ * Initial release
@@ -0,0 +1,8 @@
1
+ FAQS
2
+ ====
3
+
4
+ * Can I use Microsoft's SQL Server driver instead of jTDS?
5
+
6
+ No, not currently. Currently the DataObjects Driver implementation is tied not
7
+ only to a relational database, but to its JDBC driver implementation. You could
8
+ create an alternate database implementation.
@@ -0,0 +1,76 @@
1
+ INSTALLING
2
+ ==========
3
+
4
+ JRuby variant driver
5
+ --------------------
6
+
7
+ * Install the jTDS JDBC Driver. For your convenience, DO packages it as a Ruby
8
+ Gem. If installing from source, `cd` to `../jdbc_drivers/sqlserver/` and run
9
+ `rake install`.
10
+ * There are currently no other prerequisites for installation.
11
+
12
+ 1.8.6/7 (MRI) and 1.9.x (YARV) variant
13
+ --------------------------------------
14
+
15
+ 1. Install the DBI and DBD::ODBC dependencies:
16
+
17
+ sudo gem install dbi
18
+ sudo gem install dbd-odbc
19
+
20
+ 2. Install [ODBC Binding for Ruby][rubyodbc]. It it *not* currently available
21
+ via RubyGems, so you'll have to download the source tarball:
22
+
23
+ wget http://www.ch-werner.de/rubyodbc/ruby-odbc-0.9997.tar.gz
24
+ tar xvfz ruby-odbc-0.9997.tar.gz
25
+ cd ruby-odbc-0.9997
26
+
27
+
28
+ 3. Ensure you read the accompanying `COPYING` file, as the ODBC Binding for Ruby
29
+ is licensed under the GPL, unlike DataObjects. To install read the
30
+ accompanying `INSTALL` file. On a recent variant of OS X, installation
31
+ should look like this:
32
+
33
+ ruby extconf.rb
34
+ make
35
+ sudo make install
36
+
37
+ 4. You must also have FreeTDS or [unixODBC][unixodbc] (SQL Server license for
38
+ unixODBC is commercially licensed though) installed.
39
+
40
+ 5. To install FreeTDS on OS X, you can use MacPorts:
41
+
42
+ sudo port install freetds
43
+
44
+ ****************************************************************
45
+ Configuration file freetds.conf does not exist and has been created using
46
+ /opt/local/etc/freetds/freetds.conf.sample
47
+ Configuration file locales.conf does not exist and has been created using
48
+ /opt/local/etc/freetds/locales.conf.sample
49
+ Configuration file pool.conf does not exist and has been created using
50
+ /opt/local/etc/freetds/pool.conf.sample
51
+ ****************************************************************
52
+
53
+
54
+ * Then edit your ODBC configuration and add the FreeTDS driver.
55
+
56
+ * Using a text editor: On OS X, open `/Library/ODBC/odbcinst.ini` and add
57
+ the following entries:
58
+
59
+ [FreeTDS]
60
+ Driver = /opt/local/lib/libtdsodbc.so
61
+ Setup = /opt/local/lib/libtdsodbc.so
62
+
63
+ * You can also use a GUI for this (provided in Mac OS X 10.3 - 10.5;
64
+ [ODBCManager][odbcmanager] available for OS X 10.6)..
65
+ * Start ODBC Manager
66
+ * Go to *Drivers*, *Add...*
67
+ * Enter _FreeTDS_ as Driver Name.
68
+ * Enter `/usr/local/freetds/lib/libtdsodbc.so` as Driver File
69
+ * Enter `/usr/local/freetds/lib/libtdsodbc.so` as Setup File
70
+ * Select *System*
71
+ * Click *OK*.
72
+
73
+
74
+ [rubyodbc]:http://www.ch-werner.de/rubyodbc/README
75
+ [unixodbc]:http://www.unixodbc.org/
76
+ [odbcmanager]:http://www.odbcmanager.net/index.php
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009 Clifford Heath
2
+ Copyright (c) 2009, 2010 Alex Coles
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ # do_sqlserver
2
+
3
+ * <http://dataobjects.info>
4
+
5
+ ## Description
6
+
7
+ A Microsoft SQL Server adapter for DataObjects,
8
+
9
+ ## Features/Problems
10
+
11
+ This driver implements the DataObjects API for the Microsoft SQL Server
12
+ relational database.
13
+
14
+ Problems with MRI implementation (unreleased):
15
+
16
+ * Relies on DBI's support for either ADO or ODBC with FreeTDS
17
+ * Has no tests and no data type conversion yet
18
+
19
+ ## Synopsis
20
+
21
+ Examples of usage:
22
+
23
+ # default port (using SQL Server Express Edition)
24
+ DataObjects::Connection.new('sqlserver://user:pass@host/database;instance=SQLEXPRESS')
25
+ # port specified (using SQL Server Express Edition)
26
+ DataObjects::Connection.new('sqlserver://user:pass@host:1433/database;instance=SQLEXPRESS')
27
+
28
+ @connection = DataObjects::Connection.new("sqlserver://john:p3$$@localhost:1433/userinfo")
29
+ @reader = @connection.create_command('SELECT * FROM users').execute_reader
30
+ @reader.next!
31
+
32
+ In the future, the `Connection` constructor will be able to be passed either a
33
+ DataObjects-style URL or JDBC style URL, when using do\_sqlserver on JRuby.
34
+ However, this feature is not currently working reliably and is a known issue.
35
+
36
+ * See also the accompanying `CONNECTING.markdown`.
37
+
38
+ ## Requirements
39
+
40
+ This driver is provided for the following platforms:
41
+ * JRuby 1.3.1 + (1.4+ recommended).
42
+
43
+ Code for the following platform is in the repository, but is still under EARLY
44
+ DEVELOPMENT and is neither RELEASED or SUPPORTED:
45
+ * Ruby MRI (1.8.6/7), 1.9: tested on Linux, Mac OS X and Windows platforms.
46
+
47
+ Additionally you should have the following prerequisites:
48
+ * `data_objects` gem
49
+ * `do_jdbc` gem (shared library), if running on JRuby.
50
+ * `dbi` gem, if running on MRI.
51
+ * On non-Windows platforms, unixODBC and FreeTDS libraries.
52
+
53
+ ## Install
54
+
55
+ To install the gem:
56
+
57
+ gem install do_sqlserver
58
+
59
+ To compile and install from source:
60
+
61
+ * For MRI:
62
+ * Installation of do_sqlserver is significantly more involved than for other
63
+ drivers. Please see the accompanying `INSTALL.markdown`.
64
+
65
+ * For JRuby extensions:
66
+ * Install the Java Development Kit (provided if you are
67
+ on a recent version of Mac OS X) from <http://java.sun.com>.
68
+ * Install a recent version of JRuby. Ensure `jruby` is in your `PATH` and/or
69
+ you have configured the `JRUBY_HOME` environment variable to point to your
70
+ JRuby installation.
71
+ * Install `data_objects` and `do_jdbc` with `jruby -S rake install`.
72
+
73
+ * Then, install this driver with `(jruby -S) rake install`.
74
+
75
+ Then:
76
+
77
+ sudo gem install do_sqlserver
78
+
79
+ For more information, see the SQL Server driver wiki page:
80
+ <http://wiki.github.com/datamapper/do/sql-server>.
81
+
82
+ ## Developers
83
+
84
+ Follow the above installation instructions. Additionally, you'll need:
85
+ * `bacon` gem for running specs.
86
+ * `YARD` gem for generating documentation.
87
+
88
+ See the DataObjects wiki for more comprehensive information on installing and
89
+ contributing to the JRuby-variant of this driver:
90
+ <http://wiki.github.com/datamapper/do/jruby>.
91
+
92
+ To run specs:
93
+
94
+ rake spec
95
+
96
+ To run specs without compiling extensions first:
97
+
98
+ rake spec_no_compile
99
+
100
+ To run individual specs:
101
+
102
+ rake spec TEST=spec/connection_spec.rb
103
+
104
+ (Note that the `rake` task uses a `TEST` parameter, not `SPEC`. This is because
105
+ the `Rake::TestTask` is used for executing the Bacon specs).
106
+
107
+ ## License
108
+
109
+ This code is licensed under an **MIT (X11) License**. Please see the
110
+ accompanying `LICENSE` file.
@@ -0,0 +1,56 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/clean'
5
+
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
+
8
+ require ROOT + 'lib/do_sqlserver/version'
9
+
10
+ JRUBY = RUBY_PLATFORM =~ /java/
11
+ IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
12
+ WINDOWS = Gem.win_platform? || (JRUBY && ENV_JAVA['os.name'] =~ /windows/i)
13
+ SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
14
+
15
+ CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext-java/target ])
16
+
17
+ begin
18
+ gem 'jeweler', '~> 1.4'
19
+ require 'jeweler'
20
+
21
+ Jeweler::Tasks.new do |gem|
22
+ gem.name = 'do_sqlserver'
23
+ gem.version = DataObjects::SqlServer::VERSION
24
+ gem.summary = 'DataObjects SQL Server Driver'
25
+ gem.description = 'Implements the DataObjects API for Microsoft SQL Server'
26
+ gem.platform = 'java'
27
+ gem.files = FileList['lib/**/*.rb', 'spec/**/*.rb', 'tasks/**/*.rake',
28
+ 'LICENSE', 'Rakefile', '*.{markdown,rdoc,txt,yml}',
29
+ 'lib/*.jar']
30
+ gem.extra_rdoc_files = FileList['README*', 'ChangeLog*', 'INSTALL.markdown',
31
+ 'FAQS.markdown', 'LICENSE']
32
+ gem.test_files = FileList['spec/**/*.rb']
33
+
34
+ gem.add_dependency 'data_objects', DataObjects::SqlServer::VERSION
35
+ gem.add_dependency 'do_jdbc', DataObjects::SqlServer::VERSION
36
+ gem.add_dependency 'do-jdbc_sqlserver', '~>1.2.4'
37
+
38
+ gem.add_development_dependency 'bacon', '~>1.1'
39
+ gem.add_development_dependency 'rake-compiler', '~>0.7'
40
+
41
+ gem.has_rdoc = false
42
+
43
+ gem.rubyforge_project = 'dorb'
44
+ gem.authors = [ 'Alex Coles' ]
45
+ gem.email = 'alex@alexcolesportfolio.com'
46
+ end
47
+
48
+ Rake::Task['build'].clear_actions if Rake::Task.task_defined?('build')
49
+ task :build => [ :java, :gem ]
50
+
51
+ Jeweler::GemcutterTasks.new
52
+
53
+ FileList['tasks/**/*.rake'].each { |task| import task }
54
+ rescue LoadError
55
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
56
+ end
@@ -0,0 +1,50 @@
1
+ #
2
+ # Monkey patch DBD::ODBC to pass usernames and passwords along
3
+ # to the DB driver correctly
4
+ #
5
+
6
+ # Are you running a new version of DBI?
7
+ begin
8
+ require 'dbd/ODBC'
9
+ rescue Exception
10
+ end
11
+
12
+ # Or an old version?
13
+ begin
14
+ require 'DBD/ODBC/ODBC'
15
+ rescue Exception
16
+ end
17
+
18
+ class DBI::DBD::ODBC::Driver < DBI::BaseDriver
19
+
20
+ def connect(dbname, user, auth, attr)
21
+ driver_attrs = dbname.split(';')
22
+
23
+ if driver_attrs.size > 1
24
+ # DNS-less connection
25
+ drv = ::ODBC::Driver.new
26
+ drv.name = 'Driver1'
27
+ driver_attrs.each do |param|
28
+ pv = param.split('=')
29
+ next if pv.size < 2
30
+ drv.attrs[pv[0]] = pv[1]
31
+ end
32
+ #
33
+ # These next two lines are new
34
+ #
35
+ drv.attrs['UID'] = user unless user.nil?
36
+ drv.attrs['PWD'] = auth unless auth.nil?
37
+
38
+ db = ::ODBC::Database.new
39
+ handle = db.drvconnect(drv)
40
+ else
41
+ # DNS given
42
+ handle = ::ODBC.connect(dbname, user, auth)
43
+ end
44
+
45
+ return DBI::DBD::ODBC::Database.new(handle, attr)
46
+ rescue DBI::DBD::ODBC::ODBCErr => err
47
+ raise DBI::DatabaseError.new(err.message)
48
+ end
49
+
50
+ end
@@ -0,0 +1,296 @@
1
+ require 'data_objects'
2
+ if RUBY_PLATFORM =~ /java/
3
+ require 'do_jdbc'
4
+ require 'java'
5
+ require 'do_jdbc/sqlserver' # the JDBC driver, packaged as a gem
6
+ else # MRI and Ruby 1.9
7
+ require 'dbi' unless defined?(DBI)
8
+ require 'dbd_odbc_patch' # a monkey patch for DNS-less connections
9
+ #require 'core_ext/dbi' # a hack to work around ODBC millisecond handling in Timestamps
10
+ end
11
+
12
+ require 'bigdecimal'
13
+ require 'date'
14
+ require 'base64'
15
+ require 'do_sqlserver/do_sqlserver' if RUBY_PLATFORM =~ /java/
16
+ require 'do_sqlserver/version'
17
+ # JDBC driver has transactions implementation in Java
18
+ require 'do_sqlserver/transaction' if RUBY_PLATFORM !~ /java/
19
+
20
+ if RUBY_PLATFORM !~ /java/
21
+ module DataObjects
22
+ module SqlServer
23
+ Mode = :odbc
24
+ # The ADO mode requires a very old DBI to work with the unmaintained DBI::ADO adapter. Don't enable this unless you fix that.
25
+ # Mode = begin
26
+ # require "ADO"
27
+ # :ado
28
+ # rescue LoadError => e
29
+ # :odbc
30
+ # end
31
+
32
+ class Connection < DataObjects::Connection
33
+ def initialize uri
34
+ # REVISIT: Allow uri.query to modify this connection's mode?
35
+ #host = uri.host.blank? ? "localhost" : uri.host
36
+ host = uri.host.blank? ? nil : uri.host
37
+ user = uri.user || "sa"
38
+ password = uri.password || ""
39
+ path = uri.path.sub(%r{^/*}, '')
40
+ port = uri.port || "1433"
41
+ if Mode == :ado
42
+ connection_string = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{path};User ID=#{user};Password=#{password};"
43
+ else
44
+ # FIXME: Cannot get a DNS-less configuration without freetds.conf to
45
+ # connect successfully, i.e.:
46
+ # connection_string = "DBI:ODBC:DRIVER=FreeTDS;SERVER=#{host};DATABASE=#{path};TDS_Version=5.0;Port=#{port}"
47
+ #
48
+ # Currently need to setup a dataserver entry in freetds.conf (if
49
+ # using MacPorts, full path is /opt/local/etc/freetds/freetds.conf):
50
+ #
51
+ # [sqlserver]
52
+ # host = hostname
53
+ # port = 1433
54
+ # instance = SQLEXPRESS
55
+ # tds version = 8.0
56
+ #
57
+ connection_string = "DBI:ODBC:DRIVER=FreeTDS;SERVERNAME=sqlserver;DATABASE=#{path};"
58
+ end
59
+
60
+ begin
61
+ @connection = DBI.connect(connection_string, user, password)
62
+ rescue DBI::DatabaseError => e
63
+ # Place to debug connection failures
64
+ raise
65
+ end
66
+
67
+ @encoding = uri.query && uri.query["encoding"] || "utf8"
68
+
69
+ set_date_format = create_command("SET DATEFORMAT YMD").execute_non_query
70
+ options_reader = create_command("DBCC USEROPTIONS").execute_reader
71
+ while options_reader.next!
72
+ key, value = *options_reader.values
73
+ value = options_reader.values
74
+ case key
75
+ when "textsize" # "64512"
76
+ when "language" # "us_english", "select * from master..syslanguages" for info
77
+ when "dateformat" # "ymd"
78
+ when "datefirst" # "7" = Sunday, first day of the week, change with "SET DATEFIRST"
79
+ when "quoted_identifier" # "SET"
80
+ when "ansi_null_dflt_on" # "SET"
81
+ when "ansi_defaults" # "SET"
82
+ when "ansi_warnings" # "SET"
83
+ when "ansi_padding" # "SET"
84
+ when "ansi_nulls" # "SET"
85
+ when "concat_null_yields_null" # "SET"
86
+ else
87
+ end
88
+ end
89
+ end
90
+
91
+ def using_socket?
92
+ # This might be an unnecessary feature dragged from the mysql driver
93
+ raise "Not yet implemented"
94
+ end
95
+
96
+ def character_set
97
+ @encoding
98
+ end
99
+
100
+ def dispose
101
+ @connection.disconnect
102
+ true
103
+ rescue
104
+ false
105
+ end
106
+
107
+ def raw
108
+ @connection
109
+ end
110
+ end
111
+
112
+ class Command < DataObjects::Command
113
+ # Theoretically, SCOPE_IDENTIY should be preferred, but there are cases where it returns a stale ID, and I don't know why.
114
+ #IDENTITY_ROWCOUNT_QUERY = 'SELECT SCOPE_IDENTITY(), @@ROWCOUNT'
115
+ IDENTITY_ROWCOUNT_QUERY = 'SELECT @@IDENTITY, @@ROWCOUNT'
116
+
117
+ attr_reader :types
118
+
119
+ def set_types *t
120
+ @types = t.flatten
121
+ end
122
+
123
+ def execute_non_query *args
124
+ DataObjects::SqlServer.check_params @text, args
125
+ begin
126
+ handle = @connection.raw.execute(@text, *args)
127
+ rescue DBI::DatabaseError => e
128
+ handle = @connection.raw.handle
129
+ handle.finish if handle && handle.respond_to?(:finish) && !handle.finished?
130
+ DataObjects::SqlServer.raise_db_error(e, @text, args)
131
+ end
132
+ handle.finish if handle && handle.respond_to?(:finish) && !handle.finished?
133
+
134
+ # Get the inserted ID and the count of affected rows:
135
+ inserted_id, row_count = nil, nil
136
+ if (handle = @connection.raw.execute(IDENTITY_ROWCOUNT_QUERY))
137
+ row1 = Array(Array(handle)[0])
138
+ inserted_id, row_count = row1[0].to_i, row1[1].to_i
139
+ handle.finish
140
+ end
141
+ Result.new(self, row_count, inserted_id)
142
+ end
143
+
144
+ def execute_reader *args
145
+ DataObjects::SqlServer.check_params @text, args
146
+ massage_limit_and_offset args
147
+ begin
148
+ handle = @connection.raw.execute(@text, *args)
149
+ rescue DBI::DatabaseError => e
150
+ handle = @connection.raw.handle
151
+ DataObjects::SqlServer.raise_db_error(e, @text, args)
152
+ handle.finish if handle && handle.respond_to?(:finish) && !handle.finished?
153
+ rescue
154
+ handle = @connection.raw.handle
155
+ handle.finish if handle && handle.respond_to?(:finish) && !handle.finished?
156
+ raise
157
+ end
158
+ Reader.new(self, handle)
159
+ end
160
+
161
+ private
162
+ def massage_limit_and_offset args
163
+ @text.sub!(%r{SELECT (.*) ORDER BY (.*) LIMIT ([?0-9]*)( OFFSET ([?0-9]*))?}) {
164
+ what, order, limit, offset = $1, $2, $3, $5
165
+
166
+ # LIMIT and OFFSET will probably be set by args. We need exact values, so must
167
+ # do substitution here, and remove those args from the array. This is made easier
168
+ # because LIMIT and OFFSET are always the last args in the array.
169
+ offset = args.pop if offset == '?'
170
+ limit = args.pop if limit == '?'
171
+ offset = offset.to_i
172
+ limit = limit.to_i
173
+
174
+ # Reverse the sort direction of each field in the ORDER BY:
175
+ rev_order = order.split(/, */).map{ |f|
176
+ f =~ /(.*) DESC *$/ ? $1 : f+" DESC"
177
+ }*", "
178
+
179
+ "SELECT TOP #{limit} * FROM (SELECT TOP #{offset+limit} #{what} ORDER BY #{rev_order}) ORDER BY #{order}"
180
+ }
181
+ end
182
+ end
183
+
184
+ class Result < DataObjects::Result
185
+ end
186
+
187
+ # REVISIT: There is no data type conversion happening here. That will make DataObjects sad.
188
+ class Reader < DataObjects::Reader
189
+ def initialize command, handle
190
+ @command, @handle = command, handle
191
+ return unless @handle
192
+
193
+ @fields = handle.column_names
194
+
195
+ # REVISIT: Prefetch results like AR's adapter does. ADO is a bit strange about handle lifetimes, don't move this until you can test it.
196
+ @rows = []
197
+ types = @command.types
198
+ if types && types.size != @fields.size
199
+ @handle.finish if @handle && @handle.respond_to?(:finish) && !@handle.finished?
200
+ raise ArgumentError, "Field-count mismatch. Expected #{types.size} fields, but the query yielded #{@fields.size}"
201
+ end
202
+ @handle.each do |row|
203
+ field = -1
204
+ @rows << row.map do |value|
205
+ field += 1
206
+ next value unless types
207
+ if (t = types[field]) == Integer
208
+ Integer(value)
209
+ elsif t == Float
210
+ Float(value)
211
+ else
212
+ t.new(value)
213
+ end
214
+ end
215
+ end
216
+ @handle.finish if @handle && @handle.respond_to?(:finish) && !@handle.finished?
217
+ @current_row = -1
218
+ end
219
+
220
+ def close
221
+ if @handle
222
+ @handle.finish if @handle.respond_to?(:finish) && !@handle.finished?
223
+ @handle = nil
224
+ true
225
+ else
226
+ false
227
+ end
228
+ end
229
+
230
+ def next!
231
+ (@current_row += 1) < @rows.size
232
+ end
233
+
234
+ def values
235
+ raise StandardError.new("First row has not been fetched") if @current_row < 0
236
+ raise StandardError.new("Last row has been processed") if @current_row >= @rows.size
237
+ @rows[@current_row]
238
+ end
239
+
240
+ def fields
241
+ @fields
242
+ end
243
+
244
+ def field_count
245
+ @fields.size
246
+ end
247
+
248
+ # REVISIT: This is being deprecated
249
+ def row_count
250
+ @rows.size
251
+ end
252
+ end
253
+
254
+ private
255
+ def self.check_params cmd, args
256
+ actual = args.size
257
+ expected = param_count(cmd)
258
+ raise ArgumentError.new("Binding mismatch: #{actual} for #{expected}") if actual != expected
259
+ end
260
+
261
+ def self.raise_db_error(e, cmd, args)
262
+ msg = e.to_str
263
+ case msg
264
+ when /Too much parameters/, /No data found/
265
+ #puts "'#{cmd}' (#{args.map{|a| a.inspect}*", "}): #{e.to_str}"
266
+ check_params(cmd, args)
267
+ else
268
+ e.errstr << " running '#{cmd}'"
269
+ #puts "'#{cmd}' (#{args.map{|a| a.inspect}*", "}): #{e.to_str}"
270
+ #debugger
271
+ end
272
+ raise
273
+ end
274
+
275
+ def self.param_count cmd
276
+ cmd.gsub(/'[^']*'/,'').scan(/\?/).size
277
+ end
278
+
279
+ end
280
+
281
+ end
282
+
283
+ else
284
+
285
+ # Register SqlServer JDBC driver
286
+ java.sql.DriverManager.registerDriver Java::net.sourceforge.jtds.jdbc.Driver.new
287
+
288
+ DataObjects::SqlServer::Connection.class_eval do
289
+
290
+ def quote_boolean(value)
291
+ value ? 1 : 0
292
+ end
293
+
294
+ end
295
+
296
+ end