activerecord-jdbcteradata-adapter 0.2.0 → 0.3.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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "activerecord-jdbcteradata-adapter"
3
- s.version = "0.2.0"
3
+ s.version = "0.3.0"
4
4
  s.authors = ["Chris Parker"]
5
5
  s.email = [ "mrcsparker@gmail.com"]
6
6
  s.homepage = "https://github.com/mrcsparker/activerecord-jdbcteradata-adapter"
data/examples/models.rb CHANGED
@@ -12,61 +12,65 @@ CONFIG = {
12
12
  ActiveRecord::Base.establish_connection(CONFIG)
13
13
 
14
14
  class Acct < ActiveRecord::Base
15
- def self.table_name
16
- 'financial.accts'
17
- end
15
+ self.table_name = 'financial.accts'
16
+
17
+ belongs_to :customer, :foreign_key => 'cust_id'
18
18
  end
19
19
 
20
20
  class CheckingAcct < ActiveRecord::Base
21
- def self.table_name
22
- 'financial.checking_acct'
23
- end
21
+ self.table_name = 'financial.checking_acct'
22
+ self.primary_key = 'cust_id'
23
+
24
+ belongs_to :customer, :foreign_key => 'cust_id'
24
25
  end
25
26
 
26
27
  class CheckingTran < ActiveRecord::Base
27
- def self.table_name
28
- 'financial.checking_tran'
29
- end
28
+ self.table_name = 'financial.checking_tran'
29
+ self.primary_key = 'Tran_Id'
30
+
31
+ belongs_to :customer, :foreign_key => 'Cust_Id'
30
32
  end
31
33
 
32
34
  class CreditAcct < ActiveRecord::Base
33
- def self.table_name
34
- 'financial.credit_acct'
35
- end
35
+ self.table_name = 'financial.credit_acct'
36
+ self.primary_key = 'cust_id'
37
+
38
+ belongs_to :customer, :foreign_key => 'cust_id'
36
39
  end
37
40
 
38
41
  class CreditTran < ActiveRecord::Base
39
- def self.table_name
40
- 'financial.credit_tran'
41
- end
42
+ self.table_name ='financial.credit_tran'
43
+ self.primary_key = 'Tran_Id'
44
+
45
+ belongs_to :customer, :foreign_key => 'Cust_Id'
42
46
  end
43
47
 
44
48
  class Customer < ActiveRecord::Base
45
- def self.table_name
46
- 'financial.customer'
47
- end
49
+ self.table_name = 'financial.customer'
50
+ self.primary_key = 'cust_id'
48
51
  end
49
52
 
50
53
  class CustomerName < ActiveRecord::Base
51
- def self.table_name
52
- 'financial.customer_name'
53
- end
54
+ self.table_name = 'financial.customer_name'
55
+ self.primary_key = 'cust_id'
56
+
57
+ belongs_to :customer, :foreign_key => 'cust_id'
54
58
  end
55
59
 
56
60
  class SavingsAcct < ActiveRecord::Base
57
- def self.table_name
58
- 'financial.savings_acct'
59
- end
61
+ self.table_name = 'financial.savings_acct'
62
+ self.primary_key = 'cust_id'
63
+
64
+ belongs_to :customer, :foreign_key => 'cust_id'
60
65
  end
61
66
 
62
67
  class SavingsTran < ActiveRecord::Base
63
- def self.table_name
64
- 'financial.savings_tran'
65
- end
68
+ self.table_name = 'financial.savings_tran'
69
+ self.primary_key = 'Tran_Id'
70
+
71
+ belongs_to :customer, :foreign_key => 'Cust_Id'
66
72
  end
67
73
 
68
74
  class Tran < ActiveRecord::Base
69
- def self.table_name
70
- 'financial.trans'
71
- end
75
+ self.table_name = 'financial.trans'
72
76
  end
@@ -2,47 +2,6 @@ module Arel
2
2
  module Visitors
3
3
  class Teradata < Arel::Visitors::ToSql
4
4
 
5
- def add_limit_offset!(sql, options)
6
- if options[:limit]
7
- order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
8
- sql.sub!(/ ORDER BY.*$/i, '')
9
- replace_limit_offset!(sql, options[:limit], options[:offset], order)
10
- end
11
- end
12
-
13
- def replace_limit_offset!(sql, limit, offset, order)
14
- if limit
15
- offset ||= 0
16
- start_row = offset + 1
17
- end_row = offset + limit.to_i
18
- find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im
19
- whole, select, rest_of_query = find_select.match(sql).to_a
20
- rest_of_query.strip!
21
- if rest_of_query[0...1] == "1" && rest_of_query !~ /1 AS/i
22
- rest_of_query[0] = "*"
23
- end
24
- if rest_of_query[0] == "*"
25
- from_table = get_table_name(rest_of_query)
26
- rest_of_query = from_table + '.' + rest_of_query
27
- end
28
- new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query}"
29
- new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
30
- sql.replace(new_sql)
31
- end
32
- sql
33
- end
34
-
35
-
36
- def limit_for(limit_or_node)
37
- limit_or_node.respond_to?(:expr) ? limit_or_node.expr.to_i : limit_or_node
38
- end
39
-
40
- def select_count? o
41
- sel = o.cores.length == 1 && o.cores.first
42
- projections = sel && sel.projections.length == 1 && sel.projections
43
- projections && Arel::Nodes::Count === projections.first
44
- end
45
-
46
5
  def visit_Arel_Nodes_SelectStatement o
