activerecord5-redshift-adapter 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66f5287f209d2dadbb6b07947b674a32fea60cd1
4
+ data.tar.gz: 35fa384d4ce1cfde8da7b6d22e8a13a2b0ca4b60
5
+ SHA512:
6
+ metadata.gz: ea21f614cf1f0d0ff22a1e9754767595692092feef8163e90688b4390ccc5fdb09e768ef75a8d62f2584c24547d1adc5a21e3a5248e8f47f3a20a2acccecf997
7
+ data.tar.gz: 3311c27808e88aa7ef084002bdfcbdf5edbd7a6824962a0bb462753fb5dbbb746a55de01e69dc1bac62dbdcaa778aaa77c07265203f3c0b78b309184839a72c8
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,42 @@
1
+ activerecord5-redshift-adapter
2
+ ==============================
3
+
4
+ Amazon Redshift adapter for ActiveRecord 5 (Rails 5).
5
+ I cloned the project from aamine/activerecord4-redshift-adapter.
6
+
7
+ The Redshift driver for ActiveRecord4 already exists at:
8
+ https://github.com/aamine/activerecord4-redshift-adapter, and the one for
9
+ ActiveRecord3 exists at https://github.com/fiksu/activerecord-redshift-adapter.
10
+
11
+ There are some differences for AR5, and this driver tries to stay compatible
12
+ with those.
13
+
14
+ If anybody writes a better Redshift driver which works with ActiveRecord 5,
15
+ and maintains it, we would happily switch to that.
16
+
17
+ Usage
18
+ -------------------
19
+
20
+ For Rails 5, write following in Gemfile:
21
+ ```
22
+ gem 'activerecord5-redshift-adapter'
23
+
24
+
25
+ In database.yml
26
+ ```
27
+ development:
28
+ adapter: redshift
29
+ host: your_cluster_name.at.redshift.amazonaws.com
30
+ port: 5439
31
+ database: your_db
32
+ username: your_user
33
+ password: your_password
34
+ encoding: utf8
35
+ pool: 3
36
+ timeout: 5000
37
+ ```
38
+
39
+ License
40
+ ---------
41
+
42
+ 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,11 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class RedshiftColumn < Column #:nodoc:
4
+ delegate :oid, :fmod, to: :sql_type_metadata
5
+
6
+ def initialize(name, default, sql_type_metadata, null = true, table_name= nil, default_function = nil)
7
+ super name, default, sql_type_metadata, null, table_name, default_function, nil
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,205 @@
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
+ # The internal PostgreSQL identifier of the money data type.
76
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
77
+ # The internal PostgreSQL identifier of the BYTEA data type.
78
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
79
+
80
+ # create a 2D array representing the result set
81
+ def result_as_array(res) #:nodoc:
82
+ # check if we have any binary column and if they need escaping
83
+ ftypes = Array.new(res.nfields) do |i|
84
+ [i, res.ftype(i)]
85
+ end
86
+
87
+ rows = res.values
88
+ return rows unless ftypes.any? { |_, x|
89
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
90
+ }
91
+
92
+ typehash = ftypes.group_by { |_, type| type }
93
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
94
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
95
+
96
+ rows.each do |row|
97
+ # unescape string passed BYTEA field (OID == 17)
98
+ binaries.each do |index, _|
99
+ row[index] = unescape_bytea(row[index])
100
+ end
101
+
102
+ # If this is a money type column and there are any currency symbols,
103
+ # then strip them off. Indeed it would be prettier to do this in
104
+ # PostgreSQLColumn.string_to_decimal but would break form input
105
+ # fields that call value_before_type_cast.
106
+ monies.each do |index, _|
107
+ data = row[index]
108
+ # Because money output is formatted according to the locale, there are two
109
+ # cases to consider (note the decimal separators):
110
+ # (1) $12,345,678.12
111
+ # (2) $12.345.678,12
112
+ case data
113
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
114
+ data.gsub!(/[^-\d.]/, '')
115
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
116
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ # Queries the database and returns the results in an Array-like object
123
+ def query(sql, name = nil) #:nodoc:
124
+ log(sql, name) do
125
+ result_as_array @connection.async_exec(sql)
126
+ end
127
+ end
128
+
129
+ # Executes an SQL statement, returning a PGresult object on success
130
+ # or raising a PGError exception otherwise.
131
+ def execute(sql, name = nil)
132
+ log(sql, name) do
133
+ @connection.async_exec(sql)
134
+ end
135
+ end
136
+
137
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
138
+ execute_and_clear(sql, name, binds, prepare: prepare) do |result|
139
+ types = {}
140
+ fields = result.fields
141
+ fields.each_with_index do |fname, i|
142
+ ftype = result.ftype i
143
+ fmod = result.fmod i
144
+ types[fname] = get_oid_type(ftype, fmod, fname)
145
+ end
146
+ ActiveRecord::Result.new(fields, result.values, types)
147
+ end
148
+ end
149
+
150
+ def exec_delete(sql, name = 'SQL', binds = [])
151
+ execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
152
+ end
153
+ alias :exec_update :exec_delete
154
+
155
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
156
+ if pk.nil?
157
+ # Extract the table from the insert sql. Yuck.
158
+ table_ref = extract_table_ref_from_insert_sql(sql)
159
+ pk = primary_key(table_ref) if table_ref
160
+ end
161
+
162
+ if pk && use_insert_returning?
163
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}"
164
+ end
165
+
166
+ super
167
+ end
168
+
169
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
170
+ val = exec_query(sql, name, binds)
171
+ if !use_insert_returning? && pk
172
+ unless sequence_name
173
+ table_ref = extract_table_ref_from_insert_sql(sql)
174
+ sequence_name = default_sequence_name(table_ref, pk)
175
+ return val unless sequence_name
176
+ end
177
+ last_insert_id_result(sequence_name)
178
+ else
179
+ val
180
+ end
181
+ end
182
+
183
+ # Begins a transaction.
184
+ def begin_db_transaction
185
+ execute "BEGIN"
186
+ end
187
+
188
+ def begin_isolated_db_transaction(isolation)
189
+ begin_db_transaction
190
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
191
+ end
192
+
193
+ # Commits a transaction.
194
+ def commit_db_transaction
195
+ execute "COMMIT"
196
+ end
197
+
198
+ # Aborts a transaction.
199
+ def exec_rollback_db_transaction
200
+ execute "ROLLBACK"
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_record/connection_adapters/redshift/oid/date_time'
2
+ require 'active_record/connection_adapters/redshift/oid/decimal'
3
+ require 'active_record/connection_adapters/redshift/oid/json'
4
+ require 'active_record/connection_adapters/redshift/oid/jsonb'
5
+
6
+ require 'active_record/connection_adapters/redshift/oid/type_map_initializer'
7
+
8
+ module ActiveRecord
9
+ module ConnectionAdapters
10
+ module Redshift
11
+ module OID # :nodoc:
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ class DateTime < Type::DateTime # :nodoc:
6
+ def type_cast_for_database(value)
7
+ if has_precision? && value.acts_like?(:time) && value.year <= 0
8
+ bce_year = format("%04d", -value.year + 1)
9
+ super.sub(/^-?\d+/, bce_year) + " BC"
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ def cast_value(value)
16
+ if value.is_a?(::String)
17
+ case value
18
+ when 'infinity' then ::Float::INFINITY
19
+ when '-infinity' then -::Float::INFINITY
20
+ when / BC$/
21
+ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
22
+ super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
23
+ else
24
+ super
25
+ end
26
+ else
27
+ value
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end