minus5_mssql 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|