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 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
+