activerecord-jdbc-alt-adapter 71.0.0.alpha2-java → 72.0.0.alpha1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module MysqlConfig
5
+ def build_connection_config(config)
6
+ config = config.deep_dup
7
+
8
+ load_jdbc_driver
9
+
10
+ config[:driver] ||= database_driver_name
11
+
12
+ host = (config[:host] ||= "localhost")
13
+ port = (config[:port] ||= 3306)
14
+
15
+ # jdbc:mysql://[host][,failoverhost...][:port]/[database]
16
+ # - alternate fail-over syntax: [host:port],[host:port]/[database]
17
+ config[:url] ||= "jdbc:mysql://#{host}:#{port}/#{config[:database]}"
18
+
19
+ config[:properties] = build_properties(config)
20
+
21
+ config
22
+ end
23
+
24
+ private
25
+
26
+ def load_jdbc_driver
27
+ require "jdbc/mysql"
28
+
29
+ ::Jdbc::MySQL.load_driver(:require) if defined?(::Jdbc::MySQL.load_driver)
30
+ rescue LoadError
31
+ # assuming driver.jar is on the class-path
32
+ end
33
+
34
+ def database_driver_name
35
+ return ::Jdbc::MySQL.driver_name if defined?(::Jdbc::MySQL.driver_name)
36
+
37
+ "com.mysql.jdbc.Driver"
38
+ end
39
+
40
+ def build_properties(config)
41
+ properties = config[:properties] || {}
42
+
43
+ properties["zeroDateTimeBehavior"] ||= "CONVERT_TO_NULL"
44
+
45
+ properties["jdbcCompliantTruncation"] ||= false
46
+
47
+ charset_name = convert_mysql_encoding(config)
48
+
49
+ # do not set characterEncoding
50
+ if charset_name.eql?(false)
51
+ properties["character_set_server"] = config[:encoding] || "utf8"
52
+ else
53
+ properties["characterEncoding"] = charset_name
54
+ end
55
+
56
+ # driver also executes: "SET NAMES " + (useutf8mb4 ? "utf8mb4" : "utf8")
57
+ # thus no need to do it on configure_connection :
58
+ config[:encoding] = nil if config.key?(:encoding)
59
+
60
+ properties["connectionCollation"] ||= config[:collation] if config[:collation]
61
+
62
+ properties["autoReconnect"] ||= reconnect.to_s unless config[:reconnect].nil?
63
+
64
+ properties["noDatetimeStringSync"] = true unless properties.key?("noDatetimeStringSync")
65
+
66
+ sslcert = config[:sslcert]
67
+ sslca = config[:sslca]
68
+
69
+ if config[:sslkey] || sslcert
70
+ properties["useSSL"] ||= true
71
+ properties["requireSSL"] ||= true
72
+ properties["clientCertificateKeyStoreUrl"] ||= java.io.File.new(sslcert).to_url.to_s if sslcert
73
+
74
+ if sslca
75
+ properties["trustCertificateKeyStoreUrl"] ||= java.io.File.new(sslca).to_url.to_s
76
+ else
77
+ properties["verifyServerCertificate"] ||= false
78
+ end
79
+ else
80
+ # According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection
81
+ # must be established by default if explicit option isn't set :
82
+ properties["useSSL"] ||= false
83
+ end
84
+
85
+ # disables the effect of 'useTimezone'
86
+ properties["useLegacyDatetimeCode"] = false
87
+
88
+ properties
89
+ end
90
+
91
+ # See https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-charsets.html
92
+ # to charset-name (characterEncoding=...)
93
+ def convert_mysql_encoding(config)
94
+ # NOTE: this is "better" than passing what users are used to set on MRI
95
+ # e.g. 'utf8mb4' will fail cause the driver will check for a Java charset
96
+ # ... it's smart enough to detect utf8mb4 from server variables :
97
+ # "character_set_client" && "character_set_connection" (thus UTF-8)
98
+ encoding = config.key?(:encoding) ? config[:encoding] : "utf8"
99
+
100
+ value = MYSQL_ENCODINGS[encoding]
101
+
102
+ return false if value == false
103
+
104
+ value || encoding
105
+ end
106
+
107
+ MYSQL_ENCODINGS = {
108
+ "big5" => "Big5",
109
+ "dec8" => nil,
110
+ "hp8" => nil,
111
+ "latin1" => "Cp1252",
112
+ "latin2" => "ISO8859_2",
113
+ "swe7" => nil,
114
+ "ascii" => "US-ASCII",
115
+ "ujis" => "EUC_JP",
116
+ "sjis" => "SJIS",
117
+ "hebrew" => "ISO8859_8",
118
+ "tis620" => "TIS620",
119
+ "euckr" => "EUC_KR",
120
+ "gb2312" => "EUC_CN",
121
+ "greek" => "ISO8859_7",
122
+ "cp1250" => "Cp1250",
123
+ "gbk" => "GBK",
124
+ "armscii8" => nil,
125
+ "ucs2" => "UnicodeBig",
126
+ "cp866" => "Cp866",
127
+ "keybcs2" => nil,
128
+ "macce" => "MacCentralEurope",
129
+ "macroman" => "MacRoman",
130
+ "cp1251" => "Cp1251",
131
+ "cp1256" => "Cp1256",
132
+ "cp1257" => "Cp1257",
133
+ "binary" => false,
134
+ "geostd8" => nil,
135
+ "cp932" => "Cp932",
136
+ "utf8" => "UTF-8",
137
+ "utf8mb4" => false,
138
+ "utf16" => false,
139
+ "utf32" => false,
140
+ # "cp850" => "Cp850",
141
+ # "koi8r" => "KOI8-R",
142
+ # "koi8u" => "KOI8-R",
143
+ # "latin5" => "ISO-8859-9",
144
+ # "cp852" => "CP852",
145
+ # "latin7" => "ISO-8859-13",
146
+ # "eucjpms" => "eucJP-ms"
147
+ }.freeze
148
+ end
149
+ end
data/lib/arjdbc/mysql.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'arjdbc'
2
2
  require 'arjdbc/mysql/adapter'
