activerecord-redshift-adapter 0.9.10 → 8.0.0.beta1

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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +25 -1
  3. data/README.md +28 -86
  4. data/lib/active_record/connection_adapters/redshift_7_0/array_parser.rb +92 -0
  5. data/lib/active_record/connection_adapters/redshift_7_0/column.rb +17 -0
  6. data/lib/active_record/connection_adapters/redshift_7_0/database_statements.rb +232 -0
  7. data/lib/active_record/connection_adapters/redshift_7_0/oid/date_time.rb +36 -0
  8. data/lib/active_record/connection_adapters/redshift_7_0/oid/decimal.rb +15 -0
  9. data/lib/active_record/connection_adapters/redshift_7_0/oid/json.rb +41 -0
  10. data/lib/active_record/connection_adapters/redshift_7_0/oid/jsonb.rb +25 -0
  11. data/lib/active_record/connection_adapters/redshift_7_0/oid/type_map_initializer.rb +62 -0
  12. data/lib/active_record/connection_adapters/redshift_7_0/oid.rb +17 -0
  13. data/lib/active_record/connection_adapters/redshift_7_0/quoting.rb +99 -0
  14. data/lib/active_record/connection_adapters/redshift_7_0/referential_integrity.rb +17 -0
  15. data/lib/active_record/connection_adapters/redshift_7_0/schema_definitions.rb +70 -0
  16. data/lib/active_record/connection_adapters/redshift_7_0/schema_dumper.rb +17 -0
  17. data/lib/active_record/connection_adapters/redshift_7_0/schema_statements.rb +421 -0
  18. data/lib/active_record/connection_adapters/redshift_7_0/type_metadata.rb +39 -0
  19. data/lib/active_record/connection_adapters/redshift_7_0/utils.rb +81 -0
  20. data/lib/active_record/connection_adapters/redshift_7_0_adapter.rb +768 -0
  21. data/lib/active_record/connection_adapters/redshift_7_1/array_parser.rb +92 -0
  22. data/lib/active_record/connection_adapters/redshift_7_1/column.rb +17 -0
  23. data/lib/active_record/connection_adapters/redshift_7_1/database_statements.rb +180 -0
  24. data/lib/active_record/connection_adapters/redshift_7_1/oid/date_time.rb +36 -0
  25. data/lib/active_record/connection_adapters/redshift_7_1/oid/decimal.rb +15 -0
  26. data/lib/active_record/connection_adapters/redshift_7_1/oid/json.rb +41 -0
  27. data/lib/active_record/connection_adapters/redshift_7_1/oid/jsonb.rb +25 -0
  28. data/lib/active_record/connection_adapters/redshift_7_1/oid/type_map_initializer.rb +62 -0
  29. data/lib/active_record/connection_adapters/redshift_7_1/oid.rb +17 -0
  30. data/lib/active_record/connection_adapters/redshift_7_1/quoting.rb +161 -0
  31. data/lib/active_record/connection_adapters/redshift_7_1/referential_integrity.rb +17 -0
  32. data/lib/active_record/connection_adapters/redshift_7_1/schema_definitions.rb +70 -0
  33. data/lib/active_record/connection_adapters/redshift_7_1/schema_dumper.rb +17 -0
  34. data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +422 -0
  35. data/lib/active_record/connection_adapters/redshift_7_1/type_metadata.rb +43 -0
  36. data/lib/active_record/connection_adapters/redshift_7_1/utils.rb +81 -0
  37. data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +847 -0
  38. data/lib/active_record/connection_adapters/redshift_7_2/array_parser.rb +92 -0
  39. data/lib/active_record/connection_adapters/redshift_7_2/column.rb +17 -0
  40. data/lib/active_record/connection_adapters/redshift_7_2/database_statements.rb +180 -0
  41. data/lib/active_record/connection_adapters/redshift_7_2/oid/date_time.rb +36 -0
  42. data/lib/active_record/connection_adapters/redshift_7_2/oid/decimal.rb +15 -0
  43. data/lib/active_record/connection_adapters/redshift_7_2/oid/json.rb +41 -0
  44. data/lib/active_record/connection_adapters/redshift_7_2/oid/jsonb.rb +25 -0
  45. data/lib/active_record/connection_adapters/redshift_7_2/oid/type_map_initializer.rb +62 -0
  46. data/lib/active_record/connection_adapters/redshift_7_2/oid.rb +17 -0
  47. data/lib/active_record/connection_adapters/redshift_7_2/quoting.rb +164 -0
  48. data/lib/active_record/connection_adapters/redshift_7_2/referential_integrity.rb +17 -0
  49. data/lib/active_record/connection_adapters/redshift_7_2/schema_definitions.rb +70 -0
  50. data/lib/active_record/connection_adapters/redshift_7_2/schema_dumper.rb +17 -0
  51. data/lib/active_record/connection_adapters/redshift_7_2/schema_statements.rb +422 -0
  52. data/lib/active_record/connection_adapters/redshift_7_2/type_metadata.rb +43 -0
  53. data/lib/active_record/connection_adapters/redshift_7_2/utils.rb +81 -0
  54. data/lib/active_record/connection_adapters/redshift_7_2_adapter.rb +847 -0
  55. data/lib/active_record/connection_adapters/redshift_8_0/array_parser.rb +92 -0
  56. data/lib/active_record/connection_adapters/redshift_8_0/column.rb +17 -0
  57. data/lib/active_record/connection_adapters/redshift_8_0/database_statements.rb +181 -0
  58. data/lib/active_record/connection_adapters/redshift_8_0/oid/date_time.rb +36 -0
  59. data/lib/active_record/connection_adapters/redshift_8_0/oid/decimal.rb +15 -0
  60. data/lib/active_record/connection_adapters/redshift_8_0/oid/json.rb +41 -0
  61. data/lib/active_record/connection_adapters/redshift_8_0/oid/jsonb.rb +25 -0
  62. data/lib/active_record/connection_adapters/redshift_8_0/oid/type_map_initializer.rb +62 -0
  63. data/lib/active_record/connection_adapters/redshift_8_0/oid.rb +17 -0
  64. data/lib/active_record/connection_adapters/redshift_8_0/quoting.rb +164 -0
  65. data/lib/active_record/connection_adapters/redshift_8_0/referential_integrity.rb +17 -0
  66. data/lib/active_record/connection_adapters/redshift_8_0/schema_definitions.rb +70 -0
  67. data/lib/active_record/connection_adapters/redshift_8_0/schema_dumper.rb +17 -0
  68. data/lib/active_record/connection_adapters/redshift_8_0/schema_statements.rb +422 -0
  69. data/lib/active_record/connection_adapters/redshift_8_0/type_metadata.rb +43 -0
  70. data/lib/active_record/connection_adapters/redshift_8_0/utils.rb +81 -0
  71. data/lib/active_record/connection_adapters/redshift_8_0_adapter.rb +846 -0
  72. data/lib/active_record/connection_adapters/redshift_adapter.rb +13 -1282
  73. data/lib/active_record/tasks/redshift_7_0_tasks.rb +148 -0
  74. data/lib/active_record/tasks/redshift_7_1_tasks.rb +151 -0
  75. data/lib/active_record/tasks/redshift_7_2_tasks.rb +151 -0
  76. data/lib/active_record/tasks/redshift_8_0_tasks.rb +151 -0
  77. data/lib/active_record/tasks/redshift_tasks.rb +13 -0
  78. data/lib/activerecord-redshift-adapter.rb +13 -0
  79. metadata +112 -98
  80. data/.gitignore +0 -26
  81. data/Gemfile +0 -14
  82. data/Rakefile +0 -26
  83. data/activerecord-redshift-adapter.gemspec +0 -24
  84. data/lib/activerecord_redshift/table_manager.rb +0 -230
  85. data/lib/activerecord_redshift_adapter/version.rb +0 -4
  86. data/lib/activerecord_redshift_adapter.rb +0 -4
  87. data/lib/monkeypatch_activerecord.rb +0 -195
  88. data/lib/monkeypatch_arel.rb +0 -96
  89. data/spec/active_record/base_spec.rb +0 -37
  90. data/spec/active_record/connection_adapters/redshift_adapter_spec.rb +0 -97
  91. data/spec/dummy/config/database.example.yml +0 -12
  92. data/spec/spec_helper.rb +0 -33
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d981a65a30765fe36e5e58616007f33103f238596de10188107d5e7bbe677093
4
+ data.tar.gz: 78f40be0d77ab87f6e4b3b1f6857f84aae35d0b5184a6fe1b320b14c080bbce9
5
+ SHA512:
6
+ metadata.gz: f8a9a3801675594bc2a3400c3a4d4657247614b250558c7c169c1f720c0b8f8ce333b29e6a1e621bd3557539c69c8a5ecb160d059049e80ab24d3c531350aecb
7
+ data.tar.gz: f75478c7a7df07526ad528e562978bae1918b8146562d42bab6fcf45c8f8eff749b67a3efa5dc08530285642d5ed1003247c2fa64bf3ce60f3482b9824a79580
data/LICENSE CHANGED
@@ -1,4 +1,28 @@
1
- Copyright (c) 2010-2014, Fiksu, Inc.
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.
2
26
  All rights reserved.
