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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +9 -9
- data/Gemfile +2 -2
- data/README.md +3 -2
- data/activerecord-jdbc-adapter.gemspec +1 -1
- data/activerecord-jdbc-alt-adapter.gemspec +1 -1
- data/lib/arel/visitors/sqlserver.rb +28 -11
- data/lib/arjdbc/abstract/connection_management.rb +5 -6
- data/lib/arjdbc/abstract/database_statements.rb +4 -14
- data/lib/arjdbc/abstract/relation_query_attribute_monkey_patch.rb +24 -0
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/mssql/adapter.rb +8 -2
- data/lib/arjdbc/mssql/adapter_hash_config.rb +53 -0
- data/lib/arjdbc/mssql/database_statements.rb +2 -2
- data/lib/arjdbc/mssql/quoting.rb +56 -36
- data/lib/arjdbc/mssql/schema_statements.rb +1 -18
- data/lib/arjdbc/mssql.rb +1 -1
- data/lib/arjdbc/mysql/adapter.rb +9 -1
- data/lib/arjdbc/mysql/adapter_hash_config.rb +149 -0
- data/lib/arjdbc/mysql.rb +1 -1
- data/lib/arjdbc/postgresql/adapter.rb +188 -74
- data/lib/arjdbc/postgresql/adapter_hash_config.rb +98 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +3 -1
- data/lib/arjdbc/postgresql/database_statements.rb +20 -0
- data/lib/arjdbc/postgresql/oid_types.rb +2 -2
- data/lib/arjdbc/postgresql.rb +1 -1
- data/lib/arjdbc/sqlite3/adapter.rb +42 -27
- data/lib/arjdbc/sqlite3/adapter_hash_config.rb +91 -0
- data/lib/arjdbc/sqlite3.rb +1 -1
- data/lib/arjdbc/version.rb +1 -1
- data/lib/arjdbc.rb +7 -1
- metadata +10 -4
@@ -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
@@ -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: {
|
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
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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
|
-
|
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|
|
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
|
-
|
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
|
-
|
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
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
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
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
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
|
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
|
-
|
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
|
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.
|
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"
|