47
6
  if !o.limit && o.offset
48
7
  raise ActiveRecord::ActiveRecordError, "You must specify :limit with :offset."
@@ -60,7 +19,7 @@ module Arel
60
19
  sql = o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join
61
20
  end
62
21
 
63
- order ||= "ORDER BY #{@connection.determine_order_clause(sql)}"
22
+ order ||= "ORDER BY #{determine_order_clause(sql)}"
64
23
  replace_limit_offset!(sql, limit_for(o.limit).to_i, o.offset && o.offset.value.to_i, order)
65
24
  sql = "SELECT COUNT(*) AS count_id FROM (#{sql}) AS subquery" if subquery
66
25
  else
@@ -69,6 +28,78 @@ module Arel
69
28
 
70
29
  sql
71
30
  end
31
+
32
+ # <Helpers>
33
+ # Lots of this code was pulled from the activerecord JDBC MSSQL driver
34
+
35
+ def get_table_name(sql)
36
+ if sql =~ /^\s*insert\s+into\s+([^\(\s,]+)\s*|^\s*update\s+([^\(\s,]+)\s*/i
37
+ $1
38
+ elsif sql =~ /\bfrom\s+([^\(\s,]+)\s*/i
39
+ $1
40
+ else
41
+ nil
42
+ end
43
+ end
44
+
45
+ def determine_order_clause(sql)
46
+ return $1 if sql =~ /ORDER BY (.*)$/
47
+ table_name = get_table_name(sql)
48
+ "#{table_name}.#{determine_primary_key(table_name)}"
49
+ end
50
+
51
+ def determine_primary_key(table_name)
52
+ table_name = table_name.gsub('"', '')
53
+ primary_key = @connection.columns(table_name).detect { |column| column.primary }
54
+ return primary_key.name if primary_key
55
+ # Look for an id column. Return it, without changing case, to cover dbs with a case-sensitive collation.
56
+ columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
57
+ # Give up and provide something which is going to crash almost certainly
58
+ columns(table_name)[0].name
59
+ end
60
+
61
+ def add_limit_offset!(sql, options)
62
+ if options[:limit]
63
+ order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
64
+ sql.sub!(/ ORDER BY.*$/i, '')
65
+ replace_limit_offset!(sql, options[:limit], options[:offset], order)
66
+ end
67
+ end
68
+
69
+ def replace_limit_offset!(sql, limit, offset, order)
70
+ if limit
71
+ offset ||= 0
72
+ start_row = offset + 1
73
+ end_row = offset + limit.to_i
74
+ find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im
75
+ whole, select, rest_of_query = find_select.match(sql).to_a
76
+ rest_of_query.strip!
77
+ if rest_of_query[0...1] == "1" && rest_of_query !~ /1 AS/i
78
+ rest_of_query[0] = "*"
79
+ end
80
+ if rest_of_query[0] == "*"
81
+ from_table = get_table_name(rest_of_query)
82
+ rest_of_query = from_table + '.' + rest_of_query
83
+ end
84
+ new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query}"
85
+ new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
86
+ sql.replace(new_sql)
87
+ end
88
+ sql
89
+ end
90
+
91
+ def limit_for(limit_or_node)
92
+ limit_or_node.respond_to?(:expr) ? limit_or_node.expr.to_i : limit_or_node
93
+ end
94
+
95
+ def select_count? o
96
+ sel = o.cores.length == 1 && o.cores.first
97
+ projections = sel && sel.projections.length == 1 && sel.projections
98
+ projections && Arel::Nodes::Count === projections.first
99
+ end
100
+
101
+ # </Helpers>
102
+
72
103
  end
73
104
  end
74
105
  end
@@ -41,8 +41,9 @@ module ::ArJdbc
41
41
 
42
42
  #+ native_database_types
43
43
  def native_database_types
44
+
44
45
  super.merge({
45
- :primary_key => 'INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY',
46
+ :primary_key => 'INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1 MINVALUE -2147483647 MAXVALUE 1000000000 NO CYCLE)',
46
47
  :string => { :name => 'VARCHAR', :limit => 255 },
47
48
  :integer => { :name => "INTEGER" },
48
49
  :float => { :name => "FLOAT" },
@@ -80,6 +81,25 @@ module ::ArJdbc
80
81
  #+ do_exec
81
82
 
82
83
  #- execute
84
+ def _execute(sql, name = nil)
85
+ result = super
86
+ self.class.insert?(sql) ? last_insert_id(_table_name_from_insert(sql)) : result
87
+ end
88
+ private :_execute
89
+
90
+ def _table_name_from_insert(sql)
91
+ sql.split(" ", 4)[2].gsub('"', '').gsub("'", "")
92
+ end
93
+ private :_table_name_from_insert
94
+
95
+ def last_insert_id(table)
96
+ output = nil
97
+ pk = primary_key(table)
98
+ if pk
99
+ output = execute("SELECT TOP 1 #{pk} FROM #{table} ORDER BY #{pk} DESC").first[pk]
100
+ end
101
+ output
102
+ end
83
103
 
84
104
  #- select
85
105
 
@@ -154,65 +174,5 @@ module ::ArJdbc
154
174
  name.to_s
155
175
  end
156
176
 
157
- # <Helpers>
158
-
159
- def get_table_name(sql)
160
- if sql =~ /^\s*insert\s+into\s+([^\(\s,]+)\s*|^\s*update\s+([^\(\s,]+)\s*/i
161
- $1
162
- elsif sql =~ /\bfrom\s+([^\(\s,]+)\s*/i
163
- $1
164
- else
165
- nil
166
- end
167
- end
168
-
169
- def determine_order_clause(sql)
170
- return $1 if sql =~ /ORDER BY (.*)$/
171
- table_name = get_table_name(sql)
172
- "#{table_name}.#{determine_primary_key(table_name)}"
173
- end
174
-
175
- def determine_primary_key(table_name)
176
- table_name = table_name.gsub('"', '')
177
- primary_key = columns(table_name).detect { |column| column.primary }
178
- return primary_key.name if primary_key
179
- # Look for an id column. Return it, without changing case, to cover dbs with a case-sensitive collation.
180
- columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
181
- # Give up and provide something which is going to crash almost certainly
182
- columns(table_name)[0].name
183
- end
184
-
185
- def add_limit_offset!(sql, options)
186
- if options[:limit]
187
- order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
188
- sql.sub!(/ ORDER BY.*$/i, '')
189
- replace_limit_offset!(sql, options[:limit], options[:offset], order)
190
- end
191
- end
192
-
193
- def replace_limit_offset!(sql, limit, offset, order)
194
- if limit
195
- offset ||= 0
196
- start_row = offset + 1
197
- end_row = offset + limit.to_i
198
- find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im
199
- whole, select, rest_of_query = find_select.match(sql).to_a
200
- rest_of_query.strip!
201
- if rest_of_query[0...1] == "1" && rest_of_query !~ /1 AS/i
202
- rest_of_query[0] = "*"
203
- end
204
- if rest_of_query[0] == "*"
205
- from_table = get_table_name(rest_of_query)
206
- rest_of_query = from_table + '.' + rest_of_query
207
- end
208
- new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query}"
209
- new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
210
- sql.replace(new_sql)
211
- end
212
- sql
213
- end
214
-
215
- # </Helpers>
216
-
217
177
  end
218
178
  end
data/spec/adapter_spec.rb CHANGED
@@ -24,6 +24,26 @@ describe 'Adapter' do
24
24
  @adapter.active?.should be_true
25
25
  end
26
26
 
27
+ it '#exec_query' do
28
+ article_1 = Article.create(:title => 'exec_query_1', :body => 'exec_query_1')
29
+ article_2 = Article.create(:title => 'exec_query_2', :body => 'exec_query_2')
30
+ articles = @adapter.exec_query('select * from articles')
31
+
32
+ articles.select { |i| i['title'] == article_1.title }.first.should_not be_nil
33
+ articles.select { |i| i['title'] == article_2.title }.first.should_not be_nil
34
+ end
35
+
36
+ it '#last_insert_id(table)' do
37
+ article_1 = Article.create(:title => 'exec_query_1', :body => 'exec_query_1')
38
+ article_1.id.should eq(@adapter.last_insert_id('articles'))
39
+
40
+ article_2 = Article.create(:title => 'exec_query_2', :body => 'exec_query_2')
41
+
42
+ article_1.id.should_not eq(article_2.id)
43
+
44
+ article_2.id.should eq(@adapter.last_insert_id('articles'))
45
+ end
46
+
27
47
  it '#tables' do
28
48
  @adapter.tables.should include('articles')
29
49
  end
data/spec/simple_spec.rb CHANGED
@@ -37,13 +37,32 @@ describe 'SimpleSpec' do
37
37
  end
38
38
 
39
39
  it 'should be able to find(:where) an item' do
40
+ article = Article.create(:title => 'where', :body => 'where')
41
+ article = Article.where(:title => 'where').first
42
+ article.title.should eq('where')
43
+ end
44
+
45
+ it 'should be able to #reload and item' do
40
46
  article = Article.create(:title => 'reload', :body => 'reload')
41
- article = Article.where(:title => 'reload').first
47
+ article.reload
42
48
  article.title.should eq('reload')
43
49
  end
44
50
 
45
- it 'should be able to #reload and item' do
51
+ it 'should populate distinct ids for the primary key field after reload' do
52
+ article_1 = Article.create(:title => 'pk1_item_reload', :body => 'pk1_item_reload')
53
+ article_2 = Article.create(:title => 'pk2_item_reload', :body => 'pk2_item_reload')
54
+
55
+ article_1.reload
56
+ article_2.reload
57
+
58
+ article_1.id.should_not eq(article_2.id)
59
+ end
60
+
61
+ it 'should populate distinct ids for the primary key field' do
62
+ article_1 = Article.create(:title => 'pk1_item', :body => 'pk1_item')
63
+ article_2 = Article.create(:title => 'pk2_item', :body => 'pk2_item')
46
64
 
65
+ article_1.id.should_not eq(article_2.id)
47
66
  end
48
67
 
49
68
  after(:all) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-jdbcteradata-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-28 00:00:00.000000000 Z
12
+ date: 2013-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -108,6 +108,7 @@ executables: []
108
108
  extensions: []
109
109
  extra_rdoc_files: []
110
110
  files:
111
+ - ".gitignore"
111
112
  - ".rspec"
112
113
  - Gemfile
113
114
  - Gemfile.lock