3
27
 
4
28
  Redistribution and use in source and binary forms, with or without
data/README.md CHANGED
@@ -1,101 +1,43 @@
1
- # activerecord-redshift-adapter
1
+ activerecord-redshift-adapter
2
+ ==============================
2
3
 
3
- adapter for aws redshift for rails 3
4
+ Amazon Redshift adapter for ActiveRecord 7+ (Rails 7+).
5
+ This is a fork from https://github.com/pennylane-hq/activerecord-adapter-redshift, which is a fork from
6
+ https://rubygems.org/gems/activerecord7-redshift-adapter hosted on a private Gitlab instance.
7
+ It's itself forked the project from https://github.com/kwent/activerecord6-redshift-adapter
4
8
 
5
- Ripped from rails 3 postgresql.
9
+ Thanks to the auhors.
6
10
 
7
- ## example database.yml
8
- ```yml
9
- common: &common
10
- adapter: postgresql
11
- username: postgres
12
- encoding: SQL_ASCII
13
- template: template0
14
- pool: 5
15
- timeout: 5000
11
+ Usage
12
+ -------------------
16
13
 
17
- redshiftdb: &redshiftdb
18
- adapter: redshift
19
- host: clustername.something.us-east-1.redshift.amazonaws.com
20
- database: databasename
21
- port: 5439
22
- username: username
23
- password: password
24
-
25
- redshift_development:
26
- <<: *common
27
- <<: *redshiftdb
28
- database: databasename
29
- ```
14
+ For Rails, write following in Gemfile:
30
15
 
