activerecord4-redshift-adapter 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,909 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_record/connection_adapters/redshift/oid'
4
+ require 'active_record/connection_adapters/redshift/cast'
5
+ require 'active_record/connection_adapters/redshift/array_parser'
6
+ require 'active_record/connection_adapters/redshift/quoting'
7
+ require 'active_record/connection_adapters/redshift/schema_statements'
8
+ require 'active_record/connection_adapters/redshift/database_statements'
9
+ require 'active_record/connection_adapters/redshift/referential_integrity'
10
+ require 'arel/visitors/bind_visitor'
11
+
12
+ # Make sure we're using pg high enough for PGResult#values
13
+ gem 'pg', '~> 0.11'
14
+ require 'pg'
15
+
16
+ require 'ipaddr'
17
+
18
+ module ActiveRecord
19
+ module ConnectionHandling # :nodoc:
20
+ RS_VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
21
+ :client_encoding, :options, :application_name, :fallback_application_name,
22
+ :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
23
+ :tty, :sslmode, :requiressl, :sslcert, :sslkey, :sslrootcert, :sslcrl,
24
+ :requirepeer, :krbsrvname, :gsslib, :service]
25
+
26
+ # Establishes a connection to the database that's used by all Active Record objects
27
+ def redshift_connection(config)
28
+ conn_params = config.symbolize_keys
29
+
30
+ conn_params.delete_if { |_, v| v.nil? }
31
+
32
+ # Map ActiveRecords param names to PGs.
33
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
34
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
35
+
36
+ # Forward only valid config params to PGconn.connect.
37
+ conn_params.keep_if { |k, _| RS_VALID_CONN_PARAMS.include?(k) }
38
+
39
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
40
+ # so just pass a nil connection object for the time being.
41
+ ConnectionAdapters::RedshiftAdapter.new(nil, logger, conn_params, config)
42
+ end
43
+ end
44
+
45
+ module ConnectionAdapters
46
+ # PostgreSQL-specific extensions to column definitions in a table.
47
+ class RedshiftColumn < Column #:nodoc:
48
+ attr_accessor :array
49
+ # Instantiates a new PostgreSQL column definition in a table.
50
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
51
+ @oid_type = oid_type
52
+ if sql_type =~ /\[\]$/
53
+ @array = true
54
+ super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
55
+ else
56
+ @array = false
57
+ super(name, self.class.extract_value_from_default(default), sql_type, null)
58
+ end
59
+ end
60
+
61
+ # :stopdoc:
62
+ class << self
63
+ include ConnectionAdapters::RedshiftColumn::Cast
64
+ include ConnectionAdapters::RedshiftColumn::ArrayParser
65
+ attr_accessor :money_precision
66
+ end
67
+ # :startdoc:
68
+
69
+ # Extracts the value from a PostgreSQL column default definition.
70
+ def self.extract_value_from_default(default)
71
+ # This is a performance optimization for Ruby 1.9.2 in development.
72
+ # If the value is nil, we return nil straight away without checking
73
+ # the regular expressions. If we check each regular expression,
74
+ # Regexp#=== will call NilClass#to_str, which will trigger
75
+ # method_missing (defined by whiny nil in ActiveSupport) which
76
+ # makes this method very very slow.
77
+ return default unless default
78
+
79
+ case default
80
+ when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
81
+ $1
82
+ # Numeric types
83
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
84
+ $1
85
+ # Character types
86
+ when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
87
+ $1
88
+ # Binary data types
89
+ when /\A'(.*)'::bytea\z/m
90
+ $1
91
+ # Date/time types
92
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
93
+ $1
94
+ when /\A'(.*)'::interval\z/
95
+ $1
96
+ # Boolean type
97
+ when 'true'
98
+ true
99
+ when 'false'
100
+ false
101
+ # Geometric types
102
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
103
+ $1
104
+ # Network address types
105
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
106
+ $1
107
+ # Bit string types
108
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
109
+ $1
110
+ # XML type
111
+ when /\A'(.*)'::xml\z/m
112
+ $1
113
+ # Arrays
114
+ when /\A'(.*)'::"?\D+"?\[\]\z/
115
+ $1
116
+ # Hstore
117
+ when /\A'(.*)'::hstore\z/
118
+ $1
119
+ # JSON
120
+ when /\A'(.*)'::json\z/
121
+ $1
122
+ # Object identifier types
123
+ when /\A-?\d+\z/
124
+ $1
125
+ else
126
+ # Anything else is blank, some user type, or some function
127
+ # and we can't know the value of that, so return nil.
128
+ nil
129
+ end
130
+ end
131
+
132
+ def type_cast(value)
133
+ return if value.nil?
134
+ return super if encoded?
135
+
136
+ @oid_type.type_cast value
137
+ end
138
+
139
+ private
140
+
141
+ def extract_limit(sql_type)
142
+ case sql_type
143
+ when /^bigint/i; 8
144
+ when /^smallint/i; 2
145
+ when /^timestamp/i; nil
146
+ else super
147
+ end
148
+ end
149
+
150
+ # Extracts the scale from PostgreSQL-specific data types.
151
+ def extract_scale(sql_type)
152
+ # Money type has a fixed scale of 2.
153
+ sql_type =~ /^money/ ? 2 : super
154
+ end
155
+
156
+ # Extracts the precision from PostgreSQL-specific data types.
157
+ def extract_precision(sql_type)
158
+ if sql_type == 'money'
159
+ self.class.money_precision
160
+ elsif sql_type =~ /timestamp/i
161
+ $1.to_i if sql_type =~ /\((\d+)\)/
162
+ else
163
+ super
164
+ end
165
+ end
166
+
167
+ # Maps PostgreSQL-specific data types to logical Rails types.
168
+ def simplified_type(field_type)
169
+ case field_type
170
+ # Numeric and monetary types
171
+ when /^(?:real|double precision)$/
172
+ :float
173
+ # Monetary types
174
+ when 'money'
175
+ :decimal
176
+ when 'hstore'
177
+ :hstore
178
+ when 'ltree'
179
+ :ltree
180
+ # Network address types
181
+ when 'inet'
182
+ :inet
183
+ when 'cidr'
184
+ :cidr
185
+ when 'macaddr'
186
+ :macaddr
187
+ # Character types
188
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
189
+ :string
190
+ # Binary data types
191
+ when 'bytea'
192
+ :binary
193
+ # Date/time types
194
+ when /^timestamp with(?:out)? time zone$/
195
+ :datetime
196
+ when /^interval(?:|\(\d+\))$/
197
+ :string
198
+ # Geometric types
199
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
200
+ :string
201
+ # Bit strings
202
+ when /^bit(?: varying)?(?:\(\d+\))?$/
203
+ :string
204
+ # XML type
205
+ when 'xml'
206
+ :xml
207
+ # tsvector type
208
+ when 'tsvector'
209
+ :tsvector
210
+ # Arrays
211
+ when /^\D+\[\]$/
212
+ :string
213
+ # Object identifier types
214
+ when 'oid'
215
+ :integer
216
+ # UUID type
217
+ when 'uuid'
218
+ :uuid
219
+ # JSON type
220
+ when 'json'
221
+ :json
222
+ # Small and big integer types
223
+ when /^(?:small|big)int$/
224
+ :integer
225
+ when /(num|date|tstz|ts|int4|int8)range$/
226
+ field_type.to_sym
227
+ # Pass through all types that are not specific to PostgreSQL.
228
+ else
229
+ super
230
+ end
231
+ end
232
+ end
233
+
234
+ # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
235
+ #
236
+ # Options:
237
+ #
238
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
239
+ # the default is to connect to localhost.
240
+ # * <tt>:port</tt> - Defaults to 5432.
241
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
242
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
243
+ # * <tt>:database</tt> - Defaults to be the same as the user name.
244
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
245
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
246
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
247
+ # <encoding></tt> call on the connection.
248
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
249
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
250
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
251
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
252
+ # * <tt>:insert_returning</tt> - does nothing for Redshift.
253
+ #
254
+ # Any further options are used as connection parameters to libpq. See
255
+ # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
256
+ # list of parameters.
257
+ #
258
+ # In addition, default connection parameters of libpq can be set per environment variables.
259
+ # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
260
+ class RedshiftAdapter < AbstractAdapter
261
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
262
+ attr_accessor :array
263
+ end
264
+
265
+ module ColumnMethods
266
+ def xml(*args)
267
+ options = args.extract_options!
268
+ column(args[0], 'xml', options)
269
+ end
270
+
271
+ def tsvector(*args)
272
+ options = args.extract_options!
273
+ column(args[0], 'tsvector', options)
274
+ end
275
+
276
+ def int4range(name, options = {})
277
+ column(name, 'int4range', options)
278
+ end
279
+
280
+ def int8range(name, options = {})
281
+ column(name, 'int8range', options)
282
+ end
283
+
284
+ def tsrange(name, options = {})
285
+ column(name, 'tsrange', options)
286
+ end
287
+
288
+ def tstzrange(name, options = {})
289
+ column(name, 'tstzrange', options)
290
+ end
291
+
292
+ def numrange(name, options = {})
293
+ column(name, 'numrange', options)
294
+ end
295
+
296
+ def daterange(name, options = {})
297
+ column(name, 'daterange', options)
298
+ end
299
+
300
+ def hstore(name, options = {})
301
+ column(name, 'hstore', options)
302
+ end
303
+
304
+ def ltree(name, options = {})
305
+ column(name, 'ltree', options)
306
+ end
307
+
308
+ def inet(name, options = {})
309
+ column(name, 'inet', options)
310
+ end
311
+
312
+ def cidr(name, options = {})
313
+ column(name, 'cidr', options)
314
+ end
315
+
316
+ def macaddr(name, options = {})
317
+ column(name, 'macaddr', options)
318
+ end
319
+
320
+ def uuid(name, options = {})
321
+ column(name, 'uuid', options)
322
+ end
323
+
324
+ def json(name, options = {})
325
+ column(name, 'json', options)
326
+ end
327
+ end
328
+
329
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
330
+ include ColumnMethods
331
+
332
+ # Defines the primary key field.
333
+ # Use of the native PostgreSQL UUID type is supported, and can be used
334
+ # by defining your tables as such:
335
+ #
336
+ # create_table :stuffs, id: :uuid do |t|
337
+ # t.string :content
338
+ # t.timestamps
339
+ # end
340
+ #
341
+ # By default, this will use the +uuid_generate_v4()+ function from the
342
+ # +uuid-ossp+ extension, which MUST be enabled on your databse. To enable
343
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
344
+ # migrations To use a UUID primary key without +uuid-ossp+ enabled, you can
345
+ # set the +:default+ option to nil:
346
+ #
347
+ # create_table :stuffs, id: false do |t|
348
+ # t.primary_key :id, :uuid, default: nil
349
+ # t.uuid :foo_id
350
+ # t.timestamps
351
+ # end
352
+ #
353
+ # You may also pass a different UUID generation function from +uuid-ossp+
354
+ # or another library.
355
+ #
356
+ # Note that setting the UUID primary key default value to +nil+
357
+ # will require you to assure that you always provide a UUID value
358
+ # before saving a record (as primary keys cannot be nil). This might be
359
+ # done via the SecureRandom.uuid method and a +before_save+ callback,
360
+ # for instance.
361
+ def primary_key(name, type = :primary_key, options = {})
362
+ return super unless type == :uuid
363
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()')
364
+ options[:primary_key] = true
365
+ column name, type, options
366
+ end
367
+
368
+ def column(name, type = nil, options = {})
369
+ super
370
+ column = self[name]
371
+ column.array = options[:array]
372
+
373
+ self
374
+ end
375
+
376
+ def xml(options = {})
377
+ column(args[0], :text, options)
378
+ end
379
+
380
+ private
381
+
382
+ def create_column_definition(name, type)
383
+ ColumnDefinition.new name, type
384
+ end
385
+ end
386
+
387
+ class Table < ActiveRecord::ConnectionAdapters::Table
388
+ include ColumnMethods
389
+ end
390
+
391
+ ADAPTER_NAME = 'Redshift'
392
+
393
+ NATIVE_DATABASE_TYPES = {
394
+ primary_key: "serial primary key",
395
+ string: { name: "character varying", limit: 255 },
396
+ text: { name: "text" },
397
+ integer: { name: "integer" },
398
+ float: { name: "float" },
399
+ decimal: { name: "decimal" },
400
+ datetime: { name: "timestamp" },
401
+ timestamp: { name: "timestamp" },
402
+ time: { name: "time" },
403
+ date: { name: "date" },
404
+ daterange: { name: "daterange" },
405
+ numrange: { name: "numrange" },
406
+ tsrange: { name: "tsrange" },
407
+ tstzrange: { name: "tstzrange" },
408
+ int4range: { name: "int4range" },
409
+ int8range: { name: "int8range" },
410
+ binary: { name: "bytea" },
411
+ boolean: { name: "boolean" },
412
+ xml: { name: "xml" },
413
+ tsvector: { name: "tsvector" },
414
+ hstore: { name: "hstore" },
415
+ inet: { name: "inet" },
416
+ cidr: { name: "cidr" },
417
+ macaddr: { name: "macaddr" },
418
+ uuid: { name: "uuid" },
419
+ json: { name: "json" },
420
+ ltree: { name: "ltree" }
421
+ }
422
+
423
+ include Quoting
424
+ include ReferentialIntegrity
425
+ include SchemaStatements
426
+ include DatabaseStatements
427
+
428
+ # Returns 'PostgreSQL' as adapter name for identification purposes.
429
+ def adapter_name
430
+ ADAPTER_NAME
431
+ end
432
+
433
+ # Adds `:array` option to the default set provided by the
434
+ # AbstractAdapter
435
+ def prepare_column_options(column, types)
436
+ spec = super
437
+ spec[:array] = 'true' if column.respond_to?(:array) && column.array
438
+ spec
439
+ end
440
+
441
+ # Adds `:array` as a valid migration key
442
+ def migration_keys
443
+ super + [:array]
444
+ end
445
+
446
+ # Returns +true+, since this connection adapter supports prepared statement
447
+ # caching.
448
+ def supports_statement_cache?
449
+ true
450
+ end
451
+
452
+ def supports_index_sort_order?
453
+ true
454
+ end
455
+
456
+ def supports_partial_index?
457
+ true
458
+ end
459
+
460
+ def supports_transaction_isolation?
461
+ true
462
+ end
463
+
464
+ def index_algorithms
465
+ { concurrently: 'CONCURRENTLY' }
466
+ end
467
+
468
+ class StatementPool < ConnectionAdapters::StatementPool
469
+ def initialize(connection, max)
470
+ super
471
+ @counter = 0
472
+ @cache = Hash.new { |h,pid| h[pid] = {} }
473
+ end
474
+
475
+ def each(&block); cache.each(&block); end
476
+ def key?(key); cache.key?(key); end
477
+ def [](key); cache[key]; end
478
+ def length; cache.length; end
479
+
480
+ def next_key
481
+ "a#{@counter + 1}"
482
+ end
483
+
484
+ def []=(sql, key)
485
+ while @max <= cache.size
486
+ dealloc(cache.shift.last)
487
+ end
488
+ @counter += 1
489
+ cache[sql] = key
490
+ end
491
+
492
+ def clear
493
+ cache.each_value do |stmt_key|
494
+ dealloc stmt_key
495
+ end
496
+ cache.clear
497
+ end
498
+
499
+ def delete(sql_key)
500
+ dealloc cache[sql_key]
501
+ cache.delete sql_key
502
+ end
503
+
504
+ private
505
+
506
+ def cache
507
+ @cache[Process.pid]
508
+ end
509
+
510
+ def dealloc(key)
511
+ @connection.query "DEALLOCATE #{key}" if connection_active?
512
+ end
513
+
514
+ def connection_active?
515
+ @connection.status == PGconn::CONNECTION_OK
516
+ rescue PGError
517
+ false
518
+ end
519
+ end
520
+
521
+ class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
522
+ include Arel::Visitors::BindVisitor
523
+ end
524
+
525
+ # Initializes and connects a PostgreSQL adapter.
526
+ def initialize(connection, logger, connection_parameters, config)
527
+ super(connection, logger)
528
+
529
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
530
+ @visitor = Arel::Visitors::PostgreSQL.new self
531
+ else
532
+ @visitor = unprepared_visitor
533
+ end
534
+
535
+ connection_parameters.delete :prepared_statements
536
+ @connection_parameters, @config = connection_parameters, config
537
+
538
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
539
+ @local_tz = nil
540
+ @table_alias_length = nil
541
+
542
+ connect
543
+ @statements = StatementPool.new @connection,
544
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
545
+
546
+ initialize_type_map
547
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
548
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : false
549
+ end
550
+
551
+ # Clears the prepared statements cache.
552
+ def clear_cache!
553
+ @statements.clear
554
+ end
555
+
556
+ # Is this connection alive and ready for queries?
557
+ def active?
558
+ @connection.connect_poll != PG::PGRES_POLLING_FAILED
559
+ rescue PGError
560
+ false
561
+ end
562
+
563
+ # Close then reopen the connection.
564
+ def reconnect!
565
+ super
566
+ @connection.reset
567
+ configure_connection
568
+ end
569
+
570
+ def reset!
571
+ clear_cache!
572
+ super
573
+ end
574
+
575
+ # Disconnects from the database if already connected. Otherwise, this
576
+ # method does nothing.
577
+ def disconnect!
578
+ super
579
+ @connection.close rescue nil
580
+ end
581
+
582
+ def native_database_types #:nodoc:
583
+ NATIVE_DATABASE_TYPES
584
+ end
585
+
586
+ # Returns true, since this connection adapter supports migrations.
587
+ def supports_migrations?
588
+ true
589
+ end
590
+
591
+ # Does PostgreSQL support finding primary key on non-Active Record tables?
592
+ def supports_primary_key? #:nodoc:
593
+ true
594
+ end
595
+
596
+ def supports_insert_with_returning?
597
+ false
598
+ end
599
+
600
+ def supports_ddl_transactions?
601
+ true
602
+ end
603
+
604
+ # Returns true, since this connection adapter supports savepoints.
605
+ def supports_savepoints?
606
+ false
607
+ end
608
+
609
+ def supports_import?
610
+ true
611
+ end
612
+
613
+ # Returns true.
614
+ def supports_explain?
615
+ true
616
+ end
617
+
618
+ # Returns true if pg > 9.2
619
+ def supports_extensions?
620
+ false
621
+ end
622
+
623
+ # Range datatypes weren't introduced until PostgreSQL 9.2
624
+ def supports_ranges?
625
+ false
626
+ end
627
+
628
+ def enable_extension(name)
629
+ end
630
+
631
+ def disable_extension(name)
632
+ end
633
+
634
+ def extension_enabled?(name)
635
+ false
636
+ end
637
+
638
+ #def extensions
639
+ #end
640
+
641
+ # Returns the configured supported identifier length supported by PostgreSQL
642
+ def table_alias_length
643
+ @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
644
+ end
645
+
646
+ # Set the authorized user for this session
647
+ def session_auth=(user)
648
+ clear_cache!
649
+ exec_query "SET SESSION AUTHORIZATION #{user}"
650
+ end
651
+
652
+ module Utils
653
+ extend self
654
+
655
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
656
+ # +schema_name+ is nil if not specified in +name+.
657
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
658
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
659
+ #
660
+ # * <tt>table_name</tt>
661
+ # * <tt>"table.name"</tt>
662
+ # * <tt>schema_name.table_name</tt>
663
+ # * <tt>schema_name."table.name"</tt>
664
+ # * <tt>"schema.name"."table name"</tt>
665
+ def extract_schema_and_table(name)
666
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
667
+ [schema, table]
668
+ end
669
+ end
670
+
671
+ def use_insert_returning?
672
+ false
673
+ end
674
+
675
+ def valid_type?(type)
676
+ !native_database_types[type].nil?
677
+ end
678
+
679
+ protected
680
+
681
+ # Returns the version of the connected PostgreSQL server.
682
+ def postgresql_version
683
+ @connection.server_version
684
+ end
685
+
686
+ # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
687
+ FOREIGN_KEY_VIOLATION = "23503"
688
+ UNIQUE_VIOLATION = "23505"
689
+
690
+ def translate_exception(exception, message)
691
+ return exception unless exception.respond_to?(:result)
692
+
693
+ case exception.message
694
+ when /duplicate key value violates unique constraint/
695
+ RecordNotUnique.new(message, exception)
696
+ when /violates foreign key constraint/
697
+ InvalidForeignKey.new(message, exception)
698
+ else
699
+ super
700
+ end
701
+ end
702
+
703
+ private
704
+
705
+ def reload_type_map
706
+ OID::TYPE_MAP.clear
707
+ initialize_type_map
708
+ end
709
+
710
+ def initialize_type_map
711
+ result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
712
+ leaves, nodes = result.partition { |row| row['typelem'] == '0' }
713
+
714
+ # populate the leaf nodes
715
+ leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
716
+ OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
717
+ end
718
+
719
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
720
+
721
+ # populate composite types
722
+ nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
723
+ if OID.registered_type? row['typname']
724
+ # this composite type is explicitly registered
725
+ vector = OID::NAMES[row['typname']]
726
+ else
727
+ # use the default for composite types
728
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
729
+ end
730
+
731
+ OID::TYPE_MAP[row['oid'].to_i] = vector
732
+ end
733
+
734
+ # populate array types
735
+ arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
736
+ array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
737
+ OID::TYPE_MAP[row['oid'].to_i] = array
738
+ end
739
+ end
740
+
741
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
742
+
743
+ def exec_no_cache(sql, binds)
744
+ @connection.async_exec(sql, [])
745
+ end
746
+
747
+ def exec_cache(sql, binds)
748
+ stmt_key = prepare_statement sql
749
+
750
+ # Clear the queue
751
+ @connection.get_last_result
752
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
753
+ type_cast(val, col)
754
+ })
755
+ @connection.block
756
+ @connection.get_last_result
757
+ rescue PGError => e
758
+ # Get the PG code for the failure. Annoyingly, the code for
759
+ # prepared statements whose return value may have changed is
760
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
761
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
762
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
763
+ if FEATURE_NOT_SUPPORTED == code
764
+ @statements.delete sql_key(sql)
765
+ retry
766
+ else
767
+ raise e
768
+ end
769
+ end
770
+
771
+ # Returns the statement identifier for the client side cache
772
+ # of statements
773
+ def sql_key(sql)
774
+ "#{schema_search_path}-#{sql}"
775
+ end
776
+
777
+ # Prepare the statement if it hasn't been prepared, return
778
+ # the statement key.
779
+ def prepare_statement(sql)
780
+ sql_key = sql_key(sql)
781
+ unless @statements.key? sql_key
782
+ nextkey = @statements.next_key
783
+ @connection.prepare nextkey, sql
784
+ @statements[sql_key] = nextkey
785
+ end
786
+ @statements[sql_key]
787
+ end
788
+
789
+ # The internal PostgreSQL identifier of the money data type.
790
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
791
+ # The internal PostgreSQL identifier of the BYTEA data type.
792
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
793
+
794
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
795
+ # connected server's characteristics.
796
+ def connect
797
+ @connection = PGconn.connect(@connection_parameters)
798
+
799
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
800
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
801
+ # should know about this but can't detect it there, so deal with it here.
802
+ RedshiftColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
803
+
804
+ configure_connection
805
+ end
806
+
807
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
808
+ # This is called by #connect and should not be called manually.
809
+ def configure_connection
810
+ if @config[:encoding]
811
+ @connection.set_client_encoding(@config[:encoding])
812
+ end
813
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
814
+
815
+ # SET statements from :variables config hash
816
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
817
+ variables = @config[:variables] || {}
818
+ variables.map do |k, v|
819
+ if v == ':default' || v == :default
820
+ # Sets the value to the global or compile default
821
+ execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
822
+ elsif !v.nil?
823
+ execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
824
+ end
825
+ end
826
+ end
827
+
828
+ # Returns the current ID of a table's sequence.
829
+ def last_insert_id(sequence_name) #:nodoc:
830
+ Integer(last_insert_id_value(sequence_name))
831
+ end
832
+
833
+ def last_insert_id_value(sequence_name)
834
+ last_insert_id_result(sequence_name).rows.first.first
835
+ end
836
+
837
+ def last_insert_id_result(sequence_name) #:nodoc:
838
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
839
+ end
840
+
841
+ # Executes a SELECT query and returns the results, performing any data type
842
+ # conversions that are required to be performed here instead of in PostgreSQLColumn.
843
+ def select(sql, name = nil, binds = [])
844
+ exec_query(sql, name, binds)
845
+ end
846
+
847
+ def select_raw(sql, name = nil)
848
+ res = execute(sql, name)
849
+ results = result_as_array(res)
850
+ fields = res.fields
851
+ res.clear
852
+ return fields, results
853
+ end
854
+
855
+ # Returns the list of a table's column names, data types, and default values.
856
+ #
857
+ # The underlying query is roughly:
858
+ # SELECT column.name, column.type, default.value
859
+ # FROM column LEFT JOIN default
860
+ # ON column.table_id = default.table_id
861
+ # AND column.num = default.column_num
862
+ # WHERE column.table_id = get_table_id('table_name')
863
+ # AND column.num > 0
864
+ # AND NOT column.is_dropped
865
+ # ORDER BY column.num
866
+ #
867
+ # If the table name is not prefixed with a schema, the database will
868
+ # take the first match from the schema search path.
869
+ #
870
+ # Query implementation notes:
871
+ # - format_type includes the column size constraint, e.g. varchar(50)
872
+ # - ::regclass is a function that gives the id for a table name
873
+ def column_definitions(table_name) #:nodoc:
874
+ exec_query(<<-end_sql, 'SCHEMA').rows
875
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
876
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
877
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
878
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
879
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
880
+ AND a.attnum > 0 AND NOT a.attisdropped
881
+ ORDER BY a.attnum
882
+ end_sql
883
+ end
884
+
885
+ def extract_pg_identifier_from_name(name)
886
+ match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
887
+
888
+ if match_data
889
+ rest = name[match_data[0].length, name.length]
890
+ rest = rest[1, rest.length] if rest.start_with? "."
891
+ [match_data[1], (rest.length > 0 ? rest : nil)]
892
+ end
893
+ end
894
+
895
+ def extract_table_ref_from_insert_sql(sql)
896
+ sql[/into\s+([^\(]*).*values\s*\(/i]
897
+ $1.strip if $1
898
+ end
899
+
900
+ def create_table_definition(name, temporary, options, as = nil)
901
+ TableDefinition.new native_database_types, name, temporary, options
902
+ end
903
+
904
+ def update_table_definition(table_name, base)
905
+ Table.new(table_name, base)
906
+ end
907
+ end
908
+ end
909
+ end