activerecord-jdbc-adapter 5.0.pre1 → 51.0
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 +5 -5
- data/.gitignore +1 -2
- data/.travis.yml +15 -416
- data/Gemfile +35 -37
- data/README.md +23 -118
- data/RUNNING_TESTS.md +31 -26
- data/Rakefile +2 -3
- data/activerecord-jdbc-adapter.gemspec +1 -2
- data/lib/arjdbc/abstract/connection_management.rb +21 -0
- data/lib/arjdbc/abstract/core.rb +62 -0
- data/lib/arjdbc/abstract/database_statements.rb +46 -0
- data/lib/arjdbc/abstract/statement_cache.rb +58 -0
- data/lib/arjdbc/abstract/transaction_support.rb +86 -0
- data/lib/arjdbc/derby/adapter.rb +6 -1
- data/lib/arjdbc/discover.rb +0 -7
- data/lib/arjdbc/firebird/adapter.rb +2 -2
- data/lib/arjdbc/jdbc/adapter.rb +10 -252
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/connection.rb +6 -0
- data/lib/arjdbc/jdbc.rb +2 -2
- data/lib/arjdbc/mysql/adapter.rb +87 -944
- data/lib/arjdbc/mysql/connection_methods.rb +4 -2
- data/lib/arjdbc/postgresql/adapter.rb +288 -1023
- data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
- data/lib/arjdbc/postgresql/base/pgconn.rb +8 -5
- data/lib/arjdbc/postgresql/column.rb +10 -599
- data/lib/arjdbc/postgresql/connection_methods.rb +9 -0
- data/lib/arjdbc/postgresql/name.rb +24 -0
- data/lib/arjdbc/postgresql/oid_types.rb +25 -110
- data/lib/arjdbc/sqlite3/adapter.rb +171 -170
- data/lib/arjdbc/tasks/database_tasks.rb +1 -3
- data/lib/arjdbc/tasks/db2_database_tasks.rb +2 -2
- data/lib/arjdbc/version.rb +1 -1
- data/pom.xml +3 -3
- data/rakelib/02-test.rake +0 -12
- data/rakelib/compile.rake +1 -1
- data/rakelib/db.rake +7 -5
- data/rakelib/rails.rake +63 -64
- data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +1 -17
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +518 -1260
- data/src/java/arjdbc/mysql/MySQLModule.java +3 -3
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +53 -134
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +214 -240
- data/src/java/arjdbc/sqlite3/SQLite3Module.java +0 -20
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +85 -10
- metadata +20 -34
- data/Appraisals +0 -41
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -1
- data/lib/arjdbc/common_jdbc_methods.rb +0 -89
- data/lib/arjdbc/mysql/bulk_change_table.rb +0 -150
- data/lib/arjdbc/mysql/column.rb +0 -162
- data/lib/arjdbc/mysql/explain_support.rb +0 -82
- data/lib/arjdbc/mysql/schema_creation.rb +0 -58
- data/lib/arjdbc/oracle/adapter.rb +0 -952
- data/lib/arjdbc/oracle/column.rb +0 -126
- data/lib/arjdbc/oracle/connection_methods.rb +0 -21
- data/lib/arjdbc/oracle.rb +0 -4
- data/lib/arjdbc/postgresql/_bc_time_cast_patch.rb +0 -21
- data/lib/arjdbc/postgresql/base/oid.rb +0 -412
- data/lib/arjdbc/postgresql/base/schema_definitions.rb +0 -131
- data/lib/arjdbc/postgresql/explain_support.rb +0 -53
- data/lib/arjdbc/postgresql/oid/bytea.rb +0 -2
- data/lib/arjdbc/postgresql/schema_creation.rb +0 -60
- data/lib/arjdbc/tasks/oracle/enhanced_structure_dump.rb +0 -297
- data/lib/arjdbc/tasks/oracle_database_tasks.rb +0 -65
- data/src/java/arjdbc/oracle/OracleModule.java +0 -75
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +0 -465
@@ -2,30 +2,39 @@
|
|
2
2
|
ArJdbc.load_java_part :PostgreSQL
|
3
3
|
|
4
4
|
require 'ipaddr'
|
5
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
6
|
+
require 'active_record/connection_adapters/postgresql/column'
|
7
|
+
require 'active_record/connection_adapters/postgresql/explain_pretty_printer'
|
8
|
+
require 'active_record/connection_adapters/postgresql/quoting'
|
9
|
+
require 'active_record/connection_adapters/postgresql/referential_integrity'
|
10
|
+
require 'active_record/connection_adapters/postgresql/schema_creation'
|
11
|
+
require 'active_record/connection_adapters/postgresql/schema_dumper'
|
12
|
+
require 'active_record/connection_adapters/postgresql/schema_statements'
|
13
|
+
require 'active_record/connection_adapters/postgresql/type_metadata'
|
14
|
+
require 'active_record/connection_adapters/postgresql/utils'
|
15
|
+
require 'arjdbc/abstract/core'
|
16
|
+
require 'arjdbc/abstract/connection_management'
|
17
|
+
require 'arjdbc/abstract/database_statements'
|
18
|
+
require 'arjdbc/abstract/statement_cache'
|
19
|
+
require 'arjdbc/abstract/transaction_support'
|
20
|
+
require 'arjdbc/postgresql/base/array_decoder'
|
21
|
+
require 'arjdbc/postgresql/base/array_encoder'
|
22
|
+
require 'arjdbc/postgresql/name'
|
5
23
|
|
6
24
|
module ArJdbc
|
7
25
|
# Strives to provide Rails built-in PostgreSQL adapter (API) compatibility.
|
8
26
|
module PostgreSQL
|
9
27
|
|
10
|
-
# @deprecated no longer used
|
11
|
-
# @private
|
12
|
-
AR4_COMPAT = AR40
|
13
|
-
# @deprecated no longer used
|
14
|
-
# @private
|
15
|
-
AR42_COMPAT = AR42
|
16
|
-
|
17
28
|
require 'arjdbc/postgresql/column'
|
18
|
-
require 'arjdbc/postgresql/explain_support'
|
19
|
-
require 'arjdbc/postgresql/schema_creation' # AR 4.x
|
20
29
|
require 'arel/visitors/postgresql_jdbc'
|
21
30
|
# @private
|
22
31
|
IndexDefinition = ::ActiveRecord::ConnectionAdapters::IndexDefinition
|
23
32
|
|
24
33
|
# @private
|
25
|
-
ForeignKeyDefinition = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
|
34
|
+
ForeignKeyDefinition = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
|
26
35
|
|
27
36
|
# @private
|
28
|
-
Type = ::ActiveRecord::Type
|
37
|
+
Type = ::ActiveRecord::Type
|
29
38
|
|
30
39
|
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
|
31
40
|
def self.jdbc_connection_class
|
@@ -130,172 +139,60 @@ module ArJdbc
|
|
130
139
|
# @private
|
131
140
|
ActiveRecordError = ::ActiveRecord::ActiveRecordError
|
132
141
|
|
133
|
-
# Maps logical Rails types to PostgreSQL-specific data types.
|
134
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
135
|
-
case type.to_s
|
136
|
-
when 'binary'
|
137
|
-
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
138
|
-
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
139
|
-
case limit
|
140
|
-
when nil, 0..0x3fffffff; super(type)
|
141
|
-
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
142
|
-
end
|
143
|
-
when 'text'
|
144
|
-
# PostgreSQL doesn't support limits on text columns.
|
145
|
-
# The hard limit is 1Gb, according to section 8.3 in the manual.
|
146
|
-
case limit
|
147
|
-
when nil, 0..0x3fffffff; super(type)
|
148
|
-
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
149
|
-
end
|
150
|
-
when 'integer'
|
151
|
-
return 'integer' unless limit
|
152
|
-
|
153
|
-
case limit
|
154
|
-
when 1, 2; 'smallint'
|
155
|
-
when 3, 4; 'integer'
|
156
|
-
when 5..8; 'bigint'
|
157
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
158
|
-
end
|
159
|
-
when 'datetime'
|
160
|
-
return super unless precision
|
161
|
-
|
162
|
-
case precision
|
163
|
-
when 0..6; "timestamp(#{precision})"
|
164
|
-
else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
|
165
|
-
end
|
166
|
-
else
|
167
|
-
super
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def type_cast(value, column, array_member = false)
|
172
|
-
return super(value, nil) unless column
|
173
|
-
|
174
|
-
case value
|
175
|
-
when String
|
176
|
-
return super(value, column) unless 'bytea' == column.sql_type
|
177
|
-
value # { :value => value, :format => 1 }
|
178
|
-
when Array
|
179
|
-
case column.sql_type
|
180
|
-
when 'point'
|
181
|
-
jdbc_column_class.point_to_string(value)
|
182
|
-
when 'json', 'jsonb'
|
183
|
-
jdbc_column_class.json_to_string(value)
|
184
|
-
else
|
185
|
-
return super(value, column) unless column.array?
|
186
|
-
jdbc_column_class.array_to_string(value, column, self)
|
187
|
-
end
|
188
|
-
when NilClass
|
189
|
-
if column.array? && array_member
|
190
|
-
'NULL'
|
191
|
-
elsif column.array?
|
192
|
-
value
|
193
|
-
else
|
194
|
-
super(value, column)
|
195
|
-
end
|
196
|
-
when Hash
|
197
|
-
case column.sql_type
|
198
|
-
when 'hstore'
|
199
|
-
jdbc_column_class.hstore_to_string(value, array_member)
|
200
|
-
when 'json', 'jsonb'
|
201
|
-
jdbc_column_class.json_to_string(value)
|
202
|
-
else super(value, column)
|
203
|
-
end
|
204
|
-
when IPAddr
|
205
|
-
return super unless column.sql_type == 'inet' || column.sql_type == 'cidr'
|
206
|
-
jdbc_column_class.cidr_to_string(value)
|
207
|
-
when Range
|
208
|
-
return super(value, column) unless /range$/ =~ column.sql_type
|
209
|
-
jdbc_column_class.range_to_string(value)
|
210
|
-
else
|
211
|
-
super(value, column)
|
212
|
-
end
|
213
|
-
end if AR40 && ! AR42
|
214
|
-
|
215
|
-
# @private
|
216
|
-
def _type_cast(value)
|
217
|
-
case value
|
218
|
-
when Type::Binary::Data
|
219
|
-
# Return a bind param hash with format as binary.
|
220
|
-
# See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
|
221
|
-
# for more information
|
222
|
-
{ :value => value.to_s, :format => 1 }
|
223
|
-
when OID::Xml::Data, OID::Bit::Data
|
224
|
-
value.to_s
|
225
|
-
else
|
226
|
-
super
|
227
|
-
end
|
228
|
-
end if AR42
|
229
|
-
private :_type_cast if AR42
|
230
|
-
|
231
142
|
NATIVE_DATABASE_TYPES = {
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
235
|
-
:
|
236
|
-
:
|
237
|
-
:
|
238
|
-
:
|
239
|
-
:
|
240
|
-
:
|
241
|
-
:
|
242
|
-
:
|
243
|
-
:
|
244
|
-
:
|
245
|
-
:
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
:
|
250
|
-
:
|
251
|
-
:
|
143
|
+
primary_key: 'bigserial primary key',
|
144
|
+
bigint: { name: 'bigint' },
|
145
|
+
binary: { name: 'bytea' },
|
146
|
+
bit: { name: 'bit' },
|
147
|
+
bit_varying: { name: 'bit varying' },
|
148
|
+
boolean: { name: 'boolean' },
|
149
|
+
box: { name: 'box' },
|
150
|
+
char: { name: 'char' },
|
151
|
+
cidr: { name: 'cidr' },
|
152
|
+
circle: { name: 'circle' },
|
153
|
+
citext: { name: 'citext' },
|
154
|
+
date: { name: 'date' },
|
155
|
+
daterange: { name: 'daterange' },
|
156
|
+
datetime: { name: 'timestamp' },
|
157
|
+
decimal: { name: 'decimal' }, # :limit => 1000
|
158
|
+
float: { name: 'float' },
|
159
|
+
hstore: { name: 'hstore' },
|
160
|
+
inet: { name: 'inet' },
|
161
|
+
int4range: { name: 'int4range' },
|
162
|
+
int8range: { name: 'int8range' },
|
163
|
+
integer: { name: 'integer' },
|
164
|
+
interval: { name: 'interval' }, # This doesn't get added to AR's postgres adapter until 5.1 but it fixes broken tests in 5.0 ...
|
165
|
+
json: { name: 'json' },
|
166
|
+
jsonb: { name: 'jsonb' },
|
167
|
+
line: { name: 'line' },
|
168
|
+
lseg: { name: 'lseg' },
|
169
|
+
ltree: { name: 'ltree' },
|
170
|
+
macaddr: { name: 'macaddr' },
|
171
|
+
money: { name: 'money' },
|
172
|
+
numeric: { name: 'numeric' },
|
173
|
+
numrange: { name: 'numrange' },
|
174
|
+
oid: { name: 'oid' },
|
175
|
+
path: { name: 'path' },
|
176
|
+
point: { name: 'point' },
|
177
|
+
polygon: { name: 'polygon' },
|
178
|
+
string: { name: 'character varying' },
|
179
|
+
text: { name: 'text' },
|
180
|
+
time: { name: 'time' },
|
181
|
+
timestamp: { name: 'timestamp' },
|
182
|
+
tsrange: { name: 'tsrange' },
|
183
|
+
tstzrange: { name: 'tstzrange' },
|
184
|
+
tsvector: { name: 'tsvector' },
|
185
|
+
uuid: { name: 'uuid' },
|
186
|
+
xml: { name: 'xml' }
|
252
187
|
}
|
253
188
|
|
254
|
-
NATIVE_DATABASE_TYPES.update({
|
255
|
-
:tsvector => { :name => "tsvector" },
|
256
|
-
:hstore => { :name => "hstore" },
|
257
|
-
:inet => { :name => "inet" },
|
258
|
-
:cidr => { :name => "cidr" },
|
259
|
-
:macaddr => { :name => "macaddr" },
|
260
|
-
:uuid => { :name => "uuid" },
|
261
|
-
:json => { :name => "json" },
|
262
|
-
:jsonb => { :name => "jsonb" },
|
263
|
-
:ltree => { :name => "ltree" },
|
264
|
-
# ranges :
|
265
|
-
:daterange => { :name => "daterange" },
|
266
|
-
:numrange => { :name => "numrange" },
|
267
|
-
:tsrange => { :name => "tsrange" },
|
268
|
-
:tstzrange => { :name => "tstzrange" },
|
269
|
-
:int4range => { :name => "int4range" },
|
270
|
-
:int8range => { :name => "int8range" },
|
271
|
-
}) if AR40
|
272
|
-
|
273
|
-
NATIVE_DATABASE_TYPES.update(
|
274
|
-
:string => { :name => "character varying" },
|
275
|
-
:bigserial => "bigserial",
|
276
|
-
:bigint => { :name => "bigint" },
|
277
|
-
:bit => { :name => "bit" },
|
278
|
-
:bit_varying => { :name => "bit varying" }
|
279
|
-
) if AR42
|
280
|
-
|
281
189
|
def native_database_types
|
282
190
|
NATIVE_DATABASE_TYPES
|
283
191
|
end
|
284
192
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
spec = super
|
289
|
-
spec[:array] = 'true' if column.respond_to?(:array) && column.array
|
290
|
-
spec[:default] = "\"#{column.default_function}\"" if column.default_function
|
291
|
-
spec
|
292
|
-
end if AR40
|
293
|
-
|
294
|
-
# Adds `:array` as a valid migration key.
|
295
|
-
# @override
|
296
|
-
def migration_keys
|
297
|
-
super + [:array]
|
298
|
-
end if AR40
|
193
|
+
def valid_type?(type)
|
194
|
+
!native_database_types[type].nil?
|
195
|
+
end
|
299
196
|
|
300
197
|
# Enable standard-conforming strings if available.
|
301
198
|
def set_standard_conforming_strings
|
@@ -333,15 +230,21 @@ module ArJdbc
|
|
333
230
|
@standard_conforming_strings == true # return false if :unsupported
|
334
231
|
end
|
335
232
|
|
336
|
-
|
337
|
-
def supports_migrations?
|
338
|
-
true
|
339
|
-
end
|
233
|
+
def supports_ddl_transactions?; true end
|
340
234
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
235
|
+
def supports_explain?; true end
|
236
|
+
|
237
|
+
def supports_expression_index?; true end
|
238
|
+
|
239
|
+
def supports_index_sort_order?; true end
|
240
|
+
|
241
|
+
def supports_partial_index?; true end
|
242
|
+
|
243
|
+
def supports_savepoints?; true end
|
244
|
+
|
245
|
+
def supports_transaction_isolation?(level = nil); true end
|
246
|
+
|
247
|
+
def supports_views?; true end
|
345
248
|
|
346
249
|
# Does PostgreSQL support standard conforming strings?
|
347
250
|
def supports_standard_conforming_strings?
|
@@ -357,56 +260,24 @@ module ArJdbc
|
|
357
260
|
postgresql_version >= 80200
|
358
261
|
end
|
359
262
|
|
360
|
-
def
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
def supports_index_sort_order?; true end
|
365
|
-
|
366
|
-
def supports_partial_index?; true end if AR40
|
263
|
+
def supports_pgcrypto_uuid?
|
264
|
+
postgresql_version >= 90400
|
265
|
+
end
|
367
266
|
|
368
267
|
# Range data-types weren't introduced until PostgreSQL 9.2.
|
369
268
|
def supports_ranges?
|
370
269
|
postgresql_version >= 90200
|
371
|
-
end if AR40
|
372
|
-
|
373
|
-
def supports_transaction_isolation?(level = nil)
|
374
|
-
true
|
375
|
-
end
|
376
|
-
|
377
|
-
# @override
|
378
|
-
def supports_views?; true end
|
379
|
-
|
380
|
-
if ArJdbc::AR50
|
381
|
-
def views
|
382
|
-
select_values("SELECT table_name FROM INFORMATION_SCHEMA.views WHERE table_schema = ANY (current_schemas(false))")
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
386
|
-
# NOTE: handled by JdbcAdapter we override only to have save-point in logs :
|
387
|
-
|
388
|
-
# @override
|
389
|
-
def supports_savepoints?; true end
|
390
|
-
|
391
|
-
# @override
|
392
|
-
def create_savepoint(name = current_savepoint_name(true))
|
393
|
-
log("SAVEPOINT #{name}", 'Savepoint') { super }
|
394
|
-
end
|
395
|
-
|
396
|
-
# @override
|
397
|
-
def rollback_to_savepoint(name = current_savepoint_name(true))
|
398
|
-
log("ROLLBACK TO SAVEPOINT #{name}", 'Savepoint') { super }
|
399
|
-
end
|
400
|
-
|
401
|
-
# @override
|
402
|
-
def release_savepoint(name = current_savepoint_name(false))
|
403
|
-
log("RELEASE SAVEPOINT #{name}", 'Savepoint') { super }
|
404
270
|
end
|
405
271
|
|
406
272
|
def supports_extensions?
|
407
273
|
postgresql_version >= 90200
|
408
274
|
end # NOTE: only since AR-4.0 but should not hurt on other versions
|
409
275
|
|
276
|
+
# From AR 5.1 postgres_adapter.rb
|
277
|
+
def default_index_type?(index) # :nodoc:
|
278
|
+
index.using == :btree || super
|
279
|
+
end
|
280
|
+
|
410
281
|
def enable_extension(name)
|
411
282
|
execute("CREATE EXTENSION IF NOT EXISTS \"#{name}\"")
|
412
283
|
end
|
@@ -441,159 +312,76 @@ module ArJdbc
|
|
441
312
|
execute "SET SESSION AUTHORIZATION #{user}"
|
442
313
|
end
|
443
314
|
|
315
|
+
# Came from postgres_adapter
|
316
|
+
def get_advisory_lock(lock_id) # :nodoc:
|
317
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
318
|
+
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
|
319
|
+
end
|
320
|
+
select_value("SELECT pg_try_advisory_lock(#{lock_id});")
|
321
|
+
end
|
322
|
+
|
323
|
+
# Came from postgres_adapter
|
324
|
+
def release_advisory_lock(lock_id) # :nodoc:
|
325
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
326
|
+
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
|
327
|
+
end
|
328
|
+
select_value("SELECT pg_advisory_unlock(#{lock_id})") == 't'.freeze
|
329
|
+
end
|
330
|
+
|
444
331
|
# Returns the configured supported identifier length supported by PostgreSQL,
|
445
332
|
# or report the default of 63 on PostgreSQL 7.x.
|
446
333
|
def table_alias_length
|
447
334
|
@table_alias_length ||= (
|
448
335
|
postgresql_version >= 80000 ?
|
449
|
-
select_one('SHOW max_identifier_length')['max_identifier_length'].to_i :
|
336
|
+
select_one('SHOW max_identifier_length', 'SCHEMA'.freeze)['max_identifier_length'].to_i :
|
450
337
|
63
|
451
338
|
)
|
452
339
|
end
|
340
|
+
alias index_name_length table_alias_length
|
453
341
|
|
454
|
-
def
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
if pk && sequence
|
466
|
-
quoted_sequence = quote_column_name(sequence)
|
467
|
-
|
468
|
-
select_value <<-end_sql, 'Reset Sequence'
|
469
|
-
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
470
|
-
end_sql
|
471
|
-
end
|
472
|
-
end
|
473
|
-
|
474
|
-
# Find a table's primary key and sequence.
|
475
|
-
def pk_and_sequence_for(table)
|
476
|
-
# try looking for a seq with a dependency on the table's primary key :
|
477
|
-
result = select(<<-end_sql, 'PK and Serial Sequence')[0]
|
478
|
-
SELECT attr.attname, seq.relname
|
479
|
-
FROM pg_class seq,
|
480
|
-
pg_attribute attr,
|
481
|
-
pg_depend dep,
|
482
|
-
pg_constraint cons
|
483
|
-
WHERE seq.oid = dep.objid
|
484
|
-
AND seq.relkind = 'S'
|
485
|
-
AND attr.attrelid = dep.refobjid
|
486
|
-
AND attr.attnum = dep.refobjsubid
|
487
|
-
AND attr.attrelid = cons.conrelid
|
488
|
-
AND attr.attnum = cons.conkey[1]
|
489
|
-
AND cons.contype = 'p'
|
490
|
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
491
|
-
end_sql
|
492
|
-
|
493
|
-
if result.nil? || result.empty?
|
494
|
-
# if that fails, try parsing the primary key's default value :
|
495
|
-
result = select(<<-end_sql, 'PK and Custom Sequence')[0]
|
496
|
-
SELECT attr.attname,
|
497
|
-
CASE
|
498
|
-
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
|
499
|
-
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
500
|
-
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
501
|
-
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
502
|
-
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
503
|
-
END as relname
|
504
|
-
FROM pg_class t
|
505
|
-
JOIN pg_attribute attr ON (t.oid = attrelid)
|
506
|
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
507
|
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
508
|
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
509
|
-
AND cons.contype = 'p'
|
510
|
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
511
|
-
end_sql
|
342
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
343
|
+
val = super
|
344
|
+
if !use_insert_returning? && pk
|
345
|
+
unless sequence_name
|
346
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
347
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
348
|
+
return val unless sequence_name
|
349
|
+
end
|
350
|
+
last_insert_id_result(sequence_name)
|
351
|
+
else
|
352
|
+
val
|
512
353
|
end
|
513
|
-
|
514
|
-
[ result['attname'], result['relname'] ]
|
515
|
-
rescue
|
516
|
-
nil
|
517
354
|
end
|
518
355
|
|
519
|
-
def
|
520
|
-
|
521
|
-
|
522
|
-
FROM pg_attribute attr
|
523
|
-
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
|
524
|
-
WHERE cons.contype = 'p' AND cons.conrelid = '#{quote_table_name(table)}'::regclass
|
525
|
-
end_sql
|
526
|
-
|
527
|
-
result && result['attname']
|
528
|
-
# pk_and_sequence = pk_and_sequence_for(table)
|
529
|
-
# pk_and_sequence && pk_and_sequence.first
|
356
|
+
def explain(arel, binds = [])
|
357
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
358
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
530
359
|
end
|
531
360
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
end
|
538
|
-
|
539
|
-
if pk && use_insert_returning? # && id_value.nil?
|
540
|
-
select_value("#{to_sql(sql, binds)} RETURNING #{quote_column_name(pk)}")
|
361
|
+
# Take an id from the result of an INSERT query.
|
362
|
+
# @return [Integer, NilClass]
|
363
|
+
def last_inserted_id(result)
|
364
|
+
if result.is_a?(Hash) || result.is_a?(ActiveRecord::Result)
|
365
|
+
result.first.first[1] # .first = { "id"=>1 } .first = [ "id", 1 ]
|
541
366
|
else
|
542
|
-
|
543
|
-
unless id_value
|
544
|
-
table_ref ||= extract_table_ref_from_insert_sql(sql)
|
545
|
-
# If neither PK nor sequence name is given, look them up.
|
546
|
-
if table_ref && ! ( pk ||= primary_key(table_ref) ) && ! sequence_name
|
547
|
-
pk, sequence_name = pk_and_sequence_for(table_ref)
|
548
|
-
end
|
549
|
-
# If a PK is given, fallback to default sequence name.
|
550
|
-
# Don't fetch last insert id for a table without a PK.
|
551
|
-
if pk && sequence_name ||= default_sequence_name(table_ref, pk)
|
552
|
-
id_value = last_insert_id(table_ref, sequence_name)
|
553
|
-
end
|
554
|
-
end
|
555
|
-
id_value
|
367
|
+
result
|
556
368
|
end
|
557
369
|
end
|
558
370
|
|
559
|
-
#
|
560
|
-
|
561
|
-
unless pk
|
371
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
|
372
|
+
if pk.nil?
|
562
373
|
# Extract the table from the insert sql. Yuck.
|
563
374
|
table_ref = extract_table_ref_from_insert_sql(sql)
|
564
375
|
pk = primary_key(table_ref) if table_ref
|
565
376
|
end
|
566
377
|
|
378
|
+
pk = nil if pk.is_a?(Array)
|
379
|
+
|
567
380
|
if pk && use_insert_returning?
|
568
381
|
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
569
382
|
end
|
570
383
|
|
571
|
-
|
572
|
-
end
|
573
|
-
|
574
|
-
# @override due RETURNING clause
|
575
|
-
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
576
|
-
# NOTE: 3.2 does not pass the PK on #insert (passed only into #sql_for_insert) :
|
577
|
-
# sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
|
578
|
-
# 3.2 :
|
579
|
-
# value = exec_insert(sql, name, binds)
|
580
|
-
# 4.x :
|
581
|
-
# value = exec_insert(sql, name, binds, pk, sequence_name)
|
582
|
-
if use_insert_returning? && ( pk || (sql.is_a?(String) && sql =~ /RETURNING "?\S+"?$/) )
|
583
|
-
exec_query(sql, name, binds) # due RETURNING clause returns a result set
|
584
|
-
else
|
585
|
-
result = super
|
586
|
-
if pk
|
587
|
-
unless sequence_name
|
588
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
589
|
-
sequence_name = default_sequence_name(table_ref, pk)
|
590
|
-
return result unless sequence_name
|
591
|
-
end
|
592
|
-
last_insert_id_result(sequence_name)
|
593
|
-
else
|
594
|
-
result
|
595
|
-
end
|
596
|
-
end
|
384
|
+
super
|
597
385
|
end
|
598
386
|
|
599
387
|
# @note Only for "better" AR 4.0 compatibility.
|
@@ -601,103 +389,29 @@ module ArJdbc
|
|
601
389
|
def query(sql, name = nil)
|
602
390
|
log(sql, name) do
|
603
391
|
result = []
|
604
|
-
@connection.execute_query_raw(sql,
|
605
|
-
|
392
|
+
@connection.execute_query_raw(sql, []) do |*values|
|
393
|
+
# We need to use #deep_dup here because it appears that
|
394
|
+
# the java method is reusing an object in some cases
|
395
|
+
# which makes all of the entries in the "result"
|
396
|
+
# array end up with the same values as the last row
|
397
|
+
result << values.deep_dup
|
606
398
|
end
|
607
399
|
result
|
608
400
|
end
|
609
401
|
end
|
610
402
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
'SCHEMA')
|
618
|
-
end
|
619
|
-
|
620
|
-
# Returns true if schema exists.
|
621
|
-
def schema_exists?(name)
|
622
|
-
select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
|
623
|
-
end
|
624
|
-
|
625
|
-
# Returns the current schema name.
|
626
|
-
def current_schema
|
627
|
-
select_value('SELECT current_schema', 'SCHEMA')
|
628
|
-
end
|
629
|
-
|
630
|
-
# current database name
|
631
|
-
def current_database
|
632
|
-
select_value('SELECT current_database()', 'SCHEMA')
|
633
|
-
end
|
634
|
-
|
635
|
-
# Returns the current database encoding format.
|
636
|
-
def encoding
|
637
|
-
select_value(
|
638
|
-
"SELECT pg_encoding_to_char(pg_database.encoding)" <<
|
639
|
-
" FROM pg_database" <<
|
640
|
-
" WHERE pg_database.datname LIKE '#{current_database}'",
|
641
|
-
'SCHEMA')
|
642
|
-
end
|
643
|
-
|
644
|
-
# Returns the current database collation.
|
645
|
-
def collation
|
646
|
-
select_value(
|
647
|
-
"SELECT pg_database.datcollate" <<
|
648
|
-
" FROM pg_database" <<
|
649
|
-
" WHERE pg_database.datname LIKE '#{current_database}'",
|
650
|
-
'SCHEMA')
|
651
|
-
end
|
652
|
-
|
653
|
-
# Returns the current database ctype.
|
654
|
-
def ctype
|
655
|
-
select_value(
|
656
|
-
"SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'",
|
657
|
-
'SCHEMA')
|
658
|
-
end
|
659
|
-
|
660
|
-
# Returns the active schema search path.
|
661
|
-
def schema_search_path
|
662
|
-
@schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
|
663
|
-
end
|
664
|
-
|
665
|
-
# Sets the schema search path to a string of comma-separated schema names.
|
666
|
-
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
667
|
-
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
668
|
-
#
|
669
|
-
# This should be not be called manually but set in database.yml.
|
670
|
-
def schema_search_path=(schema_csv)
|
671
|
-
if schema_csv
|
672
|
-
execute "SET search_path TO #{schema_csv}"
|
673
|
-
@schema_search_path = schema_csv
|
674
|
-
end
|
675
|
-
end
|
676
|
-
|
677
|
-
# Take an id from the result of an INSERT query.
|
678
|
-
# @return [Integer, NilClass]
|
679
|
-
def last_inserted_id(result)
|
680
|
-
return nil if result.nil?
|
681
|
-
return result if result.is_a? Integer
|
682
|
-
# <ActiveRecord::Result @hash_rows=nil, @columns=["id"], @rows=[[3]]>
|
683
|
-
# but it will work with [{ 'id' => 1 }] Hash wrapped results as well
|
684
|
-
result.first.first[1] # .first = { "id"=>1 } .first = [ "id", 1 ]
|
685
|
-
end
|
686
|
-
|
687
|
-
def last_insert_id(table, sequence_name = nil)
|
688
|
-
sequence_name = table if sequence_name.nil? # AR-4.0 1 argument
|
689
|
-
last_insert_id_result(sequence_name)
|
403
|
+
def reset!
|
404
|
+
clear_cache!
|
405
|
+
reset_transaction
|
406
|
+
@connection.rollback # Have to deal with rollbacks differently than the AR adapter
|
407
|
+
@connection.execute 'DISCARD ALL'
|
408
|
+
configure_connection
|
690
409
|
end
|
691
410
|
|
692
411
|
def last_insert_id_result(sequence_name)
|
693
412
|
select_value("SELECT currval('#{sequence_name}')", 'SQL')
|
694
413
|
end
|
695
414
|
|
696
|
-
def recreate_database(name, options = {})
|
697
|
-
drop_database(name)
|
698
|
-
create_database(name, options)
|
699
|
-
end
|
700
|
-
|
701
415
|
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
702
416
|
# <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
|
703
417
|
# <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
@@ -733,56 +447,10 @@ module ArJdbc
|
|
733
447
|
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
734
448
|
end
|
735
449
|
|
736
|
-
def drop_database(name)
|
737
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
738
|
-
end
|
739
|
-
|
740
|
-
# Creates a schema for the given schema name.
|
741
|
-
def create_schema(schema_name, pg_username = nil)
|
742
|
-
if pg_username.nil? # AR 4.0 compatibility - accepts only single argument
|
743
|
-
execute "CREATE SCHEMA #{schema_name}"
|
744
|
-
else
|
745
|
-
execute("CREATE SCHEMA \"#{schema_name}\" AUTHORIZATION \"#{pg_username}\"")
|
746
|
-
end
|
747
|
-
end
|
748
|
-
|
749
|
-
# Drops the schema for the given schema name.
|
750
|
-
def drop_schema schema_name
|
751
|
-
execute "DROP SCHEMA #{schema_name} CASCADE"
|
752
|
-
end
|
753
|
-
|
754
450
|
def all_schemas
|
755
451
|
select('SELECT nspname FROM pg_namespace').map { |row| row["nspname"] }
|
756
452
|
end
|
757
453
|
|
758
|
-
# @deprecated no longer used - handled with (AR built-in) Rake tasks
|
759
|
-
def structure_dump
|
760
|
-
database = @config[:database]
|
761
|
-
if database.nil?
|
762
|
-
if @config[:url] =~ /\/([^\/]*)$/
|
763
|
-
database = $1
|
764
|
-
else
|
765
|
-
raise "Could not figure out what database this url is for #{@config["url"]}"
|
766
|
-
end
|
767
|
-
end
|
768
|
-
|
769
|
-
ENV['PGHOST'] = @config[:host] if @config[:host]
|
770
|
-
ENV['PGPORT'] = @config[:port].to_s if @config[:port]
|
771
|
-
ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
|
772
|
-
search_path = "--schema=#{@config[:schema_search_path]}" if @config[:schema_search_path]
|
773
|
-
|
774
|
-
@connection.connection.close
|
775
|
-
begin
|
776
|
-
definition = `pg_dump -i -U "#{@config[:username]}" -s -x -O #{search_path} #{database}`
|
777
|
-
raise "Error dumping database" if $?.exitstatus == 1
|
778
|
-
|
779
|
-
# need to patch away any references to SQL_ASCII as it breaks the JDBC driver
|
780
|
-
definition.gsub(/SQL_ASCII/, 'UNICODE')
|
781
|
-
ensure
|
782
|
-
reconnect!
|
783
|
-
end
|
784
|
-
end
|
785
|
-
|
786
454
|
# Returns the current client message level.
|
787
455
|
def client_min_messages
|
788
456
|
return nil if redshift? # not supported on Redshift
|
@@ -797,42 +465,6 @@ module ArJdbc
|
|
797
465
|
execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
|
798
466
|
end
|
799
467
|
|
800
|
-
# Gets the maximum number columns postgres has, default 32
|
801
|
-
def multi_column_index_limit
|
802
|
-
defined?(@multi_column_index_limit) && @multi_column_index_limit || 32
|
803
|
-
end
|
804
|
-
|
805
|
-
# Sets the maximum number columns postgres has, default 32
|
806
|
-
def multi_column_index_limit=(limit)
|
807
|
-
@multi_column_index_limit = limit
|
808
|
-
end
|
809
|
-
|
810
|
-
# @override
|
811
|
-
def distinct(columns, orders)
|
812
|
-
"DISTINCT #{columns_for_distinct(columns, orders)}"
|
813
|
-
end
|
814
|
-
|
815
|
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct
|
816
|
-
# queries, and requires that the ORDER BY include the distinct column.
|
817
|
-
# @override Since AR 4.0 (on 4.1 {#distinct} is gone and won't be called).
|
818
|
-
def columns_for_distinct(columns, orders)
|
819
|
-
if orders.is_a?(String)
|
820
|
-
orders = orders.split(','); orders.each(&:strip!)
|
821
|
-
end
|
822
|
-
|
823
|
-
order_columns = orders.reject(&:blank?).map! do |column|
|
824
|
-
column = column.is_a?(String) ? column.dup : column.to_sql # AREL node
|
825
|
-
column.gsub!(/\s+(?:ASC|DESC)\s*/i, '') # remove any ASC/DESC modifiers
|
826
|
-
column.gsub!(/\s*NULLS\s+(?:FIRST|LAST)?\s*/i, '')
|
827
|
-
column
|
828
|
-
end
|
829
|
-
order_columns.reject!(&:empty?)
|
830
|
-
i = -1; order_columns.map! { |column| "#{column} AS alias_#{i += 1}" }
|
831
|
-
|
832
|
-
columns = [ columns ]; columns.flatten!
|
833
|
-
columns.push( *order_columns ).join(', ')
|
834
|
-
end
|
835
|
-
|
836
468
|
# ORDER BY clause for the passed order option.
|
837
469
|
#
|
838
470
|
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON,
|
@@ -848,94 +480,6 @@ module ArJdbc
|
|
848
480
|
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
|
849
481
|
end
|
850
482
|
|
851
|
-
# @return [String]
|
852
|
-
# @override
|
853
|
-
def quote(value, column = nil)
|
854
|
-
return super unless column && column.type
|
855
|
-
return value if sql_literal?(value)
|
856
|
-
|
857
|
-
case value
|
858
|
-
when Float
|
859
|
-
if value.infinite? && ( column.type == :datetime || column.type == :timestamp )
|
860
|
-
"'#{value.to_s.downcase}'"
|
861
|
-
elsif value.infinite? || value.nan?
|
862
|
-
"'#{value.to_s}'"
|
863
|
-
else super
|
864
|
-
end
|
865
|
-
when Numeric
|
866
|
-
if column.respond_to?(:sql_type) && column.sql_type == 'money'
|
867
|
-
"'#{value}'"
|
868
|
-
elsif column.type == :string || column.type == :text
|
869
|
-
"'#{value}'"
|
870
|
-
else super
|
871
|
-
end
|
872
|
-
when String
|
873
|
-
return "E'#{escape_bytea(value)}'::bytea" if column.type == :binary
|
874
|
-
return "xml '#{quote_string(value)}'" if column.type == :xml
|
875
|
-
sql_type = column.respond_to?(:sql_type) && column.sql_type
|
876
|
-
sql_type && sql_type[0, 3] == 'bit' ? quote_bit(value) : super
|
877
|
-
when Array
|
878
|
-
if AR40 && column.array? # will be always falsy in AR < 4.0
|
879
|
-
"'#{jdbc_column_class.array_to_string(value, column, self).gsub(/'/, "''")}'"
|
880
|
-
elsif column.type == :json # only in AR-4.0
|
881
|
-
super(jdbc_column_class.json_to_string(value), column)
|
882
|
-
elsif column.type == :jsonb # only in AR-4.0
|
883
|
-
super(jdbc_column_class.json_to_string(value), column)
|
884
|
-
elsif column.type == :point # only in AR-4.0
|
885
|
-
super(jdbc_column_class.point_to_string(value), column)
|
886
|
-
else super
|
887
|
-
end
|
888
|
-
when Hash
|
889
|
-
if column.type == :hstore # only in AR-4.0
|
890
|
-
super(jdbc_column_class.hstore_to_string(value), column)
|
891
|
-
elsif column.type == :json # only in AR-4.0
|
892
|
-
super(jdbc_column_class.json_to_string(value), column)
|
893
|
-
elsif column.type == :jsonb # only in AR-4.0
|
894
|
-
super(jdbc_column_class.json_to_string(value), column)
|
895
|
-
else super
|
896
|
-
end
|
897
|
-
when Range
|
898
|
-
sql_type = column.respond_to?(:sql_type) && column.sql_type
|
899
|
-
if sql_type && sql_type[-5, 5] == 'range' && AR40
|
900
|
-
escaped = quote_string(jdbc_column_class.range_to_string(value))
|
901
|
-
"'#{escaped}'::#{sql_type}"
|
902
|
-
else super
|
903
|
-
end
|
904
|
-
when IPAddr
|
905
|
-
if column.type == :inet || column.type == :cidr # only in AR-4.0
|
906
|
-
super(jdbc_column_class.cidr_to_string(value), column)
|
907
|
-
else super
|
908
|
-
end
|
909
|
-
else
|
910
|
-
super
|
911
|
-
end
|
912
|
-
end unless AR42
|
913
|
-
|
914
|
-
# @private
|
915
|
-
def _quote(value)
|
916
|
-
case value
|
917
|
-
when Type::Binary::Data
|
918
|
-
"E'#{escape_bytea(value.to_s)}'"
|
919
|
-
when OID::Xml::Data
|
920
|
-
"xml '#{quote_string(value.to_s)}'"
|
921
|
-
when OID::Bit::Data
|
922
|
-
if value.binary?
|
923
|
-
"B'#{value}'"
|
924
|
-
elsif value.hex?
|
925
|
-
"X'#{value}'"
|
926
|
-
end
|
927
|
-
when Float
|
928
|
-
if value.infinite? || value.nan?
|
929
|
-
"'#{value}'"
|
930
|
-
else
|
931
|
-
super
|
932
|
-
end
|
933
|
-
else
|
934
|
-
super
|
935
|
-
end
|
936
|
-
end if AR42
|
937
|
-
private :_quote if AR42
|
938
|
-
|
939
483
|
# Quotes a string, escaping any ' (single quote) and \ (backslash) chars.
|
940
484
|
# @return [String]
|
941
485
|
# @override
|
@@ -947,24 +491,6 @@ module ArJdbc
|
|
947
491
|
quoted
|
948
492
|
end
|
949
493
|
|
950
|
-
# @return [String]
|
951
|
-
def quote_bit(value)
|
952
|
-
case value
|
953
|
-
# NOTE: as reported with #60 this is not quite "right" :
|
954
|
-
# "0103" will be treated as hexadecimal string
|
955
|
-
# "0102" will be treated as hexadecimal string
|
956
|
-
# "0101" will be treated as binary string
|
957
|
-
# "0100" will be treated as binary string
|
958
|
-
# ... but is kept due Rails compatibility
|
959
|
-
when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
|
960
|
-
when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
|
961
|
-
end
|
962
|
-
end
|
963
|
-
|
964
|
-
def quote_bit(value)
|
965
|
-
"B'#{value}'"
|
966
|
-
end if AR40
|
967
|
-
|
968
494
|
def escape_bytea(string)
|
969
495
|
return unless string
|
970
496
|
if supports_hex_escaped_bytea?
|
@@ -988,285 +514,19 @@ module ArJdbc
|
|
988
514
|
end
|
989
515
|
end
|
990
516
|
|
991
|
-
# @override
|
992
|
-
def quote_table_name_for_assignment(table, attr)
|
993
|
-
quote_column_name(attr)
|
994
|
-
end if AR40
|
995
|
-
|
996
517
|
# @override
|
997
518
|
def quote_column_name(name)
|
998
519
|
%("#{name.to_s.gsub("\"", "\"\"")}")
|
999
520
|
end
|
1000
|
-
|
1001
|
-
# @private
|
1002
|
-
def quote_default_value(value, column)
|
1003
|
-
# Do not quote function default values for UUID columns
|
1004
|
-
if column.type == :uuid && value =~ /\(\)/
|
1005
|
-
value
|
1006
|
-
else
|
1007
|
-
quote(value, column)
|
1008
|
-
end
|
1009
|
-
end
|
1010
|
-
|
1011
|
-
# Quote date/time values for use in SQL input.
|
1012
|
-
# Includes microseconds if the value is a Time responding to `usec`.
|
1013
|
-
# @override
|
1014
|
-
def quoted_date(value)
|
1015
|
-
result = super
|
1016
|
-
if value.acts_like?(:time) && value.respond_to?(:usec) && !AR50
|
1017
|
-
result = "#{result}.#{sprintf("%06d", value.usec)}"
|
1018
|
-
end
|
1019
|
-
result = "#{result.sub(/^-/, '')} BC" if value.year < 0
|
1020
|
-
result
|
1021
|
-
end if ::ActiveRecord::VERSION::MAJOR >= 3
|
1022
|
-
|
1023
|
-
# @override
|
1024
|
-
def supports_disable_referential_integrity?
|
1025
|
-
true
|
1026
|
-
end
|
1027
|
-
|
1028
|
-
def disable_referential_integrity
|
1029
|
-
if supports_disable_referential_integrity?
|
1030
|
-
begin
|
1031
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
1032
|
-
rescue
|
1033
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
|
1034
|
-
end
|
1035
|
-
end
|
1036
|
-
yield
|
1037
|
-
ensure
|
1038
|
-
if supports_disable_referential_integrity?
|
1039
|
-
begin
|
1040
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
1041
|
-
rescue
|
1042
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
|
1043
|
-
end
|
1044
|
-
end
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
def rename_table(table_name, new_name)
|
1048
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
1049
|
-
pk, seq = pk_and_sequence_for(new_name)
|
1050
|
-
if seq == "#{table_name}_#{pk}_seq"
|
1051
|
-
new_seq = "#{new_name}_#{pk}_seq"
|
1052
|
-
idx = "#{table_name}_pkey"
|
1053
|
-
new_idx = "#{new_name}_pkey"
|
1054
|
-
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
1055
|
-
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
1056
|
-
end
|
1057
|
-
rename_table_indexes(table_name, new_name) if respond_to?(:rename_table_indexes) # AR-4.0 SchemaStatements
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
# Adds a new column to the named table.
|
1061
|
-
# See TableDefinition#column for details of the options you can use.
|
1062
|
-
def add_column(table_name, column_name, type, options = {})
|
1063
|
-
default = options[:default]
|
1064
|
-
notnull = options[:null] == false
|
1065
|
-
|
1066
|
-
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
|
1067
|
-
sql_type << "[]" if options[:array]
|
1068
|
-
|
1069
|
-
# Add the column.
|
1070
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{sql_type}")
|
1071
|
-
|
1072
|
-
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
1073
|
-
change_column_null(table_name, column_name, false, default) if notnull
|
1074
|
-
end if ::ActiveRecord::VERSION::MAJOR < 4
|
1075
|
-
|
1076
|
-
# @private documented above
|
1077
|
-
def add_column(table_name, column_name, type, options = {}); super end if AR42
|
1078
|
-
|
1079
|
-
# Changes the column of a table.
|
1080
|
-
def change_column(table_name, column_name, type, options = {})
|
1081
|
-
quoted_table_name = quote_table_name(table_name)
|
1082
|
-
quoted_column_name = quote_table_name(column_name)
|
1083
|
-
|
1084
|
-
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
|
1085
|
-
sql_type << "[]" if options[:array]
|
1086
|
-
|
1087
|
-
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
|
1088
|
-
sql << " USING #{options[:using]}" if options[:using]
|
1089
|
-
if options[:cast_as]
|
1090
|
-
sql << " USING CAST(#{quoted_column_name} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
|
1091
|
-
end
|
1092
|
-
begin
|
1093
|
-
execute sql
|
1094
|
-
rescue ActiveRecord::StatementInvalid => e
|
1095
|
-
raise e if postgresql_version > 80000
|
1096
|
-
change_column_pg7(table_name, column_name, type, options)
|
1097
|
-
end
|
1098
|
-
|
1099
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
1100
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
1101
|
-
end # unless const_defined? :SchemaCreation
|
1102
|
-
|
1103
|
-
def change_column_pg7(table_name, column_name, type, options)
|
1104
|
-
quoted_table_name = quote_table_name(table_name)
|
1105
|
-
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
1106
|
-
begin
|
1107
|
-
begin_db_transaction
|
1108
|
-
tmp_column_name = "#{column_name}_ar_tmp"
|
1109
|
-
add_column(table_name, tmp_column_name, type, options)
|
1110
|
-
execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{sql_type})"
|
1111
|
-
remove_column(table_name, column_name)
|
1112
|
-
rename_column(table_name, tmp_column_name, column_name)
|
1113
|
-
commit_db_transaction
|
1114
|
-
rescue
|
1115
|
-
rollback_db_transaction
|
1116
|
-
end
|
1117
|
-
end
|
1118
|
-
private :change_column_pg7
|
1119
|
-
|
1120
|
-
# Changes the default value of a table column.
|
1121
|
-
def change_column_default(table_name, column_name, default)
|
1122
|
-
if column = column_for(table_name, column_name) # (backwards) compatible with AR 3.x - 4.x
|
1123
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}"
|
1124
|
-
else
|
1125
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
1126
|
-
end
|
1127
|
-
end unless AR42 # unless const_defined? :SchemaCreation
|
1128
|
-
|
1129
|
-
# @private documented above
|
1130
|
-
def change_column_default(table_name, column_name, default)
|
1131
|
-
return unless column = column_for(table_name, column_name)
|
1132
|
-
|
1133
|
-
alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
|
1134
|
-
if default.nil?
|
1135
|
-
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
|
1136
|
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
1137
|
-
execute alter_column_query % "DROP DEFAULT"
|
1138
|
-
else
|
1139
|
-
execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
|
1140
|
-
end
|
1141
|
-
end if AR42
|
1142
|
-
|
1143
|
-
# @private
|
1144
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
1145
|
-
unless null || default.nil?
|
1146
|
-
if column = column_for(table_name, column_name) # (backwards) compatible with AR 3.x - 4.x
|
1147
|
-
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL"
|
1148
|
-
else
|
1149
|
-
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL"
|
1150
|
-
end
|
1151
|
-
end
|
1152
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
1153
|
-
end unless AR42 # unless const_defined? :SchemaCreation
|
1154
|
-
|
1155
|
-
# @private
|
1156
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
1157
|
-
unless null || default.nil?
|
1158
|
-
column = column_for(table_name, column_name)
|
1159
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
|
1160
|
-
end
|
1161
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
1162
|
-
end if AR42
|
1163
|
-
|
1164
|
-
def rename_column(table_name, column_name, new_column_name)
|
1165
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
1166
|
-
rename_column_indexes(table_name, column_name, new_column_name) if respond_to?(:rename_column_indexes) # AR-4.0 SchemaStatements
|
1167
|
-
end # unless const_defined? :SchemaCreation
|
1168
|
-
|
1169
|
-
def add_index(table_name, column_name, options = {})
|
1170
|
-
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
1171
|
-
execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
|
1172
|
-
end if AR40
|
521
|
+
alias_method :quote_schema_name, :quote_column_name
|
1173
522
|
|
1174
523
|
def remove_index!(table_name, index_name)
|
1175
524
|
execute "DROP INDEX #{quote_table_name(index_name)}"
|
1176
525
|
end
|
1177
526
|
|
1178
|
-
def rename_index(table_name, old_name, new_name)
|
1179
|
-
validate_index_length!(table_name, new_name) if respond_to?(:validate_index_length!)
|
1180
|
-
|
1181
|
-
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
1182
|
-
end
|
1183
|
-
|
1184
527
|
# @override
|
1185
528
|
def supports_foreign_keys?; true end
|
1186
529
|
|
1187
|
-
def foreign_keys(table_name)
|
1188
|
-
fk_info = select_all "" <<
|
1189
|
-
"SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete " <<
|
1190
|
-
"FROM pg_constraint c " <<
|
1191
|
-
"JOIN pg_class t1 ON c.conrelid = t1.oid " <<
|
1192
|
-
"JOIN pg_class t2 ON c.confrelid = t2.oid " <<
|
1193
|
-
"JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid " <<
|
1194
|
-
"JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid " <<
|
1195
|
-
"JOIN pg_namespace t3 ON c.connamespace = t3.oid " <<
|
1196
|
-
"WHERE c.contype = 'f' " <<
|
1197
|
-
" AND t1.relname = #{quote(table_name)} " <<
|
1198
|
-
" AND t3.nspname = ANY (current_schemas(false)) " <<
|
1199
|
-
"ORDER BY c.conname "
|
1200
|
-
|
1201
|
-
fk_info.map! do |row|
|
1202
|
-
options = {
|
1203
|
-
:column => row['column'], :name => row['name'], :primary_key => row['primary_key']
|
1204
|
-
}
|
1205
|
-
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
|
1206
|
-
options[:on_update] = extract_foreign_key_action(row['on_update'])
|
1207
|
-
|
1208
|
-
ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
1209
|
-
end
|
1210
|
-
end if defined? ForeignKeyDefinition
|
1211
|
-
|
1212
|
-
# @private
|
1213
|
-
def extract_foreign_key_action(specifier)
|
1214
|
-
case specifier
|
1215
|
-
when 'c'; :cascade
|
1216
|
-
when 'n'; :nullify
|
1217
|
-
when 'r'; :restrict
|
1218
|
-
end
|
1219
|
-
end
|
1220
|
-
private :extract_foreign_key_action
|
1221
|
-
|
1222
|
-
def index_name_length
|
1223
|
-
63
|
1224
|
-
end
|
1225
|
-
|
1226
|
-
# Returns the list of all column definitions for a table.
|
1227
|
-
def columns(table_name, name = nil)
|
1228
|
-
column = jdbc_column_class
|
1229
|
-
column_definitions(table_name).map! do |row|
|
1230
|
-
# |name, type, default, notnull, oid, fmod|
|
1231
|
-
name = row[0]; type = row[1]; default = row[2]
|
1232
|
-
notnull = row[3]; oid = row[4]; fmod = row[5]
|
1233
|
-
# oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) { OID::Identity.new }
|
1234
|
-
notnull = notnull == 't' if notnull.is_a?(String) # JDBC gets true/false
|
1235
|
-
# for ID columns we get a bit of non-sense default :
|
1236
|
-
# e.g. "nextval('mixed_cases_id_seq'::regclass"
|
1237
|
-
if default =~ /^nextval\(.*?\:\:regclass\)$/
|
1238
|
-
default = nil
|
1239
|
-
elsif default =~ /^\(([-+]?[\d\.]+)\)$/ # e.g. "(-1)" for a negative default
|
1240
|
-
default = $1
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
column.new(name, default, oid, type, ! notnull, fmod, self)
|
1244
|
-
end
|
1245
|
-
end
|
1246
|
-
|
1247
|
-
# @private documented above
|
1248
|
-
def columns(table_name)
|
1249
|
-
column = jdbc_column_class
|
1250
|
-
# Limit, precision, and scale are all handled by the superclass.
|
1251
|
-
column_definitions(table_name).map! do |row|
|
1252
|
-
# |name, type, default, notnull, oid, fmod|
|
1253
|
-
name = row[0]; type = row[1]; default = row[2]
|
1254
|
-
notnull = row[3]; oid = row[4]; fmod = row[5]
|
1255
|
-
notnull = notnull == 't' if notnull.is_a?(String) # JDBC gets true/false
|
1256
|
-
|
1257
|
-
oid_type = get_oid_type(oid.to_i, fmod.to_i, name, type)
|
1258
|
-
default_value = extract_value_from_default(oid, default)
|
1259
|
-
default_function = extract_default_function(default_value, default)
|
1260
|
-
|
1261
|
-
column.new(name, default_value, oid_type, type, ! notnull, default_function, oid, self)
|
1262
|
-
end
|
1263
|
-
end if AR42
|
1264
|
-
|
1265
|
-
# @private only for API compatibility
|
1266
|
-
def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
|
1267
|
-
jdbc_column_class.new(name, default, cast_type, sql_type, null, default_function)
|
1268
|
-
end if AR42
|
1269
|
-
|
1270
530
|
# @private
|
1271
531
|
def column_for(table_name, column_name)
|
1272
532
|
column_name = column_name.to_s
|
@@ -1285,139 +545,90 @@ module ArJdbc
|
|
1285
545
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1286
546
|
# - ::regclass is a function that gives the id for a table name
|
1287
547
|
def column_definitions(table_name)
|
1288
|
-
select_rows(<<-end_sql, 'SCHEMA')
|
548
|
+
rows = select_rows(<<-end_sql, 'SCHEMA')
|
1289
549
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1290
|
-
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1291
|
-
|
1292
|
-
|
550
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
551
|
+
(SELECT c.collname FROM pg_collation c, pg_type t
|
552
|
+
WHERE c.oid = a.attcollation AND t.oid = a.atttypid
|
553
|
+
AND a.attcollation <> t.typcollation),
|
554
|
+
col_description(a.attrelid, a.attnum) AS comment
|
555
|
+
FROM pg_attribute a
|
556
|
+
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
1293
557
|
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
1294
558
|
AND a.attnum > 0 AND NOT a.attisdropped
|
1295
559
|
ORDER BY a.attnum
|
1296
560
|
end_sql
|
1297
|
-
end
|
1298
|
-
private :column_definitions
|
1299
|
-
|
1300
|
-
# @private
|
1301
|
-
TABLES_SQL = 'SELECT tablename FROM pg_tables WHERE schemaname = ANY (current_schemas(false))'
|
1302
|
-
private_constant :TABLES_SQL rescue nil
|
1303
|
-
|
1304
|
-
# @override
|
1305
|
-
def tables(name = nil)
|
1306
|
-
select_values(TABLES_SQL, 'SCHEMA')
|
1307
|
-
end
|
1308
561
|
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
if AR42 # -- (r)elation/table, (v)iew, (m)aterialized view
|
1313
|
-
TABLE_EXISTS_SQL_PREFIX << " WHERE c.relkind IN ('r','v','m')"
|
1314
|
-
else
|
1315
|
-
TABLE_EXISTS_SQL_PREFIX << " WHERE c.relkind IN ('r','v')"
|
1316
|
-
end
|
1317
|
-
TABLE_EXISTS_SQL_PREFIX << " AND c.relname = ?"
|
1318
|
-
private_constant :TABLE_EXISTS_SQL_PREFIX rescue nil
|
1319
|
-
|
1320
|
-
# Returns true if table exists.
|
1321
|
-
# If the schema is not specified as part of +name+ then it will only find tables within
|
1322
|
-
# the current schema search path (regardless of permissions to access tables in other schemas)
|
1323
|
-
def table_exists?(name)
|
1324
|
-
schema, table = extract_schema_and_table(name.to_s)
|
1325
|
-
return false unless table
|
1326
|
-
|
1327
|
-
binds = [[nil, table]]
|
1328
|
-
binds << [nil, schema] if schema
|
1329
|
-
|
1330
|
-
sql = "#{TABLE_EXISTS_SQL_PREFIX} AND n.nspname = #{schema ? "?" : 'ANY (current_schemas(false))'}"
|
1331
|
-
|
1332
|
-
log(sql, 'SCHEMA', binds) do
|
1333
|
-
@connection.execute_query_raw(sql, binds).first['table_count'] > 0
|
562
|
+
# Force the notnull attribute to a boolean
|
563
|
+
rows.each do |row|
|
564
|
+
row[3] = row[3] == 't' if row[3].is_a?(String)
|
1334
565
|
end
|
1335
566
|
end
|
1336
|
-
|
1337
|
-
|
1338
|
-
# @private
|
1339
|
-
DATA_SOURCES_SQL = 'SELECT c.relname FROM pg_class c'
|
1340
|
-
DATA_SOURCES_SQL << ' LEFT JOIN pg_namespace n ON n.oid = c.relnamespace'
|
1341
|
-
DATA_SOURCES_SQL << " WHERE c.relkind IN ('r', 'v','m')" # -- (r)elation/table, (v)iew, (m)aterialized view
|
1342
|
-
DATA_SOURCES_SQL << ' AND n.nspname = ANY (current_schemas(false))'
|
1343
|
-
private_constant :DATA_SOURCES_SQL rescue nil
|
1344
|
-
|
1345
|
-
# @override
|
1346
|
-
def data_sources
|
1347
|
-
select_values(DATA_SOURCES_SQL, 'SCHEMA')
|
1348
|
-
end
|
1349
|
-
|
1350
|
-
def drop_table(table_name, options = {})
|
1351
|
-
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
1352
|
-
end
|
567
|
+
private :column_definitions
|
1353
568
|
|
1354
569
|
def truncate(table_name, name = nil)
|
1355
570
|
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
1356
571
|
end
|
1357
572
|
|
1358
|
-
def index_name_exists?(table_name, index_name, default)
|
1359
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
1360
|
-
SELECT COUNT(*)
|
1361
|
-
FROM pg_class t
|
1362
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
1363
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
1364
|
-
WHERE i.relkind = 'i'
|
1365
|
-
AND i.relname = '#{index_name}'
|
1366
|
-
AND t.relname = '#{table_name}'
|
1367
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
1368
|
-
SQL
|
1369
|
-
end if AR42
|
1370
|
-
|
1371
573
|
# Returns an array of indexes for the given table.
|
1372
574
|
def indexes(table_name, name = nil)
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
575
|
+
if name
|
576
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
577
|
+
Passing name to #indexes is deprecated without replacement.
|
578
|
+
MSG
|
579
|
+
end
|
580
|
+
|
581
|
+
# FIXME: AR version => table = Utils.extract_schema_qualified_name(table_name.to_s)
|
582
|
+
schema, table = extract_schema_and_table(table_name.to_s)
|
583
|
+
|
584
|
+
result = query(<<-SQL, 'SCHEMA')
|
585
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
586
|
+
pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
|
587
|
+
(SELECT COUNT(*) FROM pg_opclass o
|
588
|
+
JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
|
589
|
+
ON o.oid = c.oid WHERE o.opcdefault = 'f')
|
590
|
+
FROM pg_class t
|
591
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
592
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
593
|
+
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
594
|
+
WHERE i.relkind = 'i'
|
595
|
+
AND d.indisprimary = 'f'
|
596
|
+
AND t.relname = '#{table}'
|
597
|
+
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
598
|
+
ORDER BY i.relname
|
1384
599
|
SQL
|
1385
600
|
|
1386
|
-
result.map
|
601
|
+
result.map do |row|
|
1387
602
|
index_name = row[0]
|
603
|
+
# FIXME: These values [1,2] are returned in a different format than AR expects, maybe we could update it on the Java side to be more accurate
|
1388
604
|
unique = row[1].is_a?(String) ? row[1] == 't' : row[1] # JDBC gets us a boolean
|
1389
605
|
indkey = row[2].is_a?(Java::OrgPostgresqlUtil::PGobject) ? row[2].value : row[2]
|
1390
|
-
indkey = indkey.split(" ")
|
606
|
+
indkey = indkey.split(" ").map(&:to_i)
|
1391
607
|
inddef = row[3]
|
1392
608
|
oid = row[4]
|
609
|
+
comment = row[5]
|
610
|
+
opclass = row[6]
|
1393
611
|
|
1394
|
-
|
1395
|
-
SELECT a.attnum, a.attname
|
1396
|
-
FROM pg_attribute a
|
1397
|
-
WHERE a.attrelid = #{oid}
|
1398
|
-
AND a.attnum IN (#{indkey.join(",")})
|
1399
|
-
SQL
|
612
|
+
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
|
1400
613
|
|
1401
|
-
|
1402
|
-
|
614
|
+
if indkey.include?(0) || opclass > 0
|
615
|
+
columns = expressions
|
616
|
+
else
|
617
|
+
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
|
618
|
+
SELECT a.attnum, a.attname
|
619
|
+
FROM pg_attribute a
|
620
|
+
WHERE a.attrelid = #{oid}
|
621
|
+
AND a.attnum IN (#{indkey.join(",")})
|
622
|
+
SQL
|
1403
623
|
|
1404
|
-
unless column_names.empty?
|
1405
624
|
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
if ::ActiveRecord::VERSION::MAJOR > 3 # AR4 supports `where` and `using` index options
|
1410
|
-
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
1411
|
-
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
1412
|
-
|
1413
|
-
IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
|
1414
|
-
else
|
1415
|
-
new_index_definition(table_name, index_name, unique, column_names, [], orders)
|
1416
|
-
end
|
625
|
+
orders = Hash[
|
626
|
+
expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
|
627
|
+
]
|
1417
628
|
end
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
629
|
+
|
630
|
+
IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
|
631
|
+
end.compact
|
1421
632
|
end
|
1422
633
|
|
1423
634
|
# @private
|
@@ -1428,16 +639,48 @@ module ArJdbc
|
|
1428
639
|
when 'average' then 'avg'
|
1429
640
|
else operation.downcase
|
1430
641
|
end
|
1431
|
-
end
|
642
|
+
end
|
1432
643
|
|
1433
644
|
private
|
1434
645
|
|
646
|
+
# Pulled from ActiveRecord's Postgres adapter and modified to use execute
|
647
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
648
|
+
@case_insensitive_cache ||= {}
|
649
|
+
@case_insensitive_cache[column.sql_type] ||= begin
|
650
|
+
sql = <<-end_sql
|
651
|
+
SELECT exists(
|
652
|
+
SELECT * FROM pg_proc
|
653
|
+
WHERE proname = 'lower'
|
654
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
655
|
+
) OR exists(
|
656
|
+
SELECT * FROM pg_proc
|
657
|
+
INNER JOIN pg_cast
|
658
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
659
|
+
WHERE proname = 'lower'
|
660
|
+
AND castsource = #{quote column.sql_type}::regtype
|
661
|
+
)
|
662
|
+
end_sql
|
663
|
+
select_rows(sql, 'SCHEMA').first.first == 't'
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
1435
667
|
def translate_exception(exception, message)
|
668
|
+
# TODO: Can we base these on an error code of some kind?
|
1436
669
|
case exception.message
|
1437
670
|
when /duplicate key value violates unique constraint/
|
1438
|
-
::ActiveRecord::RecordNotUnique.new(message
|
671
|
+
::ActiveRecord::RecordNotUnique.new(message)
|
672
|
+
when /violates not-null constraint/
|
673
|
+
::ActiveRecord::NotNullViolation.new(message)
|
1439
674
|
when /violates foreign key constraint/
|
1440
|
-
::ActiveRecord::InvalidForeignKey.new(message
|
675
|
+
::ActiveRecord::InvalidForeignKey.new(message)
|
676
|
+
when /value too long/
|
677
|
+
::ActiveRecord::ValueTooLong.new(message)
|
678
|
+
when /out of range/
|
679
|
+
::ActiveRecord::RangeError.new(message)
|
680
|
+
when /could not serialize/
|
681
|
+
::ActiveRecord::SerializationFailure.new(message)
|
682
|
+
when /deadlock detected/
|
683
|
+
::ActiveRecord::Deadlocked.new(message)
|
1441
684
|
else
|
1442
685
|
super
|
1443
686
|
end
|
@@ -1477,30 +720,37 @@ require 'arjdbc/util/quoted_cache'
|
|
1477
720
|
|
1478
721
|
module ActiveRecord::ConnectionAdapters
|
1479
722
|
|
1480
|
-
remove_const(:PostgreSQLColumn) if const_defined?(:PostgreSQLColumn)
|
1481
|
-
|
1482
|
-
class PostgreSQLColumn < JdbcColumn
|
1483
|
-
include ::ArJdbc::PostgreSQL::Column
|
1484
|
-
end
|
1485
|
-
|
1486
723
|
# NOTE: seems needed on 4.x due loading of '.../postgresql/oid' which
|
1487
724
|
# assumes: class PostgreSQLAdapter < AbstractAdapter
|
1488
725
|
remove_const(:PostgreSQLAdapter) if const_defined?(:PostgreSQLAdapter)
|
1489
726
|
|
1490
|
-
class PostgreSQLAdapter <
|
1491
|
-
include ::ArJdbc::PostgreSQL
|
1492
|
-
include ::ArJdbc::PostgreSQL::ExplainSupport
|
727
|
+
class PostgreSQLAdapter < AbstractAdapter
|
1493
728
|
|
1494
|
-
|
1495
|
-
|
729
|
+
# Try to use as much of the built in postgres logic as possible
|
730
|
+
# maybe someday we can extend the actual adapter
|
731
|
+
include ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDumper
|
732
|
+
include ActiveRecord::ConnectionAdapters::PostgreSQL::ReferentialIntegrity
|
733
|
+
include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
|
734
|
+
include ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting
|
1496
735
|
|
1497
|
-
|
736
|
+
include ArJdbc::Abstract::Core
|
737
|
+
include ArJdbc::Abstract::ConnectionManagement
|
738
|
+
include ArJdbc::Abstract::DatabaseStatements
|
739
|
+
include ArJdbc::Abstract::StatementCache
|
740
|
+
include ArJdbc::Abstract::TransactionSupport
|
741
|
+
include ArJdbc::PostgreSQL
|
1498
742
|
|
1499
|
-
|
743
|
+
require 'arjdbc/postgresql/oid_types'
|
744
|
+
include ::ArJdbc::PostgreSQL::OIDTypes
|
745
|
+
|
746
|
+
include ::ArJdbc::PostgreSQL::ColumnHelpers
|
1500
747
|
|
1501
748
|
include ::ArJdbc::Util::QuotedCache
|
1502
749
|
|
1503
|
-
|
750
|
+
# AR expects OID to be available on the adapter
|
751
|
+
OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
|
752
|
+
|
753
|
+
def initialize(connection, logger = nil, config = {})
|
1504
754
|
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
1505
755
|
@local_tz = nil
|
1506
756
|
|
@@ -1508,7 +758,7 @@ module ActiveRecord::ConnectionAdapters
|
|
1508
758
|
|
1509
759
|
@table_alias_length = nil
|
1510
760
|
|
1511
|
-
initialize_type_map(@type_map = Type::HashLookupTypeMap.new)
|
761
|
+
initialize_type_map(@type_map = Type::HashLookupTypeMap.new)
|
1512
762
|
|
1513
763
|
@use_insert_returning = @config.key?(:insert_returning) ?
|
1514
764
|
self.class.type_cast_config_to_boolean(@config[:insert_returning]) : nil
|
@@ -1518,34 +768,49 @@ module ActiveRecord::ConnectionAdapters
|
|
1518
768
|
Arel::Visitors::PostgreSQL.new(self)
|
1519
769
|
end
|
1520
770
|
|
1521
|
-
|
1522
|
-
require 'active_record/connection_adapters/postgresql/schema_definitions'
|
1523
|
-
else
|
1524
|
-
require 'arjdbc/postgresql/base/schema_definitions'
|
1525
|
-
end
|
1526
|
-
|
1527
|
-
ColumnDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDefinition
|
771
|
+
require 'active_record/connection_adapters/postgresql/schema_definitions'
|
1528
772
|
|
1529
773
|
ColumnMethods = ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods
|
1530
774
|
TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
|
775
|
+
Table = ActiveRecord::ConnectionAdapters::PostgreSQL::Table
|
1531
776
|
|
1532
|
-
def
|
1533
|
-
|
777
|
+
def create_table_definition(*args) # :nodoc:
|
778
|
+
TableDefinition.new(*args)
|
1534
779
|
end
|
1535
780
|
|
1536
|
-
|
781
|
+
def exec_query(sql, name = nil, binds = [], prepare: false)
|
782
|
+
super
|
783
|
+
rescue ActiveRecord::StatementInvalid => e
|
784
|
+
raise unless e.cause.message.include?('cached plan must not change result type'.freeze)
|
785
|
+
|
786
|
+
if open_transactions > 0
|
787
|
+
# In a transaction, have to fail it - See AR code for details
|
788
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
789
|
+
else
|
790
|
+
# Not in a transaction, clear the prepared statement and try again
|
791
|
+
delete_cached_statement(sql)
|
792
|
+
retry
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
def schema_creation # :nodoc:
|
797
|
+
PostgreSQL::SchemaCreation.new self
|
798
|
+
end
|
1537
799
|
|
1538
800
|
def update_table_definition(table_name, base)
|
1539
801
|
Table.new(table_name, base)
|
1540
|
-
end
|
802
|
+
end
|
1541
803
|
|
1542
804
|
def jdbc_connection_class(spec)
|
1543
805
|
::ArJdbc::PostgreSQL.jdbc_connection_class
|
1544
806
|
end
|
1545
807
|
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
808
|
+
private
|
809
|
+
|
810
|
+
# Prepared statements aren't schema aware so we need to make sure we
|
811
|
+
# store different PreparedStatement objects for different schemas
|
812
|
+
def cached_statement_key(sql)
|
813
|
+
"#{schema_search_path}-#{sql}"
|
1549
814
|
end
|
1550
815
|
|
1551
816
|
end
|