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