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

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,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"