do_sqlserver 0.10.1-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|