activerecord-redshift-adapter-ng 0.9.0 → 0.9.1
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.
- checksums.yaml +4 -4
- data/LICENSE +0 -33
- data/README.md +12 -16
- data/lib/active_record/connection_adapters/redshift/column.rb +11 -1
- data/lib/active_record/connection_adapters/redshift/oid.rb +15 -0
- data/lib/active_record/connection_adapters/redshift/oid/array.rb +96 -0
- data/lib/active_record/connection_adapters/redshift/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/redshift/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/redshift/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +0 -9
- data/lib/active_record/connection_adapters/redshift/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/redshift/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/redshift/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/json.rb +1 -1
- data/lib/active_record/connection_adapters/redshift/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/redshift/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/redshift/oid/range.rb +76 -0
- data/lib/active_record/connection_adapters/redshift/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +42 -20
- data/lib/active_record/connection_adapters/redshift/oid/uuid.rb +28 -0
- data/lib/active_record/connection_adapters/redshift/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/redshift/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/redshift/quoting.rb +10 -0
- data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +15 -0
- data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +85 -0
- data/lib/active_record/connection_adapters/redshift/schema_statements.rb +171 -8
- data/lib/active_record/connection_adapters/redshift/utils.rb +4 -15
- data/lib/active_record/connection_adapters/redshift_adapter.rb +119 -38
- metadata +26 -9
@@ -14,6 +14,10 @@ module ActiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def add_column_options!(sql, options)
|
17
|
+
if options[:array] || options[:column].try(:array)
|
18
|
+
sql << '[]'
|
19
|
+
end
|
20
|
+
|
17
21
|
column = options.fetch(:column) { return super }
|
18
22
|
if column.type == :uuid && options[:default] =~ /\(\)/
|
19
23
|
sql << " DEFAULT #{options[:default]}"
|
@@ -39,7 +43,7 @@ module ActiveRecord
|
|
39
43
|
create_database(name, options)
|
40
44
|
end
|
41
45
|
|
42
|
-
# Create a new PostgreSQL database. Options include <tt>:owner</tt>,
|
46
|
+
# Create a new PostgreSQL database. Options include <tt>:owner</tt>,
|
43
47
|
# <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
|
44
48
|
# <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
45
49
|
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
|
@@ -54,6 +58,16 @@ module ActiveRecord
|
|
54
58
|
memo += case key
|
55
59
|
when :owner
|
56
60
|
" OWNER = \"#{value}\""
|
61
|
+
when :encoding
|
62
|
+
" ENCODING = '#{value}'"
|
63
|
+
when :collation
|
64
|
+
" LC_COLLATE = '#{value}'"
|
65
|
+
when :ctype
|
66
|
+
" LC_CTYPE = '#{value}'"
|
67
|
+
when :tablespace
|
68
|
+
" TABLESPACE = \"#{value}\""
|
69
|
+
when :connection_limit
|
70
|
+
" CONNECTION LIMIT = #{value}"
|
57
71
|
else
|
58
72
|
""
|
59
73
|
end
|
@@ -72,7 +86,7 @@ module ActiveRecord
|
|
72
86
|
|
73
87
|
# Returns the list of all tables in the schema search path or a specified schema.
|
74
88
|
def tables(name = nil)
|
75
|
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
89
|
+
query(<<-SQL, 'SCHEMA').map { |row| "#{row[0]}.#{row[1]}" }
|
76
90
|
SELECT tablename
|
77
91
|
FROM pg_tables
|
78
92
|
WHERE schemaname = ANY (current_schemas(false))
|
@@ -110,7 +124,16 @@ module ActiveRecord
|
|
110
124
|
end
|
111
125
|
|
112
126
|
def index_name_exists?(table_name, index_name, default)
|
113
|
-
|
127
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
128
|
+
SELECT COUNT(*)
|
129
|
+
FROM pg_class t
|
130
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
131
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
132
|
+
WHERE i.relkind = 'i'
|
133
|
+
AND i.relname = '#{index_name}'
|
134
|
+
AND t.relname = '#{table_name}'
|
135
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
136
|
+
SQL
|
114
137
|
end
|
115
138
|
|
116
139
|
# Returns an array of indexes for the given table.
|
@@ -151,10 +174,18 @@ module ActiveRecord
|
|
151
174
|
end_sql
|
152
175
|
end
|
153
176
|
|
177
|
+
# Returns the current database collation.
|
154
178
|
def collation
|
179
|
+
query(<<-end_sql, 'SCHEMA')[0][0]
|
180
|
+
SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
|
181
|
+
end_sql
|
155
182
|
end
|
156
183
|
|
184
|
+
# Returns the current database ctype.
|
157
185
|
def ctype
|
186
|
+
query(<<-end_sql, 'SCHEMA')[0][0]
|
187
|
+
SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
|
188
|
+
end_sql
|
158
189
|
end
|
159
190
|
|
160
191
|
# Returns an array of schema names.
|
@@ -195,6 +226,17 @@ module ActiveRecord
|
|
195
226
|
@schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
|
196
227
|
end
|
197
228
|
|
229
|
+
# Returns the current client message level.
|
230
|
+
def client_min_messages
|
231
|
+
query('SHOW client_min_messages', 'SCHEMA')[0][0]
|
232
|
+
end
|
233
|
+
|
234
|
+
# Set the client message level.
|
235
|
+
def client_min_messages=(level)
|
236
|
+
# can't set client_min_messages in redshift
|
237
|
+
# execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
|
238
|
+
end
|
239
|
+
|
198
240
|
# Returns the sequence name for a table's primary key or some other specified key.
|
199
241
|
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
200
242
|
result = serial_sequence(table_name, pk || 'id')
|
@@ -211,26 +253,113 @@ module ActiveRecord
|
|
211
253
|
result.rows.first.first
|
212
254
|
end
|
213
255
|
|
256
|
+
# Sets the sequence of a table's primary key to the specified value.
|
214
257
|
def set_pk_sequence!(table, value) #:nodoc:
|
258
|
+
pk, sequence = pk_and_sequence_for(table)
|
259
|
+
|
260
|
+
if pk
|
261
|
+
if sequence
|
262
|
+
quoted_sequence = quote_table_name(sequence)
|
263
|
+
|
264
|
+
select_value <<-end_sql, 'SCHEMA'
|
265
|
+
SELECT setval('#{quoted_sequence}', #{value})
|
266
|
+
end_sql
|
267
|
+
else
|
268
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
269
|
+
end
|
270
|
+
end
|
215
271
|
end
|
216
272
|
|
273
|
+
# Resets the sequence of a table's primary key to the maximum value.
|
217
274
|
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
275
|
+
unless pk and sequence
|
276
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
277
|
+
|
278
|
+
pk ||= default_pk
|
279
|
+
sequence ||= default_sequence
|
280
|
+
end
|
281
|
+
|
282
|
+
if @logger && pk && !sequence
|
283
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence"
|
284
|
+
end
|
285
|
+
|
286
|
+
if pk && sequence
|
287
|
+
quoted_sequence = quote_table_name(sequence)
|
288
|
+
|
289
|
+
select_value <<-end_sql, 'SCHEMA'
|
290
|
+
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
291
|
+
end_sql
|
292
|
+
end
|
218
293
|
end
|
219
294
|
|
295
|
+
# Returns a table's primary key and belonging sequence.
|
220
296
|
def pk_and_sequence_for(table) #:nodoc:
|
221
|
-
|
297
|
+
# First try looking for a sequence with a dependency on the
|
298
|
+
# given table's primary key.
|
299
|
+
result = query(<<-end_sql, 'SCHEMA')[0]
|
300
|
+
SELECT attr.attname, nsp.nspname, seq.relname
|
301
|
+
FROM pg_class seq,
|
302
|
+
pg_attribute attr,
|
303
|
+
pg_depend dep,
|
304
|
+
pg_constraint cons,
|
305
|
+
pg_namespace nsp
|
306
|
+
WHERE seq.oid = dep.objid
|
307
|
+
AND seq.relkind = 'S'
|
308
|
+
AND attr.attrelid = dep.refobjid
|
309
|
+
AND attr.attnum = dep.refobjsubid
|
310
|
+
AND attr.attrelid = cons.conrelid
|
311
|
+
AND attr.attnum = cons.conkey[1]
|
312
|
+
AND seq.relnamespace = nsp.oid
|
313
|
+
AND cons.contype = 'p'
|
314
|
+
AND dep.classid = 'pg_class'::regclass
|
315
|
+
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
316
|
+
end_sql
|
317
|
+
|
318
|
+
if result.nil? or result.empty?
|
319
|
+
result = query(<<-end_sql, 'SCHEMA')[0]
|
320
|
+
SELECT attr.attname, nsp.nspname,
|
321
|
+
CASE
|
322
|
+
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
|
323
|
+
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
324
|
+
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
325
|
+
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
326
|
+
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
327
|
+
END
|
328
|
+
FROM pg_class t
|
329
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
330
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
331
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
332
|
+
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
333
|
+
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
334
|
+
AND cons.contype = 'p'
|
335
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
336
|
+
end_sql
|
337
|
+
end
|
338
|
+
|
339
|
+
pk = result.shift
|
340
|
+
if result.last
|
341
|
+
[pk, Redshift::Name.new(*result)]
|
342
|
+
else
|
343
|
+
[pk, nil]
|
344
|
+
end
|
345
|
+
rescue
|
346
|
+
nil
|
222
347
|
end
|
223
348
|
|
224
349
|
# Returns just a table's primary key
|
225
350
|
def primary_key(table)
|
351
|
+
# Passing the env variable REDSHIFT_SCHEMA will allow the correct generation of id
|
352
|
+
# fields that are not of integer/uuid/serial type
|
353
|
+
return if ENV['REDSHIFT_SCHEMA']
|
226
354
|
pks = exec_query(<<-end_sql, 'SCHEMA').rows
|
227
355
|
SELECT DISTINCT attr.attname
|
228
356
|
FROM pg_attribute attr
|
229
357
|
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
|
230
|
-
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum =
|
358
|
+
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
|
231
359
|
WHERE cons.contype = 'p'
|
232
360
|
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
233
361
|
end_sql
|
362
|
+
|
234
363
|
return nil unless pks.count == 1
|
235
364
|
pks[0][0]
|
236
365
|
end
|
@@ -244,9 +373,21 @@ module ActiveRecord
|
|
244
373
|
def rename_table(table_name, new_name)
|
245
374
|
clear_cache!
|
246
375
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
376
|
+
pk, seq = pk_and_sequence_for(new_name)
|
377
|
+
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
|
378
|
+
new_seq = "#{new_name}_#{pk}_seq"
|
379
|
+
idx = "#{table_name}_pkey"
|
380
|
+
new_idx = "#{new_name}_pkey"
|
381
|
+
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
382
|
+
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
383
|
+
end
|
384
|
+
|
385
|
+
rename_table_indexes(table_name, new_name)
|
247
386
|
end
|
248
387
|
|
249
|
-
|
388
|
+
# Adds a new column to the named table.
|
389
|
+
# See TableDefinition#column for details of the options you can use.
|
390
|
+
def add_column(table_name, column_name, type, options = {})
|
250
391
|
clear_cache!
|
251
392
|
super
|
252
393
|
end
|
@@ -294,24 +435,30 @@ module ActiveRecord
|
|
294
435
|
end
|
295
436
|
|
296
437
|
# Renames a column in a table.
|
297
|
-
def rename_column(table_name, column_name, new_column_name)
|
438
|
+
def rename_column(table_name, column_name, new_column_name)
|
298
439
|
clear_cache!
|
299
440
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
300
441
|
rename_column_indexes(table_name, column_name, new_column_name)
|
301
442
|
end
|
302
443
|
|
303
444
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
445
|
+
# ignore
|
304
446
|
end
|
305
447
|
|
306
448
|
def remove_index!(table_name, index_name) #:nodoc:
|
449
|
+
# ignore
|
307
450
|
end
|
308
451
|
|
309
452
|
def rename_index(table_name, old_name, new_name)
|
453
|
+
validate_index_length!(table_name, new_name)
|
454
|
+
|
455
|
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
456
|
+
# ignore
|
310
457
|
end
|
311
458
|
|
312
459
|
def foreign_keys(table_name)
|
313
460
|
fk_info = select_all <<-SQL.strip_heredoc
|
314
|
-
SELECT t2.
|
461
|
+
SELECT t2.oid::regclass 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
|
315
462
|
FROM pg_constraint c
|
316
463
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
317
464
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
@@ -353,6 +500,22 @@ module ActiveRecord
|
|
353
500
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
354
501
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
355
502
|
case type.to_s
|
503
|
+
when 'boolean'
|
504
|
+
super(type)
|
505
|
+
when 'binary'
|
506
|
+
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
507
|
+
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
508
|
+
case limit
|
509
|
+
when nil, 0..0x3fffffff; super(type)
|
510
|
+
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
511
|
+
end
|
512
|
+
when 'text'
|
513
|
+
# PostgreSQL doesn't support limits on text columns.
|
514
|
+
# The hard limit is 1Gb, according to section 8.3 in the manual.
|
515
|
+
case limit
|
516
|
+
when nil, 0..0x3fffffff; super(type)
|
517
|
+
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
518
|
+
end
|
356
519
|
when 'integer'
|
357
520
|
return 'integer' unless limit
|
358
521
|
|
@@ -18,11 +18,7 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def quoted
|
21
|
-
|
22
|
-
PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
|
23
|
-
else
|
24
|
-
PGconn.quote_ident(identifier)
|
25
|
-
end
|
21
|
+
parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR
|
26
22
|
end
|
27
23
|
|
28
24
|
def ==(o)
|
@@ -36,11 +32,8 @@ module ActiveRecord
|
|
36
32
|
|
37
33
|
protected
|
38
34
|
def unquote(part)
|
39
|
-
|
40
|
-
|
41
|
-
else
|
42
|
-
part
|
43
|
-
end
|
35
|
+
return unless part
|
36
|
+
part.gsub(/(^"|"$)/,'')
|
44
37
|
end
|
45
38
|
|
46
39
|
def parts
|
@@ -64,11 +57,7 @@ module ActiveRecord
|
|
64
57
|
# * <tt>"schema_name".table_name</tt>
|
65
58
|
# * <tt>"schema.name"."table name"</tt>
|
66
59
|
def extract_schema_qualified_name(string)
|
67
|
-
|
68
|
-
if table.nil?
|
69
|
-
table = schema
|
70
|
-
schema = nil
|
71
|
-
end
|
60
|
+
table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse
|
72
61
|
Redshift::Name.new(schema, table)
|
73
62
|
end
|
74
63
|
end
|
@@ -13,14 +13,14 @@ require 'active_record/connection_adapters/redshift/database_statements'
|
|
13
13
|
require 'arel/visitors/bind_visitor'
|
14
14
|
|
15
15
|
# Make sure we're using pg high enough for PGResult#values
|
16
|
-
gem 'pg', '
|
16
|
+
gem 'pg', '~> 0.15'
|
17
17
|
require 'pg'
|
18
18
|
|
19
19
|
require 'ipaddr'
|
20
20
|
|
21
21
|
module ActiveRecord
|
22
22
|
module ConnectionHandling # :nodoc:
|
23
|
-
|
23
|
+
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
|
24
24
|
:client_encoding, :options, :application_name, :fallback_application_name,
|
25
25
|
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
|
26
26
|
:tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
|
@@ -37,7 +37,7 @@ module ActiveRecord
|
|
37
37
|
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
38
38
|
|
39
39
|
# Forward only valid config params to PGconn.connect.
|
40
|
-
conn_params.keep_if { |k, _|
|
40
|
+
conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
|
41
41
|
|
42
42
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
43
43
|
# so just pass a nil connection object for the time being.
|
@@ -64,7 +64,8 @@ module ActiveRecord
|
|
64
64
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
65
65
|
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
66
66
|
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
67
|
-
# * <tt>:insert_returning</tt> -
|
67
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
68
|
+
# defaults to true.
|
68
69
|
#
|
69
70
|
# Any further options are used as connection parameters to libpq. See
|
70
71
|
# http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
|
@@ -76,17 +77,40 @@ module ActiveRecord
|
|
76
77
|
ADAPTER_NAME = 'Redshift'.freeze
|
77
78
|
|
78
79
|
NATIVE_DATABASE_TYPES = {
|
79
|
-
primary_key: "integer identity
|
80
|
-
|
81
|
-
|
80
|
+
primary_key: "integer identity",
|
81
|
+
bigserial: { name: "bigint" },
|
82
|
+
string: { name: "character varying" },
|
83
|
+
text: { name: "text" },
|
82
84
|
integer: { name: "integer" },
|
83
85
|
float: { name: "float" },
|
84
86
|
decimal: { name: "decimal" },
|
85
87
|
datetime: { name: "timestamp" },
|
86
88
|
time: { name: "time" },
|
87
89
|
date: { name: "date" },
|
88
|
-
|
90
|
+
daterange: { name: "daterange" },
|
91
|
+
numrange: { name: "numrange" },
|
92
|
+
tsrange: { name: "tsrange" },
|
93
|
+
tstzrange: { name: "tstzrange" },
|
94
|
+
int4range: { name: "int4range" },
|
95
|
+
int8range: { name: "int8range" },
|
96
|
+
binary: { name: "bytea" },
|
89
97
|
boolean: { name: "boolean" },
|
98
|
+
bigint: { name: "bigint" },
|
99
|
+
xml: { name: "xml" },
|
100
|
+
tsvector: { name: "tsvector" },
|
101
|
+
hstore: { name: "hstore" },
|
102
|
+
inet: { name: "inet" },
|
103
|
+
cidr: { name: "cidr" },
|
104
|
+
macaddr: { name: "macaddr" },
|
105
|
+
uuid: { name: "uuid" },
|
106
|
+
json: { name: "json" },
|
107
|
+
jsonb: { name: "jsonb" },
|
108
|
+
ltree: { name: "ltree" },
|
109
|
+
citext: { name: "citext" },
|
110
|
+
point: { name: "point" },
|
111
|
+
bit: { name: "bit" },
|
112
|
+
bit_varying: { name: "bit varying" },
|
113
|
+
money: { name: "money" },
|
90
114
|
}
|
91
115
|
|
92
116
|
OID = Redshift::OID #:nodoc:
|
@@ -95,6 +119,7 @@ module ActiveRecord
|
|
95
119
|
include Redshift::ReferentialIntegrity
|
96
120
|
include Redshift::SchemaStatements
|
97
121
|
include Redshift::DatabaseStatements
|
122
|
+
include Savepoints
|
98
123
|
|
99
124
|
def schema_creation # :nodoc:
|
100
125
|
Redshift::SchemaCreation.new self
|
@@ -104,10 +129,16 @@ module ActiveRecord
|
|
104
129
|
# AbstractAdapter
|
105
130
|
def prepare_column_options(column, types) # :nodoc:
|
106
131
|
spec = super
|
132
|
+
spec[:array] = 'true' if column.respond_to?(:array) && column.array
|
107
133
|
spec[:default] = "\"#{column.default_function}\"" if column.default_function
|
108
134
|
spec
|
109
135
|
end
|
110
136
|
|
137
|
+
# Adds +:array+ as a valid migration key
|
138
|
+
def migration_keys
|
139
|
+
super + [:array]
|
140
|
+
end
|
141
|
+
|
111
142
|
# Returns +true+, since this connection adapter supports prepared statement
|
112
143
|
# caching.
|
113
144
|
def supports_statement_cache?
|
@@ -115,15 +146,15 @@ module ActiveRecord
|
|
115
146
|
end
|
116
147
|
|
117
148
|
def supports_index_sort_order?
|
118
|
-
|
149
|
+
true
|
119
150
|
end
|
120
151
|
|
121
152
|
def supports_partial_index?
|
122
|
-
|
153
|
+
true
|
123
154
|
end
|
124
155
|
|
125
156
|
def supports_transaction_isolation?
|
126
|
-
|
157
|
+
true
|
127
158
|
end
|
128
159
|
|
129
160
|
def supports_foreign_keys?
|
@@ -196,8 +227,13 @@ module ActiveRecord
|
|
196
227
|
super(connection, logger)
|
197
228
|
|
198
229
|
@visitor = Arel::Visitors::PostgreSQL.new self
|
199
|
-
|
230
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
231
|
+
@prepared_statements = true
|
232
|
+
else
|
233
|
+
@prepared_statements = false
|
234
|
+
end
|
200
235
|
|
236
|
+
connection_parameters.delete :prepared_statements
|
201
237
|
@connection_parameters, @config = connection_parameters, config
|
202
238
|
|
203
239
|
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
@@ -269,14 +305,6 @@ module ActiveRecord
|
|
269
305
|
true
|
270
306
|
end
|
271
307
|
|
272
|
-
# Enable standard-conforming strings if available.
|
273
|
-
def set_standard_conforming_strings
|
274
|
-
old, self.client_min_messages = client_min_messages, 'panic'
|
275
|
-
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
276
|
-
ensure
|
277
|
-
self.client_min_messages = old
|
278
|
-
end
|
279
|
-
|
280
308
|
def supports_ddl_transactions?
|
281
309
|
true
|
282
310
|
end
|
@@ -285,30 +313,46 @@ module ActiveRecord
|
|
285
313
|
true
|
286
314
|
end
|
287
315
|
|
316
|
+
# Returns true if pg > 9.1
|
288
317
|
def supports_extensions?
|
289
|
-
|
318
|
+
redshift_version >= 90100
|
290
319
|
end
|
291
320
|
|
321
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
292
322
|
def supports_ranges?
|
293
|
-
|
323
|
+
redshift_version >= 90200
|
294
324
|
end
|
295
325
|
|
296
326
|
def supports_materialized_views?
|
297
|
-
|
298
|
-
end
|
299
|
-
|
300
|
-
def supports_import?
|
301
|
-
true
|
327
|
+
redshift_version >= 90300
|
302
328
|
end
|
303
329
|
|
304
330
|
def enable_extension(name)
|
331
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
332
|
+
reload_type_map
|
333
|
+
}
|
305
334
|
end
|
306
335
|
|
307
336
|
def disable_extension(name)
|
337
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
338
|
+
reload_type_map
|
339
|
+
}
|
308
340
|
end
|
309
341
|
|
310
342
|
def extension_enabled?(name)
|
311
|
-
|
343
|
+
if supports_extensions?
|
344
|
+
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
|
345
|
+
'SCHEMA'
|
346
|
+
res.cast_values.first
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def extensions
|
351
|
+
if supports_extensions?
|
352
|
+
exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
|
353
|
+
else
|
354
|
+
super
|
355
|
+
end
|
312
356
|
end
|
313
357
|
|
314
358
|
# Returns the configured supported identifier length supported by PostgreSQL
|
@@ -356,13 +400,17 @@ module ActiveRecord
|
|
356
400
|
@connection.server_version
|
357
401
|
end
|
358
402
|
|
403
|
+
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
|
404
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
405
|
+
UNIQUE_VIOLATION = "23505"
|
406
|
+
|
359
407
|
def translate_exception(exception, message)
|
360
408
|
return exception unless exception.respond_to?(:result)
|
361
409
|
|
362
|
-
case exception.
|
363
|
-
when
|
410
|
+
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
411
|
+
when UNIQUE_VIOLATION
|
364
412
|
RecordNotUnique.new(message, exception)
|
365
|
-
when
|
413
|
+
when FOREIGN_KEY_VIOLATION
|
366
414
|
InvalidForeignKey.new(message, exception)
|
367
415
|
else
|
368
416
|
super
|
@@ -377,7 +425,11 @@ module ActiveRecord
|
|
377
425
|
end
|
378
426
|
|
379
427
|
type_map.fetch(oid, fmod, sql_type) {
|
380
|
-
|
428
|
+
# oid=2205 maps to t2.oid::regclass, which seems to do fine as string
|
429
|
+
if oid != 2205
|
430
|
+
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
|
431
|
+
end
|
432
|
+
|
381
433
|
Type::Value.new.tap do |cast_type|
|
382
434
|
type_map.register_type(oid, cast_type)
|
383
435
|
end
|
@@ -397,10 +449,36 @@ module ActiveRecord
|
|
397
449
|
m.alias_type 'name', 'varchar'
|
398
450
|
m.alias_type 'bpchar', 'varchar'
|
399
451
|
m.register_type 'bool', Type::Boolean.new
|
452
|
+
register_class_with_limit m, 'bit', OID::Bit
|
453
|
+
register_class_with_limit m, 'varbit', OID::BitVarying
|
400
454
|
m.alias_type 'timestamptz', 'timestamp'
|
401
455
|
m.register_type 'date', OID::Date.new
|
402
456
|
m.register_type 'time', OID::Time.new
|
403
457
|
|
458
|
+
m.register_type 'money', OID::Money.new
|
459
|
+
m.register_type 'bytea', OID::Bytea.new
|
460
|
+
m.register_type 'point', OID::Point.new
|
461
|
+
m.register_type 'hstore', OID::Hstore.new
|
462
|
+
m.register_type 'json', OID::Json.new
|
463
|
+
m.register_type 'jsonb', OID::Jsonb.new
|
464
|
+
m.register_type 'cidr', OID::Cidr.new
|
465
|
+
m.register_type 'inet', OID::Inet.new
|
466
|
+
m.register_type 'uuid', OID::Uuid.new
|
467
|
+
m.register_type 'xml', OID::Xml.new
|
468
|
+
m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
|
469
|
+
m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
|
470
|
+
m.register_type 'citext', OID::SpecializedString.new(:citext)
|
471
|
+
m.register_type 'ltree', OID::SpecializedString.new(:ltree)
|
472
|
+
|
473
|
+
# FIXME: why are we keeping these types as strings?
|
474
|
+
m.alias_type 'interval', 'varchar'
|
475
|
+
m.alias_type 'path', 'varchar'
|
476
|
+
m.alias_type 'line', 'varchar'
|
477
|
+
m.alias_type 'polygon', 'varchar'
|
478
|
+
m.alias_type 'circle', 'varchar'
|
479
|
+
m.alias_type 'lseg', 'varchar'
|
480
|
+
m.alias_type 'box', 'varchar'
|
481
|
+
|
404
482
|
m.register_type 'timestamp' do |_, _, sql_type|
|
405
483
|
precision = extract_precision(sql_type)
|
406
484
|
OID::DateTime.new(precision: precision)
|
@@ -450,7 +528,7 @@ module ActiveRecord
|
|
450
528
|
when 'true', 'false'
|
451
529
|
default
|
452
530
|
# Numeric types
|
453
|
-
when /\A\(?(-?\d+(\.\d*)?)\)?\z/
|
531
|
+
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
|
454
532
|
$1
|
455
533
|
# Object identifier types
|
456
534
|
when /\A-?\d+\z/
|
@@ -471,8 +549,6 @@ module ActiveRecord
|
|
471
549
|
end
|
472
550
|
|
473
551
|
def load_additional_types(type_map, oids = nil) # :nodoc:
|
474
|
-
initializer = OID::TypeMapInitializer.new(type_map)
|
475
|
-
|
476
552
|
if supports_ranges?
|
477
553
|
query = <<-SQL
|
478
554
|
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
@@ -490,9 +566,9 @@ module ActiveRecord
|
|
490
566
|
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
|
491
567
|
end
|
492
568
|
|
493
|
-
|
494
|
-
|
495
|
-
|
569
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
570
|
+
records = execute(query, 'SCHEMA')
|
571
|
+
initializer.run(records)
|
496
572
|
end
|
497
573
|
|
498
574
|
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
@@ -567,6 +643,11 @@ module ActiveRecord
|
|
567
643
|
def connect
|
568
644
|
@connection = PGconn.connect(@connection_parameters)
|
569
645
|
|
646
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
647
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
648
|
+
# should know about this but can't detect it there, so deal with it here.
|
649
|
+
OID::Money.precision = (redshift_version >= 80300) ? 19 : 10
|
650
|
+
|
570
651
|
configure_connection
|
571
652
|
rescue ::PG::Error => error
|
572
653
|
if error.message.include?("does not exist")
|