3
- require 'arjdbc/mysql/connection_methods'
3
+ # require 'arjdbc/mysql/connection_methods'
@@ -22,10 +22,14 @@ require 'arjdbc/abstract/transaction_support'
22
22
  require 'arjdbc/postgresql/base/array_decoder'
23
23
  require 'arjdbc/postgresql/base/array_encoder'
24
24
  require 'arjdbc/postgresql/name'
25
+ require 'arjdbc/postgresql/database_statements'
25
26
  require 'arjdbc/postgresql/schema_statements'
27
+ require "arjdbc/postgresql/adapter_hash_config"
26
28
 
27
29
  require 'active_model'
28
30
 
31
+ require "arjdbc/abstract/relation_query_attribute_monkey_patch"
32
+
29
33
  module ArJdbc
30
34
  # Strives to provide Rails built-in PostgreSQL adapter (API) compatibility.
31
35
  module PostgreSQL
@@ -120,7 +124,8 @@ module ArJdbc
120
124
  citext: { name: 'citext' },
121
125
  date: { name: 'date' },
122
126
  daterange: { name: 'daterange' },
123
- datetime: { name: 'timestamp' },
127
+ datetime: {}, # set dynamically based on datetime_type
128
+ timestamptz: { name: 'timestamptz' },
124
129
  decimal: { name: 'decimal' }, # :limit => 1000
125
130
  float: { name: 'float' },
126
131
  hstore: { name: 'hstore' },
@@ -150,17 +155,10 @@ module ArJdbc
150
155
  tstzrange: { name: 'tstzrange' },
151
156
  tsvector: { name: 'tsvector' },
152
157
  uuid: { name: 'uuid' },
153
- xml: { name: 'xml' }
158
+ xml: { name: 'xml' },
159
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
154
160
  }
155
161
 
156
- def native_database_types
157
- NATIVE_DATABASE_TYPES
158
- end
159
-
160
- def valid_type?(type)
161
- !native_database_types[type].nil?
162
- end
163
-
164
162
  def set_standard_conforming_strings
165
163
  execute("SET standard_conforming_strings = on", "SCHEMA")
166
164
  end
@@ -232,10 +230,18 @@ module ArJdbc
232
230
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
233
231
  alias supports_insert_conflict_target? supports_insert_on_conflict?
234
232
 
233
+ def supports_virtual_columns?
234
+ database_version >= 12_00_00 # >= 12.0
235
+ end
236
+
235
237
  def supports_identity_columns? # :nodoc:
236
238
  database_version >= 10_00_00 # >= 10.0
237
239
  end
238
240
 
241
+ def supports_nulls_not_distinct?
242
+ database_version >= 15_00_00 # >= 15.0
243
+ end
244
+
239
245
  def index_algorithms
