activerecord-redshift-adapter 0.9.12 → 8.0.0.beta2

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 +5 -13
  2. data/LICENSE +25 -1
  3. data/README.md +29 -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 +765 -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 +844 -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 +844 -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 +843 -0
  72. data/lib/active_record/connection_adapters/redshift_adapter.rb +13 -1286
  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 +110 -84
  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
@@ -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,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module DatabaseStatements
7
+ def explain(arel, binds = [], options = [])
8
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
9
+ result = internal_exec_query(sql, "EXPLAIN", binds)
10
+ PostgreSQL::ExplainPrettyPrinter.new.pp(result)
11
+ end
12
+
13
+ # Queries the database and returns the results in an Array-like object
14
+ def query(sql, name = nil) # :nodoc:
15
+ mark_transaction_written_if_write(sql)
16
+
17
+ log(sql, name) do
18
+ with_raw_connection do |conn|
19
+ result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
+ verified!
21
+ result
22
+ end
23
+ end
24
+ end
25
+
26
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
27
+ :close, :declare, :fetch, :move, :set, :show
28
+ ) # :nodoc:
29
+ private_constant :READ_QUERY
30
+
31
+ def write_query?(sql) # :nodoc:
32
+ !READ_QUERY.match?(sql)
33
+ rescue ArgumentError # Invalid encoding
34
+ !READ_QUERY.match?(sql.b)
35
+ end
36
+
37
+ # Executes an SQL statement, returning a PG::Result object on success
38
+ # or raising a PG::Error exception otherwise.
39
+ #
40
+ # Setting +allow_retry+ to true causes the db to reconnect and retry
41
+ # executing the SQL statement in case of a connection-related exception.
42
+ # This option should only be enabled for known idempotent queries.
43
+ #
44
+ # Note: the PG::Result object is manually memory managed; if you don't
45
+ # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
46
+ def execute(...) # :nodoc:
47
+ super
48
+ ensure
49
+ @notice_receiver_sql_warnings = []
50
+ end
51
+
52
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
53
+ log(sql, name, async: async) do
54
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
55
+ result = conn.async_exec(sql)
56
+ verified!
57
+ handle_warnings(result)
58
+ result
59
+ end
60
+ end
61
+ end
62
+
63
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
64
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
65
+ types = {}
66
+ fields = result.fields
67
+ fields.each_with_index do |fname, i|
68
+ ftype = result.ftype i
69
+ fmod = result.fmod i
70
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
71
+ end
72
+ build_result(columns: fields, rows: result.values, column_types: types)
73
+ end
74
+ end
75
+
76
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
77
+ execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
78
+ end
79
+ alias :exec_update :exec_delete
80
+
81
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
82
+ if use_insert_returning? || pk == false
83
+ super
84
+ else
85
+ result = internal_exec_query(sql, name, binds)
86
+ unless sequence_name
87
+ table_ref = extract_table_ref_from_insert_sql(sql)
88
+ if table_ref
89
+ pk = primary_key(table_ref) if pk.nil?
90
+ pk = suppress_composite_primary_key(pk)
91
+ sequence_name = default_sequence_name(table_ref, pk)
92
+ end
93
+ return result unless sequence_name
94
+ end
95
+ last_insert_id_result(sequence_name)
96
+ end
97
+ end
98
+
99
+ # Begins a transaction.
100
+ def begin_db_transaction
101
+ internal_execute('BEGIN', allow_retry: true, materialize_transactions: false)
102
+ end
103
+
104
+ def begin_isolated_db_transaction(isolation)
105
+ begin_db_transaction
106
+ internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", allow_retry: true, materialize_transactions: false)
107
+ end
108
+
109
+ # Commits a transaction.
110
+ def commit_db_transaction
111
+ internal_execute('COMMIT', allow_retry: false, materialize_transactions: true)
112
+ end
113
+
114
+ # Aborts a transaction.
115
+ def exec_rollback_db_transaction
116
+ internal_execute('ROLLBACK', allow_retry: false, materialize_transactions: true)
117
+ end
118
+
119
+ def exec_restart_db_transaction # :nodoc:
120
+ cancel_any_running_query
121
+ exec_rollback_db_transaction
122
+ begin_db_transaction
123
+ end
124
+
125
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
126
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
127
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
128
+
129
+ def high_precision_current_timestamp
130
+ HIGH_PRECISION_CURRENT_TIMESTAMP
131
+ end
132
+
133
+ def build_explain_clause(options = [])
134
+ return "EXPLAIN" if options.empty?
135
+
136
+ "EXPLAIN (#{options.join(", ").upcase})"
137
+ end
138
+
139
+ private
140
+
141
+ IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
142
+ private_constant :IDLE_TRANSACTION_STATUSES
143
+
144
+ def cancel_any_running_query
145
+ return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
146
+
147
+ @raw_connection.cancel
148
+ @raw_connection.block
149
+ rescue PG::Error
150
+ end
151
+
152
+ # Returns the current ID of a table's sequence.
153
+ def last_insert_id_result(sequence_name)
154
+ internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
155
+ end
156
+
157
+ def returning_column_values(result)
158
+ result.rows.first
159
+ end
160
+
161
+ def suppress_composite_primary_key(pk)
162
+ pk unless pk.is_a?(Array)
163
+ end
164
+
165
+ def handle_warnings(sql)
166
+ @notice_receiver_sql_warnings.each do |warning|
167
+ next if warning_ignored?(warning)
168
+
169
+ warning.sql = sql
170
+ ActiveRecord.db_warnings_action.call(warning)
171
+ end
172
+ end
173
+
174
+ def warning_ignored?(warning)
175
+ ["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
176
+ end
177
+ end
178
+ end
179
+ end
180
+ 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
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'oid/date_time'
4
+ require_relative 'oid/decimal'
5
+ require_relative 'oid/json'
6
+ require_relative 'oid/jsonb'
7
+
8
+ require_relative 'oid/type_map_initializer'
9
+
10
+ module ActiveRecord
11
+ module ConnectionAdapters
12
+ module Redshift
13
+ module OID # :nodoc:
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module Quoting
7
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
+
10
+ # Escapes binary strings for bytea input to the database.
11
+ def escape_bytea(value)
12
+ valid_raw_connection.escape_bytea(value) if value
13
+ end
14
+
15
+ # Unescapes bytea output from a database to the binary string it represents.
16
+ # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
17
+ # on escaped binary output from database drive.
18
+ def unescape_bytea(value)
19
+ valid_raw_connection.unescape_bytea(value) if value
20
+ end
21
+
22
+ # Quotes strings for use in SQL input.
23
+ def quote_string(s) # :nodoc:
24
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
25
+ connection.escape(s)
26
+ end
27
+ end
28
+
29
+ # Checks the following cases:
30
+ #
31
+ # - table_name
32
+ # - "table.name"
33
+ # - schema_name.table_name
34
+ # - schema_name."table.name"
35
+ # - "schema.name".table_name
36
+ # - "schema.name"."table.name"
37
+ def quote_table_name(name)
38
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
39
+ end
40
+
41
+ def quote_table_name_for_assignment(_table, attr)
42
+ quote_column_name(attr)
43
+ end
44
+
45
+ # Quotes column names for use in SQL queries.
46
+ def quote_column_name(name) # :nodoc:
47
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
48
+ end
49
+
50
+ # Quotes schema names for use in SQL queries.
51
+ def quote_schema_name(name)
52
+ PG::Connection.quote_ident(name)
53
+ end
54
+
55
+ # Quote date/time values for use in SQL input.
56
+ def quoted_date(value) # :nodoc:
57
+ if value.year <= 0
58
+ bce_year = format("%04d", -value.year + 1)
59
+ super.sub(/^-?\d+/, bce_year) + " BC"
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def quoted_binary(value) # :nodoc:
66
+ "'#{escape_bytea(value.to_s)}'"
67
+ end
68
+
69
+ # Does not quote function default values for UUID columns
70
+ def quote_default_value(value, column) # :nodoc:
71
+ if column.type == :uuid && value =~ /\(\)/
72
+ value
73
+ else
74
+ quote(value, column)
75
+ end
76
+ end
77
+
78
+ def quote(value)
79
+ case value
80
+ when Numeric
81
+ if value.finite?
82
+ super
83
+ else
84
+ "'#{value}'"
85
+ end
86
+ when Type::Binary::Data
87
+ "'#{escape_bytea(value.to_s)}'"
88
+ else
89
+ super
90
+ end
91
+ end
92
+
93
+ def quote_default_expression(value, column) # :nodoc:
94
+ if value.is_a?(Proc)
95
+ value.call
96
+ elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
97
+ value # Does not quote function default values for UUID columns
98
+ else
99
+ super
100
+ end
101
+ end
102
+
103
+ def type_cast(value)
104
+ case value
105
+ when Type::Binary::Data
106
+ # Return a bind param hash with format as binary.
107
+ # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
108
+ # for more information
109
+ { value: value.to_s, format: 1 }
110
+ else
111
+ super
112
+ end
113
+ end
114
+
115
+ def lookup_cast_type_from_column(column) # :nodoc:
116
+ verify! if type_map.nil?
117
+ type_map.lookup(column.oid, column.fmod, column.sql_type)
118
+ end
119
+
120
+ def column_name_matcher
121
+ COLUMN_NAME
122
+ end
123
+
124
+ def column_name_with_order_matcher
125
+ COLUMN_NAME_WITH_ORDER
126
+ end
127
+
128
+ COLUMN_NAME = /
129
+ \A
130
+ (
131
+ (?:
132
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
133
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
134
+ )
135
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
136
+ )
137
+ (?:\s*,\s*\g<1>)*
138
+ \z
139
+ /ix
140
+
141
+ COLUMN_NAME_WITH_ORDER = /
142
+ \A
143
+ (
144
+ (?:
145
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
146
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
147
+ )
148
+ (?:\s+COLLATE\s+"\w+")?
149
+ (?:\s+ASC|\s+DESC)?
150
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
151
+ )
152
+ (?:\s*,\s*\g<1>)*
153
+ \z
154
+ /ix
155
+
156
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
157
+
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module ReferentialIntegrity # :nodoc:
7
+ def supports_disable_referential_integrity? # :nodoc:
8
+ true
9
+ end
10
+
11
+ def disable_referential_integrity # :nodoc:
12
+ yield
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end