activerecord-redshift-adapter-ng 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (24) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +54 -0
  3. data/README.md +39 -0
  4. data/lib/active_record/connection_adapters/redshift/array_parser.rb +93 -0
  5. data/lib/active_record/connection_adapters/redshift/column.rb +10 -0
  6. data/lib/active_record/connection_adapters/redshift/database_statements.rb +232 -0
  7. data/lib/active_record/connection_adapters/redshift/oid.rb +21 -0
  8. data/lib/active_record/connection_adapters/redshift/oid/date.rb +11 -0
  9. data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +36 -0
  10. data/lib/active_record/connection_adapters/redshift/oid/decimal.rb +13 -0
  11. data/lib/active_record/connection_adapters/redshift/oid/float.rb +21 -0
  12. data/lib/active_record/connection_adapters/redshift/oid/infinity.rb +13 -0
  13. data/lib/active_record/connection_adapters/redshift/oid/integer.rb +11 -0
  14. data/lib/active_record/connection_adapters/redshift/oid/json.rb +35 -0
  15. data/lib/active_record/connection_adapters/redshift/oid/jsonb.rb +23 -0
  16. data/lib/active_record/connection_adapters/redshift/oid/time.rb +11 -0
  17. data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +63 -0
  18. data/lib/active_record/connection_adapters/redshift/quoting.rb +98 -0
  19. data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +15 -0
  20. data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +67 -0
  21. data/lib/active_record/connection_adapters/redshift/schema_statements.rb +393 -0
  22. data/lib/active_record/connection_adapters/redshift/utils.rb +77 -0
  23. data/lib/active_record/connection_adapters/redshift_adapter.rb +653 -0
  24. metadata +127 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: de733c7700e8cdcabfb21c0f586ebdf5f982586b
