minus5_mssql 0.1.0
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/Rakefile +14 -0
- data/lib/minus5_mssql.rb +6 -0
- data/lib/minus5_mssql/adapter.rb +132 -0
- data/test/test_db_mirroring.rb +83 -0
- metadata +85 -0
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rake/testtask"
|
2
|
+
require 'rubygems/package_task'
|
3
|
+
load 'minus5_mssql.gemspec'
|
4
|
+
|
5
|
+
task :default => [:test]
|
6
|
+
|
7
|
+
Rake::TestTask.new do |test|
|
8
|
+
test.libs << "test"
|
9
|
+
test.test_files = Dir[ "test/test_*.rb" ]
|
10
|
+
test.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
Gem::PackageTask.new(GEMSPEC) do |pkg|
|
14
|
+
end
|
data/lib/minus5_mssql.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
module Minus5
|
2
|
+
|
3
|
+
module Mssql
|
4
|
+
|
5
|
+
class Adapter
|
6
|
+
|
7
|
+
# params - tiny_tds connection params: https://github.com/rails-sqlserver/tiny_tds
|
8
|
+
# with additon of mirror_host
|
9
|
+
# Example:
|
10
|
+
# SqlBase.new({ :username => "rails",
|
11
|
+
# :password => "",
|
12
|
+
# :host => "bedem",
|
13
|
+
# :mirror_host => "mssql",
|
14
|
+
# :database => "activerecord_unittest_mirroring"
|
15
|
+
# })
|
16
|
+
def initialize(params)
|
17
|
+
params = YAML.load_file(params).symbolize_keys if params.kind_of?(String)
|
18
|
+
@params = params
|
19
|
+
@params_cache = {}
|
20
|
+
connect
|
21
|
+
end
|
22
|
+
|
23
|
+
# Insert row into table_name.
|
24
|
+
# Data is hash {column_name => value, ...}
|
25
|
+
# Acutal column names will be discovered from database.
|
26
|
+
def insert(table_name, data)
|
27
|
+
columns = get_params(table_name).reject{|c| c == "id"}
|
28
|
+
values = hash_to_values columns, data
|
29
|
+
sql = "insert into #{table_name} (#{columns.join(',')}) values (#{values.join(',')})"
|
30
|
+
execute(sql).insert
|
31
|
+
end
|
32
|
+
|
33
|
+
# Delete rows from table_name.
|
34
|
+
# Data is hash with keys eg. {:id => 123}
|
35
|
+
def delete(table_name, data)
|
36
|
+
columns, values = hash_to_columns_values(data)
|
37
|
+
keys = []
|
38
|
+
for i in (0..columns.size-1)
|
39
|
+
keys << "#{columns[i]} = #{values[i]}"
|
40
|
+
end
|
41
|
+
sql = "delete from #{table_name} where #{keys.join(' and ')}"
|
42
|
+
execute(sql).cancel
|
43
|
+
end
|
44
|
+
|
45
|
+
# Send query to the database. With reconnect in case of db mirroring failover.
|
46
|
+
def execute(sql)
|
47
|
+
@connection.execute(sql)
|
48
|
+
rescue TinyTds::Error => e
|
49
|
+
print "execute error #{e}\n"
|
50
|
+
connect
|
51
|
+
execute(sql)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns results first column of the first row.
|
55
|
+
def select_value(sql)
|
56
|
+
rows = execute(sql).each
|
57
|
+
return nil if rows.size == 0
|
58
|
+
row = rows[0]
|
59
|
+
row[row.keys[0]]
|
60
|
+
end
|
61
|
+
|
62
|
+
def select(sql)
|
63
|
+
rows = execute(sql).each
|
64
|
+
rows.size == 1 ? rows[0] : rows
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def connect
|
70
|
+
print "connecting to #{@params[:host]} "
|
71
|
+
@connection = TinyTds::Client.new(@params)
|
72
|
+
print "successful\n"
|
73
|
+
rescue TinyTds::Error => e
|
74
|
+
print "#{e.to_s}\n"
|
75
|
+
throw unless @params[:mirror_host]
|
76
|
+
to_mirror
|
77
|
+
connect
|
78
|
+
end
|
79
|
+
|
80
|
+
# Switch host and mirror_host in @params
|
81
|
+
def to_mirror
|
82
|
+
host = @params[:host]
|
83
|
+
@params[:host] = @params[:mirror_host]
|
84
|
+
@params[:mirror_host] = host
|
85
|
+
@params[:dataserver] = "#{@params[:host]}:#{@params[:port] || 1433}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Read table or stored procedure param names from database.
|
89
|
+
# Returns array of table column names or stored procedure param
|
90
|
+
def get_params(name)
|
91
|
+
@params_cache[name] ||=
|
92
|
+
begin
|
93
|
+
sql = "select name from sys.syscolumns where id = object_id('#{name}')"
|
94
|
+
@connection.execute(sql).each.map{|row| row["name"]}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def hash_to_values(columns, data)
|
99
|
+
values = columns.map do |column|
|
100
|
+
value = data[column.to_sym]
|
101
|
+
value = data[column.to_s] if value.nil?
|
102
|
+
if column == "time" && value.kind_of?(String)
|
103
|
+
value = Time.parse(value)
|
104
|
+
end
|
105
|
+
if value.nil?
|
106
|
+
'null'
|
107
|
+
elsif value.kind_of?(String)
|
108
|
+
#"'#{value.gsub("\'","''")}'"
|
109
|
+
"'#{@connection.escape(value)}'"
|
110
|
+
elsif value.kind_of?(Date)
|
111
|
+
"'#{value.strftime("%Y-%m-%d")}'"
|
112
|
+
elsif value.kind_of?(Time) || value.kind_of?(DateTime)
|
113
|
+
"'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
114
|
+
elsif value.kind_of?(Integer) || value.kind_of?(Fixnum) || value.kind_of?(Bignum) || value.kind_of?(Float) || value.kind_of?(Rational)
|
115
|
+
"#{value.to_s}"
|
116
|
+
else
|
117
|
+
"'#{@connection.escape(value.to_s)}'"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
values
|
121
|
+
end
|
122
|
+
|
123
|
+
def hash_to_columns_values(data)
|
124
|
+
columns = data.each_key.map{|key| key.to_s}
|
125
|
+
[columns, hash_to_values(columns, data)]
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
6
|
+
require 'minus5_mssql.rb'
|
7
|
+
|
8
|
+
class Reader < Minus5::Mssql::Adapter
|
9
|
+
|
10
|
+
def read
|
11
|
+
execute("select * from people").each(:symbolize_keys => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def failover
|
15
|
+
execute("use master; ALTER DATABASE activerecord_unittest_mirroring SET PARTNER FAILOVER")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class DbMirroring < Test::Unit::TestCase
|
21
|
+
|
22
|
+
def setup
|
23
|
+
@reader = Reader.new({:username => "rails",
|
24
|
+
:password => "",
|
25
|
+
:host => "bedem",
|
26
|
+
:mirror_host => "mssql",
|
27
|
+
:database => "activerecord_unittest_mirroring"})
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_get_params
|
31
|
+
columns = @reader.send(:get_params, 'people')
|
32
|
+
assert_equal 3, columns.size
|
33
|
+
assert columns.include?("id")
|
34
|
+
assert columns.include?("first_name")
|
35
|
+
assert columns.include?("last_name")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_insert
|
39
|
+
rollback_transaction(@reader) do
|
40
|
+
id = @reader.insert('people', {:first_name => "Igor", :last_name => "Anic"})
|
41
|
+
assert_equal 3, @reader.select_value("select count(*) from people")
|
42
|
+
@reader.delete('people', {:id => id})
|
43
|
+
assert_equal 2, @reader.select_value("select count(*) from people")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_read
|
48
|
+
rows = @reader.read
|
49
|
+
data_test rows
|
50
|
+
pp rows
|
51
|
+
@reader.failover
|
52
|
+
rows = @reader.read
|
53
|
+
data_test rows
|
54
|
+
end
|
55
|
+
|
56
|
+
def data_test(rows)
|
57
|
+
assert_equal 2, rows.size
|
58
|
+
assert "Sasa", rows[0][:first_name]
|
59
|
+
assert "Goran", rows[1][:first_name]
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def setup_table
|
65
|
+
@reader.execute "
|
66
|
+
if not exists(select * from sys.objects where name = 'people')
|
67
|
+
create table people (id int identity, first_name varchar(255), last_name varchar(255))
|
68
|
+
"
|
69
|
+
|
70
|
+
@reader.execute "truncate table people"
|
71
|
+
@reader.execute "insert into people (first_name, last_name) values ('Sasa', 'Juric')"
|
72
|
+
@reader.execute "insert into people (first_name, last_name) values ('Goran', 'Pizent')"
|
73
|
+
end
|
74
|
+
|
75
|
+
def rollback_transaction(client)
|
76
|
+
client.execute("BEGIN TRANSACTION").do
|
77
|
+
yield
|
78
|
+
ensure
|
79
|
+
client.execute("ROLLBACK TRANSACTION").do
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: minus5_mssql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Igor Anic
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-31 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: tiny_tds
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 4
|
33
|
+
- 5
|
34
|
+
version: 0.4.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: " minus5_mssql is a simple lib for working with Microsoft Sql Server\n it is built on top of tiny_tds (https://github.com/rails-sqlserver/tiny_tds)\n"
|
38
|
+
email: ianic@minus5.hr
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- lib/minus5_mssql.rb
|
47
|
+
- lib/minus5_mssql/adapter.rb
|
48
|
+
- test/test_db_mirroring.rb
|
49
|
+
- Rakefile
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://www.minus5.hr
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.5.3
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: minus5 mssql library
|
84
|
+
test_files: []
|
85
|
+
|