activerecord-redshift-adapter 0.9.1 → 0.9.3
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/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.
|