240
246
  { concurrently: 'CONCURRENTLY' }
241
247
  end
@@ -335,33 +341,100 @@ module ArJdbc
335
341
  # Returns a list of defined enum types, and their values.
336
342
  def enum_types
337
343
  query = <<~SQL
338
- SELECT
339
- type.typname AS name,
340
- string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
341
- FROM pg_enum AS enum
342
- JOIN pg_type AS type
343
- ON (type.oid = enum.enumtypid)
344
- GROUP BY type.typname;
344
+ SELECT
345
+ type.typname AS name,
346
+ type.OID AS oid,
347
+ n.nspname AS schema,
348
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
349
+ FROM pg_enum AS enum
350
+ JOIN pg_type AS type ON (type.oid = enum.enumtypid)
351
+ JOIN pg_namespace n ON type.typnamespace = n.oid
352
+ WHERE n.nspname = ANY (current_schemas(false))
353
+ GROUP BY type.OID, n.nspname, type.typname;
345
354
  SQL
346
- exec_query(query, "SCHEMA").cast_values
355
+
356
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
357
+ name, schema = row[0], row[2]
358
+ schema = nil if schema == current_schema
359
+ full_name = [schema, name].compact.join(".")
360
+ memo[full_name] = row.last
361
+ end.to_a
347
362
  end
348
363
 
349
364
  # Given a name and an array of values, creates an enum type.
350
- def create_enum(name, values)
351
- sql_values = values.map { |s| "'#{s}'" }.join(", ")
365
+ def create_enum(name, values, **options)
366
+ sql_values = values.map { |s| quote(s) }.join(", ")
367
+ scope = quoted_scope(name)
368
+ query = <<~SQL
369
+ DO $$
370
+ BEGIN
371
+ IF NOT EXISTS (
372
+ SELECT 1
373
+ FROM pg_type t
374
+ JOIN pg_namespace n ON t.typnamespace = n.oid
375
+ WHERE t.typname = #{scope[:name]}
376
+ AND n.nspname = #{scope[:schema]}
377
+ ) THEN
378
+ CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
379
+ END IF;
380
+ END
381
+ $$;
382
+ SQL
383
+
384
+ internal_exec_query(query).tap { reload_type_map }
385
+ end
386
+
387
+ # Drops an enum type.
388
+ #
389
+ # If the <tt>if_exists: true</tt> option is provided, the enum is dropped
390
+ # only if it exists. Otherwise, if the enum doesn't exist, an error is
391
+ # raised.
392
+ #
393
+ # The +values+ parameter will be ignored if present. It can be helpful
394
+ # to provide this in a migration's +change+ method so it can be reverted.
395
+ # In that case, +values+ will be used by #create_enum.
396
+ def drop_enum(name, values = nil, **options)
352
397
  query = <<~SQL
353
- DO $$
354
- BEGIN
355
- IF NOT EXISTS (
356
- SELECT 1 FROM pg_type t
357
- WHERE t.typname = '#{name}'
358
- ) THEN
359
- CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
360
- END IF;
361
- END
362
- $$;
398
+ DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
363
399
  SQL
364
- exec_query(query)
400
+ internal_exec_query(query).tap { reload_type_map }
401
+ end
402
+
403
+ # Rename an existing enum type to something else.
404
+ def rename_enum(name, options = {})
405
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
406
+
407
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
408
+ end
409
+
410
+ # Add enum value to an existing enum type.
411
+ def add_enum_value(type_name, value, options = {})
412
+ before, after = options.values_at(:before, :after)
413
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
414
+
415
+ if before && after
416
+ raise ArgumentError, "Cannot have both :before and :after at the same time"
417
+ elsif before
418
+ sql << " BEFORE '#{before}'"
419
+ elsif after
420
+ sql << " AFTER '#{after}'"
421
+ end
422
+
423
+ execute(sql).tap { reload_type_map }
424
+ end
425
+
426
+ # Rename enum value on an existing enum type.
427
+ def rename_enum_value(type_name, options = {})
428
+ unless database_version >= 10_00_00 # >= 10.0
429
+ raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
430
+ end
431
+
432
+ from = options.fetch(:from) { raise ArgumentError, ":from is required" }
433
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
434
+
435
+ execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
436
+ reload_type_map
437
+ }
365
438
  end
366
439
 
