postshift 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class RedshiftColumn < PostgreSQLColumn #:nodoc:
4
+ delegate :encoding, to: :sql_type_metadata
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module ReferentialIntegrity # :nodoc:
5
+ def disable_referential_integrity # :nodoc:
6
+ yield
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ if ActiveRecord.version < Gem::Version.new('5.1')
5
+ # All this to add 'encoding' to Structure
6
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment, :encoding)
7
+ # From PostgreSQL to maintain compatability
8
+ attr_accessor :array
9
+
10
+ # From Abstract to maintain compatability
11
+ def primary_key?
12
+ primary_key || type.to_sym == :primary_key
13
+ end
14
+ end
15
+
16
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
17
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods
18
+
19
+ def new_column_definition(name, type, options) # :nodoc:
20
+ super.tap do |column|
21
+ column.encoding = options[:encoding]
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def create_column_definition(name, type, options=nil)
28
+ ColumnDefinition.new name, type, options
29
+ end
30
+ end
31
+ else
32
+ class TableDefinition < ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
33
+ def primary_key(name, type=:primary_key, **options)
34
+ ints = %i(integer bigint)
35
+ options[:auto_increment] ||= true if ints.include?(type) && !options.key?(:default)
36
+ type = :primary_key if ints.include?(type) && options.delete(:auto_increment) == true
37
+ super
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module ColumnDumper
5
+ def column_spec_for_primary_key(column)
6
+ super.tap do |spec|
7
+ spec[:id] = ':primary_key' if column.sql_type == 'primary_key'
8
+ end
9
+ end
10
+
11
+ # Adds +:encoding+ option to the default set
12
+ def prepare_column_options(column)
13
+ super.tap do |spec|
14
+ spec[:encoding] = "'#{column.sql_type_metadata.encoding}'" if column.sql_type_metadata.encoding.present?
15
+ end
16
+ end
17
+
18
+ # Adds +:encoding+ as a valid migration key
19
+ def migration_keys
20
+ super + [:encoding]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,154 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ class SchemaCreation < PostgreSQL::SchemaCreation
5
+ private
6
+
7
+ def add_column_options!(sql, options)
8
+ sql = super
9
+ if (encoding = encoding_option(options)).present?
10
+ sql << " ENCODE #{encoding}"
11
+ end
12
+ sql
13
+ end
14
+
15
+ def encoding_option(options)
16
+ if ActiveRecord.version < Gem::Version.new('5.1')
17
+ options[:column].encoding
18
+ else
19
+ options[:encoding]
20
+ end
21
+ end
22
+ end
23
+
24
+ module SchemaStatements
25
+ # Create a new Redshift database. Options include <tt>:owner</tt> and <tt>:connection_limit</tt>
26
+ # Example:
27
+ # create_database config[:database], config
28
+ # create_database 'foo_development', encoding: 'unicode'
29
+ def create_database(name, options = {})
30
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
31
+
32
+ option_string = options.inject("") do |memo, (key, value)|
33
+ memo += case key
34
+ when :owner
35
+ " OWNER = \"#{value}\""
36
+ when :connection_limit
37
+ " CONNECTION LIMIT = #{value}"
38
+ else
39
+ ''
40
+ end
41
+ end
42
+
43
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
44
+ end
45
+
46
+ def create_table(table_name, comment: nil, **options)
47
+ options[:options] ||= ''
48
+ options[:options] += "DISTKEY(#{options.delete(:distkey)}) " if options.key?(:distkey)
49
+ options[:options] += "SORTKEY(#{options.delete(:sortkey)}) " if options.key?(:sortkey)
50
+ super
51
+ end
52
+
53
+ def indexes(*)
54
+ []
55
+ end
56
+
57
+ # Returns the list of all column definitions for a table.
58
+ def columns(table_name)
59
+ column_definitions(table_name.to_s).map do |column_name, type, default, notnull, oid, fmod, encoding|
60
+ default_value = extract_value_from_default(default)
61
+ type = determine_primary_key_type_conversion(type, default)
62
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod, encoding)
63
+ default_function = extract_default_function(default_value, default)
64
+ new_column(column_name, default_value, type_metadata, notnull == false, table_name, default_function)
65
+ end
66
+ end
67
+
68
+ def determine_primary_key_type_conversion(type, default)
69
+ return 'primary_key' if (type == 'integer' && default.to_s.starts_with?('"identity"'))
70
+ type
71
+ end
72
+
73
+ def new_column(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil) # :nodoc:
74
+ RedshiftColumn.new(name, default, sql_type_metadata, null, table_name, default_function)
75
+ end
76
+
77
+ def table_options(table_name) # :nodoc:
78
+ {}.tap do |options|
79
+ if (distkey = table_distkey(table_name)).present?
80
+ options[:distkey] = distkey
81
+ end
82
+ if (sortkey = table_sortkey(table_name)).present?
83
+ options[:sortkey] = sortkey
84
+ end
85
+ end
86
+ end
87
+
88
+ def table_distkey(table_name) # :nodoc:
89
+ select_value("SELECT \"column\" FROM pg_table_def WHERE tablename = #{quote(table_name)} AND distkey = true")
90
+ end
91
+
92
+ def table_sortkey(table_name) # :nodoc:
93
+ columns = select_values("SELECT \"column\" FROM pg_table_def WHERE tablename = #{quote(table_name)} AND sortkey > 0 ORDER BY sortkey ASC")
94
+ columns.present? ? columns.join(', ') : nil
95
+ end
96
+
97
+ # Returns just a table's primary key
98
+ def primary_keys(table)
99
+ pks = query(<<-end_sql, 'SCHEMA')
100
+ SELECT DISTINCT attr.attname
101
+ FROM pg_attribute attr
102
+ INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
103
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
104
+ WHERE cons.contype = 'p'
105
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
106
+ end_sql
107
+ pks.present? ? pks[0] : pks
108
+ end
109
+
110
+ # TODO: This entire method block for 't2.oid::regclass::text' to 't2.relname'
111
+ def foreign_keys(table_name)
112
+ fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
113
+ 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
114
+ FROM pg_constraint c
115
+ JOIN pg_class t1 ON c.conrelid = t1.oid
116
+ JOIN pg_class t2 ON c.confrelid = t2.oid
117
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
118
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
119
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
120
+ WHERE c.contype = 'f'
121
+ AND t1.relname = #{quote(table_name)}
122
+ AND t3.nspname = ANY (current_schemas(false))
123
+ ORDER BY c.conname
124
+ SQL
125
+
126
+ fk_info.map do |row|
127
+ options = {
128
+ column: row['column'],
129
+ name: row['name'],
130
+ primary_key: row['primary_key']
131
+ }
132
+
133
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
134
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
135
+
136
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
137
+ end
138
+ end
139
+
140
+ def fetch_type_metadata(column_name, sql_type, oid, fmod, encoding)
141
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
142
+ simple_type = SqlTypeMetadata.new(
143
+ sql_type: sql_type,
144
+ type: cast_type.type,
145
+ limit: cast_type.limit,
146
+ precision: cast_type.precision,
147
+ scale: cast_type.scale,
148
+ )
149
+ RedshiftSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod, encoding: encoding)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class RedshiftSQLTypeMetadata < PostgreSQLTypeMetadata
4
+ attr_reader :encoding
5
+
6
+ def initialize(type_metadata, oid: nil, fmod: nil, encoding: nil)
7
+ super(type_metadata, oid: oid, fmod: fmod)
8
+ @encoding = encoding unless encoding == 'none'
9
+ end
10
+
11
+ protected
12
+
13
+ def attributes_for_hash
14
+ super << encoding
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,220 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/module/deprecation'
3
+
4
+ require 'active_record'
5
+ require 'active_record/connection_adapters/postgresql_adapter'
6
+
7
+ require 'active_record/connection_adapters/redshift/column'
8
+ require 'active_record/connection_adapters/redshift/referential_integrity'
9
+ require 'active_record/connection_adapters/redshift/schema_definitions'
10
+ require 'active_record/connection_adapters/redshift/schema_dumper'
11
+ require 'active_record/connection_adapters/redshift/schema_statements'
12
+ require 'active_record/connection_adapters/redshift/type_metadata'
13
+
14
+ module ActiveRecord
15
+ module ConnectionHandling # :nodoc
16
+ RS_VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
17
+ :client_encoding, :options, :application_name, :fallback_application_name,
18
+ :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
19
+ :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
20
+ :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
21
+
22
+ # Establishes a connection to the database that's used by all Active Record objects
23
+ def redshift_connection(config)
24
+ conn_params = config.symbolize_keys
25
+
26
+ conn_params.delete_if { |_, v| v.nil? }
27
+
28
+ # Map ActiveRecords param names to PGs.
29
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
30
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
31
+
32
+ # Forward only valid config params to PGconn.connect.
33
+ conn_params.keep_if { |k, _| RS_VALID_CONN_PARAMS.include?(k) }
34
+
35
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
36
+ # so just pass a nil connection object for the time being.
37
+ ConnectionAdapters::RedshiftAdapter.new(nil, logger, conn_params, config)
38
+ end
39
+ end
40
+
41
+ module ConnectionAdapters
42
+ class RedshiftAdapter < PostgreSQLAdapter
43
+ ADAPTER_NAME = 'Redshift'.freeze
44
+
45
+ NATIVE_DATABASE_TYPES = {
46
+ primary_key: 'integer identity primary key',
47
+ string: { name: 'varchar' },
48
+ text: { name: 'varchar' },
49
+ integer: { name: 'integer' },
50
+ float: { name: 'float' },
51
+ decimal: { name: 'decimal' },
52
+ datetime: { name: 'timestamp' },
53
+ time: { name: 'timestamptz' },
54
+ date: { name: 'date' },
55
+ bigint: { name: 'bigint' },
56
+ boolean: { name: 'boolean' },
57
+ }.freeze
58
+
59
+ include Redshift::ColumnDumper
60
+ include Redshift::ReferentialIntegrity
61
+ include Redshift::SchemaStatements
62
+
63
+ def schema_creation # :nodoc:
64
+ Redshift::SchemaCreation.new self
65
+ end
66
+
67
+ def supports_index_sort_order?
68
+ false
69
+ end
70
+
71
+ def supports_partial_index?
72
+ false
73
+ end
74
+
75
+ def supports_expression_index?
76
+ false
77
+ end
78
+
79
+ def supports_transaction_isolation?
80
+ false
81
+ end
82
+
83
+ def supports_json?
84
+ false
85
+ end
86
+
87
+ def supports_savepoints?
88
+ false
89
+ end
90
+
91
+ def native_database_types #:nodoc:
92
+ NATIVE_DATABASE_TYPES
93
+ end
94
+
95
+ def supports_extensions?
96
+ false
97
+ end
98
+
99
+ def use_insert_returning?
100
+ false
101
+ end
102
+
103
+ def supports_advisory_locks?
104
+ false
105
+ end
106
+
107
+ def supports_ranges?
108
+ false
109
+ end
110
+
111
+ def supports_materialized_views?
112
+ false
113
+ end
114
+
115
+ def postgresql_version
116
+ # Will pass all inernal version support checks
117
+ Float::INFINITY
118
+ end
119
+
120
+ private
121
+
122
+ # TODO: Copied from PostgreSQL with minor registration changes. If broken out, could override segments, etc
123
+ def initialize_type_map(m) # :nodoc:
124
+ register_class_with_limit m, 'int2', Type::Integer
125
+ register_class_with_limit m, 'int4', Type::Integer
126
+ register_class_with_limit m, 'int8', Type::Integer
127
+ m.alias_type 'oid', 'int2'
128
+ m.register_type 'float4', Type::Float.new
129
+ m.alias_type 'float8', 'float4'
130
+ m.register_type 'text', Type::Text.new
131
+ register_class_with_limit m, 'varchar', Type::String
132
+ m.alias_type 'char', 'varchar'
133
+ m.alias_type 'name', 'varchar'
134
+ m.alias_type 'bpchar', 'varchar'
135
+ m.register_type 'bool', Type::Boolean.new
136
+ m.alias_type 'timestamptz', 'timestamp'
137
+ m.register_type 'date', Type::Date.new
138
+
139
+ m.register_type 'timestamp' do |_, _, sql_type|
140
+ precision = extract_precision(sql_type)
141
+ OID::DateTime.new(precision: precision)
142
+ end
143
+
144
+ m.register_type 'numeric' do |_, fmod, sql_type|
145
+ precision = extract_precision(sql_type)
146
+ scale = extract_scale(sql_type)
147
+
148
+ # The type for the numeric depends on the width of the field,
149
+ # so we'll do something special here.
150
+ #
151
+ # When dealing with decimal columns:
152
+ #
153
+ # places after decimal = fmod - 4 & 0xffff
154
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
155
+ if fmod && (fmod - 4 & 0xffff).zero?
156
+ # FIXME: Remove this class, and the second argument to
157
+ # lookups on PG
158
+ Type::DecimalWithoutScale.new(precision: precision)
159
+ else
160
+ OID::Decimal.new(precision: precision, scale: scale)
161
+ end
162
+ end
163
+ end
164
+
165
+ def configure_connection
166
+ if @config[:encoding]
167
+ @connection.set_client_encoding(@config[:encoding])
168
+ end
169
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
170
+
171
+ # SET statements from :variables config hash
172
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
173
+ variables = @config[:variables] || {}
174
+ variables.map do |k, v|
175
+ if v == ':default' || v == :default
176
+ # Sets the value to the global or compile default
177
+ execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
178
+ elsif !v.nil?
179
+ execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
180
+ end
181
+ end
182
+ end
183
+
184
+ # Returns the list of a table's column names, data types, and default values.
185
+ #
186
+ # The underlying query is roughly:
187
+ # SELECT column.name, column.type, default.value
188
+ # FROM column LEFT JOIN default
189
+ # ON column.table_id = default.table_id
190
+ # AND column.num = default.column_num
191
+ # WHERE column.table_id = get_table_id('table_name')
192
+ # AND column.num > 0
193
+ # AND NOT column.is_dropped
194
+ # ORDER BY column.num
195
+ #
196
+ # If the table name is not prefixed with a schema, the database will
197
+ # take the first match from the schema search path.
198
+ #
199
+ # Query implementation notes:
200
+ # - format_type includes the column size constraint, e.g. varchar(50)
201
+ # - ::regclass is a function that gives the id for a table name
202
+ def column_definitions(table_name) # :nodoc:
203
+ query(<<-end_sql, 'SCHEMA')
204
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
205
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
206
+ format_encoding(a.attencodingtype::integer)
207
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
208
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
209
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
210
+ AND a.attnum > 0 AND NOT a.attisdropped
211
+ ORDER BY a.attnum
212
+ end_sql
213
+ end
214
+
215
+ def create_table_definition(*args) # :nodoc:
216
+ Redshift::TableDefinition.new(*args)
217
+ end
218
+ end
219
+ end
220
+ end