activerecord-jdbc-adapter 70.2-java → 71.0-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 +18 -18
- data/.gitignore +8 -0
- data/Gemfile +17 -4
- data/README.md +8 -3
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/lib/arjdbc/abstract/connection_management.rb +25 -10
- data/lib/arjdbc/abstract/core.rb +5 -12
- data/lib/arjdbc/abstract/database_statements.rb +35 -35
- data/lib/arjdbc/abstract/relation_query_attribute_monkey_patch.rb +24 -0
- data/lib/arjdbc/abstract/statement_cache.rb +2 -7
- data/lib/arjdbc/abstract/transaction_support.rb +37 -22
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +0 -34
- data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
- data/lib/arjdbc/mysql/adapter.rb +106 -27
- data/lib/arjdbc/postgresql/adapter.rb +252 -105
- data/lib/arjdbc/postgresql/database_statements.rb +20 -0
- data/lib/arjdbc/postgresql/oid_types.rb +8 -27
- data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
- data/lib/arjdbc/sqlite3/adapter.rb +213 -145
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +1 -1
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +11 -10
@@ -13,6 +13,7 @@ require 'active_record/connection_adapters/postgresql/schema_dumper'
|
|
13
13
|
require 'active_record/connection_adapters/postgresql/schema_statements'
|
14
14
|
require 'active_record/connection_adapters/postgresql/type_metadata'
|
15
15
|
require 'active_record/connection_adapters/postgresql/utils'
|
16
|
+
|
16
17
|
require 'arjdbc/abstract/core'
|
17
18
|
require 'arjdbc/abstract/connection_management'
|
18
19
|
require 'arjdbc/abstract/database_statements'
|
@@ -21,8 +22,13 @@ require 'arjdbc/abstract/transaction_support'
|
|
21
22
|
require 'arjdbc/postgresql/base/array_decoder'
|
22
23
|
require 'arjdbc/postgresql/base/array_encoder'
|
23
24
|
require 'arjdbc/postgresql/name'
|
25
|
+
require 'arjdbc/postgresql/database_statements'
|
26
|
+
require 'arjdbc/postgresql/schema_statements'
|
27
|
+
|
24
28
|
require 'active_model'
|
25
29
|
|
30
|
+
require "arjdbc/abstract/relation_query_attribute_monkey_patch"
|
31
|
+
|
26
32
|
module ArJdbc
|
27
33
|
# Strives to provide Rails built-in PostgreSQL adapter (API) compatibility.
|
28
34
|
module PostgreSQL
|
@@ -35,11 +41,6 @@ module ArJdbc
|
|
35
41
|
# @private
|
36
42
|
Type = ::ActiveRecord::Type
|
37
43
|
|
38
|
-
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
|
39
|
-
def self.jdbc_connection_class
|
40
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
|
41
|
-
end
|
42
|
-
|
43
44
|
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
|
44
45
|
def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn end
|
45
46
|
|
@@ -52,8 +53,8 @@ module ArJdbc
|
|
52
53
|
def redshift?
|
53
54
|
# SELECT version() :
|
54
55
|
# PostgreSQL 8.0.2 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3), Redshift 1.0.647
|
55
|
-
if (
|
56
|
-
redshift = !! (
|
56
|
+
if (redshift = @config[:redshift]).nil?
|
57
|
+
redshift = !! (valid_raw_connection.database_product || '').index('Redshift')
|
57
58
|
end
|
58
59
|
redshift
|
59
60
|
end
|
@@ -73,8 +74,8 @@ module ArJdbc
|
|
73
74
|
# see http://jdbc.postgresql.org/documentation/91/connect.html
|
74
75
|
# self.set_client_encoding(encoding)
|
75
76
|
#end
|
76
|
-
self.client_min_messages = config[:min_messages] || 'warning'
|
77
|
-
self.schema_search_path = config[:schema_search_path] || config[:schema_order]
|
77
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
78
|
+
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
78
79
|
|
79
80
|
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
|
80
81
|
set_standard_conforming_strings
|
@@ -93,7 +94,7 @@ module ArJdbc
|
|
93
94
|
|
94
95
|
# SET statements from :variables config hash
|
95
96
|
# http://www.postgresql.org/docs/8.3/static/sql-set.html
|
96
|
-
(config[:variables] || {}).map do |k, v|
|
97
|
+
(@config[:variables] || {}).map do |k, v|
|
97
98
|
if v == ':default' || v == :default
|
98
99
|
# Sets the value to the global or compile default
|
99
100
|
execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
|
@@ -101,6 +102,8 @@ module ArJdbc
|
|
101
102
|
execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
|
102
103
|
end
|
103
104
|
end
|
105
|
+
|
106
|
+
reload_type_map
|
104
107
|
end
|
105
108
|
|
106
109
|
# @private
|
@@ -120,14 +123,15 @@ module ArJdbc
|
|
120
123
|
citext: { name: 'citext' },
|
121
124
|
date: { name: 'date' },
|
122
125
|
daterange: { name: 'daterange' },
|
123
|
-
datetime: {
|
126
|
+
datetime: {}, # set dynamically based on datetime_type
|
127
|
+
timestamptz: { name: 'timestamptz' },
|
124
128
|
decimal: { name: 'decimal' }, # :limit => 1000
|
125
129
|
float: { name: 'float' },
|
126
130
|
hstore: { name: 'hstore' },
|
127
131
|
inet: { name: 'inet' },
|
128
132
|
int4range: { name: 'int4range' },
|
129
133
|
int8range: { name: 'int8range' },
|
130
|
-
integer: { name: 'integer' },
|
134
|
+
integer: { name: 'integer', limit: 4 },
|
131
135
|
interval: { name: 'interval' },
|
132
136
|
json: { name: 'json' },
|
133
137
|
jsonb: { name: 'jsonb' },
|
@@ -150,17 +154,10 @@ module ArJdbc
|
|
150
154
|
tstzrange: { name: 'tstzrange' },
|
151
155
|
tsvector: { name: 'tsvector' },
|
152
156
|
uuid: { name: 'uuid' },
|
153
|
-
xml: { name: 'xml' }
|
157
|
+
xml: { name: 'xml' },
|
158
|
+
enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
|
154
159
|
}
|
155
160
|
|
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
161
|
def set_standard_conforming_strings
|
165
162
|
execute("SET standard_conforming_strings = on", "SCHEMA")
|
166
163
|
end
|
@@ -232,6 +229,18 @@ module ArJdbc
|
|
232
229
|
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
233
230
|
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
234
231
|
|
232
|
+
def supports_virtual_columns?
|
233
|
+
database_version >= 12_00_00 # >= 12.0
|
234
|
+
end
|
235
|
+
|
236
|
+
def supports_identity_columns? # :nodoc:
|
237
|
+
database_version >= 10_00_00 # >= 10.0
|
238
|
+
end
|
239
|
+
|
240
|
+
def supports_nulls_not_distinct?
|
241
|
+
database_version >= 15_00_00 # >= 15.0
|
242
|
+
end
|
243
|
+
|
235
244
|
def index_algorithms
|
236
245
|
{ concurrently: 'CONCURRENTLY' }
|
237
246
|
end
|
@@ -297,14 +306,21 @@ module ArJdbc
|
|
297
306
|
query_value("SELECT pg_advisory_unlock(#{lock_id})")
|
298
307
|
end
|
299
308
|
|
300
|
-
def enable_extension(name)
|
301
|
-
|
302
|
-
|
303
|
-
}
|
309
|
+
def enable_extension(name, **)
|
310
|
+
schema, name = name.to_s.split(".").values_at(-2, -1)
|
311
|
+
sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
|
312
|
+
sql << " SCHEMA #{schema}" if schema
|
313
|
+
|
314
|
+
internal_exec_query(sql).tap { reload_type_map }
|
304
315
|
end
|
305
316
|
|
306
|
-
|
307
|
-
|
317
|
+
# Removes an extension from the database.
|
318
|
+
#
|
319
|
+
# [<tt>:force</tt>]
|
320
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
321
|
+
# Defaults to false.
|
322
|
+
def disable_extension(name, force: false)
|
323
|
+
internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
|
308
324
|
reload_type_map
|
309
325
|
}
|
310
326
|
end
|
@@ -318,39 +334,106 @@ module ArJdbc
|
|
318
334
|
end
|
319
335
|
|
320
336
|
def extensions
|
321
|
-
|
337
|
+
internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
|
322
338
|
end
|
323
339
|
|
324
340
|
# Returns a list of defined enum types, and their values.
|
325
341
|
def enum_types
|
326
342
|
query = <<~SQL
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
343
|
+
SELECT
|
344
|
+
type.typname AS name,
|
345
|
+
type.OID AS oid,
|
346
|
+
n.nspname AS schema,
|
347
|
+
string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
|
348
|
+
FROM pg_enum AS enum
|
349
|
+
JOIN pg_type AS type ON (type.oid = enum.enumtypid)
|
350
|
+
JOIN pg_namespace n ON type.typnamespace = n.oid
|
351
|
+
WHERE n.nspname = ANY (current_schemas(false))
|
352
|
+
GROUP BY type.OID, n.nspname, type.typname;
|
334
353
|
SQL
|
335
|
-
|
354
|
+
|
355
|
+
internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
|
356
|
+
name, schema = row[0], row[2]
|
357
|
+
schema = nil if schema == current_schema
|
358
|
+
full_name = [schema, name].compact.join(".")
|
359
|
+
memo[full_name] = row.last
|
360
|
+
end.to_a
|
336
361
|
end
|
337
362
|
|
338
363
|
# Given a name and an array of values, creates an enum type.
|
339
|
-
def create_enum(name, values)
|
340
|
-
sql_values = values.map { |s|
|
364
|
+
def create_enum(name, values, **options)
|
365
|
+
sql_values = values.map { |s| quote(s) }.join(", ")
|
366
|
+
scope = quoted_scope(name)
|
367
|
+
query = <<~SQL
|
368
|
+
DO $$
|
369
|
+
BEGIN
|
370
|
+
IF NOT EXISTS (
|
371
|
+
SELECT 1
|
372
|
+
FROM pg_type t
|
373
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
374
|
+
WHERE t.typname = #{scope[:name]}
|
375
|
+
AND n.nspname = #{scope[:schema]}
|
376
|
+
) THEN
|
377
|
+
CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
|
378
|
+
END IF;
|
379
|
+
END
|
380
|
+
$$;
|
381
|
+
SQL
|
382
|
+
|
383
|
+
internal_exec_query(query).tap { reload_type_map }
|
384
|
+
end
|
385
|
+
|
386
|
+
# Drops an enum type.
|
387
|
+
#
|
388
|
+
# If the <tt>if_exists: true</tt> option is provided, the enum is dropped
|
389
|
+
# only if it exists. Otherwise, if the enum doesn't exist, an error is
|
390
|
+
# raised.
|
391
|
+
#
|
392
|
+
# The +values+ parameter will be ignored if present. It can be helpful
|
393
|
+
# to provide this in a migration's +change+ method so it can be reverted.
|
394
|
+
# In that case, +values+ will be used by #create_enum.
|
395
|
+
def drop_enum(name, values = nil, **options)
|
341
396
|
query = <<~SQL
|
342
|
-
|
343
|
-
BEGIN
|
344
|
-
IF NOT EXISTS (
|
345
|
-
SELECT 1 FROM pg_type t
|
346
|
-
WHERE t.typname = '#{name}'
|
347
|
-
) THEN
|
348
|
-
CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
|
349
|
-
END IF;
|
350
|
-
END
|
351
|
-
$$;
|
397
|
+
DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
|
352
398
|
SQL
|
353
|
-
|
399
|
+
internal_exec_query(query).tap { reload_type_map }
|
400
|
+
end
|
401
|
+
|
402
|
+
# Rename an existing enum type to something else.
|
403
|
+
def rename_enum(name, options = {})
|
404
|
+
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
405
|
+
|
406
|
+
exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
|
407
|
+
end
|
408
|
+
|
409
|
+
# Add enum value to an existing enum type.
|
410
|
+
def add_enum_value(type_name, value, options = {})
|
411
|
+
before, after = options.values_at(:before, :after)
|
412
|
+
sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
|
413
|
+
|
414
|
+
if before && after
|
415
|
+
raise ArgumentError, "Cannot have both :before and :after at the same time"
|
416
|
+
elsif before
|
417
|
+
sql << " BEFORE '#{before}'"
|
418
|
+
elsif after
|
419
|
+
sql << " AFTER '#{after}'"
|
420
|
+
end
|
421
|
+
|
422
|
+
execute(sql).tap { reload_type_map }
|
423
|
+
end
|
424
|
+
|
425
|
+
# Rename enum value on an existing enum type.
|
426
|
+
def rename_enum_value(type_name, options = {})
|
427
|
+
unless database_version >= 10_00_00 # >= 10.0
|
428
|
+
raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
|
429
|
+
end
|
430
|
+
|
431
|
+
from = options.fetch(:from) { raise ArgumentError, ":from is required" }
|
432
|
+
to = options.fetch(:to) { raise ArgumentError, ":to is required" }
|
433
|
+
|
434
|
+
execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
|
435
|
+
reload_type_map
|
436
|
+
}
|
354
437
|
end
|
355
438
|
|
356
439
|
# Returns the configured supported identifier length supported by PostgreSQL
|
@@ -370,7 +453,7 @@ module ArJdbc
|
|
370
453
|
|
371
454
|
def get_database_version # :nodoc:
|
372
455
|
begin
|
373
|
-
version =
|
456
|
+
version = valid_raw_connection.database_product
|
374
457
|
if match = version.match(/([\d\.]*\d).*?/)
|
375
458
|
version = match[1].split('.').map(&:to_i)
|
376
459
|
# PostgreSQL version representation does not have more than 4 digits
|
@@ -426,8 +509,7 @@ module ArJdbc
|
|
426
509
|
end
|
427
510
|
end
|
428
511
|
|
429
|
-
|
430
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
512
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
431
513
|
val = super
|
432
514
|
if !use_insert_returning? && pk
|
433
515
|
unless sequence_name
|
@@ -445,11 +527,6 @@ module ArJdbc
|
|
445
527
|
execute(combine_multi_statements(statements), name)
|
446
528
|
end
|
447
529
|
|
448
|
-
def explain(arel, binds = [])
|
449
|
-
sql, binds = to_sql_and_binds(arel, binds)
|
450
|
-
ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query("EXPLAIN #{sql}", 'EXPLAIN', binds))
|
451
|
-
end
|
452
|
-
|
453
530
|
# from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
|
454
531
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
455
532
|
:close, :declare, :fetch, :move, :set, :show
|
@@ -464,18 +541,33 @@ module ArJdbc
|
|
464
541
|
# since apparently calling close on the statement object
|
465
542
|
# doesn't always free the server resources and calling
|
466
543
|
# 'DISCARD ALL' fails if we are inside a transaction
|
467
|
-
def clear_cache!
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
end
|
544
|
+
# def clear_cache!
|
545
|
+
# super
|
546
|
+
# # Make sure all query plans are *really* gone
|
547
|
+
# @connection.execute 'DEALLOCATE ALL' if active?
|
548
|
+
# end
|
472
549
|
|
473
550
|
def reset!
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
551
|
+
@lock.synchronize do
|
552
|
+
return connect! unless @raw_connection
|
553
|
+
|
554
|
+
# Have to deal with rollbacks differently than the AR adapter
|
555
|
+
@raw_connection.rollback
|
556
|
+
|
557
|
+
@raw_connection.execute("DISCARD ALL")
|
558
|
+
|
559
|
+
super
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
# Disconnects from the database if already connected. Otherwise, this
|
564
|
+
# method does nothing.
|
565
|
+
def disconnect!
|
566
|
+
@lock.synchronize do
|
567
|
+
super
|
568
|
+
@raw_connection&.close
|
569
|
+
@raw_connection = nil
|
570
|
+
end
|
479
571
|
end
|
480
572
|
|
481
573
|
def default_sequence_name(table_name, pk = "id") #:nodoc:
|
@@ -593,17 +685,19 @@ module ArJdbc
|
|
593
685
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
594
686
|
# - ::regclass is a function that gives the id for a table name
|
595
687
|
def column_definitions(table_name)
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
688
|
+
query(<<~SQL, "SCHEMA")
|
689
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
690
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
691
|
+
c.collname, col_description(a.attrelid, a.attnum) AS comment,
|
692
|
+
#{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
|
693
|
+
#{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
|
694
|
+
FROM pg_attribute a
|
695
|
+
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
696
|
+
LEFT JOIN pg_type t ON a.atttypid = t.oid
|
697
|
+
LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
|
698
|
+
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
|
699
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
700
|
+
ORDER BY a.attnum
|
607
701
|
SQL
|
608
702
|
end
|
609
703
|
|
@@ -618,22 +712,27 @@ module ArJdbc
|
|
618
712
|
|
619
713
|
# Pulled from ActiveRecord's Postgres adapter and modified to use execute
|
620
714
|
def can_perform_case_insensitive_comparison_for?(column)
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
715
|
+
# NOTE: citext is an exception. It is possible to perform a
|
716
|
+
# case-insensitive comparison using `LOWER()`, but it is
|
717
|
+
# unnecessary, as `citext` is case-insensitive by definition.
|
718
|
+
@case_insensitive_cache ||= { "citext" => false }
|
719
|
+
@case_insensitive_cache.fetch(column.sql_type) do
|
720
|
+
@case_insensitive_cache[column.sql_type] = begin
|
721
|
+
sql = <<~SQL
|
722
|
+
SELECT exists(
|
723
|
+
SELECT * FROM pg_proc
|
724
|
+
WHERE proname = 'lower'
|
725
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
726
|
+
) OR exists(
|
727
|
+
SELECT * FROM pg_proc
|
728
|
+
INNER JOIN pg_cast
|
729
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
730
|
+
WHERE proname = 'lower'
|
731
|
+
AND castsource = #{quote column.sql_type}::regtype
|
732
|
+
)
|
733
|
+
SQL
|
734
|
+
select_value(sql, 'SCHEMA')
|
735
|
+
end
|
637
736
|
end
|
638
737
|
end
|
639
738
|
|
@@ -642,6 +741,8 @@ module ArJdbc
|
|
642
741
|
|
643
742
|
# TODO: Can we base these on an error code of some kind?
|
644
743
|
case exception.message
|
744
|
+
when /could not create unique index/
|
745
|
+
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
645
746
|
when /duplicate key value violates unique constraint/
|
646
747
|
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
|
647
748
|
when /violates not-null constraint/
|
@@ -660,6 +761,10 @@ module ArJdbc
|
|
660
761
|
::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
|
661
762
|
when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
|
662
763
|
::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
|
764
|
+
when /relation .* does not exist/i
|
765
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
766
|
+
when /syntax error at or near/i
|
767
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
663
768
|
else
|
664
769
|
super
|
665
770
|
end
|
@@ -742,7 +847,7 @@ module ActiveRecord::ConnectionAdapters
|
|
742
847
|
include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
|
743
848
|
include ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting
|
744
849
|
|
745
|
-
include Jdbc::ConnectionPoolCallbacks
|
850
|
+
# include Jdbc::ConnectionPoolCallbacks
|
746
851
|
|
747
852
|
include ArJdbc::Abstract::Core
|
748
853
|
include ArJdbc::Abstract::ConnectionManagement
|
@@ -753,6 +858,8 @@ module ActiveRecord::ConnectionAdapters
|
|
753
858
|
|
754
859
|
require 'arjdbc/postgresql/oid_types'
|
755
860
|
include ::ArJdbc::PostgreSQL::OIDTypes
|
861
|
+
include ::ArJdbc::PostgreSQL::DatabaseStatements
|
862
|
+
include ::ArJdbc::PostgreSQL::SchemaStatements
|
756
863
|
|
757
864
|
include ::ArJdbc::PostgreSQL::ColumnHelpers
|
758
865
|
|
@@ -761,16 +868,46 @@ module ActiveRecord::ConnectionAdapters
|
|
761
868
|
# AR expects OID to be available on the adapter
|
762
869
|
OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
|
763
870
|
|
764
|
-
|
871
|
+
class << self
|
872
|
+
def jdbc_connection_class
|
873
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
|
874
|
+
end
|
875
|
+
|
876
|
+
def new_client(conn_params, adapter_instance)
|
877
|
+
jdbc_connection_class.new(conn_params, adapter_instance)
|
878
|
+
end
|
879
|
+
|
880
|
+
def dbconsole(config, options = {})
|
881
|
+
pg_config = config.configuration_hash
|
882
|
+
|
883
|
+
ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
|
884
|
+
ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
|
885
|
+
ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
|
886
|
+
ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
|
887
|
+
ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
|
888
|
+
ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
|
889
|
+
ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
|
890
|
+
ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
|
891
|
+
if pg_config[:variables]
|
892
|
+
ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
|
893
|
+
"-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
|
894
|
+
end.join(" ")
|
895
|
+
end
|
896
|
+
find_cmd_and_exec("psql", config.database)
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
def initialize(...)
|
901
|
+
super
|
902
|
+
|
903
|
+
conn_params = @config.compact
|
904
|
+
|
905
|
+
@connection_parameters = conn_params
|
906
|
+
|
765
907
|
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
766
908
|
@local_tz = nil
|
767
909
|
@max_identifier_length = nil
|
768
910
|
|
769
|
-
super(connection, logger, config) # configure_connection happens in super
|
770
|
-
|
771
|
-
@type_map = Type::HashLookupTypeMap.new
|
772
|
-
initialize_type_map
|
773
|
-
|
774
911
|
@use_insert_returning = @config.key?(:insert_returning) ?
|
775
912
|
self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
776
913
|
end
|
@@ -793,8 +930,16 @@ module ActiveRecord::ConnectionAdapters
|
|
793
930
|
public :sql_for_insert
|
794
931
|
alias :postgresql_version :database_version
|
795
932
|
|
796
|
-
def
|
797
|
-
|
933
|
+
def native_database_types # :nodoc:
|
934
|
+
self.class.native_database_types
|
935
|
+
end
|
936
|
+
|
937
|
+
def self.native_database_types # :nodoc:
|
938
|
+
@native_database_types ||= begin
|
939
|
+
types = NATIVE_DATABASE_TYPES.dup
|
940
|
+
types[:datetime] = types[datetime_type]
|
941
|
+
types
|
942
|
+
end
|
798
943
|
end
|
799
944
|
|
800
945
|
private
|
@@ -829,8 +974,10 @@ module ActiveRecord::ConnectionAdapters
|
|
829
974
|
|
830
975
|
type_casted_binds = type_casted_binds(binds)
|
831
976
|
log(sql, name, binds, type_casted_binds, async: async) do
|
832
|
-
|
833
|
-
|
977
|
+
with_raw_connection do |conn|
|
978
|
+
result = conn.exec_params(sql, type_casted_binds)
|
979
|
+
verified!
|
980
|
+
result
|
834
981
|
end
|
835
982
|
end
|
836
983
|
end
|
@@ -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
|
@@ -67,28 +67,6 @@ module ArJdbc
|
|
67
67
|
|
68
68
|
# @private
|
69
69
|
module OIDTypes
|
70
|
-
|
71
|
-
# @override
|
72
|
-
def enable_extension(name)
|
73
|
-
result = super(name)
|
74
|
-
@extensions = nil
|
75
|
-
reload_type_map
|
76
|
-
result
|
77
|
-
end
|
78
|
-
|
79
|
-
# @override
|
80
|
-
def disable_extension(name)
|
81
|
-
result = super(name)
|
82
|
-
@extensions = nil
|
83
|
-
reload_type_map
|
84
|
-
result
|
85
|
-
end
|
86
|
-
|
87
|
-
# @override
|
88
|
-
def extensions
|
89
|
-
@extensions ||= super
|
90
|
-
end
|
91
|
-
|
92
70
|
def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
|
93
71
|
# Note: type_map is storing a bunch of oid type prefixed with a namespace even
|
94
72
|
# if they are not namespaced (e.g. ""."oidvector"). builtin types which are
|
@@ -118,8 +96,15 @@ module ArJdbc
|
|
118
96
|
end
|
119
97
|
|
120
98
|
def reload_type_map
|
121
|
-
|
99
|
+
@lock.synchronize do
|
100
|
+
if @type_map
|
101
|
+
type_map.clear
|
102
|
+
else
|
103
|
+
@type_map = Type::HashLookupTypeMap.new
|
104
|
+
end
|
105
|
+
|
122
106
|
initialize_type_map
|
107
|
+
end
|
123
108
|
end
|
124
109
|
|
125
110
|
def initialize_type_map_inner(m)
|
@@ -274,10 +259,6 @@ module ArJdbc
|
|
274
259
|
$1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
|
275
260
|
end
|
276
261
|
|
277
|
-
def extract_limit(sql_type)
|
278
|
-
$1.to_i if sql_type =~ /\((.*)\)/
|
279
|
-
end
|
280
|
-
|
281
262
|
# Support arrays/ranges for defining attributes that don't exist in the db
|
282
263
|
ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
|
283
264
|
ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
|