367
440
  # Returns the configured supported identifier length supported by PostgreSQL
@@ -455,11 +528,6 @@ module ArJdbc
455
528
  execute(combine_multi_statements(statements), name)
456
529
  end
457
530
 
458
- def explain(arel, binds = [])
459
- sql, binds = to_sql_and_binds(arel, binds)
460
- ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query("EXPLAIN #{sql}", 'EXPLAIN', binds))
461
- end
462
-
463
531
  # from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
464
532
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
465
533
  :close, :declare, :fetch, :move, :set, :show
@@ -493,6 +561,16 @@ module ArJdbc
493
561
  end
494
562
  end
495
563
 
564
+ # Disconnects from the database if already connected. Otherwise, this
565
+ # method does nothing.
566
+ def disconnect!
567
+ @lock.synchronize do
568
+ super
569
+ @raw_connection&.close
570
+ @raw_connection = nil
571
+ end
572
+ end
573
+
496
574
  def default_sequence_name(table_name, pk = "id") #:nodoc:
497
575
  serial_sequence(table_name, pk)
498
576
  rescue ActiveRecord::StatementInvalid
@@ -608,17 +686,19 @@ module ArJdbc
608
686
  # - format_type includes the column size constraint, e.g. varchar(50)
609
687
  # - ::regclass is a function that gives the id for a table name
610
688
  def column_definitions(table_name)
611
- select_rows(<<~SQL, 'SCHEMA')
612
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
613
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
614
- c.collname, col_description(a.attrelid, a.attnum) AS comment
615
- FROM pg_attribute a
616
- LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
617
- LEFT JOIN pg_type t ON a.atttypid = t.oid
618
- LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
619
- WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
620
- AND a.attnum > 0 AND NOT a.attisdropped
621
- ORDER BY a.attnum
689
+ query(<<~SQL, "SCHEMA")
690
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
691
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
692
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
693
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
694
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
695
+ FROM pg_attribute a
696
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
697
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
698
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
699
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
700
+ AND a.attnum > 0 AND NOT a.attisdropped
701
+ ORDER BY a.attnum
622
702
  SQL
623
703
  end
624
704
 
@@ -633,22 +713,27 @@ module ArJdbc
633
713
 
634
714
  # Pulled from ActiveRecord's Postgres adapter and modified to use execute
635
715
  def can_perform_case_insensitive_comparison_for?(column)
636
- @case_insensitive_cache ||= {}
637
- @case_insensitive_cache[column.sql_type] ||= begin
638
- sql = <<~SQL
639
- SELECT exists(
640
- SELECT * FROM pg_proc
641
- WHERE proname = 'lower'
642
- AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
643
- ) OR exists(
644
- SELECT * FROM pg_proc
645
- INNER JOIN pg_cast
646
- ON ARRAY[casttarget]::oidvector = proargtypes
647
- WHERE proname = 'lower'
648
- AND castsource = #{quote column.sql_type}::regtype
649
- )
650
- SQL
651
- select_value(sql, 'SCHEMA')
716
+ # NOTE: citext is an exception. It is possible to perform a
717
+ # case-insensitive comparison using `LOWER()`, but it is
718
+ # unnecessary, as `citext` is case-insensitive by definition.
719
+ @case_insensitive_cache ||= { "citext" => false }
720
+ @case_insensitive_cache.fetch(column.sql_type) do
721
+ @case_insensitive_cache[column.sql_type] = begin
722
+ sql = <<~SQL
723
+ SELECT exists(
724
+ SELECT * FROM pg_proc
725
+ WHERE proname = 'lower'
726
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
727
+ ) OR exists(
728
+ SELECT * FROM pg_proc
729
+ INNER JOIN pg_cast
730
+ ON ARRAY[casttarget]::oidvector = proargtypes
731
+ WHERE proname = 'lower'
732
+ AND castsource = #{quote column.sql_type}::regtype
733
+ )
734
+ SQL
735
+ select_value(sql, 'SCHEMA')
736
+ end
652
737
  end
653
738
  end
654
739
 
@@ -657,6 +742,8 @@ module ArJdbc
657
742
 
658
743
  # TODO: Can we base these on an error code of some kind?
659
744
  case exception.message
745
+ when /could not create unique index/
746
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
660
747
  when /duplicate key value violates unique constraint/
661
748
  ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
662
749
  when /violates not-null constraint/
@@ -675,7 +762,9 @@ module ArJdbc
675
762
  ::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
