activerecord-redshift-adapter-ng 0.9.0

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