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