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