activerecord-jdbcteradata-adapter 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/activerecord-jdbcteradata-adapter.gemspec +1 -1
- data/examples/models.rb +34 -30
- data/lib/arel/visitors/teradata.rb +73 -42
- data/lib/arjdbc/teradata/adapter.rb +21 -61
- data/spec/adapter_spec.rb +20 -0
- data/spec/simple_spec.rb +21 -2
- metadata +3 -2
data/.gitignore
ADDED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "activerecord-jdbcteradata-adapter"
|
3
|
-
s.version = "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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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 #{
|
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
|
47
|
+
article.reload
|
42
48
|
article.title.should eq('reload')
|
43
49
|
end
|
44
50
|
|
45
|
-
it 'should
|
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.
|
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-
|
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
|