676
763
  when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
677
764
  ::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
678
- when /relation "animals" does not exist/i
765
+ when /relation .* does not exist/i
766
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
767
+ when /syntax error at or near/i
679
768
  ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
680
769
  else
681
770
  super
@@ -767,9 +856,11 @@ module ActiveRecord::ConnectionAdapters
767
856
  include ArJdbc::Abstract::StatementCache
768
857
  include ArJdbc::Abstract::TransactionSupport
769
858
  include ArJdbc::PostgreSQL
859
+ include ArJdbc::PostgreSQLConfig
770
860
 
771
861
  require 'arjdbc/postgresql/oid_types'
772
862
  include ::ArJdbc::PostgreSQL::OIDTypes
863
+ include ::ArJdbc::PostgreSQL::DatabaseStatements
773
864
  include ::ArJdbc::PostgreSQL::SchemaStatements
774
865
 
775
866
  include ::ArJdbc::PostgreSQL::ColumnHelpers
@@ -787,12 +878,32 @@ module ActiveRecord::ConnectionAdapters
787
878
  def new_client(conn_params, adapter_instance)
788
879
  jdbc_connection_class.new(conn_params, adapter_instance)
789
880
  end
881
+
882
+ def dbconsole(config, options = {})
883
+ pg_config = config.configuration_hash
884
+
885
+ ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
886
+ ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
887
+ ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
888
+ ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
889
+ ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
890
+ ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
891
+ ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
892
+ ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
893
+ if pg_config[:variables]
894
+ ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
895
+ "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
896
+ end.join(" ")
897
+ end
898
+ find_cmd_and_exec("psql", config.database)
899
+ end
790
900
  end
791
901
 
792
902
  def initialize(...)
793
903
  super
794
904
 
795
- conn_params = @config.compact
905
+ # assign arjdbc extra connection params
906
+ conn_params = build_connection_config(@config.compact)
796
907
 
797
908
  @connection_parameters = conn_params
798
909
 
@@ -804,15 +915,6 @@ module ActiveRecord::ConnectionAdapters
804
915
  self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
805
916
  end
806
917
 
807
- def self.database_exists?(config)
808
- conn = ActiveRecord::Base.postgresql_connection(config)
809
- conn && conn.really_valid?
810
- rescue ActiveRecord::NoDatabaseError
811
- false
812
- ensure
813
- conn.disconnect! if conn
814
- end
815
-
816
918
  require 'active_record/connection_adapters/postgresql/schema_definitions'
817
919
 
818
920
  ColumnMethods = ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods
@@ -822,6 +924,18 @@ module ActiveRecord::ConnectionAdapters
822
924
  public :sql_for_insert
823
925
  alias :postgresql_version :database_version
824
926
 
927
+ def native_database_types # :nodoc:
928
+ self.class.native_database_types
929
+ end
930
+
931
+ def self.native_database_types # :nodoc:
932
+ @native_database_types ||= begin
933
+ types = NATIVE_DATABASE_TYPES.dup
934
+ types[:datetime] = types[datetime_type]
935
+ types
936
+ end
937
+ end
938
+
825
939
  private
826
940
 
