activerecord-redshift-adapter 0.9.1 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +52 -0
- data/activerecord-redshift-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/redshift_adapter.rb +9 -6
- data/lib/activerecord_redshift/table_manager.rb +218 -0
- data/lib/activerecord_redshift_adapter.rb +3 -0
- data/lib/activerecord_redshift_adapter/version.rb +1 -1
- data/lib/monkeypatch_activerecord.rb +195 -0
- data/lib/monkeypatch_arel.rb +96 -0
- data/spec/dummy/config/database.example.yml +2 -0
- metadata +6 -3
data/README.md
CHANGED
@@ -33,6 +33,7 @@ redshift_development:
|
|
33
33
|
```
|
34
34
|
|
35
35
|
## options
|
36
|
+
```html
|
36
37
|
<table>
|
37
38
|
<tr>
|
38
39
|
<th>option</th>
|
@@ -47,3 +48,54 @@ redshift_development:
|
|
47
48
|
<td>force timezone for datetime when select values. ActiveRecord default timezone will set if not given.</td>
|
48
49
|
</tr>
|
49
50
|
</table>
|
51
|
+
```
|
52
|
+
|
53
|
+
## TableManager
|
54
|
+
|
55
|
+
Helpful code to clone redshift tables
|
56
|
+
|
57
|
+
```sql
|
58
|
+
create table foos
|
59
|
+
(
|
60
|
+
id int not null primary key distkey,
|
61
|
+
name varchar(255) unique sortkey
|
62
|
+
);
|
63
|
+
```
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
class Foo < ActiveRecord::Base
|
67
|
+
end
|
68
|
+
|
69
|
+
require 'activerecord_redshift_adapter'
|
70
|
+
|
71
|
+
table_manager = ActiverecordRedshift::TableManager.new(Foo.connection, :exemplar_table_name => Foo.table_name)
|
72
|
+
table_manager.duplicate_table
|
73
|
+
```
|
74
|
+
|
75
|
+
yields:
|
76
|
+
|
77
|
+
```sql
|
78
|
+
select oid from pg_namespace where nspname = 'public' limit 1;
|
79
|
+
|
80
|
+
select oid,reldiststyle from pg_class where relnamespace = 2200 and relname = 'foos' limit 1;
|
81
|
+
|
82
|
+
select contype,conkey from pg_constraint where connamespace = 2200 and conrelid = 212591;
|
83
|
+
|
84
|
+
select attname,attnum from pg_attribute where attrelid = 212591 and attnum in (2,1);
|
85
|
+
|
86
|
+
show search_path;
|
87
|
+
|
88
|
+
set search_path = 'public';
|
89
|
+
|
90
|
+
select * from pg_table_def where tablename = 'foos' and schemaname = 'public';
|
91
|
+
|
92
|
+
create temporary table temporary_events_25343
|
93
|
+
(
|
94
|
+
id integer not null distkey,
|
95
|
+
name character varying(255),
|
96
|
+
primary key (id),
|
97
|
+
unique (name)
|
98
|
+
) sortkey (name);
|
99
|
+
|
100
|
+
set search_path = '$user','public';
|
101
|
+
```
|
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.name = 'activerecord-redshift-adapter'
|
8
8
|
s.version = ActiverecordRedshiftAdapter::VERSION
|
9
9
|
s.license = 'New BSD License'
|
10
|
-
s.date = '2013-
|
10
|
+
s.date = '2013-09-17'
|
11
11
|
s.summary = "Rails 3 database adapter support for AWS RedShift."
|
12
12
|
s.description = "This gem provides the Rails 3 with database adapter for AWS RedShift."
|
13
13
|
s.authors = ["Keith Gabryelski"]
|
@@ -249,7 +249,7 @@ module ActiveRecord
|
|
249
249
|
ADAPTER_NAME = 'Redshift'
|
250
250
|
|
251
251
|
NATIVE_DATABASE_TYPES = {
|
252
|
-
:primary_key => "
|
252
|
+
:primary_key => "bigint primary key",
|
253
253
|
:string => { :name => "character varying", :limit => 255 },
|
254
254
|
:text => { :name => "text" },
|
255
255
|
:integer => { :name => "integer" },
|
@@ -413,7 +413,7 @@ module ActiveRecord
|
|
413
413
|
end
|
414
414
|
|
415
415
|
def supports_insert_with_returning?
|
416
|
-
|
416
|
+
false
|
417
417
|
end
|
418
418
|
|
419
419
|
def supports_ddl_transactions?
|
@@ -538,7 +538,7 @@ module ActiveRecord
|
|
538
538
|
# REFERENTIAL INTEGRITY ====================================
|
539
539
|
|
540
540
|
def supports_disable_referential_integrity? #:nodoc:
|
541
|
-
|
541
|
+
false
|
542
542
|
end
|
543
543
|
|
544
544
|
def disable_referential_integrity #:nodoc:
|
@@ -609,9 +609,12 @@ module ActiveRecord
|
|
609
609
|
table_ref = extract_table_ref_from_insert_sql(sql)
|
610
610
|
pk = primary_key(table_ref) if table_ref
|
611
611
|
end
|
612
|
-
|
613
|
-
if pk
|
612
|
+
|
613
|
+
if pk && use_insert_returning?
|
614
614
|
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
615
|
+
elsif pk
|
616
|
+
super
|
617
|
+
last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
|
615
618
|
else
|
616
619
|
super
|
617
620
|
end
|
@@ -709,7 +712,7 @@ module ActiveRecord
|
|
709
712
|
pk = primary_key(table_ref) if table_ref
|
710
713
|
end
|
711
714
|
|
712
|
-
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
715
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk && use_insert_returning?
|
713
716
|
|
714
717
|
[sql, binds]
|
715
718
|
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
module ActiverecordRedshift
|
2
|
+
class TableManager
|
3
|
+
attr_reader :default_options
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = { :exemplar_table_name => nil, :add_identity => false, :temporary => true}
|
6
|
+
|
7
|
+
def initialize(connection, default_options = {})
|
8
|
+
@connection = connection
|
9
|
+
table_name_options = {}
|
10
|
+
if default_options[:partitioned_model]
|
11
|
+
model = default_options[:partitioned_model]
|
12
|
+
default_options[:exemplar_table_name] = model.table_name
|
13
|
+
default_options[:schema_name] = model.configurator.schema_name
|
14
|
+
end
|
15
|
+
|
16
|
+
if default_options[:table_name].blank?
|
17
|
+
connection_pid = @connection.execute("select pg_backend_pid() as pid").first['pid'].to_i
|
18
|
+
table_name_options[:table_name] = "temporary_events_#{connection_pid}"
|
19
|
+
end
|
20
|
+
@default_options = DEFAULT_OPTIONS.merge(table_name_options).merge(default_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def partitioned_model
|
24
|
+
return @default_options[:partitioned_model]
|
25
|
+
end
|
26
|
+
|
27
|
+
def schema_name
|
28
|
+
return @default_options[:schema_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def exemplar_table_name
|
32
|
+
return @default_options[:exemplar_table_name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_identity
|
36
|
+
return @default_options[:add_identity]
|
37
|
+
end
|
38
|
+
|
39
|
+
def temporary
|
40
|
+
return @default_options[:temporary]
|
41
|
+
end
|
42
|
+
|
43
|
+
def base_table_name
|
44
|
+
return @default_options[:table_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
def table_name
|
48
|
+
if schema_name.blank?
|
49
|
+
return base_table_name
|
50
|
+
end
|
51
|
+
return "#{schema_name}.#{base_table_name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def drop_table
|
55
|
+
@connection.execute("drop table #{table_name}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def duplicate_table(options = {})
|
59
|
+
current_options = @default_options.merge(options)
|
60
|
+
target_table_name = current_options[:table_name]
|
61
|
+
raise "target_table_name not set" if target_table_name.blank?
|
62
|
+
exemplar_table_name = current_options[:exemplar_table_name]
|
63
|
+
raise "exemplar_table_name not set" if exemplar_table_name.blank?
|
64
|
+
table_name_elements = exemplar_table_name.split('.');
|
65
|
+
if table_name_elements.length == 1
|
66
|
+
table_name_elements.unshift("public")
|
67
|
+
end
|
68
|
+
schema_name = table_name_elements[0]
|
69
|
+
parent_table_name = table_name_elements[1]
|
70
|
+
|
71
|
+
# first find the diststyle
|
72
|
+
## namespace first
|
73
|
+
sql = "select oid from pg_namespace where nspname = '#{schema_name}' limit 1"
|
74
|
+
schema_oid = @connection.execute(sql).first['oid'].to_i
|
75
|
+
|
76
|
+
## now the diststyle 0 = even, 1 = some column
|
77
|
+
sql = "select oid,reldiststyle from pg_class where relnamespace = #{schema_oid} and relname = '#{parent_table_name}' limit 1"
|
78
|
+
pg_class_row = @connection.execute(sql).first
|
79
|
+
reldiststyle = pg_class_row['reldiststyle'].to_i
|
80
|
+
even_diststyle = (reldiststyle == 0)
|
81
|
+
table_oid = pg_class_row['oid'].to_i
|
82
|
+
|
83
|
+
## get unique and primary key constraints (pg_constraints)
|
84
|
+
sql = "select contype,conkey from pg_constraint where connamespace = #{schema_oid} and conrelid = #{table_oid}"
|
85
|
+
primary_key = nil
|
86
|
+
uniques = []
|
87
|
+
@connection.execute(sql).each do |row|
|
88
|
+
if row['contype'] == 'p'
|
89
|
+
# primary key
|
90
|
+
primary_key = row['conkey'][1..-2].split(',')
|
91
|
+
elsif row['contype'] == 'u'
|
92
|
+
# unique
|
93
|
+
uniques << row['conkey'][1..-2].split(',')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
attnums = uniques.clone
|
98
|
+
unless primary_key.blank?
|
99
|
+
attnums << primary_key
|
100
|
+
end
|
101
|
+
attnums = attnums.flatten.uniq
|
102
|
+
|
103
|
+
sql = "select attname,attnum from pg_attribute where attrelid = #{table_oid} and attnum in (#{attnums.join(',')})"
|
104
|
+
column_names = {}
|
105
|
+
@connection.execute(sql).each do |row|
|
106
|
+
column_names[row['attnum']] = row['attname']
|
107
|
+
end
|
108
|
+
|
109
|
+
with_search_path([schema_name]) do
|
110
|
+
# select * from pg_table_def where tablename = 'bids' and schemaname = 'public';
|
111
|
+
## column, type, encoding, distkey, sortkey, not null
|
112
|
+
sortkeys = []
|
113
|
+
sql_columns = []
|
114
|
+
|
115
|
+
if current_options[:add_identity]
|
116
|
+
sql_columns << "_identity bigint identity"
|
117
|
+
end
|
118
|
+
|
119
|
+
sql = "select * from pg_table_def where tablename = '#{parent_table_name}' and schemaname = '#{schema_name}'"
|
120
|
+
sql_column_rows = @connection.execute(sql)
|
121
|
+
sql_column_rows.each do |row|
|
122
|
+
column_info = []
|
123
|
+
column_name = row['column']
|
124
|
+
column_info << column_name
|
125
|
+
column_info << row['type']
|
126
|
+
if row['notnull'] == "t"
|
127
|
+
column_info << "not null"
|
128
|
+
end
|
129
|
+
if row['distkey'] == "t"
|
130
|
+
column_info << "distkey"
|
131
|
+
end
|
132
|
+
if row['encoding'] != 'none'
|
133
|
+
column_info << "encode #{row['encoding']}"
|
134
|
+
end
|
135
|
+
if row['sortkey'] != "0"
|
136
|
+
sortkeys[row['sortkey'].to_i - 1] = column_name
|
137
|
+
end
|
138
|
+
sql_columns << column_info.join(" ")
|
139
|
+
end
|
140
|
+
|
141
|
+
unless primary_key.blank?
|
142
|
+
sql_columns << "primary key (#{primary_key.map{|pk| column_names[pk]}.join(',')})"
|
143
|
+
end
|
144
|
+
|
145
|
+
uniques.each do |unique|
|
146
|
+
sql_columns << "unique (#{unique.map{|uk| column_names[uk]}.join(',')})"
|
147
|
+
end
|
148
|
+
|
149
|
+
if sortkeys.blank?
|
150
|
+
sql_sortkeys = ""
|
151
|
+
else
|
152
|
+
sql_sortkeys = " sortkey (#{sortkeys.join(',')})"
|
153
|
+
end
|
154
|
+
sql = <<-SQL
|
155
|
+
create #{"temporary " if current_options[:temporary]}table #{table_name}
|
156
|
+
(
|
157
|
+
#{sql_columns.join(', ')}
|
158
|
+
) #{"diststyle even " if even_diststyle}#{sql_sortkeys}
|
159
|
+
SQL
|
160
|
+
@connection.execute(sql)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def table_def(table_name)
|
165
|
+
table_parts = table_name.split('.')
|
166
|
+
if table_parts.length == 1
|
167
|
+
name = table_parts.first
|
168
|
+
search_path = ["public"]
|
169
|
+
else
|
170
|
+
name = table_parts.last
|
171
|
+
search_path = [table_parts.first]
|
172
|
+
end
|
173
|
+
|
174
|
+
with_search_path(search_path) do
|
175
|
+
return @connection.execute("select * from pg_table_def where tablename = '#{name}'").to_a
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# search_path = array
|
180
|
+
# modes: :prefix, :suffix, :replace
|
181
|
+
def with_search_path(search_path, mode = :replace, &block)
|
182
|
+
unless search_path.is_a? Array
|
183
|
+
raise "search_path must be an Array"
|
184
|
+
end
|
185
|
+
|
186
|
+
old_search_path = get_search_path
|
187
|
+
if mode == :prefix
|
188
|
+
new_search_path = search_path + old_search_path
|
189
|
+
elsif mode == :suffix
|
190
|
+
new_search_path = old_search_path + search_path
|
191
|
+
elsif mode == :replace
|
192
|
+
new_search_path = search_path
|
193
|
+
else
|
194
|
+
raise "mode must be :prefix, :suffix, :replace"
|
195
|
+
end
|
196
|
+
|
197
|
+
set_search_path(new_search_path)
|
198
|
+
begin
|
199
|
+
yield
|
200
|
+
ensure
|
201
|
+
set_search_path(old_search_path)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_search_path
|
206
|
+
return @connection.execute("show search_path").to_a.first["search_path"].split(',').map{|p| p.delete('" ')}
|
207
|
+
end
|
208
|
+
|
209
|
+
def set_search_path(search_path)
|
210
|
+
unless search_path.is_a? Array
|
211
|
+
raise "search_path must be an Array"
|
212
|
+
end
|
213
|
+
quoted_search_path = search_path.map{|sp| "'#{sp}'"}.join(',')
|
214
|
+
@connection.execute("set search_path = #{quoted_search_path}")
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Querying
|
3
|
+
delegate :unload, :copy, :to => :scoped
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module ActiveRecord::QueryMethods
|
8
|
+
module CopyUnloadParser
|
9
|
+
def self.parse_options(options, options_hash, valid_switches, valid_options, valid_unquoted_options, valid_special_options)
|
10
|
+
# credentials first
|
11
|
+
credentials = nil
|
12
|
+
if options_hash.has_key?(:credentials)
|
13
|
+
credentials = options_hash[:credentials]
|
14
|
+
else
|
15
|
+
creds = {}
|
16
|
+
creds[:aws_access_key_id] = options_hash[:aws_access_key_id] if options_hash.has_key?(:aws_access_key_id)
|
17
|
+
creds[:aws_secret_access_key] = options_hash[:aws_secret_access_key] if options_hash.has_key?(:aws_secret_access_key)
|
18
|
+
creds[:token] = options_hash[:token] if options_hash.has_key?(:token)
|
19
|
+
creds[:master_symmetric_key] = options_hash[:master_symmetric_key] if options_hash.has_key?(:master_symmetric_key)
|
20
|
+
credentials = creds.map{|k,v| "#{k}=#{v}"}.join(';')
|
21
|
+
end
|
22
|
+
|
23
|
+
option_list = []
|
24
|
+
option_list << "WITH CREDENTIALS AS #{connection.quote_value(credentials)}" unless credentials.blank?
|
25
|
+
|
26
|
+
valid_switches.each do |switch_name|
|
27
|
+
if options.include? switch_name
|
28
|
+
option_list << switch_name.to_s.upcase
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
valid_options.each do |option_name|
|
33
|
+
if options_hash.has_key? option_name
|
34
|
+
option_list << "#{option_name.to_s.upcase} AS #{connection.quote_value(options_hash[option_name])}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
valid_unquoted_options.each do |option_name|
|
39
|
+
if options_hash.has_key? option_name
|
40
|
+
option_list << "#{option_name.to_s.upcase} #{options_hash[option_name]}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return credentials, option_list
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ActiveRecord
|
50
|
+
module QueryMethods
|
51
|
+
# UNLOAD ('select_statement')
|
52
|
+
# TO 's3_path'
|
53
|
+
# [ WITH ] CREDENTIALS [AS] 'aws_access_credentials'
|
54
|
+
# [ option [ ... ] ]
|
55
|
+
#
|
56
|
+
# where option is
|
57
|
+
#
|
58
|
+
# { DELIMITER [ AS ] 'delimiter_char'
|
59
|
+
# | FIXEDWIDTH [ AS ] 'fixedwidth_spec' }
|
60
|
+
# | ENCRYPTED
|
61
|
+
# | GZIP
|
62
|
+
# | ADDQUOTES
|
63
|
+
# | NULL [ AS ] 'null_string'
|
64
|
+
# | ESCAPE
|
65
|
+
# | ALLOWOVERWRITE
|
66
|
+
VALID_UNLOAD_SWITCHES = [
|
67
|
+
:gzip,
|
68
|
+
:addquotes,
|
69
|
+
:escape,
|
70
|
+
:allowoverwrite
|
71
|
+
]
|
72
|
+
VALID_UNLOAD_OPTIONS = [
|
73
|
+
:delimiter,
|
74
|
+
:fixedwidth,
|
75
|
+
:null
|
76
|
+
]
|
77
|
+
VALID_UNQUOTED_UNLOAD_OPTIONS = [ ]
|
78
|
+
VALID_SPECIAL_UNLOAD_OPTIONS = [
|
79
|
+
:credentials,
|
80
|
+
:aws_access_key_id,
|
81
|
+
:aws_secret_access_key,
|
82
|
+
:master_symmetric_key,
|
83
|
+
:token
|
84
|
+
]
|
85
|
+
|
86
|
+
def unload(to_s3_filename, *options)
|
87
|
+
if options.last.is_a? Hash
|
88
|
+
options_hash = options.last
|
89
|
+
else
|
90
|
+
options_hash = {}
|
91
|
+
end
|
92
|
+
|
93
|
+
credentials, unload_options =
|
94
|
+
ActiveRecord::QueryMethods::CopyUnloadParser.parse_options(options, options_hash,
|
95
|
+
VALID_UNLOAD_SWITCHES, VALID_UNLOAD_OPTIONS, VALID_UNQUOTED_UNLOAD_OPTIONS, VALID_SPECIAL_UNLOAD_OPTIONS)
|
96
|
+
|
97
|
+
|
98
|
+
relation = Arel::Nodes::UnloadStatement.new(Arel::Nodes::Unload.new(Arel::Nodes::Relation.new(clone), to_s3_filename), unload_options.join(" "))
|
99
|
+
relation
|
100
|
+
end
|
101
|
+
|
102
|
+
VALID_COPY_SWITCHES = [
|
103
|
+
:encrypted,
|
104
|
+
:gzip,
|
105
|
+
:removequotes,
|
106
|
+
:explicit_ids,
|
107
|
+
:escape,
|
108
|
+
:acceptanydate,
|
109
|
+
:ignoreblanklines,
|
110
|
+
:truncatecolumns,
|
111
|
+
:fillrecord,
|
112
|
+
:trimblanks,
|
113
|
+
:noload,
|
114
|
+
:emptyasnull,
|
115
|
+
:blanksasnull,
|
116
|
+
:escape,
|
117
|
+
:roundec
|
118
|
+
]
|
119
|
+
VALID_COPY_OPTIONS = [
|
120
|
+
:delimiter,
|
121
|
+
:fixedwidth,
|
122
|
+
:csv,
|
123
|
+
:acceptinvchars,
|
124
|
+
:dateformat,
|
125
|
+
:timeformat,
|
126
|
+
:null
|
127
|
+
]
|
128
|
+
|
129
|
+
VALID_UNQUOTED_COPY_OPTIONS = [
|
130
|
+
:maxerror,
|
131
|
+
:ignoreheader,
|
132
|
+
:comprows,
|
133
|
+
:compupdate,
|
134
|
+
:statupdate
|
135
|
+
]
|
136
|
+
|
137
|
+
VALID_SPECIAL_COPY_OPTIONS = [
|
138
|
+
:credentials,
|
139
|
+
:aws_access_key_id,
|
140
|
+
:aws_secret_access_key,
|
141
|
+
:master_symmetric_key,
|
142
|
+
:token
|
143
|
+
]
|
144
|
+
|
145
|
+
# COPY table_name [ (column1 [,column2, ...]) ]
|
146
|
+
# FROM 's3://objectpath'
|
147
|
+
# [ WITH ] CREDENTIALS [AS] 'aws_access_credentials'
|
148
|
+
# [ option [ ... ] ]
|
149
|
+
|
150
|
+
# where option is
|
151
|
+
|
152
|
+
# { FIXEDWIDTH 'fixedwidth_spec'
|
153
|
+
# | [DELIMITER [ AS ] 'delimiter_char']
|
154
|
+
# [CSV [QUOTE [ AS ] 'quote_character']}
|
155
|
+
|
156
|
+
# | ENCRYPTED
|
157
|
+
# | GZIP
|
158
|
+
# | REMOVEQUOTES
|
159
|
+
# | EXPLICIT_IDS
|
160
|
+
|
161
|
+
# | ACCEPTINVCHARS [ AS ] ['replacement_char']
|
162
|
+
# | MAXERROR [ AS ] error_count
|
163
|
+
# | DATEFORMAT [ AS ] { 'dateformat_string' | 'auto' }
|
164
|
+
# | TIMEFORMAT [ AS ] { 'timeformat_string' | 'auto' | 'epochsecs' | 'epochmillisecs' }
|
165
|
+
# | IGNOREHEADER [ AS ] number_rows
|
166
|
+
# | ACCEPTANYDATE
|
167
|
+
# | IGNOREBLANKLINES
|
168
|
+
# | TRUNCATECOLUMNS
|
169
|
+
# | FILLRECORD
|
170
|
+
# | TRIMBLANKS
|
171
|
+
# | NOLOAD
|
172
|
+
# | NULL [ AS ] 'null_string'
|
173
|
+
# | EMPTYASNULL
|
174
|
+
# | BLANKSASNULL
|
175
|
+
# | COMPROWS numrows
|
176
|
+
# | COMPUPDATE [ { ON | TRUE } | { OFF | FALSE } ]
|
177
|
+
# | STATUPDATE [ { ON | TRUE } | { OFF | FALSE } ]
|
178
|
+
# | ESCAPE
|
179
|
+
# | ROUNDEC
|
180
|
+
def copy(to_s3_filename, *options)
|
181
|
+
if options.last.is_a? Hash
|
182
|
+
options_hash = options.last
|
183
|
+
else
|
184
|
+
options_hash = {}
|
185
|
+
end
|
186
|
+
|
187
|
+
credentials, copy_options =
|
188
|
+
::ActiveRecord::QueryMethods::CopyUnloadParser.parse_options(options, options_hash,
|
189
|
+
VALID_COPY_SWITCHES, VALID_COPY_OPTIONS, VALID_UNQUOTED_COPY_OPTIONS, VALID_SPECIAL_COPY_OPTIONS)
|
190
|
+
|
191
|
+
|
192
|
+
conncection.execute(Arel::Nodes::CopyStatement.new(Arel::Nodes::Copy.new(table_name, to_s3_filename), copy_options.join(" ")).to_sql)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Arel
|
2
|
+
module Nodes
|
3
|
+
class Relation < Arel::Nodes::Unary
|
4
|
+
end
|
5
|
+
|
6
|
+
class Unload < Arel::Nodes::Binary
|
7
|
+
alias :statement :left
|
8
|
+
alias :statement= :left=
|
9
|
+
alias :to :right
|
10
|
+
alias :to= :right=
|
11
|
+
def initialize statement = nil, to = nil
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_copy other
|
16
|
+
super
|
17
|
+
@right = @right.clone
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class UnloadStatement < Arel::Nodes::Binary
|
22
|
+
alias :relation :left
|
23
|
+
alias :relation= :left=
|
24
|
+
alias :options :right
|
25
|
+
alias :options= :right=
|
26
|
+
|
27
|
+
def initialize relation = nil, options = []
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize_copy other
|
32
|
+
super
|
33
|
+
@right = @right.clone
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Copy < Arel::Nodes::Binary
|
38
|
+
alias :statement :left
|
39
|
+
alias :statement= :left=
|
40
|
+
alias :from :right
|
41
|
+
alias :from= :right=
|
42
|
+
def initialize statement = nil, from = nil
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize_copy other
|
47
|
+
super
|
48
|
+
@right = @right.clone
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class CopyStatement < Arel::Nodes::Binary
|
53
|
+
alias :relation :left
|
54
|
+
alias :relation= :left=
|
55
|
+
alias :options :right
|
56
|
+
alias :options= :right=
|
57
|
+
|
58
|
+
def initialize relation = nil, options = []
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize_copy other
|
63
|
+
super
|
64
|
+
@right = @right.clone
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Arel
|
72
|
+
module Visitors
|
73
|
+
class ToSql < Arel::Visitors::Visitor
|
74
|
+
|
75
|
+
def visit_Arel_Nodes_UnloadStatement o
|
76
|
+
"#{visit o.relation} #{o.options}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def visit_Arel_Nodes_Unload o
|
80
|
+
"UNLOAD (#{visit o.statement}) TO #{visit o.to}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def visit_Arel_Nodes_CopyStatement o
|
84
|
+
"#{visit o.relation} #{o.options}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def visit_Arel_Nodes_Copy o
|
88
|
+
"COPY #{o.statement} FROM #{visit o.from}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit_Arel_Nodes_Relation o
|
92
|
+
visit o.expr.to_sql
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-redshift-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.3
|
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-
|
12
|
+
date: 2013-09-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg
|
@@ -72,8 +72,11 @@ files:
|
|
72
72
|
- Rakefile
|
73
73
|
- activerecord-redshift-adapter.gemspec
|
74
74
|
- lib/active_record/connection_adapters/redshift_adapter.rb
|
75
|
+
- lib/activerecord_redshift/table_manager.rb
|
75
76
|
- lib/activerecord_redshift_adapter.rb
|
76
77
|
- lib/activerecord_redshift_adapter/version.rb
|
78
|
+
- lib/monkeypatch_activerecord.rb
|
79
|
+
- lib/monkeypatch_arel.rb
|
77
80
|
- spec/active_record/base_spec.rb
|
78
81
|
- spec/active_record/connection_adapters/redshift_adapter_spec.rb
|
79
82
|
- spec/dummy/config/database.example.yml
|
@@ -99,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
102
|
version: '0'
|
100
103
|
requirements: []
|
101
104
|
rubyforge_project:
|
102
|
-
rubygems_version: 1.8.
|
105
|
+
rubygems_version: 1.8.24
|
103
106
|
signing_key:
|
104
107
|
specification_version: 3
|
105
108
|
summary: Rails 3 database adapter support for AWS RedShift.
|