activerecord4-redshift-adapter 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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