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
@@ -1,1290 +1,17 @@
1
- require 'active_record/connection_adapters/abstract_adapter'
2
- require 'active_support/core_ext/object/blank'
3
- require 'active_record/connection_adapters/statement_pool'
4
- require 'arel/visitors/bind_visitor'
1
+ # frozen_string_literal: true
5
2
 
6
- # Make sure we're using pg high enough for PGResult#values
7
- gem 'pg', '~> 0.11'
8
3
  require 'pg'
9
4
 
10
- module ActiveRecord
11
- class Base
12
- # Establishes a connection to the database that's used by all Active Record objects
13
- def self.redshift_connection(config) # :nodoc:
14
- config = config.symbolize_keys
15
- host = config[:host]
16
- port = config[:port] || 5432
17
- username = config[:username].to_s if config[:username]
18
- password = config[:password].to_s if config[:password]
19
-
20
- if config.key?(:database)
21
- database = config[:database]
22
- else
23
- raise ArgumentError, "No database specified. Missing argument: database."
24
- end
25
-
26
- # The postgres drivers don't allow the creation of an unconnected PGconn object,
27
- # so just pass a nil connection object for the time being.
28
- ConnectionAdapters::RedshiftAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
29
- end
30
- end
31
-
32
- module ConnectionAdapters
33
- # Redshift-specific extensions to column definitions in a table.
34
- class RedshiftColumn < Column #:nodoc:
35
- attr_accessor :timezone
36
- # Instantiates a new Redshift column definition in a table.
37
- def initialize(name, default, sql_type = nil, null = true, timezone = nil)
38
- self.timezone = timezone
39
- super(name, self.class.extract_value_from_default(default), sql_type, null)
40
- end
41
-
42
- def ==(other)
43
- name == other.name && default == other.default && sql_type == other.sql_type && null == other.null
44
- end
45
-
46
- def type_cast(value)
47
- return nil if value.nil?
48
- return coder.load(value) if encoded?
49
-
50
- if timezone && [:datetime, :timestamp].include?(type)
51
- self.class.string_to_time_with_timezone(string, timezone)
52
- else
53
- super
54
- end
55
- end
56
-
57
- def type_cast_code(var_name)
58
- if timezone && [:datetime, :timestamp].include?(type)
59
- "#{self.class.name}.string_to_time_with_timezone(#{var_name},'#{timezone}')"
60
- else
61
- super
62
- end
63
- end
64
-
65
- # :stopdoc:
66
- class << self
67
- attr_accessor :money_precision
68
- def string_to_time(string)
69
- return string unless String === string
70
-
71
- case string
72
- when 'infinity' then 1.0 / 0.0
73
- when '-infinity' then -1.0 / 0.0
74
- else
75
- super
76
- end
77
- end
78
-
79
- def string_to_time_with_timezone(string, timezone)
80
- if string =~ self::Format::ISO_DATETIME
81
- Time.parse("#{string} #{timezone}")
82
- else
83
- string_to_time(string)
84
- end
85
- end
86
- end
87
- # :startdoc:
88
-
89
- private
90
- def extract_limit(sql_type)
91
- case sql_type
92
- when /^bigint/i; 8
93
- when /^smallint/i; 2
94
- else super
95
- end
96
- end
97
-
98
- # Extracts the scale from Redshift-specific data types.
99
- def extract_scale(sql_type)
100
- # Money type has a fixed scale of 2.
101
- sql_type =~ /^money/ ? 2 : super
102
- end
103
-
104
- # Extracts the precision from Redshift-specific data types.
105
- def extract_precision(sql_type)
106
- if sql_type == 'money'
107
- self.class.money_precision
108
- else
109
- super
110
- end
111
- end
112
-
113
- # Maps Redshift-specific data types to logical Rails types.
114
- def simplified_type(field_type)
115
- case field_type
116
- # Numeric and monetary types
117
- when /^(?:real|double precision)$/
118
- :float
119
- # Monetary types
120
- when 'money'
121
- :decimal
122
- # Character types
123
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
124
- :string
125
- # Binary data types
126
- when 'bytea'
127
- :binary
128
- # Date/time types
129
- when /^timestamp with(?:out)? time zone$/
130
- :datetime
131
- when 'interval'
132
- :string
133
- # Geometric types
134
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
135
- :string
136
- # Network address types
137
- when /^(?:cidr|inet|macaddr)$/
138
- :string
139
- # Bit strings
140
- when /^bit(?: varying)?(?:\(\d+\))?$/
141
- :string
142
- # XML type
143
- when 'xml'
144
- :xml
145
- # tsvector type
146
- when 'tsvector'
147
- :tsvector
148
- # Arrays
149
- when /^\D+\[\]$/
150
- :string
151
- # Object identifier types
152
- when 'oid'
153
- :integer
154
- # UUID type
155
- when 'uuid'
156
- :string
157
- # Small and big integer types
158
- when /^(?:small|big)int$/
159
- :integer
160
- # Pass through all types that are not specific to Redshift.
161
- else
162
- super
163
- end
164
- end
165
-
166
- # Extracts the value from a Redshift column default definition.
167
- def self.extract_value_from_default(default)
168
- case default
169
- # This is a performance optimization for Ruby 1.9.2 in development.
170
- # If the value is nil, we return nil straight away without checking
171
- # the regular expressions. If we check each regular expression,
172
- # Regexp#=== will call NilClass#to_str, which will trigger
173
- # method_missing (defined by whiny nil in ActiveSupport) which
174
- # makes this method very very slow.
175
- when NilClass
176
- nil
177
- # Numeric types
178
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
179
- $1
180
- # Character types
181
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
182
- $1
183
- # Binary data types
184
- when /\A'(.*)'::bytea\z/m
185
- $1
186
- # Date/time types
187
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
188
- $1
189
- when /\A'(.*)'::interval\z/
190
- $1
191
- # Boolean type
192
- when 'true'
193
- true
194
- when 'false'
195
- false
196
- # Geometric types
197
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
198
- $1
199
- # Network address types
200
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
201
- $1
202
- # Bit string types
203
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
204
- $1
205
- # XML type
206
- when /\A'(.*)'::xml\z/m
207
- $1
208
- # Arrays
209
- when /\A'(.*)'::"?\D+"?\[\]\z/
210
- $1
211
- # Object identifier types
212
- when /\A-?\d+\z/
213
- $1
214
- else
215
- # Anything else is blank, some user type, or some function
216
- # and we can't know the value of that, so return nil.
217
- nil
218
- end
219
- end
220
- end
221
-
222
- # The Redshift adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
223
- # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
224
- #
225
- # Options:
226
- #
227
- # * <tt>:host</tt> - Defaults to "localhost".
228
- # * <tt>:port</tt> - Defaults to 5432.
229
- # * <tt>:username</tt> - Defaults to nothing.
230
- # * <tt>:password</tt> - Defaults to nothing.
231
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
232
- # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
233
- # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
234
- # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
235
- # <encoding></tt> call on the connection.
236
- class RedshiftAdapter < AbstractAdapter
237
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
238
- def xml(*args)
239
- options = args.extract_options!
240
- column(args[0], 'xml', options)
241
- end
242
-
243
- def tsvector(*args)
244
- options = args.extract_options!
245
- column(args[0], 'tsvector', options)
246
- end
247
-
248
- def identity(name)
249
- column(name, :identity)
250
- end
251
- end
252
-
253
- ADAPTER_NAME = 'Redshift'
254
-
255
- NATIVE_DATABASE_TYPES = {
256
- :primary_key => "bigint primary key",
257
- :identity => "bigint identity primary key",
258
- :string => { :name => "character varying", :limit => 255 },
259
- :text => { :name => "text" },
260
- :integer => { :name => "integer" },
261
- :float => { :name => "float" },
262
- :decimal => { :name => "decimal" },
263
- :datetime => { :name => "timestamp" },
264
- :timestamp => { :name => "timestamp" },
265
- :time => { :name => "time" },
266
- :date => { :name => "date" },
267
- :binary => { :name => "bytea" },
268
- :boolean => { :name => "boolean" },
269
- :xml => { :name => "xml" },
270
- :tsvector => { :name => "tsvector" }
271
- }
272
-
273
- # Returns 'Redshift' as adapter name for identification purposes.
274
- def adapter_name
275
- ADAPTER_NAME
276
- end
277
-
278
- # Returns +true+, since this connection adapter supports prepared statement
279
- # caching.
280
- def supports_statement_cache?
281
- true
282
- end
283
-
284
- def supports_index_sort_order?
285
- true
286
- end
287
-
288
- class StatementPool < ConnectionAdapters::StatementPool
289
- def initialize(connection, max)
290
- super
291
- @counter = 0
292
- @cache = Hash.new { |h,pid| h[pid] = {} }
293
- end
294
-
295
- def each(&block); cache.each(&block); end
296
- def key?(key); cache.key?(key); end
297
- def [](key); cache[key]; end
298
- def length; cache.length; end
299
-
300
- def next_key
301
- "a#{@counter + 1}"
302
- end
303
-
304
- def []=(sql, key)
305
- while @max <= cache.size
306
- dealloc(cache.shift.last)
307
- end
308
- @counter += 1
309
- cache[sql] = key
310
- end
311
-
312
- def clear
313
- cache.each_value do |stmt_key|
314
- dealloc stmt_key
315
- end
316
- cache.clear
317
- end
318
-
319
- def delete(sql_key)
320
- dealloc cache[sql_key]
321
- cache.delete sql_key
322
- end
323
-
324
- private
325
- def cache
326
- @cache[$$]
327
- end
328
-
329
- def dealloc(key)
330
- @connection.query "DEALLOCATE #{key}" if connection_active?
331
- end
332
-
333
- def connection_active?
334
- @connection.status == PGconn::CONNECTION_OK
335
- rescue PGError
336
- false
337
- end
338
- end
339
-
340
- class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
341
- include Arel::Visitors::BindVisitor
342
- end
343
-
344
- # Initializes and connects a Redshift adapter.
345
- def initialize(connection, logger, connection_parameters, config)
346
- super(connection, logger)
347
-
348
- if config.fetch(:prepared_statements) { true }
349
- @visitor = Arel::Visitors::PostgreSQL.new self
350
- else
351
- @visitor = BindSubstitution.new self
352
- end
353
-
354
- connection_parameters.delete :prepared_statements
355
-
356
- @connection_parameters, @config = connection_parameters, config
357
-
358
- # @local_tz is initialized as nil to avoid warnings when connect tries to use it
359
- @local_tz = nil
360
- @table_alias_length = nil
361
-
362
- connect
363
- @statements = StatementPool.new @connection,
364
- config.fetch(:statement_limit) { 1000 }
365
-
366
- if redshift_version < 80002
367
- raise "Your version of Redshift (#{redshift_version}) is too old, please upgrade!"
368
- end
369
-
370
- @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
371
- end
372
-
373
- # Clears the prepared statements cache.
374
- def clear_cache!
375
- @statements.clear
376
- end
377
-
378
- # Is this connection alive and ready for queries?
379
- def active?
380
- @connection.query 'SELECT 1'
381
- true
382
- rescue PGError
383
- false
384
- end
385
-
386
- # Close then reopen the connection.
387
- def reconnect!
388
- clear_cache!
389
- @connection.reset
390
- @open_transactions = 0
391
- configure_connection
392
- end
393
-
394
- def reset!
395
- clear_cache!
396
- super
397
- end
398
-
399
- # Disconnects from the database if already connected. Otherwise, this
400
- # method does nothing.
401
- def disconnect!
402
- clear_cache!
403
- @connection.close rescue nil
404
- end
405
-
406
- def native_database_types #:nodoc:
407
- NATIVE_DATABASE_TYPES
408
- end
409
-
410
- # Returns true, since this connection adapter supports migrations.
411
- def supports_migrations?
412
- true
413
- end
414
-
415
- # Does Redshift support finding primary key on non-Active Record tables?
416
- def supports_primary_key? #:nodoc:
417
- true
418
- end
419
-
420
- def supports_insert_with_returning?
421
- false
422
- end
423
-
424
- def supports_ddl_transactions?
425
- true
426
- end
427
-
428
- # Returns true, since this connection adapter supports savepoints.
429
- def supports_savepoints?
430
- true
431
- end
432
-
433
- # Returns true.
434
- def supports_explain?
435
- true
436
- end
437
-
438
- # Returns the configured supported identifier length supported by Redshift
439
- def table_alias_length
440
- @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
441
- end
442
-
443
- # QUOTING ==================================================
444
-
445
- # Escapes binary strings for bytea input to the database.
446
- def escape_bytea(value)
447
- @connection.escape_bytea(value) if value
448
- end
449
-
450
- # Unescapes bytea output from a database to the binary string it represents.
451
- # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
452
- # on escaped binary output from database drive.
453
- def unescape_bytea(value)
454
- @connection.unescape_bytea(value) if value
455
- end
456
-
457
- # Quotes Redshift-specific data types for SQL input.
458
- def quote(value, column = nil) #:nodoc:
459
- return super unless column
460
-
461
- case value
462
- when Float
463
- return super unless value.infinite? && column.type == :datetime
464
- "'#{value.to_s.downcase}'"
465
- when Numeric
466
- return super unless column.sql_type == 'money'
467
- # Not truly string input, so doesn't require (or allow) escape string syntax.
468
- "'#{value}'"
469
- when String
470
- case column.sql_type
471
- when 'bytea' then "'#{escape_bytea(value)}'"
472
- when 'xml' then "xml '#{quote_string(value)}'"
473
- when /^bit/
474
- case value
475
- when /^[01]*$/ then "B'#{value}'" # Bit-string notation
476
- when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
477
- end
478
- else
479
- super
480
- end
481
- else
482
- super
483
- end
484
- end
485
-
486
- def type_cast(value, column)
487
- return super unless column
488
-
489
- case value
490
- when String
491
- return super unless 'bytea' == column.sql_type
492
- { :value => value, :format => 1 }
493
- else
494
- super
495
- end
496
- end
497
-
498
- # Quotes strings for use in SQL input.
499
- def quote_string(s) #:nodoc:
500
- @connection.escape(s)
501
- end
502
-
503
- # Checks the following cases:
504
- #
505
- # - table_name
506
- # - "table.name"
507
- # - schema_name.table_name
508
- # - schema_name."table.name"
509
- # - "schema.name".table_name
510
- # - "schema.name"."table.name"
511
- def quote_table_name(name)
512
- schema, name_part = extract_pg_identifier_from_name(name.to_s)
513
-
514
- unless name_part
515
- quote_column_name(schema)
516
- else
517
- table_name, name_part = extract_pg_identifier_from_name(name_part)
518
- "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
519
- end
520
- end
521
-
522
- # Quotes column names for use in SQL queries.
523
- def quote_column_name(name) #:nodoc:
524
- PGconn.quote_ident(name.to_s)
525
- end
526
-
527
- # Quote date/time values for use in SQL input. Includes microseconds
528
- # if the value is a Time responding to usec.
529
- def quoted_date(value) #:nodoc:
530
- if value.acts_like?(:time) && value.respond_to?(:usec)
531
- "#{super}.#{sprintf("%06d", value.usec)}"
532
- else
533
- super
534
- end
535
- end
536
-
537
- # Set the authorized user for this session
538
- def session_auth=(user)
539
- clear_cache!
540
- exec_query "SET SESSION AUTHORIZATION #{user}"
541
- end
542
-
543
- # REFERENTIAL INTEGRITY ====================================
544
-
545
- def supports_disable_referential_integrity? #:nodoc:
546
- false
547
- end
548
-
549
- def disable_referential_integrity #:nodoc:
550
- if supports_disable_referential_integrity? then
551
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
552
- end
553
- yield
554
- ensure
555
- if supports_disable_referential_integrity? then
556
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
557
- end
558
- end
559
-
560
- # DATABASE STATEMENTS ======================================
561
-
562
- def explain(arel, binds = [])
563
- sql = "EXPLAIN #{to_sql(arel, binds)}"
564
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
565
- end
566
-
567
- class ExplainPrettyPrinter # :nodoc:
568
- # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
569
- # Redshift shell:
570
- #
571
- # QUERY PLAN
572
- # ------------------------------------------------------------------------------
573
- # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
574
- # Join Filter: (posts.user_id = users.id)
575
- # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
576
- # Index Cond: (id = 1)
577
- # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
578
- # Filter: (posts.user_id = 1)
579
- # (6 rows)
580
- #
581
- def pp(result)
582
- header = result.columns.first
583
- lines = result.rows.map(&:first)
584
-
585
- # We add 2 because there's one char of padding at both sides, note
586
- # the extra hyphens in the example above.
587
- width = [header, *lines].map(&:length).max + 2
588
-
589
- pp = []
590
-
591
- pp << header.center(width).rstrip
592
- pp << '-' * width
593
-
594
- pp += lines.map {|line| " #{line}"}
595
-
596
- nrows = result.rows.length
597
- rows_label = nrows == 1 ? 'row' : 'rows'
598
- pp << "(#{nrows} #{rows_label})"
599
-
600
- pp.join("\n") + "\n"
601
- end
602
- end
603
-
604
- # Executes a SELECT query and returns an array of rows. Each row is an
605
- # array of field values.
606
- def select_rows(sql, name = nil)
607
- select_raw(sql, name).last
608
- end
609
-
610
- # Executes an INSERT query and returns the new record's ID
611
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
612
- unless pk
613
- # Extract the table from the insert sql. Yuck.
614
- table_ref = extract_table_ref_from_insert_sql(sql)
615
- pk = primary_key(table_ref) if table_ref
616
- end
617
-
618
- if pk && use_insert_returning?
619
- select_value("#{sql} RETURNING #{quote_column_name(pk)}")
620
- elsif pk
621
- super
622
- last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
623
- else
624
- super
625
- end
626
- end
627
- alias :create :insert
628
-
629
- # create a 2D array representing the result set
630
- def result_as_array(res) #:nodoc:
631
- # check if we have any binary column and if they need escaping
632
- ftypes = Array.new(res.nfields) do |i|
633
- [i, res.ftype(i)]
634
- end
635
-
636
- rows = res.values
637
- return rows unless ftypes.any? { |_, x|
638
- x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
639
- }
640
-
641
- typehash = ftypes.group_by { |_, type| type }
642
- binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
643
- monies = typehash[MONEY_COLUMN_TYPE_OID] || []
644
-
645
- rows.each do |row|
646
- # unescape string passed BYTEA field (OID == 17)
647
- binaries.each do |index, _|
648
- row[index] = unescape_bytea(row[index])
649
- end
650
-
651
- # If this is a money type column and there are any currency symbols,
652
- # then strip them off. Indeed it would be prettier to do this in
653
- # RedshiftColumn.string_to_decimal but would break form input
654
- # fields that call value_before_type_cast.
655
- monies.each do |index, _|
656
- data = row[index]
657
- # Because money output is formatted according to the locale, there are two
658
- # cases to consider (note the decimal separators):
659
- # (1) $12,345,678.12
660
- # (2) $12.345.678,12
661
- case data
662
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
663
- data.gsub!(/[^-\d.]/, '')
664
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
665
- data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
666
- end
667
- end
668
- end
669
- end
670
-
671
-
672
- # Queries the database and returns the results in an Array-like object
673
- def query(sql, name = nil) #:nodoc:
674
- log(sql, name) do
675
- result_as_array @connection.async_exec(sql)
676
- end
677
- end
678
-
679
- # Executes an SQL statement, returning a PGresult object on success
680
- # or raising a PGError exception otherwise.
681
- def execute(sql, name = nil)
682
- log(sql, name) do
683
- @connection.async_exec(sql)
684
- end
685
- end
686
-
687
- def substitute_at(column, index)
688
- Arel::Nodes::BindParam.new "$#{index + 1}"
689
- end
690
-
691
- def exec_query(sql, name = 'SQL', binds = [])
692
- log(sql, name, binds) do
693
- result = binds.empty? ? exec_no_cache(sql, binds) :
694
- exec_cache(sql, binds)
695
-
696
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
697
- result.clear
698
- return ret
699
- end
700
- end
701
-
702
- def exec_delete(sql, name = 'SQL', binds = [])
703
- log(sql, name, binds) do
704
- result = binds.empty? ? exec_no_cache(sql, binds) :
705
- exec_cache(sql, binds)
706
- affected = result.cmd_tuples
707
- result.clear
708
- affected
709
- end
710
- end
711
- alias :exec_update :exec_delete
712
-
713
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
714
- unless pk
715
- # Extract the table from the insert sql. Yuck.
716
- table_ref = extract_table_ref_from_insert_sql(sql)
717
- pk = primary_key(table_ref) if table_ref
718
- end
719
-
720
- sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk && use_insert_returning?
721
-
722
- [sql, binds]
723
- end
724
-
725
- # Executes an UPDATE query and returns the number of affected tuples.
726
- def update_sql(sql, name = nil)
727
- super.cmd_tuples
728
- end
729
-
730
- # Begins a transaction.
731
- def begin_db_transaction
732
- execute "BEGIN"
733
- end
734
-
735
- # Commits a transaction.
736
- def commit_db_transaction
737
- execute "COMMIT"
738
- end
739
-
740
- # Aborts a transaction.
741
- def rollback_db_transaction
742
- execute "ROLLBACK"
743
- end
744
-
745
- def outside_transaction?
746
- @connection.transaction_status == PGconn::PQTRANS_IDLE
747
- end
748
-
749
- def create_savepoint
750
- execute("SAVEPOINT #{current_savepoint_name}")
751
- end
752
-
753
- def rollback_to_savepoint
754
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
755
- end
756
-
757
- def release_savepoint
758
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
759
- end
760
-
761
- def use_insert_returning?
762
- @use_insert_returning
763
- end
764
-
765
- # SCHEMA STATEMENTS ========================================
766
-
767
- # Drops the database specified on the +name+ attribute
768
- # and creates it again using the provided +options+.
769
- def recreate_database(name, options = {}) #:nodoc:
770
- drop_database(name)
771
- create_database(name, options)
772
- end
773
-
774
- # Create a new Redshift database. Options include <tt>:owner</tt>, <tt>:template</tt>,
775
- # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
776
- # <tt>:charset</tt> while Redshift uses <tt>:encoding</tt>).
777
- #
778
- # Example:
779
- # create_database config[:database], config
780
- # create_database 'foo_development', :encoding => 'unicode'
781
- def create_database(name, options = {})
782
- options = options.reverse_merge(:encoding => "utf8")
783
-
784
- option_string = options.symbolize_keys.sum do |key, value|
785
- case key
786
- when :owner
787
- " OWNER = \"#{value}\""
788
- when :template
789
- " TEMPLATE = \"#{value}\""
790
- when :encoding
791
- " ENCODING = '#{value}'"
792
- when :tablespace
793
- " TABLESPACE = \"#{value}\""
794
- when :connection_limit
795
- " CONNECTION LIMIT = #{value}"
796
- else
797
- ""
798
- end
799
- end
800
-
801
- execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
802
- end
803
-
804
- # Drops a Redshift database.
805
- #
806
- # Example:
807
- # drop_database 'matt_development'
808
- def drop_database(name) #:nodoc:
809
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
810
- end
811
-
812
- # Returns the list of all tables in the schema search path or a specified schema.
813
- def tables(name = nil)
814
- query(<<-SQL, 'SCHEMA').map { |row| "#{row[0]}.#{row[1]}" }
815
- SELECT schemaname, tablename
816
- FROM pg_tables
817
- WHERE schemaname = ANY (current_schemas(false))
818
- SQL
819
- end
820
-
821
- # Returns true if table exists.
822
- # If the schema is not specified as part of +name+ then it will only find tables within
823
- # the current schema search path (regardless of permissions to access tables in other schemas)
824
- def table_exists?(name)
825
- schema, table = Utils.extract_schema_and_table(name.to_s)
826
- return false unless table
827
-
828
- binds = [[nil, table]]
829
- binds << [nil, schema] if schema
830
-
831
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
832
- SELECT COUNT(*)
833
- FROM pg_class c
834
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
835
- WHERE c.relkind in ('v','r')
836
- AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
837
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
838
- SQL
839
- end
840
-
841
- # Returns true if schema exists.
842
- def schema_exists?(name)
843
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
844
- SELECT COUNT(*)
845
- FROM pg_namespace
846
- WHERE nspname = '#{name}'
847
- SQL
848
- end
849
-
850
- # Returns an array of indexes for the given table.
851
- def indexes(table_name, name = nil)
852
- []
853
- end
854
-
855
- # Returns the list of all column definitions for a table.
856
- def columns(table_name, name = nil)
857
- # Limit, precision, and scale are all handled by the superclass.
858
- column_definitions(table_name).collect do |column_name, type, default, notnull|
859
- RedshiftColumn.new(column_name, default, type, notnull == 'f', @config[:read_timezone])
860
- end
861
- end
862
-
863
- # Returns the current database name.
864
- def current_database
865
- query('select current_database()', 'SCHEMA')[0][0]
866
- end
867
-
868
- # Returns the current schema name.
869
- def current_schema
870
- query('SELECT current_schema', 'SCHEMA')[0][0]
871
- end
872
-
873
- # Returns the current database encoding format.
874
- def encoding
875
- query(<<-end_sql, 'SCHEMA')[0][0]
876
- SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
877
- WHERE pg_database.datname LIKE '#{current_database}'
878
- end_sql
879
- end
880
-
881
- # Sets the schema search path to a string of comma-separated schema names.
882
- # Names beginning with $ have to be quoted (e.g. $user => '$user').
883
- # See: http://www.redshift.org/docs/current/static/ddl-schemas.html
884
- #
885
- # This should be not be called manually but set in database.yml.
886
- def schema_search_path=(schema_csv)
887
- if schema_csv
888
- execute("SET search_path TO #{schema_csv}", 'SCHEMA')
889
- @schema_search_path = schema_csv
890
- end
891
- end
892
-
893
- # Returns the active schema search path.
894
- def schema_search_path
895
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
896
- end
897
-
898
- # Returns the sequence name for a table's primary key or some other specified key.
899
- def default_sequence_name(table_name, pk = nil) #:nodoc:
900
- serial_sequence(table_name, pk || 'id').split('.').last
901
- rescue ActiveRecord::StatementInvalid
902
- "#{table_name}_#{pk || 'id'}_seq"
903
- end
904
-
905
- def serial_sequence(table, column)
906
- result = exec_query(<<-eosql, 'SCHEMA')
907
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
908
- eosql
909
- result.rows.first.first
910
- end
911
-
912
- # Resets the sequence of a table's primary key to the maximum value.
913
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
914
- unless pk and sequence
915
- default_pk, default_sequence = pk_and_sequence_for(table)
916
-
917
- pk ||= default_pk
918
- sequence ||= default_sequence
919
- end
920
-
921
- if @logger && pk && !sequence
922
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
923
- end
924
-
925
- if pk && sequence
926
- quoted_sequence = quote_table_name(sequence)
927
-
928
- select_value <<-end_sql, 'SCHEMA'
929
- 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)
930
- end_sql
931
- end
932
- end
933
-
934
- # Returns a table's primary key and belonging sequence.
935
- def pk_and_sequence_for(table) #:nodoc:
936
- # First try looking for a sequence with a dependency on the
937
- # given table's primary key.
938
- result = query(<<-end_sql, 'SCHEMA')[0]
939
- SELECT attr.attname, seq.relname
940
- FROM pg_class seq,
941
- pg_attribute attr,
942
- pg_depend dep,
943
- pg_namespace name,
944
- pg_constraint cons
945
- WHERE seq.oid = dep.objid
946
- AND seq.relkind = 'S'
947
- AND attr.attrelid = dep.refobjid
948
- AND attr.attnum = dep.refobjsubid
949
- AND attr.attrelid = cons.conrelid
950
- AND attr.attnum = cons.conkey[1]
951
- AND cons.contype = 'p'
952
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
953
- end_sql
954
-
955
- if result.nil? or result.empty?
956
- result = query(<<-end_sql, 'SCHEMA')[0]
957
- SELECT attr.attname,
958
- CASE
959
- WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
960
- substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
961
- strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
962
- ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
963
- END
964
- FROM pg_class t
965
- JOIN pg_attribute attr ON (t.oid = attrelid)
966
- JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
967
- JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
968
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
969
- AND cons.contype = 'p'
970
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
971
- end_sql
972
- end
973
-
974
- [result.first, result.last]
975
- rescue
976
- nil
977
- end
978
-
979
- # Returns just a table's primary key
980
- def primary_key(table)
981
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
982
- SELECT DISTINCT(attr.attname)
983
- FROM pg_attribute attr
984
- INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
985
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
986
- WHERE cons.contype = 'p'
987
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
988
- end_sql
989
-
990
- row && row.first
991
- end
992
-
993
- # Renames a table.
994
- # Also renames a table's primary key sequence if the sequence name matches the
995
- # Active Record default.
996
- #
997
- # Example:
998
- # rename_table('octopuses', 'octopi')
999
- def rename_table(name, new_name)
1000
- clear_cache!
1001
- execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
1002
- pk, seq = pk_and_sequence_for(new_name)
1003
- if seq == "#{name}_#{pk}_seq"
1004
- new_seq = "#{new_name}_#{pk}_seq"
1005
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
1006
- end
1007
- end
1008
-
1009
- # Adds a new column to the named table.
1010
- # See TableDefinition#column for details of the options you can use.
1011
- def add_column(table_name, column_name, type, options = {})
1012
- clear_cache!
1013
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1014
- add_column_options!(add_column_sql, options)
1015
-
1016
- execute add_column_sql
1017
- end
1018
-
1019
- # Changes the column of a table.
1020
- def change_column(table_name, column_name, type, options = {})
1021
- clear_cache!
1022
- quoted_table_name = quote_table_name(table_name)
1023
-
1024
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1025
-
1026
- change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
1027
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
1028
- end
1029
-
1030
- # Changes the default value of a table column.
1031
- def change_column_default(table_name, column_name, default)
1032
- clear_cache!
1033
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
1034
- end
1035
-
1036
- def change_column_null(table_name, column_name, null, default = nil)
1037
- clear_cache!
1038
- unless null || default.nil?
1039
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
1040
- end
1041
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
1042
- end
1043
-
1044
- # Renames a column in a table.
1045
- def rename_column(table_name, column_name, new_column_name)
1046
- clear_cache!
1047
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1048
- end
1049
-
1050
- def add_index(*)
1051
- # XXX nothing to do
1052
- end
1053
-
1054
- def remove_index!(table_name, index_name) #:nodoc:
1055
- end
1056
-
1057
- def rename_index(table_name, old_name, new_name)
1058
- end
1059
-
1060
- def index_name_length
1061
- 63
1062
- end
1063
-
1064
- # Maps logical Rails types to Redshift-specific data types.
1065
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1066
- case type.to_s
1067
- when 'binary'
1068
- # Redshift doesn't support limits on binary (bytea) columns.
1069
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
1070
- case limit
1071
- when nil, 0..0x3fffffff; super(type)
1072
- else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
1073
- end
1074
- when 'integer'
1075
- return 'integer' unless limit
1076
-
1077
- case limit
1078
- when 1, 2; 'smallint'
1079
- when 3, 4; 'integer'
1080
- when 5..8; 'bigint'
1081
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
1082
- end
1083
- else
1084
- super
1085
- end
1086
- end
1087
-
1088
- # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1089
- #
1090
- # Redshift requires the ORDER BY columns in the select list for distinct queries, and
1091
- # requires that the ORDER BY include the distinct column.
1092
- #
1093
- # distinct("posts.id", "posts.created_at desc")
1094
- def distinct(columns, orders) #:nodoc:
1095
- return "DISTINCT #{columns}" if orders.empty?
1096
-
1097
- # Construct a clean list of column names from the ORDER BY clause, removing
1098
- # any ASC/DESC modifiers
1099
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
1100
- order_columns.delete_if { |c| c.blank? }
1101
- order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
1102
-
1103
- "DISTINCT #{columns}, #{order_columns * ', '}"
1104
- end
1105
-
1106
- module Utils
1107
- extend self
1108
-
1109
- # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
1110
- # +schema_name+ is nil if not specified in +name+.
1111
- # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
1112
- # +name+ supports the range of schema/table references understood by Redshift, for example:
1113
- #
1114
- # * <tt>table_name</tt>
1115
- # * <tt>"table.name"</tt>
1116
- # * <tt>schema_name.table_name</tt>
1117
- # * <tt>schema_name."table.name"</tt>
1118
- # * <tt>"schema.name"."table name"</tt>
1119
- def extract_schema_and_table(name)
1120
- table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
1121
- [schema, table]
1122
- end
1123
- end
1124
-
1125
- protected
1126
- # Returns the version of the connected Redshift server.
1127
- def redshift_version
1128
- @connection.server_version
1129
- end
1130
-
1131
- def translate_exception(exception, message)
1132
- case exception.message
1133
- when /duplicate key value violates unique constraint/
1134
- RecordNotUnique.new(message, exception)
1135
- when /violates foreign key constraint/
1136
- InvalidForeignKey.new(message, exception)
1137
- else
1138
- super
1139
- end
1140
- end
1141
-
1142
- private
1143
- FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1144
-
1145
- def exec_no_cache(sql, binds)
1146
- @connection.async_exec(sql)
1147
- end
1148
-
1149
- def exec_cache(sql, binds)
1150
- begin
1151
- stmt_key = prepare_statement sql
1152
-
1153
- # Clear the queue
1154
- @connection.get_last_result
1155
- @connection.send_query_prepared(stmt_key, binds.map { |col, val|
1156
- type_cast(val, col)
1157
- })
1158
- @connection.block
1159
- @connection.get_last_result
1160
- rescue PGError => e
1161
- # Get the PG code for the failure. Annoyingly, the code for
1162
- # prepared statements whose return value may have changed is
1163
- # FEATURE_NOT_SUPPORTED. Check here for more details:
1164
- # http://git.redshift.org/gitweb/?p=redshift.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1165
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
1166
- if FEATURE_NOT_SUPPORTED == code
1167
- @statements.delete sql_key(sql)
1168
- retry
1169
- else
1170
- raise e
1171
- end
1172
- end
1173
- end
1174
-
1175
- # Returns the statement identifier for the client side cache
1176
- # of statements
1177
- def sql_key(sql)
1178
- "#{schema_search_path}-#{sql}"
1179
- end
1180
-
1181
- # Prepare the statement if it hasn't been prepared, return
1182
- # the statement key.
1183
- def prepare_statement(sql)
1184
- sql_key = sql_key(sql)
1185
- unless @statements.key? sql_key
1186
- nextkey = @statements.next_key
1187
- @connection.prepare nextkey, sql
1188
- @statements[sql_key] = nextkey
1189
- end
1190
- @statements[sql_key]
1191
- end
1192
-
1193
- # The internal Redshift identifier of the money data type.
1194
- MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
1195
- # The internal Redshift identifier of the BYTEA data type.
1196
- BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
1197
-
1198
- # Connects to a Redshift server and sets up the adapter depending on the
1199
- # connected server's characteristics.
1200
- def connect
1201
- @connection = PGconn.connect(*@connection_parameters)
1202
-
1203
- # Money type has a fixed precision of 10 in Redshift 8.2 and below, and as of
1204
- # Redshift 8.3 it has a fixed precision of 19. RedshiftColumn.extract_precision
1205
- # should know about this but can't detect it there, so deal with it here.
1206
- RedshiftColumn.money_precision = (redshift_version >= 80300) ? 19 : 10
1207
-
1208
- configure_connection
1209
- end
1210
-
1211
- # Configures the encoding, verbosity, schema search path, and time zone of the connection.
1212
- # This is called by #connect and should not be called manually.
1213
- def configure_connection
1214
- if @config[:encoding]
1215
- @connection.set_client_encoding(@config[:encoding])
1216
- end
1217
- self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
1218
- end
1219
-
1220
- # Returns the current ID of a table's sequence.
1221
- def last_insert_id(sequence_name) #:nodoc:
1222
- r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1223
- Integer(r.rows.first.first)
1224
- end
1225
-
1226
- # Executes a SELECT query and returns the results, performing any data type
1227
- # conversions that are required to be performed here instead of in RedshiftColumn.
1228
- def select(sql, name = nil, binds = [])
1229
- exec_query(sql, name, binds).to_a
1230
- end
1231
-
1232
- def select_raw(sql, name = nil)
1233
- res = execute(sql, name)
1234
- results = result_as_array(res)
1235
- fields = res.fields
1236
- res.clear
1237
- return fields, results
1238
- end
1239
-
1240
- # Returns the list of a table's column names, data types, and default values.
1241
- #
1242
- # The underlying query is roughly:
1243
- # SELECT column.name, column.type, default.value
1244
- # FROM column LEFT JOIN default
1245
- # ON column.table_id = default.table_id
1246
- # AND column.num = default.column_num
1247
- # WHERE column.table_id = get_table_id('table_name')
1248
- # AND column.num > 0
1249
- # AND NOT column.is_dropped
1250
- # ORDER BY column.num
1251
- #
1252
- # If the table name is not prefixed with a schema, the database will
1253
- # take the first match from the schema search path.
1254
- #
1255
- # Query implementation notes:
1256
- # - format_type includes the column size constraint, e.g. varchar(50)
1257
- # - ::regclass is a function that gives the id for a table name
1258
- def column_definitions(table_name) #:nodoc:
1259
- exec_query(<<-end_sql, 'SCHEMA').rows
1260
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1261
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
1262
- FROM pg_attribute a LEFT JOIN pg_attrdef d
1263
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1264
- WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
1265
- AND a.attnum > 0 AND NOT a.attisdropped
1266
- ORDER BY a.attnum
1267
- end_sql
1268
- end
1269
-
1270
- def extract_pg_identifier_from_name(name)
1271
- match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1272
-
1273
- if match_data
1274
- rest = name[match_data[0].length, name.length]
1275
- rest = rest[1, rest.length] if rest.start_with? "."
1276
- [match_data[1], (rest.length > 0 ? rest : nil)]
1277
- end
1278
- end
1279
-
1280
- def extract_table_ref_from_insert_sql(sql)
1281
- sql[/into\s+([^\(]*).*values\s*\(/i]
1282
- $1.strip if $1
1283
- end
1284
-
1285
- def table_definition
1286
- TableDefinition.new(self)
1287
- end
1288
- end
1289
- end
5
+ require_relative "../tasks/redshift_tasks"
6
+
7
+ if ActiveRecord.version >= Gem::Version.new('8.0.0')
8
+ require_relative 'redshift_8_0_adapter'
9
+ elsif ActiveRecord.version >= Gem::Version.new('7.2.0')
10
+ require_relative 'redshift_7_2_adapter'
11
+ elsif ActiveRecord.version >= Gem::Version.new('7.1.0')
12
+ require_relative 'redshift_7_1_adapter'
13
+ elsif ActiveRecord.version >= Gem::Version.new('7.0.0')
14
+ require_relative 'redshift_7_0_adapter'
15
+ else
16
+ raise 'no compatible version of ActiveRecord detected'
1290
17
  end