activerecord-redshift-adapter-ng 0.9.0

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 (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