activerecord6-redshift-adapter 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ class TypeMetadata < DelegateClass(SqlTypeMetadata)
5
+ attr_reader :oid, :fmod, :array
6
+
7
+ def initialize(type_metadata, oid: nil, fmod: nil)
8
+ super(type_metadata)
9
+ @type_metadata = type_metadata
10
+ @oid = oid
11
+ @fmod = fmod
12
+ @array = /\[\]$/ === type_metadata.sql_type
13
+ end
14
+
15
+ def sql_type
16
+ super.gsub(/\[\]$/, "".freeze)
17
+ end
18
+
19
+ def ==(other)
20
+ other.is_a?(Redshift::TypeMetadata) &&
21
+ attributes_for_hash == other.attributes_for_hash
22
+ end
23
+ alias eql? ==
24
+
25
+ def hash
26
+ attributes_for_hash.hash
27
+ end
28
+
29
+ protected
30
+
31
+ def attributes_for_hash
32
+ [self.class, @type_metadata, oid, fmod]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ # Value Object to hold a schema qualified name.
5
+ # This is usually the name of a PostgreSQL relation but it can also represent
6
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
7
+ # double quoting.
8
+ class Name # :nodoc:
9
+ SEPARATOR = "."
10
+ attr_reader :schema, :identifier
11
+
12
+ def initialize(schema, identifier)
13
+ @schema, @identifier = unquote(schema), unquote(identifier)
14
+ end
15
+
16
+ def to_s
17
+ parts.join SEPARATOR
18
+ end
19
+
20
+ def quoted
21
+ if schema
22
+ PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
23
+ else
24
+ PG::Connection.quote_ident(identifier)
25
+ end
26
+ end
27
+
28
+ def ==(o)
29
+ o.class == self.class && o.parts == parts
30
+ end
31
+ alias_method :eql?, :==
32
+
33
+ def hash
34
+ parts.hash
35
+ end
36
+
37
+ protected
38
+ def unquote(part)
39
+ if part && part.start_with?('"')
40
+ part[1..-2]
41
+ else
42
+ part
43
+ end
44
+ end
45
+
46
+ def parts
47
+ @parts ||= [@schema, @identifier].compact
48
+ end
49
+ end
50
+
51
+ module Utils # :nodoc:
52
+ extend self
53
+
54
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
55
+ # extracted from +string+.
56
+ # +schema+ is nil if not specified in +string+.
57
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
58
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
59
+ #
60
+ # * <tt>table_name</tt>
61
+ # * <tt>"table.name"</tt>
62
+ # * <tt>schema_name.table_name</tt>
63
+ # * <tt>schema_name."table.name"</tt>
64
+ # * <tt>"schema_name".table_name</tt>
65
+ # * <tt>"schema.name"."table name"</tt>
66
+ def extract_schema_qualified_name(string)
67
+ schema, table = string.scan(/[^".\s]+|"[^"]*"/)
68
+ if table.nil?
69
+ table = schema
70
+ schema = nil
71
+ end
72
+ Redshift::Name.new(schema, table)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,635 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+
4
+ require 'active_record/connection_adapters/redshift/utils'
5
+ require 'active_record/connection_adapters/redshift/column'
6
+ require 'active_record/connection_adapters/redshift/oid'
7
+ require 'active_record/connection_adapters/redshift/quoting'
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
+ require 'active_record/connection_adapters/redshift/database_statements'
14
+
15
+ require 'pg'
16
+
17
+ require 'ipaddr'
18
+
19
+ module ActiveRecord
20
+ module ConnectionHandling # :nodoc:
21
+ RS_VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
22
+ :client_encoding, :options, :application_name, :fallback_application_name,
23
+ :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
24
+ :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
25
+ :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
26
+
27
+ # Establishes a connection to the database that's used by all Active Record objects
28
+ def redshift_connection(config)
29
+ conn_params = config.symbolize_keys
30
+
31
+ conn_params.delete_if { |_, v| v.nil? }
32
+
33
+ # Map ActiveRecords param names to PGs.
34
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
35
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
36
+
37
+ # Forward only valid config params to PG::Connection.connect.
38
+ conn_params.keep_if { |k, _| RS_VALID_CONN_PARAMS.include?(k) }
39
+
40
+ # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
41
+ # so just pass a nil connection object for the time being.
42
+ ConnectionAdapters::RedshiftAdapter.new(nil, logger, conn_params, config)
43
+ end
44
+ end
45
+
46
+ module ConnectionAdapters
47
+ # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
48
+ #
49
+ # Options:
50
+ #
51
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
52
+ # the default is to connect to localhost.
53
+ # * <tt>:port</tt> - Defaults to 5432.
54
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
55
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
56
+ # * <tt>:database</tt> - Defaults to be the same as the user name.
57
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
58
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
59
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
60
+ # <encoding></tt> call on the connection.
61
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
62
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
63
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
64
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
65
+ # * <tt>:insert_returning</tt> - Does nothing for Redshift.
66
+ #
67
+ # Any further options are used as connection parameters to libpq. See
68
+ # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
69
+ # list of parameters.
70
+ #
71
+ # In addition, default connection parameters of libpq can be set per environment variables.
72
+ # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
73
+ class RedshiftAdapter < AbstractAdapter
74
+ ADAPTER_NAME = 'Redshift'.freeze
75
+
76
+ NATIVE_DATABASE_TYPES = {
77
+ primary_key: "integer identity primary key",
78
+ string: { name: "varchar" },
79
+ text: { name: "varchar" },
80
+ integer: { name: "integer" },
81
+ float: { name: "float" },
82
+ decimal: { name: "decimal" },
83
+ datetime: { name: "timestamp" },
84
+ time: { name: "time" },
85
+ date: { name: "date" },
86
+ bigint: { name: "bigint" },
87
+ boolean: { name: "boolean" },
88
+ }
89
+
90
+ OID = Redshift::OID #:nodoc:
91
+
92
+ include Redshift::Quoting
93
+ include Redshift::ReferentialIntegrity
94
+ include Redshift::SchemaStatements
95
+ include Redshift::DatabaseStatements
96
+
97
+ def schema_creation # :nodoc:
98
+ Redshift::SchemaCreation.new self
99
+ end
100
+
101
+ def supports_index_sort_order?
102
+ false
103
+ end
104
+
105
+ def supports_partial_index?
106
+ false
107
+ end
108
+
109
+ def supports_transaction_isolation?
110
+ false
111
+ end
112
+
113
+ def supports_foreign_keys?
114
+ true
115
+ end
116
+
117
+ def supports_views?
118
+ true
119
+ end
120
+
121
+ def index_algorithms
122
+ { concurrently: 'CONCURRENTLY' }
123
+ end
124
+
125
+ class StatementPool < ConnectionAdapters::StatementPool
126
+ def initialize(connection, max)
127
+ super(max)
128
+ @connection = connection
129
+ @counter = 0
130
+ @cache = Hash.new { |h,pid| h[pid] = {} }
131
+ end
132
+
133
+ def each(&block); cache.each(&block); end
134
+ def key?(key); cache.key?(key); end
135
+ def [](key); cache[key]; end
136
+ def length; cache.length; end
137
+
138
+ def next_key
139
+ "a#{@counter + 1}"
140
+ end
141
+
142
+ def []=(sql, key)
143
+ while @max <= cache.size
144
+ dealloc(cache.shift.last)
145
+ end
146
+ @counter += 1
147
+ cache[sql] = key
148
+ end
149
+
150
+ def clear
151
+ cache.each_value do |stmt_key|
152
+ dealloc stmt_key
153
+ end
154
+ cache.clear
155
+ end
156
+
157
+ def delete(sql_key)
158
+ dealloc cache[sql_key]
159
+ cache.delete sql_key
160
+ end
161
+
162
+ private
163
+
164
+ def cache
165
+ @cache[Process.pid]
166
+ end
167
+
168
+ def dealloc(key)
169
+ @connection.query "DEALLOCATE #{key}" if connection_active?
170
+ end
171
+
172
+ def connection_active?
173
+ @connection.status == PG::Connection::CONNECTION_OK
174
+ rescue PG::Error
175
+ false
176
+ end
177
+ end
178
+
179
+ # Initializes and connects a PostgreSQL adapter.
180
+ def initialize(connection, logger, connection_parameters, config)
181
+ super(connection, logger, config)
182
+
183
+ @visitor = Arel::Visitors::PostgreSQL.new self
184
+ @visitor.extend(ConnectionAdapters::DetermineIfPreparableVisitor)
185
+ @prepared_statements = false
186
+
187
+ @connection_parameters = connection_parameters
188
+
189
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
190
+ @local_tz = nil
191
+ @table_alias_length = nil
192
+
193
+ connect
194
+ @statements = StatementPool.new @connection,
195
+ self.class.type_cast_config_to_integer(config[:statement_limit])
196
+
197
+ @type_map = Type::HashLookupTypeMap.new
198
+ initialize_type_map(type_map)
199
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
200
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : false
201
+ end
202
+
203
+ # Clears the prepared statements cache.
204
+ def clear_cache!
205
+ @statements.clear
206
+ end
207
+
208
+ def truncate(table_name, name = nil)
209
+ exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
210
+ end
211
+
212
+ # Is this connection alive and ready for queries?
213
+ def active?
214
+ @connection.query 'SELECT 1'
215
+ true
216
+ rescue PG::Error
217
+ false
218
+ end
219
+
220
+ # Close then reopen the connection.
221
+ def reconnect!
222
+ super
223
+ @connection.reset
224
+ configure_connection
225
+ end
226
+
227
+ def reset!
228
+ clear_cache!
229
+ reset_transaction
230
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
231
+ @connection.query 'ROLLBACK'
232
+ end
233
+ @connection.query 'DISCARD ALL'
234
+ configure_connection
235
+ end
236
+
237
+ # Disconnects from the database if already connected. Otherwise, this
238
+ # method does nothing.
239
+ def disconnect!
240
+ super
241
+ @connection.close rescue nil
242
+ end
243
+
244
+ def native_database_types #:nodoc:
245
+ NATIVE_DATABASE_TYPES
246
+ end
247
+
248
+ # Returns true, since this connection adapter supports migrations.
249
+ def supports_migrations?
250
+ true
251
+ end
252
+
253
+ # Does PostgreSQL support finding primary key on non-Active Record tables?
254
+ def supports_primary_key? #:nodoc:
255
+ true
256
+ end
257
+
258
+ # Enable standard-conforming strings if available.
259
+ def set_standard_conforming_strings
260
+ old, self.client_min_messages = client_min_messages, 'panic'
261
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
262
+ ensure
263
+ self.client_min_messages = old
264
+ end
265
+
266
+ def supports_ddl_transactions?
267
+ true
268
+ end
269
+
270
+ def supports_explain?
271
+ true
272
+ end
273
+
274
+ def supports_extensions?
275
+ false
276
+ end
277
+
278
+ def supports_ranges?
279
+ false
280
+ end
281
+
282
+ def supports_materialized_views?
283
+ false
284
+ end
285
+
286
+ def supports_import?
287
+ true
288
+ end
289
+
290
+ def enable_extension(name)
291
+ end
292
+
293
+ def disable_extension(name)
294
+ end
295
+
296
+ def extension_enabled?(name)
297
+ false
298
+ end
299
+
300
+ # Returns the configured supported identifier length supported by PostgreSQL
301
+ def table_alias_length
302
+ @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
303
+ end
304
+
305
+ # Set the authorized user for this session
306
+ def session_auth=(user)
307
+ clear_cache!
308
+ exec_query "SET SESSION AUTHORIZATION #{user}"
309
+ end
310
+
311
+ def use_insert_returning?
312
+ false
313
+ end
314
+
315
+ def valid_type?(type)
316
+ !native_database_types[type].nil?
317
+ end
318
+
319
+ def update_table_definition(table_name, base) #:nodoc:
320
+ Redshift::Table.new(table_name, base)
321
+ end
322
+
323
+ def lookup_cast_type(sql_type) # :nodoc:
324
+ oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
325
+ super(oid)
326
+ end
327
+
328
+ def column_name_for_operation(operation, node) # :nodoc:
329
+ OPERATION_ALIASES.fetch(operation) { operation.downcase }
330
+ end
331
+
332
+ OPERATION_ALIASES = { # :nodoc:
333
+ "maximum" => "max",
334
+ "minimum" => "min",
335
+ "average" => "avg",
336
+ }
337
+
338
+ protected
339
+
340
+ # Returns the version of the connected PostgreSQL server.
341
+ def redshift_version
342
+ @connection.server_version
343
+ end
344
+
345
+ def translate_exception(exception, message)
346
+ return exception unless exception.respond_to?(:result)
347
+
348
+ case exception.message
349
+ when /duplicate key value violates unique constraint/
350
+ RecordNotUnique.new(message, exception)
351
+ when /violates foreign key constraint/
352
+ InvalidForeignKey.new(message, exception)
353
+ else
354
+ super
355
+ end
356
+ end
357
+
358
+ private
359
+
360
+ def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
361
+ if !type_map.key?(oid)
362
+ load_additional_types(type_map, [oid])
363
+ end
364
+
365
+ type_map.fetch(oid, fmod, sql_type) {
366
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
367
+ Type::Value.new.tap do |cast_type|
368
+ type_map.register_type(oid, cast_type)
369
+ end
370
+ }
371
+ end
372
+
373
+ def initialize_type_map(m) # :nodoc:
374
+ register_class_with_limit m, 'int2', Type::Integer
375
+ register_class_with_limit m, 'int4', Type::Integer
376
+ register_class_with_limit m, 'int8', Type::Integer
377
+ m.alias_type 'oid', 'int2'
378
+ m.register_type 'float4', Type::Float.new
379
+ m.alias_type 'float8', 'float4'
380
+ m.register_type 'text', Type::Text.new
381
+ register_class_with_limit m, 'varchar', Type::String
382
+ m.alias_type 'char', 'varchar'
383
+ m.alias_type 'name', 'varchar'
384
+ m.alias_type 'bpchar', 'varchar'
385
+ m.register_type 'bool', Type::Boolean.new
386
+ m.alias_type 'timestamptz', 'timestamp'
387
+ m.register_type 'date', Type::Date.new
388
+ m.register_type 'time', Type::Time.new
389
+
390
+ m.register_type 'timestamp' do |_, _, sql_type|
391
+ precision = extract_precision(sql_type)
392
+ OID::DateTime.new(precision: precision)
393
+ end
394
+
395
+ m.register_type 'numeric' do |_, fmod, sql_type|
396
+ precision = extract_precision(sql_type)
397
+ scale = extract_scale(sql_type)
398
+
399
+ # The type for the numeric depends on the width of the field,
400
+ # so we'll do something special here.
401
+ #
402
+ # When dealing with decimal columns:
403
+ #
404
+ # places after decimal = fmod - 4 & 0xffff
405
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
406
+ if fmod && (fmod - 4 & 0xffff).zero?
407
+ # FIXME: Remove this class, and the second argument to
408
+ # lookups on PG
409
+ Type::DecimalWithoutScale.new(precision: precision)
410
+ else
411
+ OID::Decimal.new(precision: precision, scale: scale)
412
+ end
413
+ end
414
+
415
+ load_additional_types(m)
416
+ end
417
+
418
+ def extract_limit(sql_type) # :nodoc:
419
+ case sql_type
420
+ when /^bigint/i, /^int8/i
421
+ 8
422
+ when /^smallint/i
423
+ 2
424
+ else
425
+ super
426
+ end
427
+ end
428
+
429
+ # Extracts the value from a PostgreSQL column default definition.
430
+ def extract_value_from_default(default) # :nodoc:
431
+ case default
432
+ # Quoted types
433
+ when /\A[\(B]?'(.*)'::/m
434
+ $1.gsub(/''/, "'")
435
+ # Boolean types
436
+ when 'true', 'false'
437
+ default
438
+ # Numeric types
439
+ when /\A\(?(-?\d+(\.\d*)?)\)?\z/
440
+ $1
441
+ # Object identifier types
442
+ when /\A-?\d+\z/
443
+ $1
444
+ else
445
+ # Anything else is blank, some user type, or some function
446
+ # and we can't know the value of that, so return nil.
447
+ nil
448
+ end
449
+ end
450
+
451
+ def extract_default_function(default_value, default) # :nodoc:
452
+ default if has_default_function?(default_value, default)
453
+ end
454
+
455
+ def has_default_function?(default_value, default) # :nodoc:
456
+ !default_value && (%r{\w+\(.*\)} === default)
457
+ end
458
+
459
+ def load_additional_types(type_map, oids = nil) # :nodoc:
460
+ initializer = OID::TypeMapInitializer.new(type_map)
461
+
462
+ if supports_ranges?
463
+ query = <<-SQL
464
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
465
+ FROM pg_type as t
466
+ LEFT JOIN pg_range as r ON oid = rngtypid
467
+ SQL
468
+ else
469
+ query = <<-SQL
470
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
471
+ FROM pg_type as t
472
+ SQL
473
+ end
474
+
475
+ if oids
476
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
477
+ end
478
+
479
+ execute_and_clear(query, 'SCHEMA', []) do |records|
480
+ initializer.run(records)
481
+ end
482
+ end
483
+
484
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
485
+
486
+ def execute_and_clear(sql, name, binds, prepare: false)
487
+ if without_prepared_statement?(binds)
488
+ result = exec_no_cache(sql, name, [])
489
+ elsif !prepare
490
+ result = exec_no_cache(sql, name, binds)
491
+ else
492
+ result = exec_cache(sql, name, binds)
493
+ end
494
+ ret = yield result
495
+ result.clear
496
+ ret
497
+ end
498
+
499
+ def exec_no_cache(sql, name, binds)
500
+ log(sql, name, binds) { @connection.async_exec(sql, []) }
501
+ end
502
+
503
+ def exec_cache(sql, name, binds)
504
+ stmt_key = prepare_statement(sql)
505
+ type_casted_binds = binds.map { |col, val|
506
+ [col, type_cast(val, col)]
507
+ }
508
+
509
+ log(sql, name, type_casted_binds, stmt_key) do
510
+ @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
511
+ end
512
+ rescue ActiveRecord::StatementInvalid => e
513
+ pgerror = e.original_exception
514
+
515
+ # Get the PG code for the failure. Annoyingly, the code for
516
+ # prepared statements whose return value may have changed is
517
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
518
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
519
+ begin
520
+ code = pgerror.result.result_error_field(PG::Result::PG_DIAG_SQLSTATE)
521
+ rescue
522
+ raise e
523
+ end
524
+ if FEATURE_NOT_SUPPORTED == code
525
+ @statements.delete sql_key(sql)
526
+ retry
527
+ else
528
+ raise e
529
+ end
530
+ end
531
+
532
+ # Returns the statement identifier for the client side cache
533
+ # of statements
534
+ def sql_key(sql)
535
+ "#{schema_search_path}-#{sql}"
536
+ end
537
+
538
+ # Prepare the statement if it hasn't been prepared, return
539
+ # the statement key.
540
+ def prepare_statement(sql)
541
+ sql_key = sql_key(sql)
542
+ unless @statements.key? sql_key
543
+ nextkey = @statements.next_key
544
+ begin
545
+ @connection.prepare nextkey, sql
546
+ rescue => e
547
+ raise translate_exception_class(e, sql)
548
+ end
549
+ # Clear the queue
550
+ @connection.get_last_result
551
+ @statements[sql_key] = nextkey
552
+ end
553
+ @statements[sql_key]
554
+ end
555
+
556
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
557
+ # connected server's characteristics.
558
+ def connect
559
+ @connection = PG::Connection.connect(@connection_parameters)
560
+
561
+ configure_connection
562
+ rescue ::PG::Error => error
563
+ if error.message.include?("does not exist")
564
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
565
+ else
566
+ raise
567
+ end
568
+ end
569
+
570
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
571
+ # This is called by #connect and should not be called manually.
572
+ def configure_connection
573
+ if @config[:encoding]
574
+ @connection.set_client_encoding(@config[:encoding])
575
+ end
576
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
577
+
578
+ # SET statements from :variables config hash
579
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
580
+ variables = @config[:variables] || {}
581
+ variables.map do |k, v|
582
+ if v == ':default' || v == :default
583
+ # Sets the value to the global or compile default
584
+ execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
585
+ elsif !v.nil?
586
+ execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
587
+ end
588
+ end
589
+ end
590
+
591
+ def last_insert_id_result(sequence_name) #:nodoc:
592
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
593
+ end
594
+
595
+ # Returns the list of a table's column names, data types, and default values.
596
+ #
597
+ # The underlying query is roughly:
598
+ # SELECT column.name, column.type, default.value
599
+ # FROM column LEFT JOIN default
600
+ # ON column.table_id = default.table_id
601
+ # AND column.num = default.column_num
602
+ # WHERE column.table_id = get_table_id('table_name')
603
+ # AND column.num > 0
604
+ # AND NOT column.is_dropped
605
+ # ORDER BY column.num
606
+ #
607
+ # If the table name is not prefixed with a schema, the database will
608
+ # take the first match from the schema search path.
609
+ #
610
+ # Query implementation notes:
611
+ # - format_type includes the column size constraint, e.g. varchar(50)
612
+ # - ::regclass is a function that gives the id for a table name
613
+ def column_definitions(table_name) # :nodoc:
614
+ query(<<-end_sql, 'SCHEMA')
615
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
616
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
617
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
618
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
619
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
620
+ AND a.attnum > 0 AND NOT a.attisdropped
621
+ ORDER BY a.attnum
622
+ end_sql
623
+ end
624
+
625
+ def extract_table_ref_from_insert_sql(sql) # :nodoc:
626
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
627
+ $1.strip if $1
628
+ end
629
+
630
+ def create_table_definition(*args) # :nodoc:
631
+ Redshift::TableDefinition.new(*args)
632
+ end
633
+ end
634
+ end
635
+ end