4
+ data.tar.gz: 74dad27ced8d0ff64e8638fb6c464112eb55c083
5
+ SHA512:
6
+ metadata.gz: f07eb341fdbc4b144b86760bb662483a015747c0d70479b6f4bf1a90018d01dab69aa53af883db67c5afac8e0b65f303eb2df4f602c841dcdad9420a6fda8643
7
+ data.tar.gz: cf00590cf283dfbad15e0936d1f1d87c44b3d1b4668d9dfa8a7cbf3e5ef638f146adb0515154845fe74f81d32d9b3555f9abeee4e6ba127a29e304dde22df016
data/LICENSE ADDED
@@ -0,0 +1,54 @@
1
+ -----------------------------------------------------------------------------------
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2004-2013 David Heinemeier Hansson (original code author)
5
+ Copyright (c) 2013 Minero Aoki
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
8
+ this software and associated documentation files (the "Software"), to deal in
9
+ the Software without restriction, including without limitation the rights to
10
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
+ the Software, and to permit persons to whom the Software is furnished to do so,
12
+ subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ -----------------------------------------------------------------------------------
25
+ Copyright (c) 2010-2013, Fiksu, Inc.
26
+ All rights reserved.
27
+
28
+ Redistribution and use in source and binary forms, with or without
29
+ modification, are permitted provided that the following conditions are
30
+ met:
31
+
32
+ o Redistributions of source code must retain the above copyright
33
+ notice, this list of conditions and the following disclaimer.
34
+
35
+ o Redistributions in binary form must reproduce the above copyright
36
+ notice, this list of conditions and the following disclaimer in the
37
+ documentation and/or other materials provided with the
38
+ distribution.
39
+
40
+ o Fiksu, Inc. nor the names of its contributors may be used to
41
+ endorse or promote products derived from this software without
42
+ specific prior written permission.
43
+
44
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
45
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
46
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
47
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
48
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
49
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
50
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
51
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
52
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
54
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # activerecord-redshift-adapter-ng
2
+
3
+ Amazon Redshift adapter for ActiveRecord.
4
+ I copied PostgreSQL driver from ActiveRecord and modified for Redshift.
5
+ "Barely works" patch was borrowed from fiksu/activerecord-redshift-adapter.
6
+
7
+ I know Redshift driver already exists (https://github.com/fiksu/activerecord-redshift-adapter),
8
+ but it currently supports only ActiveRecord 3. Also, AR4 code is magnifically
9
+ different from AR3 code because of file separation, patching does not work well.
10
+ I want to use Rails 4 with Redshift NOW, so I wrote this driver.
11
+ If anybody write better Redshift driver which works with ActiveRecord 4,
12
+ I abandon this driver.
13
+
14
+ Usage
15
+ -------------------
16
+
17
+ Write following in Gemfile:
18
+ ```
19
+ gem 'activerecord-redshift-adapter-ng'
20
+ ```
21
+
22
+ In database.yml
23
+ ```
24
+ development:
25
+ adapter: redshift
26
+ host: your_cluster_name.hashhash.ap-northeast-1.redshift.amazonaws.com
27
+ port: 5439
28
+ database: dev
29
+ username: your_user
30
+ password: your_password
31
+ encoding: utf8
32
+ pool: 3
33
+ timeout: 5000
34
+ ```
35
+
36
+ License
37
+ ---------
38
+
39
+ MIT license (same as ActiveRecord)
@@ -0,0 +1,93 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module ArrayParser # :nodoc:
5
+
6
+ DOUBLE_QUOTE = '"'
7
+ BACKSLASH = "\\"
8
+ COMMA = ','
9
+ BRACKET_OPEN = '{'
10
+ BRACKET_CLOSE = '}'
11
+
12
+ def parse_pg_array(string) # :nodoc:
13
+ local_index = 0
14
+ array = []
15
+ while(local_index < string.length)
16
+ case string[local_index]
17
+ when BRACKET_OPEN
18
+ local_index,array = parse_array_contents(array, string, local_index + 1)
19
+ when BRACKET_CLOSE
20
+ return array
21
+ end
22
+ local_index += 1
23
+ end
24
+
25
+ array
26
+ end
27
+
28
+ private
29
+
30
+ def parse_array_contents(array, string, index)
31
+ is_escaping = false
32
+ is_quoted = false
33
+ was_quoted = false
34
+ current_item = ''
35
+
36
+ local_index = index
37
+ while local_index
38
+ token = string[local_index]
39
+ if is_escaping
40
+ current_item << token
41
+ is_escaping = false
42
+ else
43
+ if is_quoted
44
+ case token
45
+ when DOUBLE_QUOTE
46
+ is_quoted = false
47
+ was_quoted = true
48
+ when BACKSLASH
49
+ is_escaping = true
50
+ else
51
+ current_item << token
52
+ end
53
+ else
54
+ case token
55
+ when BACKSLASH
56
+ is_escaping = true
57
+ when COMMA
58
+ add_item_to_array(array, current_item, was_quoted)
59
+ current_item = ''
60
+ was_quoted = false
61
+ when DOUBLE_QUOTE
62
+ is_quoted = true
63
+ when BRACKET_OPEN
64
+ internal_items = []
65
+ local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1)
66
+ array.push(internal_items)
67
+ when BRACKET_CLOSE
68
+ add_item_to_array(array, current_item, was_quoted)
69
+ return local_index,array
70
+ else
71
+ current_item << token
72
+ end
73
+ end
74
+ end
75
+
76
+ local_index += 1
77
+ end
78
+ return local_index,array
79
+ end
80
+
81
+ def add_item_to_array(array, current_item, quoted)
82
+ return if !quoted && current_item.length == 0
83
+
84
+ if !quoted && current_item == 'NULL'
85
+ array.push nil
86
+ else
87
+ array.push current_item
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class RedshiftColumn < Column #:nodoc:
4
+ def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
5
+ super name, default, cast_type, sql_type, null
6
+ @default_function = default_function
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,232 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module DatabaseStatements
5
+ def explain(arel, binds = [])
6
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
7
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
8
+ end
9
+
10
+ class ExplainPrettyPrinter # :nodoc:
11
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
12
+ # PostgreSQL shell:
13
+ #
14
+ # QUERY PLAN
15
+ # ------------------------------------------------------------------------------
16
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
17
+ # Join Filter: (posts.user_id = users.id)
18
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
19
+ # Index Cond: (id = 1)
20
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
21
+ # Filter: (posts.user_id = 1)
22
+ # (6 rows)
23
+ #
24
+ def pp(result)
25
+ header = result.columns.first
26
+ lines = result.rows.map(&:first)
27
+
28
+ # We add 2 because there's one char of padding at both sides, note
29
+ # the extra hyphens in the example above.
30
+ width = [header, *lines].map(&:length).max + 2
31
+
32
+ pp = []
33
+
34
+ pp << header.center(width).rstrip
35
+ pp << '-' * width
36
+
37
+ pp += lines.map {|line| " #{line}"}
38
+
39
+ nrows = result.rows.length
40
+ rows_label = nrows == 1 ? 'row' : 'rows'
41
+ pp << "(#{nrows} #{rows_label})"
42
+
43
+ pp.join("\n") + "\n"
44
+ end
45
+ end
46
+
47
+ def select_value(arel, name = nil, binds = [])
48
+ arel, binds = binds_from_relation arel, binds
49
+ sql = to_sql(arel, binds)
50
+ execute_and_clear(sql, name, binds) do |result|
51
+ result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
52
+ end
53
+ end
54
+
55
+ def select_values(arel, name = nil)
56
+ arel, binds = binds_from_relation arel, []
57
+ sql = to_sql(arel, binds)
58
+ execute_and_clear(sql, name, binds) do |result|
59
+ if result.nfields > 0
60
+ result.column_values(0)
61
+ else
62
+ []
63
+ end
64
+ end
65
+ end
66
+
67
+ # Executes a SELECT query and returns an array of rows. Each row is an
68
+ # array of field values.
69
+ def select_rows(sql, name = nil, binds = [])
70
+ execute_and_clear(sql, name, binds) do |result|
71
+ result.values
72
+ end
73
+ end
74
+
75
+ # Executes an INSERT query and returns the new record's ID
76
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
77
+ unless pk
78
+ # Extract the table from the insert sql. Yuck.
79
+ table_ref = extract_table_ref_from_insert_sql(sql)
80
+ pk = primary_key(table_ref) if table_ref
81
+ end
82
+
83
+ if pk && use_insert_returning?
84
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
85
+ elsif pk
86
+ super
87
+ last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
88
+ else
89
+ super
90
+ end
91
+ end
92
+
93
+ def create
94
+ super.insert
95
+ end
96
+
97
+ # The internal PostgreSQL identifier of the money data type.
98
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
99
+ # The internal PostgreSQL identifier of the BYTEA data type.
100
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
101
+
102
+ # create a 2D array representing the result set
103
+ def result_as_array(res) #:nodoc:
104
+ # check if we have any binary column and if they need escaping
105
+ ftypes = Array.new(res.nfields) do |i|
106
+ [i, res.ftype(i)]
107
+ end
108
+
109
+ rows = res.values
110
+ return rows unless ftypes.any? { |_, x|
111
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
112
+ }
113
+
114
+ typehash = ftypes.group_by { |_, type| type }
115
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
116
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
117
+
118
+ rows.each do |row|
119
+ # unescape string passed BYTEA field (OID == 17)
120
+ binaries.each do |index, _|
121
+ row[index] = unescape_bytea(row[index])
122
+ end
123
+
124
+ # If this is a money type column and there are any currency symbols,
125
+ # then strip them off. Indeed it would be prettier to do this in
126
+ # PostgreSQLColumn.string_to_decimal but would break form input
127
+ # fields that call value_before_type_cast.
128
+ monies.each do |index, _|
129
+ data = row[index]
130
+ # Because money output is formatted according to the locale, there are two
131
+ # cases to consider (note the decimal separators):
132
+ # (1) $12,345,678.12
133
+ # (2) $12.345.678,12
134
+ case data
135
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
136
+ data.gsub!(/[^-\d.]/, '')
137
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
138
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Queries the database and returns the results in an Array-like object
145
+ def query(sql, name = nil) #:nodoc:
146
+ log(sql, name) do
147
+ result_as_array @connection.async_exec(sql)
148
+ end
149
+ end
150
+
151
+ # Executes an SQL statement, returning a PGresult object on success
152
+ # or raising a PGError exception otherwise.
153
+ def execute(sql, name = nil)
154
+ log(sql, name) do
155
+ @connection.async_exec(sql)
156
+ end
157
+ end
158
+
159
+ def exec_query(sql, name = 'SQL', binds = [])
160
+ execute_and_clear(sql, name, binds) do |result|
161
+ types = {}
162
+ fields = result.fields
163
+ fields.each_with_index do |fname, i|
164
+ ftype = result.ftype i
165
+ fmod = result.fmod i
166
+ types[fname] = get_oid_type(ftype, fmod, fname)
167
+ end
168
+ ActiveRecord::Result.new(fields, result.values, types)
169
+ end
170
+ end
171
+
172
+ def exec_delete(sql, name = 'SQL', binds = [])
173
+ execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
174
+ end
175
+ alias :exec_update :exec_delete
176
+
177
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
178
+ unless pk
179
+ # Extract the table from the insert sql. Yuck.
180
+ table_ref = extract_table_ref_from_insert_sql(sql)
181
+ pk = primary_key(table_ref) if table_ref
182
+ end
183
+
184
+ if pk && use_insert_returning?
185
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}"
186
+ end
187
+
188
+ [sql, binds]
189
+ end
190
+
191
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
192
+ val = exec_query(sql, name, binds)
193
+ if !use_insert_returning? && pk
194
+ unless sequence_name
195
+ table_ref = extract_table_ref_from_insert_sql(sql)
196
+ sequence_name = default_sequence_name(table_ref, pk)
197
+ return val unless sequence_name
198
+ end
199
+ last_insert_id_result(sequence_name)
200
+ else
201
+ val
202
+ end
203
+ end
204
+
205
+ # Executes an UPDATE query and returns the number of affected tuples.
206
+ def update_sql(sql, name = nil)
207
+ super.cmd_tuples
208
+ end
209
+
210
+ # Begins a transaction.
211
+ def begin_db_transaction
212
+ execute "BEGIN"
213
+ end
214
+
215
+ def begin_isolated_db_transaction(isolation)
216
+ begin_db_transaction
217
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
218
+ end
219
+
220
+ # Commits a transaction.
221
+ def commit_db_transaction
222
+ execute "COMMIT"
223
+ end
224
+
225
+ # Aborts a transaction.
226
+ def exec_rollback_db_transaction
227
+ execute "ROLLBACK"
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_record/connection_adapters/redshift/oid/infinity'
2
+
3
+ require 'active_record/connection_adapters/redshift/oid/date'
4
+ require 'active_record/connection_adapters/redshift/oid/date_time'
5
+ require 'active_record/connection_adapters/redshift/oid/decimal'
6
+ require 'active_record/connection_adapters/redshift/oid/float'
7
+ require 'active_record/connection_adapters/redshift/oid/integer'
8
+ require 'active_record/connection_adapters/redshift/oid/json'
9
+ require 'active_record/connection_adapters/redshift/oid/jsonb'
10
+ require 'active_record/connection_adapters/redshift/oid/time'
11
+
12
+ require 'active_record/connection_adapters/redshift/oid/type_map_initializer'
13
+
14
+ module ActiveRecord
15
+ module ConnectionAdapters
16
+ module Redshift
17
+ module OID # :nodoc:
18
+ end
19
+ end
20
+ end
21
+ end