minus5_mssql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'tiny_tds'
3
+ require 'yaml'
4
+
5
+ $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/minus5_mssql/"
6
+ require 'adapter.rb'
@@ -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
+