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
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module ColumnMethods
7
+ # Defines the primary key field.
8
+ # Use of the native PostgreSQL UUID type is supported, and can be used
9
+ # by defining your tables as such:
10
+ #
11
+ # create_table :stuffs, id: :uuid do |t|
12
+ # t.string :content
13
+ # t.timestamps
14
+ # end
15
+ #
16
+ # By default, this will use the +uuid_generate_v4()+ function from the
17
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
18
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
19
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
20
+ # set the +:default+ option to +nil+:
21
+ #
22
+ # create_table :stuffs, id: false do |t|
23
+ # t.primary_key :id, :uuid, default: nil
24
+ # t.uuid :foo_id
25
+ # t.timestamps
26
+ # end
27
+ #
28
+ # You may also pass a different UUID generation function from +uuid-ossp+
29
+ # or another library.
30
+ #
31
+ # Note that setting the UUID primary key default value to +nil+ will
32
+ # require you to assure that you always provide a UUID value before saving
33
+ # a record (as primary keys cannot be +nil+). This might be done via the
34
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
35
+ def primary_key(name, type = :primary_key, **options)
36
+ return super unless type == :uuid
37
+
38
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()')
39
+ options[:primary_key] = true
40
+ column name, type, options
41
+ end
42
+
43
+ def json(name, **options)
44
+ column(name, :json, options)
45
+ end
46
+
47
+ def jsonb(name, **options)
48
+ column(name, :jsonb, options)
49
+ end
50
+ end
51
+
52
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
53
+ end
54
+
55
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
56
+ include ColumnMethods
57
+
58
+ private
59
+
60
+ def create_column_definition(*args)
61
+ Redshift::ColumnDefinition.new(*args)
62
+ end
63
+ end
64
+
65
+ class Table < ActiveRecord::ConnectionAdapters::Table
66
+ include ColumnMethods
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module ColumnDumper
7
+ # Adds +:array+ option to the default set provided by the
8
+ # AbstractAdapter
9
+ def prepare_column_options(column) # :nodoc:
10
+ spec = super
11
+ spec[:default] = "\"#{column.default_function}\"" if column.default_function
12
+ spec
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,422 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ class SchemaCreation < SchemaCreation
7
+ private
8
+
9
+ def visit_ColumnDefinition(o)
10
+ o.sql_type = type_to_sql(o.type, limit: o.limit, precision: o.precision, scale: o.scale)
11
+ super
12
+ end
13
+
14
+ def add_column_options!(sql, options)
15
+ column = options.fetch(:column) { return super }
16
+ if column.type == :uuid && options[:default] =~ /\(\)/
17
+ sql << " DEFAULT #{options[:default]}"
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+
24
+ module SchemaStatements
25
+ # Drops the database specified on the +name+ attribute
26
+ # and creates it again using the provided +options+.
27
+ def recreate_database(name, options = {}) # :nodoc:
28
+ drop_database(name)
29
+ create_database(name, options)
30
+ end
31
+
32
+ # Create a new Redshift database. Options include <tt>:owner</tt>, <tt>:template</tt>,
33
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
34
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
35
+ # <tt>:charset</tt> while Redshift uses <tt>:encoding</tt>).
36
+ #
37
+ # Example:
38
+ # create_database config[:database], config
39
+ # create_database 'foo_development', encoding: 'unicode'
40
+ def create_database(name, options = {})
41
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
42
+
43
+ option_string = options.inject('') do |memo, (key, value)|
44
+ next memo unless key == :owner
45
+
46
+ memo + " OWNER = \"#{value}\""
47
+ end
48
+
49
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
50
+ end
51
+
52
+ # Drops a Redshift database.
53
+ #
54
+ # Example:
55
+ # drop_database 'matt_development'
56
+ def drop_database(name) # :nodoc:
57
+ execute "DROP DATABASE #{quote_table_name(name)}"
58
+ end
59
+
60
+ # Returns an array of table names defined in the database.
61
+ def tables
62
+ select_values('SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))', 'SCHEMA')
63
+ end
64
+
65
+ # :nodoc
66
+ def data_sources
67
+ select_values(<<-SQL, 'SCHEMA')
68
+ SELECT c.relname
69
+ FROM pg_class c
70
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
71
+ WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
72
+ AND n.nspname = ANY (current_schemas(false))
73
+ SQL
74
+ end
75
+
76
+ # Returns true if table exists.
77
+ # If the schema is not specified as part of +name+ then it will only find tables within
78
+ # the current schema search path (regardless of permissions to access tables in other schemas)
79
+ def table_exists?(name)
80
+ name = Utils.extract_schema_qualified_name(name.to_s)
81
+ return false unless name.identifier
82
+
83
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
84
+ SELECT COUNT(*)
85
+ FROM pg_class c
86
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
87
+ WHERE c.relkind = 'r' -- (r)elation/table
88
+ AND c.relname = '#{name.identifier}'
89
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
90
+ SQL
91
+ end
92
+
93
+ def data_source_exists?(name)
94
+ name = Utils.extract_schema_qualified_name(name.to_s)
95
+ return false unless name.identifier
96
+
97
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
98
+ SELECT COUNT(*)
99
+ FROM pg_class c
100
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
101
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
102
+ AND c.relname = '#{name.identifier}'
103
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
104
+ SQL
105
+ end
106
+
107
+ def views # :nodoc:
108
+ select_values(<<-SQL, 'SCHEMA')
109
+ SELECT c.relname
110
+ FROM pg_class c
111
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
112
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
113
+ AND n.nspname = ANY (current_schemas(false))
114
+ SQL
115
+ end
116
+
117
+ def view_exists?(view_name) # :nodoc:
118
+ name = Utils.extract_schema_qualified_name(view_name.to_s)
119
+ return false unless name.identifier
120
+
121
+ select_values(<<-SQL, 'SCHEMA').any?
122
+ SELECT c.relname
123
+ FROM pg_class c
124
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
125
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
126
+ AND c.relname = '#{name.identifier}'
127
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
128
+ SQL
129
+ end
130
+
131
+ def drop_table(table_name, **options)
132
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
133
+ end
134
+
135
+ # Returns true if schema exists.
136
+ def schema_exists?(name)
137
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
138
+ end
139
+
140
+ def index_name_exists?(_table_name, _index_name)
141
+ false
142
+ end
143
+
144
+ # Returns an array of indexes for the given table.
145
+ def indexes(_table_name)
146
+ []
147
+ end
148
+
149
+ # Returns the list of all column definitions for a table.
150
+ def columns(table_name)
151
+ column_definitions(table_name.to_s).map do |column_name, type, default, notnull, oid, fmod|
152
+ default_value = extract_value_from_default(default)
153
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
154
+ default_function = extract_default_function(default_value, default)
155
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function)
156
+ end
157
+ end
158
+
159
+ def new_column(name, default, sql_type_metadata = nil, null = true, _table_name = nil, default_function = nil) # :nodoc:
160
+ RedshiftColumn.new(name, default, sql_type_metadata, null, default_function)
161
+ end
162
+
163
+ # Returns the current database name.
164
+ def current_database
165
+ select_value('select current_database()', 'SCHEMA')
166
+ end
167
+
168
+ # Returns the current schema name.
169
+ def current_schema
170
+ select_value('SELECT current_schema', 'SCHEMA')
171
+ end
172
+
173
+ # Returns the current database encoding format.
174
+ def encoding
175
+ select_value(
176
+ "SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA'
177
+ )
178
+ end
179
+
180
+ def collation; end
181
+
182
+ def ctype; end
183
+
184
+ # Returns an array of schema names.
185
+ def schema_names
186
+ select_values(<<-SQL, 'SCHEMA')
187
+ SELECT nspname
188
+ FROM pg_namespace
189
+ WHERE nspname !~ '^pg_.*'
190
+ AND nspname NOT IN ('information_schema')
191
+ ORDER by nspname;
192
+ SQL
193
+ end
194
+
195
+ # Creates a schema for the given schema name.
196
+ def create_schema(schema_name)
197
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
198
+ end
199
+
200
+ # Drops the schema for the given schema name.
201
+ def drop_schema(schema_name, **options)
202
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
203
+ end
204
+
205
+ # Sets the schema search path to a string of comma-separated schema names.
206
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
207
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
208
+ #
209
+ # This should be not be called manually but set in database.yml.
210
+ def schema_search_path=(schema_csv)
211
+ return unless schema_csv
212
+
213
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
214
+ @schema_search_path = schema_csv
215
+ end
216
+
217
+ # Returns the active schema search path.
218
+ def schema_search_path
219
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
220
+ end
221
+
222
+ # Returns the sequence name for a table's primary key or some other specified key.
223
+ def default_sequence_name(table_name, pk = nil) # :nodoc:
224
+ result = serial_sequence(table_name, pk || 'id')
225
+ return nil unless result
226
+
227
+ Utils.extract_schema_qualified_name(result).to_s
228
+ rescue ActiveRecord::StatementInvalid
229
+ Redshift::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
230
+ end
231
+
232
+ def serial_sequence(table, column)
233
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
234
+ end
235
+
236
+ def set_pk_sequence!(table, value); end
237
+
238
+ def reset_pk_sequence!(table, pk = nil, sequence = nil); end
239
+
240
+ def pk_and_sequence_for(_table) # :nodoc:
241
+ [nil, nil]
242
+ end
243
+
244
+ # Returns just a table's primary key
245
+ def primary_keys(table)
246
+ pks = query(<<-END_SQL, 'SCHEMA')
247
+ SELECT DISTINCT attr.attname
248
+ FROM pg_attribute attr
249
+ INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
250
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
251
+ WHERE cons.contype = 'p'
252
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
253
+ END_SQL
254
+ pks.present? ? pks[0] : pks
255
+ end
256
+
257
+ # Renames a table.
258
+ # Also renames a table's primary key sequence if the sequence name exists and
259
+ # matches the Active Record default.
260
+ #
261
+ # Example:
262
+ # rename_table('octopuses', 'octopi')
263
+ def rename_table(table_name, new_name, **options)
264
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
265
+ clear_cache!
266
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
267
+ end
268
+
269
+ def add_column(table_name, column_name, type, **options) # :nodoc:
270
+ clear_cache!
271
+ super
272
+ end
273
+
274
+ # Changes the column of a table.
275
+ def change_column(table_name, column_name, type, **options)
276
+ clear_cache!
277
+ quoted_table_name = quote_table_name(table_name)
278
+ sql_type = type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])
279
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
280
+ sql << " USING #{options[:using]}" if options[:using]
281
+ if options[:cast_as]
282
+ sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as],
283
+ limit: options[:limit], precision: options[:precision], scale: options[:scale])})"
284
+ end
285
+ execute sql
286
+
287
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
288
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
289
+ end
290
+
291
+ # Changes the default value of a table column.
292
+ def change_column_default(table_name, column_name, default_or_changes)
293
+ clear_cache!
294
+ column = column_for(table_name, column_name)
295
+ return unless column
296
+
297
+ default = extract_new_default_value(default_or_changes)
298
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
299
+ if default.nil?
300
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
301
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
302
+ execute alter_column_query % 'DROP DEFAULT'
303
+ else
304
+ execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
305
+ end
306
+ end
307
+
308
+ def change_column_null(table_name, column_name, null, default = nil)
309
+ clear_cache!
310
+ unless null || default.nil?
311
+ column = column_for(table_name, column_name)
312
+ if column
313
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(
314
+ default, column
315
+ )} WHERE #{quote_column_name(column_name)} IS NULL")
316
+ end
317
+ end
318
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
319
+ end
320
+
321
+ # Renames a column in a table.
322
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
323
+ clear_cache!
324
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
325
+ end
326
+
327
+ def add_index(table_name, column_name, **options); end
328
+
329
+ def remove_index!(table_name, index_name); end
330
+
331
+ def rename_index(table_name, old_name, new_name); end
332
+
333
+ def foreign_keys(table_name)
334
+ fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
335
+ SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
336
+ FROM pg_constraint c
337
+ JOIN pg_class t1 ON c.conrelid = t1.oid
338
+ JOIN pg_class t2 ON c.confrelid = t2.oid
339
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
340
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
341
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
342
+ WHERE c.contype = 'f'
343
+ AND t1.relname = #{quote(table_name)}
344
+ AND t3.nspname = ANY (current_schemas(false))
345
+ ORDER BY c.conname
346
+ SQL
347
+
348
+ fk_info.map do |row|
349
+ options = {
350
+ column: row['column'],
351
+ name: row['name'],
352
+ primary_key: row['primary_key']
353
+ }
354
+
355
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
356
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
357
+
358
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
359
+ end
360
+ end
361
+
362
+ FOREIGN_KEY_ACTIONS = {
363
+ 'c' => :cascade,
364
+ 'n' => :nullify,
365
+ 'r' => :restrict
366
+ }.freeze
367
+
368
+ def extract_foreign_key_action(specifier)
369
+ FOREIGN_KEY_ACTIONS[specifier]
370
+ end
371
+
372
+ def index_name_length
373
+ 63
374
+ end
375
+
376
+ # Maps logical Rails types to PostgreSQL-specific data types.
377
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
378
+ case type.to_s
379
+ when 'integer'
380
+ return 'integer' unless limit
381
+
382
+ case limit
383
+ when 1, 2 then 'smallint'
384
+ when nil, 3, 4 then 'integer'
385
+ when 5..8 then 'bigint'
386
+ else raise(ActiveRecordError,
387
+ "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
388
+ end
389
+ else
390
+ super
391
+ end
392
+ end
393
+
394
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
395
+ # requires that the ORDER BY include the distinct column.
396
+ def columns_for_distinct(columns, orders) # :nodoc:
397
+ order_columns = orders.compact_blank.map { |s|
398
+ # Convert Arel node to string
399
+ s = visitor.compile(s) unless s.is_a?(String)
400
+ # Remove any ASC/DESC modifiers
401
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
402
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
403
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
404
+
405
+ (order_columns << super).join(", ")
406
+ end
407
+
408
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
409
+ cast_type = get_oid_type(oid.to_i, fmod.to_i, column_name, sql_type)
410
+ simple_type = SqlTypeMetadata.new(
411
+ sql_type: sql_type,
412
+ type: cast_type.type,
413
+ limit: cast_type.limit,
414
+ precision: cast_type.precision,
415
+ scale: cast_type.scale
416
+ )
417
+ TypeMetadata.new(simple_type, oid: oid, fmod: fmod)
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ class TypeMetadata < DelegateClass(SqlTypeMetadata)
7
+ undef to_yaml if method_defined?(:to_yaml)
8
+
9
+ include Deduplicable
10
+
11
+ attr_reader :oid, :fmod
12
+
13
+ def initialize(type_metadata, oid: nil, fmod: nil)
14
+ super(type_metadata)
15
+ @oid = oid
16
+ @fmod = fmod
17
+ end
18
+
19
+ def ==(other)
20
+ other.is_a?(TypeMetadata) &&
21
+ __getobj__ == other.__getobj__ &&
22
+ oid == other.oid &&
23
+ fmod == other.fmod
24
+ end
25
+ alias eql? ==
26
+
27
+ def hash
28
+ TypeMetadata.hash ^
29
+ __getobj__.hash ^
30
+ oid.hash ^
31
+ fmod.hash
32
+ end
33
+
34
+ private
35
+ def deduplicated
36
+ __setobj__(__getobj__.deduplicate)
37
+ super
38
+ end
39
+ end
40
+ end
41
+ RedshiftTypeMetadata = Redshift::TypeMetadata
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ # Value Object to hold a schema qualified name.
7
+ # This is usually the name of a PostgreSQL relation but it can also represent
8
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
9
+ # double quoting.
10
+ class Name # :nodoc:
11
+ SEPARATOR = '.'
12
+ attr_reader :schema, :identifier
13
+
14
+ def initialize(schema, identifier)
15
+ @schema = unquote(schema)
16
+ @identifier = unquote(identifier)
17
+ end
18
+
19
+ def to_s
20
+ parts.join SEPARATOR
21
+ end
22
+
23
+ def quoted
24
+ if schema
25
+ PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
26
+ else
27
+ PG::Connection.quote_ident(identifier)
28
+ end
29
+ end
30
+
31
+ def ==(other)
32
+ other.class == self.class && other.parts == parts
33
+ end
34
+ alias eql? ==
35
+
36
+ def hash
37
+ parts.hash
38
+ end
39
+
40
+ protected
41
+
42
+ def unquote(part)
43
+ if part&.start_with?('"')
44
+ part[1..-2]
45
+ else
46
+ part
47
+ end
48
+ end
49
+
50
+ def parts
51
+ @parts ||= [@schema, @identifier].compact
52
+ end
53
+ end
54
+
55
+ module Utils # :nodoc:
56
+ module_function
57
+
58
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
59
+ # extracted from +string+.
60
+ # +schema+ is nil if not specified in +string+.
61
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
62
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
63
+ #
64
+ # * <tt>table_name</tt>
65
+ # * <tt>"table.name"</tt>
66
+ # * <tt>schema_name.table_name</tt>
67
+ # * <tt>schema_name."table.name"</tt>
68
+ # * <tt>"schema_name".table_name</tt>
69
+ # * <tt>"schema.name"."table name"</tt>
70
+ def extract_schema_qualified_name(string)
71
+ schema, table = string.scan(/[^".\s]+|"[^"]*"/)
72
+ if table.nil?
73
+ table = schema
74
+ schema = nil
75
+ end
76
+ Redshift::Name.new(schema, table)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end