31
- ## options
32
- ```html
33
- <table>
34
- <tr>
35
- <th>option</th>
36
- <th>description</th>
37
- </tr>
38
- <tr>
39
- <th>schema_search_path</th>
40
- <td>set schema_search_path. use default value if not given.</td>
41
- </tr>
42
- <tr>
43
- <th>read_timezone</th>
44
- <td>force timezone for datetime when select values. ActiveRecord default timezone will set if not given.</td>
45
- </tr>
46
- </table>
16
+ ```ruby
17
+ gem 'activerecord-redshift-adapter'
47
18
  ```
48
19
 
49
- ## Have you considered using Partitioned gem? It works with redshift!
50
-
51
- https://github.com/fiksu/partitioned
20
+ Specify the adapter name in the `database.yml` file:
52
21
 
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
- );
22
+ ```YAML
23
+ development:
24
+ adapter: redshift
25
+ host: host
26
+ port: port
27
+ database: db
28
+ username: user
29
+ password: password
30
+ encoding: utf8
63
31
  ```
64
32
 
33
+ or use it directly in the URL when establishing a connection:
65
34
  ```ruby
66
- class Foo < ActiveRecord::Base
35
+ class SomeModel < ApplicationRecord
36
+ establish_connection('redshift://username:password@host/database')
67
37
  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
