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.
- data/CONNECTING.markdown +41 -0
- data/ChangeLog.markdown +25 -0
- data/FAQS.markdown +8 -0
- data/INSTALL.markdown +76 -0
- data/LICENSE +21 -0
- data/README.markdown +110 -0
- data/Rakefile +56 -0
- data/lib/dbd_odbc_patch.rb +50 -0
- data/lib/do_sqlserver.rb +296 -0
- data/lib/do_sqlserver/do_sqlserver.jar +0 -0
- data/lib/do_sqlserver/transaction.rb +36 -0
- data/lib/do_sqlserver/version.rb +5 -0
- data/spec/command_spec.rb +9 -0
- data/spec/connection_spec.rb +21 -0
- data/spec/encoding_spec.rb +8 -0
- data/spec/reader_spec.rb +8 -0
- data/spec/result_spec.rb +16 -0
- data/spec/spec_helper.rb +147 -0
- data/spec/typecast/array_spec.rb +8 -0
- data/spec/typecast/bigdecimal_spec.rb +9 -0
- data/spec/typecast/boolean_spec.rb +9 -0
- data/spec/typecast/byte_array_spec.rb +31 -0
- data/spec/typecast/class_spec.rb +8 -0
- data/spec/typecast/date_spec.rb +11 -0
- data/spec/typecast/datetime_spec.rb +9 -0
- data/spec/typecast/float_spec.rb +9 -0
- data/spec/typecast/integer_spec.rb +8 -0
- data/spec/typecast/nil_spec.rb +10 -0
- data/spec/typecast/other_spec.rb +8 -0
- data/spec/typecast/range_spec.rb +8 -0
- data/spec/typecast/string_spec.rb +8 -0
- data/spec/typecast/time_spec.rb +8 -0
- data/tasks/compile.rake +29 -0
- data/tasks/release.rake +14 -0
- data/tasks/spec.rake +23 -0
- metadata +161 -0
data/CONNECTING.markdown
ADDED
@@ -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`.
|
data/ChangeLog.markdown
ADDED
@@ -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
|
data/FAQS.markdown
ADDED
@@ -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.
|
data/INSTALL.markdown
ADDED
@@ -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.
|
data/README.markdown
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/do_sqlserver.rb
ADDED
@@ -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
|