827
941
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module PostgreSQLConfig
5
+ def build_connection_config(config)
6
+ config = config.deep_dup
7
+
8
+ load_jdbc_driver
9
+
10
+ config[:driver] ||= database_driver_name
11
+
12
+ host = (config[:host] ||= config[:hostaddr] || ENV["PGHOST"] || "localhost")
13
+ port = (config[:port] ||= ENV["PGPORT"] || 5432)
14
+ database = config[:database] || config[:dbname] || ENV["PGDATABASE"]
15
+
16
+ app = config[:application_name] || config[:appname] || config[:application]
17
+
18
+ config[:url] ||= if app
19
+ "jdbc:postgresql://#{host}:#{port}/#{database}?ApplicationName=#{app}"
20
+ else
21
+ "jdbc:postgresql://#{host}:#{port}/#{database}"
22
+ end
23
+
24
+ config[:url] << config[:pg_params] if config[:pg_params]
25
+
26
+ config[:username] ||= config[:user] || ENV["PGUSER"] || ENV_JAVA["user.name"]
27
+ config[:password] ||= ENV["PGPASSWORD"] unless config.key?(:password)
28
+
29
+ config[:properties] = build_properties(config)
30
+
31
+ config
32
+ end
33
+
34
+ private
35
+
36
+ def load_jdbc_driver
37
+ require "jdbc/postgres"
38
+
39
+ ::Jdbc::Postgres.load_driver(:require) if defined?(::Jdbc::Postgres.load_driver)
40
+ rescue LoadError
41
+ # assuming driver.jar is on the class-path
42
+ end
43
+
44
+ def database_driver_name
45
+ return ::Jdbc::Postgres.driver_name if defined?(::Jdbc::Postgres.driver_name)
46
+
47
+ "org.postgresql.Driver"
48
+ end
49
+
50
+ def build_properties(config)
51
+ properties = config[:properties] || {}
52
+
53
+ # PG :connect_timeout - maximum time to wait for connection to succeed
54
+ connect_timeout = config[:connect_timeout] || ENV["PGCONNECT_TIMEOUT"]
55
+
56
+ properties["socketTimeout"] ||= connect_timeout if connect_timeout
57
+
58
+ login_timeout = config[:login_timeout]
59
+
60
+ properties["loginTimeout"] ||= login_timeout if login_timeout
61
+
62
+ sslmode = config.key?(:sslmode) ? config[:sslmode] : config[:requiressl]
63
+ # NOTE: makes not much sense since this needs some JVM options :
64
+ sslmode = ENV["PGSSLMODE"] || ENV["PGREQUIRESSL"] if sslmode.nil?
65
+
66
+ # PG :sslmode - disable|allow|prefer|require
67
+ unless sslmode.nil? || !(sslmode == true || sslmode.to_s == "require")
68
+ # JRuby/JVM needs to be started with :
69
+ # -Djavax.net.ssl.trustStore=mystore -Djavax.net.ssl.trustStorePassword=...
70
+ # or a non-validating connection might be used (for testing) :
71
+ # :sslfactory = 'org.postgresql.ssl.NonValidatingFactory'
72
+
73
+ if config[:driver].start_with?("org.postgresql.")
74
+ properties["sslfactory"] ||= "org.postgresql.ssl.NonValidatingFactory"
75
+ end
76
+
77
+ properties["ssl"] ||= "true"
78
+ end
79
+
80
+ properties["tcpKeepAlive"] ||= config[:keepalives] if config.key?(:keepalives)
81
+ properties["kerberosServerName"] ||= config[:krbsrvname] if config[:krbsrvname]
82
+
83
+ prepared_statements = config.fetch(:prepared_statements, true)
84
+
85
+ prepared_statements = false if prepared_statements == "false"
86
+
87
+ if prepared_statements
88
+ # this makes the pgjdbc driver handle hot compatibility internally
89
+ properties["autosave"] ||= "conservative"
90
+ else
91
+ # If prepared statements are off, lets make sure they are really *off*
92
+ properties["prepareThreshold"] = 0
93
+ end
94
+
95
+ properties
96
+ end
97
+ end
98
+ end
@@ -12,7 +12,9 @@ module ActiveRecord::ConnectionAdapters::PostgreSQL::OID
12
12
  'text'.freeze
13
13
  else
14
14
  base_type = name.chomp('[]').to_sym
15
- ActiveRecord::Base.connection.native_database_types[base_type][:name]
15
+ ActiveRecord::Base.with_connection do |connection|
16
+ connection.native_database_types[base_type][:name]
17
+ end
16
18
  end
17
19
  end
18
20
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module PostgreSQL
5
+ module DatabaseStatements
6
+ def explain(arel, binds = [], options = [])
7
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
8
+
9
+ result = internal_exec_query(sql, "EXPLAIN", binds)
10
+ ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(result)
11
+ end
12
+
13
+ def build_explain_clause(options = [])
14
+ return "EXPLAIN" if options.empty?
15
+
16
+ "EXPLAIN (#{options.join(", ").upcase})"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -112,8 +112,8 @@ module ArJdbc
112
112
  m.register_type "int4", Type::Integer.new(limit: 4)
113
113
  m.register_type "int8", Type::Integer.new(limit: 8)
114
114
  m.register_type "oid", OID::Oid.new
115
- m.register_type "float4", Type::Float.new
116
- m.alias_type "float8", "float4"
115
+ m.register_type "float4", Type::Float.new(limit: 24)
116
+ m.register_type "float8", Type::Float.new
117
117
  m.register_type "text", Type::Text.new
118
118
  register_class_with_limit m, "varchar", Type::String
119
119
  m.alias_type "char", "varchar"