38
  ```
74
39
 
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);
40
+ License
41
+ ---------
85
42
 
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
- ```
43
+ MIT license (same as ActiveRecord)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module ArrayParser # :nodoc:
7
+ DOUBLE_QUOTE = '"'
8
+ BACKSLASH = '\\'
9
+ COMMA = ','
10
+ BRACKET_OPEN = '{'
11
+ BRACKET_CLOSE = '}'
12
+
13
+ def parse_pg_array(string) # :nodoc:
14
+ local_index = 0
15
+ array = []
16
+ while local_index < string.length
17
+ case string[local_index]
18
+ when BRACKET_OPEN
19
+ local_index, array = parse_array_contents(array, string, local_index + 1)
20
+ when BRACKET_CLOSE
21
+ return array
22
+ end
23
+ local_index += 1
24
+ end
25
+
26
+ array
27
+ end
28
+
29
+ private
30
+
31
+ def parse_array_contents(array, string, index)
32
+ is_escaping = false
33
+ is_quoted = false
34
+ was_quoted = false
35
+ current_item = ''
36
+
37
+ local_index = index
38
+ while local_index
39
+ token = string[local_index]
40
+ if is_escaping
41
+ current_item << token
42
+ is_escaping = false
43
+ elsif 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
+
75
+ local_index += 1
76
+ end
77
+ [local_index, array]
78
+ end
79
+
80
+ def add_item_to_array(array, current_item, quoted)
81
+ return if !quoted && current_item.empty?
82
+
83
+ if !quoted && current_item == 'NULL'
84
+ array.push nil
85
+ else
86
+ array.push current_item
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class RedshiftColumn < Column # :nodoc:
6
+ delegate :oid, :fmod, to: :sql_type_metadata
7
+
8
+ # Required for Rails 6.1, see https://github.com/rails/rails/pull/41756
9
+ mattr_reader :array, default: false
10
+ alias array? array
11
+
12
+ def initialize(name, default, sql_type_metadata, null = true, default_function = nil, **)
13
+ super name, default, sql_type_metadata, null, default_function
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module DatabaseStatements
7
+ def explain(arel, binds = [])
8
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
9
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
10
+ end
11
+
12
+ class ExplainPrettyPrinter # :nodoc:
13
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
14
+ # PostgreSQL shell:
15
+ #
16
+ # QUERY PLAN
17
+ # ------------------------------------------------------------------------------
18
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
19
+ # Join Filter: (posts.user_id = users.id)
20
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
21
+ # Index Cond: (id = 1)
22
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
23
+ # Filter: (posts.user_id = 1)
24
+ # (6 rows)
25
+ #
26
+ def pp(result)
27
+ header = result.columns.first
28
+ lines = result.rows.map(&:first)
29
+
30
+ # We add 2 because there's one char of padding at both sides, note
31
+ # the extra hyphens in the example above.
32
+ width = [header, *lines].map(&:length).max + 2
33
+
34
+ pp = []
35
+
36
+ pp << header.center(width).rstrip
37
+ pp << '-' * width
38
+
39
+ pp += lines.map { |line| " #{line}" }
40
+
41
+ nrows = result.rows.length
42
+ rows_label = nrows == 1 ? 'row' : 'rows'
43
+ pp << "(#{nrows} #{rows_label})"
44
+
45
+ "#{pp.join("\n")}\n"
46
+ end
47
+ end
48
+
49
+ def select_value(arel, name = nil, binds = [])
50
+ # In Rails 5.2, arel_from_relation replaced binds_from_relation,
51
+ # so we see which method exists to get the variables
52
+ #
53
+ # In Rails 6.0 to_sql_and_binds began only returning sql, with
54
+ # to_sql_and_binds serving as a replacement
55
+ if respond_to?(:arel_from_relation, true)
56
+ arel = arel_from_relation(arel)
57
+ sql, binds = to_sql_and_binds(arel, binds)
58
+ else
59
+ arel, binds = binds_from_relation arel, binds
60
+ sql = to_sql(arel, binds)
61
+ end
62
+ execute_and_clear(sql, name, binds) do |result|
63
+ result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
64
+ end
65
+ end
66
+
67
+ def select_values(arel, name = nil)
68
+ # In Rails 5.2, arel_from_relation replaced binds_from_relation,
69
+ # so we see which method exists to get the variables
70
+ #
71
+ # In Rails 6.0 to_sql_and_binds began only returning sql, with
72
+ # to_sql_and_binds serving as a replacement
73
+ if respond_to?(:arel_from_relation, true)
74
+ arel = arel_from_relation(arel)
75
+ sql, binds = to_sql_and_binds(arel, [])
76
+ else
77
+ arel, binds = binds_from_relation arel, []
78
+ sql = to_sql(arel, binds)
79
+ end
80
+
81
+ execute_and_clear(sql, name, binds) do |result|
82
+ if result.nfields > 0
83
+ result.column_values(0)
84
+ else
85
+ []
86
+ end
87
+ end
88
+ end
89
+
90
+ # Executes a SELECT query and returns an array of rows. Each row is an
91
+ # array of field values.
92
+ def select_rows(arel, name = nil, binds = [])
93
+ if respond_to?(:arel_from_relation, true)
94
+ arel = arel_from_relation(arel)
95
+ sql, binds = to_sql_and_binds(arel, [])
96
+ else
97
+ arel, binds = binds_from_relation arel, []
98
+ sql = to_sql(arel, binds)
99
+ end
100
+ execute_and_clear(sql, name, binds, &:values)
101
+ end
102
+
103
+ # The internal PostgreSQL identifier of the money data type.
104
+ MONEY_COLUMN_TYPE_OID = 790 # :nodoc:
105
+ # The internal PostgreSQL identifier of the BYTEA data type.
106
+ BYTEA_COLUMN_TYPE_OID = 17 # :nodoc:
107
+
108
+ # create a 2D array representing the result set
109
+ def result_as_array(res) # :nodoc:
110
+ # check if we have any binary column and if they need escaping
111
+ ftypes = Array.new(res.nfields) do |i|
112
+ [i, res.ftype(i)]
113
+ end
114
+
115
+ rows = res.values
116
+ return rows unless ftypes.any? do |_, x|
117
+ [BYTEA_COLUMN_TYPE_OID, MONEY_COLUMN_TYPE_OID].include?(x)
118
+ end
119
+
120
+ typehash = ftypes.group_by { |_, type| type }
121
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
122
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
123
+
124
+ rows.each do |row|
125
+ # unescape string passed BYTEA field (OID == 17)
126
+ binaries.each do |index, _|
127
+ row[index] = unescape_bytea(row[index])
128
+ end
129
+
130
+ # If this is a money type column and there are any currency symbols,
131
+ # then strip them off. Indeed it would be prettier to do this in
132
+ # PostgreSQLColumn.string_to_decimal but would break form input
133
+ # fields that call value_before_type_cast.
134
+ monies.each do |index, _|
135
+ data = row[index]
136
+ # Because money output is formatted according to the locale, there are two
137
+ # cases to consider (note the decimal separators):
138
+ # (1) $12,345,678.12
139
+ # (2) $12.345.678,12
140
+ case data
141
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
142
+ data.gsub!(/[^-\d.]/, '')
143
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
144
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ # Queries the database and returns the results in an Array-like object
151
+ def query(sql, name = nil) # :nodoc:
152
+ log(sql, name) do
153
+ result_as_array @connection.async_exec(sql)
154
+ end
155
+ end
156
+
157
+ # Executes an SQL statement, returning a PG::Result object on success
158
+ # or raising a PG::Error exception otherwise.
159
+ def execute(sql, name = nil)
160
+ log(sql, name) do
161
+ @connection.async_exec(sql)
162
+ end
163
+ end
164
+
165
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
166
+ execute_and_clear(sql, name, binds, prepare: prepare) do |result|
167
+ types = {}
168
+ fields = result.fields
169
+ fields.each_with_index do |fname, i|
170
+ ftype = result.ftype i
171
+ fmod = result.fmod i
172
+ types[fname] = get_oid_type(ftype, fmod, fname)
173
+ end
174
+ ActiveRecord::Result.new(fields, result.values, types)
175
+ end
176
+ end
177
+ alias internal_exec_query exec_query
178
+
179
+ def exec_delete(sql, name = 'SQL', binds = [])
180
+ execute_and_clear(sql, name, binds, &:cmd_tuples)
181
+ end
182
+ alias exec_update exec_delete
183
+
184
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
185
+ if pk.nil?
186
+ # Extract the table from the insert sql. Yuck.
187
+ table_ref = extract_table_ref_from_insert_sql(sql)
188
+ pk = primary_key(table_ref) if table_ref
189
+ end
190
+
191
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk && use_insert_returning?
192
+
193
+ super
194
+ end
195
+
196
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
197
+ val = exec_query(sql, name, binds)
198
+ if !use_insert_returning? && pk
199
+ unless sequence_name
200
+ table_ref = extract_table_ref_from_insert_sql(sql)
201
+ sequence_name = default_sequence_name(table_ref, pk)
202
+ return val unless sequence_name
203
+ end
204
+ last_insert_id_result(sequence_name)
205
+ else
206
+ val
207
+ end
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,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class DateTime < Type::DateTime # :nodoc:
8
+ def type_cast_for_database(value)
9
+ if has_precision? && value.acts_like?(:time) && value.year <= 0
10
+ bce_year = format('%04d', -value.year + 1)
11
+ "#{super.sub(/^-?\d+/, bce_year)} BC"
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def cast_value(value)
18
+ if value.is_a?(::String)
19
+ case value
20
+ when 'infinity' then ::Float::INFINITY
21
+ when '-infinity' then -::Float::INFINITY
22
+ when / BC$/
23
+ astronomical_year = format('%04d', -value[/^\d+/].to_i + 1)
24
+ super(value.sub(/ BC$/, '').sub(/^\d+/, astronomical_year))
25
+ else
26
+ super
27
+ end
28
+ else
29
+ value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class Decimal < Type::Decimal # :nodoc:
8
+ def infinity(options = {})
9
+ BigDecimal('Infinity') * (options[:negative] ? -1 : 1)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class Json < Type::Value # :nodoc:
8
+ include ActiveModel::Type::Helpers::Mutable
9
+
10
+ def type
11
+ :json
12
+ end
13
+
14
+ def type_cast_from_database(value)
15
+ if value.is_a?(::String)
16
+ begin
17
+ ::ActiveSupport::JSON.decode(value)
18
+ rescue StandardError
19
+ nil
20
+ end
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def type_cast_for_database(value)
27
+ if value.is_a?(::Array) || value.is_a?(::Hash)
28
+ ::ActiveSupport::JSON.encode(value)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def accessor
35
+ ActiveRecord::Store::StringKeyedHashAccessor
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class Jsonb < Json # :nodoc:
8
+ def type
9
+ :jsonb
10
+ end
11
+
12
+ def changed_in_place?(raw_old_value, new_value)
13
+ # Postgres does not preserve insignificant whitespaces when
14
+ # roundtripping jsonb columns. This causes some false positives for
15
+ # the comparison here. Therefore, we need to parse and re-dump the
16
+ # raw value here to ensure the insignificant whitespaces are
17
+ # consistent with our encoder's output.
18
+ raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
19
+ super(raw_old_value, new_value)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ # This class uses the data from PostgreSQL pg_type table to build
8
+ # the OID -> Type mapping.
9
+ # - OID is an integer representing the type.
10
+ # - Type is an OID::Type object.
11
+ # This class has side effects on the +store+ passed during initialization.
12
+ class TypeMapInitializer # :nodoc:
13
+ def initialize(store, run_complex_types = true)
14
+ @store = store
15
+ @run_complex_types = run_complex_types
16
+ end
17
+
18
+ def run(records)
19
+ records
20
+ .reject { |row| @store.key? row['oid'].to_i }
21
+ .select { |row| @store.key? row['typname'] }
22
+ .each { |row| register_mapped_type(row) }
23
+ end
24
+
25
+ private
26
+
27
+ def register_mapped_type(row)
28
+ alias_type row['oid'], row['typname']
29
+ end
30
+
31
+ def register(oid, oid_type = nil, &block)
32
+ oid = assert_valid_registration(oid, oid_type || block)
33
+ if block_given?
34
+ @store.register_type(oid, &block)
35
+ else
36
+ @store.register_type(oid, oid_type)
37
+ end
38
+ end
39
+
40
+ def alias_type(oid, target)
41
+ oid = assert_valid_registration(oid, target)
42
+ @store.alias_type(oid, target)
43
+ end
44
+
45
+ def register_with_subtype(oid, target_oid)
46
+ return unless @store.key?(target_oid)
47
+
48
+ register(oid) do |_, *args|
49
+ yield @store.lookup(target_oid, *args)
50
+ end
51
+ end
52
+
53
+ def assert_valid_registration(oid, oid_type)
54
+ raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
55
+
56
+ oid.to_i
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end