activerecord-postgresql-extensions 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +23 -0
- data/README.rdoc +32 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/lib/activerecord-postgresql-extensions.rb +30 -0
- data/lib/postgresql_extensions/foreign_key_associations.rb +367 -0
- data/lib/postgresql_extensions/postgresql_adapter_extensions.rb +646 -0
- data/lib/postgresql_extensions/postgresql_constraints.rb +579 -0
- data/lib/postgresql_extensions/postgresql_functions.rb +345 -0
- data/lib/postgresql_extensions/postgresql_geometry.rb +212 -0
- data/lib/postgresql_extensions/postgresql_indexes.rb +219 -0
- data/lib/postgresql_extensions/postgresql_languages.rb +80 -0
- data/lib/postgresql_extensions/postgresql_permissions.rb +322 -0
- data/lib/postgresql_extensions/postgresql_rules.rb +112 -0
- data/lib/postgresql_extensions/postgresql_schemas.rb +49 -0
- data/lib/postgresql_extensions/postgresql_sequences.rb +222 -0
- data/lib/postgresql_extensions/postgresql_tables.rb +308 -0
- data/lib/postgresql_extensions/postgresql_triggers.rb +131 -0
- data/lib/postgresql_extensions/postgresql_types.rb +17 -0
- data/lib/postgresql_extensions/postgresql_views.rb +103 -0
- data/postgresql-extensions.gemspec +50 -0
- data/test/adapter_test.rb +45 -0
- data/test/constraints_test.rb +98 -0
- data/test/functions_test.rb +112 -0
- data/test/geometry_test.rb +43 -0
- data/test/index_test.rb +68 -0
- data/test/languages_test.rb +48 -0
- data/test/permissions_test.rb +163 -0
- data/test/rules_test.rb +32 -0
- data/test/schemas_test.rb +43 -0
- data/test/sequences_test.rb +90 -0
- data/test/tables_test.rb +49 -0
- data/test/test_helper.rb +64 -0
- metadata +97 -0
@@ -0,0 +1,646 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
class PostgreSQLAdapter < AbstractAdapter
|
5
|
+
if defined?(Rails)
|
6
|
+
LOGGER_REGEXP = /^#{Rails.root}(?!\/vendor\/rails)/
|
7
|
+
|
8
|
+
def query_with_extra_logging(sql, name = nil) #:nodoc:
|
9
|
+
if ActiveRecord::Base.enable_extended_logging && Rails.logger && Rails.logger.level == Logger::DEBUG
|
10
|
+
unless (sql =~ /(pg_get_constraintdef|pg_attribute|pg_class)/)
|
11
|
+
Rails.logger.debug
|
12
|
+
Rails.logger.debug(caller.select { |x|
|
13
|
+
ActiveRecord::Base.enable_really_extended_logging || x.match(LOGGER_REGEXP)
|
14
|
+
}.join("\n"))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
query_without_extra_logging(sql, name)
|
18
|
+
end
|
19
|
+
alias_method_chain :query, :extra_logging
|
20
|
+
|
21
|
+
def execute_with_extra_logging(sql, name = nil) #:nodoc:
|
22
|
+
if ActiveRecord::Base.enable_extended_logging && Rails.logger && Rails.logger.level == Logger::DEBUG
|
23
|
+
unless (sql =~ /(pg_get_constraintdef|pg_attribute|pg_class)/)
|
24
|
+
Rails.logger.debug
|
25
|
+
Rails.logger.debug(caller.select { |x|
|
26
|
+
ActiveRecord::Base.enable_really_extended_logging || x.match(LOGGER_REGEXP)
|
27
|
+
}.join("\n"))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
execute_without_extra_logging(sql, name)
|
31
|
+
end
|
32
|
+
alias_method_chain :execute, :extra_logging
|
33
|
+
end
|
34
|
+
|
35
|
+
# There seems to be a bug in ActiveRecord where it isn't setting
|
36
|
+
# the schema search path properly because it's using ',' as a
|
37
|
+
# separator rather than /,\s+/.
|
38
|
+
def schema_search_path_with_fix_csv=(schema_csv)
|
39
|
+
if schema_csv
|
40
|
+
csv = schema_csv.gsub(/,\s+/, ',')
|
41
|
+
execute "SET search_path TO #{csv}"
|
42
|
+
@schema_search_path = csv
|
43
|
+
end
|
44
|
+
end
|
45
|
+
alias_method_chain :schema_search_path=, :fix_csv
|
46
|
+
|
47
|
+
# Fix ActiveRecord bug when grabbing the current search_path.
|
48
|
+
def schema_search_path_with_fix_csv
|
49
|
+
@schema_search_path ||= query('SHOW search_path')[0][0].gsub(/,\s+/, ',')
|
50
|
+
end
|
51
|
+
alias_method_chain :schema_search_path, :fix_csv
|
52
|
+
|
53
|
+
# with_schema is kind of like with_scope. It wraps various
|
54
|
+
# object names in SQL statements into a PostgreSQL schema. You
|
55
|
+
# can have multiple with_schemas wrapped around each other, and
|
56
|
+
# hopefully they won't collide with one another.
|
57
|
+
#
|
58
|
+
# Examples:
|
59
|
+
#
|
60
|
+
# ### ruby
|
61
|
+
# # should produce '"geospatial"."my_tables"
|
62
|
+
# with_schema :geospatial do
|
63
|
+
# quote_table_name('my_table')
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# # should produce 'SELECT * FROM "geospatial"."models"'
|
67
|
+
# with_schema :geospatial do
|
68
|
+
# Model.find(:all)
|
69
|
+
# end
|
70
|
+
def with_schema schema
|
71
|
+
scoped_schemas << schema
|
72
|
+
begin
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
scoped_schemas.pop
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# When using with_schema, you can temporarily ignore the scoped
|
80
|
+
# schemas with ignore_block.
|
81
|
+
#
|
82
|
+
# Example:
|
83
|
+
#
|
84
|
+
# ### ruby
|
85
|
+
# with_schema :geospatial do
|
86
|
+
# create_table(:test) do |t|
|
87
|
+
# ignore_schema do
|
88
|
+
# t.integer(
|
89
|
+
# :ref_id,
|
90
|
+
# :references => {
|
91
|
+
# :table => :refs,
|
92
|
+
# :column => :id,
|
93
|
+
# :deferrable => true
|
94
|
+
# }
|
95
|
+
# )
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# Should produce:
|
101
|
+
#
|
102
|
+
# ### sql
|
103
|
+
# CREATE TABLE "geospatial"."test" (
|
104
|
+
# "id" serial primary key,
|
105
|
+
# "ref_id" integer DEFAULT NULL NULL,
|
106
|
+
# FOREIGN KEY ("ref_id") REFERENCES "refs" ("id")
|
107
|
+
# )
|
108
|
+
#
|
109
|
+
# Here we see that we used the geospatial schema when naming the
|
110
|
+
# test table and dropped back to not specifying a schema when
|
111
|
+
# setting up the foreign key to the refs table. If we had not
|
112
|
+
# used ignore_schema, the foreign key would have been defined
|
113
|
+
# thusly:
|
114
|
+
#
|
115
|
+
# ### sql
|
116
|
+
# FOREIGN KEY ("ref_id") REFERENCES "geospatial"."refs" ("id")
|
117
|
+
def ignore_schema
|
118
|
+
with_schema nil do
|
119
|
+
yield
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# See what the current scoped schemas are. Should be thread-safe
|
124
|
+
# if using the PostgreSQL adapter's concurrency mode.
|
125
|
+
def scoped_schemas
|
126
|
+
scoped_schemas = (Thread.current[:scoped_schemas] ||= {})
|
127
|
+
scoped_schemas[self] ||= []
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the current scoped schema.
|
131
|
+
def current_schema
|
132
|
+
scoped_schemas.last
|
133
|
+
end
|
134
|
+
|
135
|
+
# A generic quoting method for PostgreSQL.
|
136
|
+
def quote_generic(g)
|
137
|
+
PGconn.quote_ident(g.to_s)
|
138
|
+
end
|
139
|
+
|
140
|
+
# A generic quoting method for PostgreSQL that specifically ignores
|
141
|
+
# any and all schemas.
|
142
|
+
def quote_generic_ignore_schema(g)
|
143
|
+
if g.is_a?(Hash)
|
144
|
+
quote_generic g.values.first
|
145
|
+
else
|
146
|
+
quote_generic g
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# A generic quoting method for PostgreSQL with our special schema
|
151
|
+
# support.
|
152
|
+
def quote_generic_with_schema(g)
|
153
|
+
if g.is_a?(Hash)
|
154
|
+
"#{quote_schema(g.keys.first)}.#{quote_generic(g.values.first)}"
|
155
|
+
else
|
156
|
+
if current_schema
|
157
|
+
quote_schema(current_schema) << '.'
|
158
|
+
end.to_s << quote_generic(g)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Quoting method for roles.
|
163
|
+
def quote_role(role)
|
164
|
+
quote_generic(role)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Quoting method for rules.
|
168
|
+
def quote_rule(rule)
|
169
|
+
quote_generic(rule)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Quoting method for procedural languages.
|
173
|
+
def quote_language(language)
|
174
|
+
quote_generic(language)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Quoting method for schemas. When the schema is :public or
|
178
|
+
# 'public' or some form thereof, we'll convert that to "PUBLIC"
|
179
|
+
# without quoting.
|
180
|
+
def quote_schema(schema)
|
181
|
+
if schema.to_s.upcase == 'PUBLIC'
|
182
|
+
'PUBLIC'
|
183
|
+
else
|
184
|
+
quote_generic(schema)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Quoting method for sequences. This really just goes to the
|
189
|
+
# quoting method for table names, as sequences can belong to
|
190
|
+
# specific schemas.
|
191
|
+
def quote_sequence(name)
|
192
|
+
quote_generic_with_schema(name)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Quoting method for server-side functions.
|
196
|
+
def quote_function(name)
|
197
|
+
quote_generic_with_schema(name)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Quoting method for table names. This method has been extended
|
201
|
+
# beyond the standard ActiveRecord quote_table_name to allow for
|
202
|
+
#
|
203
|
+
# * scoped schema support with with_schema. When using with_schema,
|
204
|
+
# table names will be prefixed with the current scoped schema
|
205
|
+
# name.
|
206
|
+
# * you can specify a specific schema using a Hash containing a
|
207
|
+
# single value pair where the key is the schema name and the
|
208
|
+
# key is the table name.
|
209
|
+
#
|
210
|
+
# Example of using a Hash as a table name:
|
211
|
+
#
|
212
|
+
# ### ruby
|
213
|
+
# quote_table_name(:geospatial => :epois) # => "geospatial"."epois"
|
214
|
+
# # => "geospatial"."epois"
|
215
|
+
#
|
216
|
+
# quote_table_name(:epois)
|
217
|
+
# # => "epois"
|
218
|
+
#
|
219
|
+
# with_schema(:geospatial) { quote_table_name(:epois) }
|
220
|
+
# # => "geospatial"."epois"
|
221
|
+
#
|
222
|
+
# with_schema(:geospatial) do
|
223
|
+
# ignore_schema do
|
224
|
+
# quote_table_name(:epois)
|
225
|
+
# end
|
226
|
+
# end
|
227
|
+
# # => "epois"
|
228
|
+
def quote_table_name_with_schemas(name)
|
229
|
+
if current_schema || name.is_a?(Hash)
|
230
|
+
quote_generic_with_schema(name)
|
231
|
+
else
|
232
|
+
quote_table_name_without_schemas(name)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
alias_method_chain :quote_table_name, :schemas
|
236
|
+
|
237
|
+
# Quoting method for view names. This really just goes to the
|
238
|
+
# quoting method for table names, as views can belong to specific
|
239
|
+
# schemas.
|
240
|
+
def quote_view_name(name)
|
241
|
+
quote_table_name(name)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Quoting method for tablespaces.
|
245
|
+
def quote_tablespace(name)
|
246
|
+
quote_generic(name)
|
247
|
+
end
|
248
|
+
|
249
|
+
def extract_schema_name(name)
|
250
|
+
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
251
|
+
schema if name_part
|
252
|
+
end
|
253
|
+
|
254
|
+
def extract_table_name(name)
|
255
|
+
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
256
|
+
|
257
|
+
unless name_part
|
258
|
+
schema
|
259
|
+
else
|
260
|
+
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
261
|
+
table_name
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def extract_schema_and_table_names(name)
|
266
|
+
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
267
|
+
|
268
|
+
unless name_part
|
269
|
+
quote_column_name(schema)
|
270
|
+
[ nil, schema ]
|
271
|
+
else
|
272
|
+
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
273
|
+
[ schema, table_name ]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Copies the contents of a file into a table. This uses
|
278
|
+
# PostgreSQL's COPY FROM command.
|
279
|
+
#
|
280
|
+
# The COPY FROM command requires the input file to be readable
|
281
|
+
# on the server the database is actually running on. In our method,
|
282
|
+
# you have the choice of a file on your client's local file system
|
283
|
+
# or one on the server's local file system. See the
|
284
|
+
# <tt>:local</tt> option below.
|
285
|
+
#
|
286
|
+
# See the PostgreSQL documentation for details on COPY FROM.
|
287
|
+
#
|
288
|
+
# ==== Options
|
289
|
+
#
|
290
|
+
# * <tt>:columns</tt> - allows you to specify column names.
|
291
|
+
# * <tt>:binary</tt> - adds the BINARY clause.
|
292
|
+
# * <tt>:oids</tt> - adds the OIDS clause.
|
293
|
+
# * <tt>:delimiter</tt> - sets the delimiter for the data fields.
|
294
|
+
# The default COPY FROM delimiter in ASCII mode is a tab
|
295
|
+
# character, while in CSV it is a comma.
|
296
|
+
# * <tt>:null</tt> - allows you to set a default value for null
|
297
|
+
# fields. The default for this option is unset.
|
298
|
+
# * <tt>:local</tt> - allows you to specify that the file to be
|
299
|
+
# copied from is on a file system that is directly accessible
|
300
|
+
# from the database server itself. The default is true, i.e.
|
301
|
+
# the file is local to the client. See below for a more thorough
|
302
|
+
# explanation.
|
303
|
+
# * <tt>:csv</tt> - allows you to specify a CSV file. This option
|
304
|
+
# can be set to true, in which case you'll be using the server
|
305
|
+
# defaults for its CSV imports, or a Hash, in which case you can
|
306
|
+
# modify various CSV options like quote and escape characters.
|
307
|
+
#
|
308
|
+
# ===== CSV Options
|
309
|
+
#
|
310
|
+
# * <tt>:header</tt> - uses the first line as a CSV header row and
|
311
|
+
# skips over it.
|
312
|
+
# * <tt>:quote</tt> - the character to use for quoting. The default
|
313
|
+
# is a double-quote.
|
314
|
+
# * <tt>:escape</tt> - the character to use when escaping a quote
|
315
|
+
# character. Usually this is another double-quote.
|
316
|
+
# * <tt>:not_null</tt> - allows you to specify one or more columns
|
317
|
+
# to be inserted with a default value rather than NULL for any
|
318
|
+
# missing values.
|
319
|
+
#
|
320
|
+
# ==== Local Server Files vs. Local Client Files
|
321
|
+
#
|
322
|
+
# The copy_from_file method allows you to import rows from a file
|
323
|
+
# that exists on either your client's file system or on the
|
324
|
+
# database server's file system using the <tt>:local</tt> option.
|
325
|
+
#
|
326
|
+
# To process a file on the remote database server's file system:
|
327
|
+
#
|
328
|
+
# * the file must be given as an absolute path;
|
329
|
+
# * must be readable by the user that the actual PostgreSQL
|
330
|
+
# database server runs under; and
|
331
|
+
# * the COPY FROM command itself can only be performed by database
|
332
|
+
# superusers.
|
333
|
+
#
|
334
|
+
# In comparison, reading the file from the local client does not
|
335
|
+
# have restrictions enforced by PostgreSQL and can be performed on
|
336
|
+
# the client machine. When using a local file, the file itself is
|
337
|
+
# actually opened in Ruby and pushed into the database via a
|
338
|
+
# "COPY FROM STDIN" command. Thus, the file must be readable by
|
339
|
+
# the user your Ruby process is running as. PostgreSQL will not
|
340
|
+
# enforce the superuser restriction in this case since you are not
|
341
|
+
# touching the database server's local file system.
|
342
|
+
#
|
343
|
+
# Some considerations:
|
344
|
+
#
|
345
|
+
# * A copy from the database's local file system is faster than a
|
346
|
+
# local copy, as the data need not be read into Ruby and dumped
|
347
|
+
# across the network or UNIX socket to the database server.
|
348
|
+
# * A local copy is generally more flexible as it bypasses some of
|
349
|
+
# PostgreSQL's security considerations.
|
350
|
+
# * Copies from the server's file system require that the file
|
351
|
+
# exists on the file system accessible to the database server,
|
352
|
+
# something that you may not even have access to in the first
|
353
|
+
# place.
|
354
|
+
def copy_from_file(table_name, file, options = {})
|
355
|
+
options = {
|
356
|
+
:local => true
|
357
|
+
}.merge(options)
|
358
|
+
|
359
|
+
sql = "COPY #{quote_table_name(table_name)} "
|
360
|
+
|
361
|
+
unless options[:columns].blank?
|
362
|
+
sql << '(' << Array(options[:columns]).collect { |c| quote_column_name(c) }.join(', ') << ')'
|
363
|
+
end
|
364
|
+
|
365
|
+
if options[:local]
|
366
|
+
sql << " FROM STDIN"
|
367
|
+
else
|
368
|
+
sql << " FROM #{quote(file)}"
|
369
|
+
end
|
370
|
+
|
371
|
+
sql << ' BINARY' if options[:binary]
|
372
|
+
sql << ' OIDS' if options[:oids]
|
373
|
+
sql << " DELIMITER AS #{quote(options[:delimiter])}" if options[:delimiter]
|
374
|
+
sql << " NULL AS #{quote(options[:null_as])}" if options[:null]
|
375
|
+
|
376
|
+
if options[:csv]
|
377
|
+
sql << ' CSV'
|
378
|
+
if options[:csv].is_a?(Hash)
|
379
|
+
sql << ' HEADER' if options[:csv][:header]
|
380
|
+
sql << " QUOTE AS #{quote(options[:csv][:quote])}" if options[:csv][:quote]
|
381
|
+
sql << " ESCAPE AS #{quote(options[:csv][:escape])}" if options[:csv][:escape]
|
382
|
+
sql << ' FORCE NOT NULL ' << Array(options[:csv][:not_null]).collect do |c|
|
383
|
+
quote_column_name(c)
|
384
|
+
end.join(', ') if options[:csv][:not_null]
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
execute sql
|
389
|
+
|
390
|
+
if options[:local]
|
391
|
+
File.open(file, 'r').each do |l|
|
392
|
+
self.raw_connection.put_copy_data(l)
|
393
|
+
end
|
394
|
+
self.raw_connection.put_copy_end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Returns an Array of database views.
|
399
|
+
def views(name = nil)
|
400
|
+
query(<<-SQL, name).map { |row| row[0] }
|
401
|
+
SELECT viewname
|
402
|
+
FROM pg_views
|
403
|
+
WHERE schemaname = ANY (current_schemas(false))
|
404
|
+
SQL
|
405
|
+
end
|
406
|
+
|
407
|
+
def view_exists?(name)
|
408
|
+
name = name.to_s
|
409
|
+
schema, view = name.split('.', 2)
|
410
|
+
|
411
|
+
unless view # A view was provided without a schema
|
412
|
+
view = schema
|
413
|
+
schema = nil
|
414
|
+
end
|
415
|
+
|
416
|
+
if name =~ /^"/ # Handle quoted view names
|
417
|
+
view = name
|
418
|
+
schema = nil
|
419
|
+
end
|
420
|
+
|
421
|
+
query(<<-SQL).first[0].to_i > 0
|
422
|
+
SELECT COUNT(*)
|
423
|
+
FROM pg_views
|
424
|
+
WHERE viewname = '#{view.gsub(/(^"|"$)/,'')}'
|
425
|
+
#{schema ? "AND schemaname = '#{schema}'" : ''}
|
426
|
+
SQL
|
427
|
+
end
|
428
|
+
|
429
|
+
# Returns an Array of tables to ignore.
|
430
|
+
def ignored_tables(name = nil)
|
431
|
+
query(<<-SQL, name).map { |row| row[0] }
|
432
|
+
SELECT tablename
|
433
|
+
FROM pg_tables
|
434
|
+
WHERE schemaname IN ('pg_catalog')
|
435
|
+
SQL
|
436
|
+
end
|
437
|
+
|
438
|
+
def tables_with_views(name = nil) #:nodoc:
|
439
|
+
tables_without_views(name) + views(name)
|
440
|
+
end
|
441
|
+
alias_method_chain :tables, :views
|
442
|
+
|
443
|
+
def schema_search_path_with_csv_fix=(schema_csv) #:nodoc:
|
444
|
+
self.schema_search_path_without_csv_fix = schema_csv.gsub(/, /, ',') if schema_csv
|
445
|
+
end
|
446
|
+
alias_method_chain :schema_search_path=, :csv_fix
|
447
|
+
|
448
|
+
def schema_search_path_with_csv_fix #:nodoc:
|
449
|
+
@schema_search_path ||= query('SHOW search_path')[0][0].gsub(/, /, ',')
|
450
|
+
end
|
451
|
+
alias_method_chain :schema_search_path, :csv_fix
|
452
|
+
|
453
|
+
def disable_referential_integrity_with_views #:nodoc:
|
454
|
+
if supports_disable_referential_integrity?() then
|
455
|
+
execute((tables - views).collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
456
|
+
end
|
457
|
+
yield
|
458
|
+
ensure
|
459
|
+
if supports_disable_referential_integrity?() then
|
460
|
+
execute((tables - views).collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
461
|
+
end
|
462
|
+
end
|
463
|
+
alias_method_chain :disable_referential_integrity, :views
|
464
|
+
|
465
|
+
# Returns an Array of foreign keys for a particular table. The
|
466
|
+
# Array itself is an Array of Arrays, where each particular Array
|
467
|
+
# contains the table being referenced, the foreign key and the
|
468
|
+
# name of the column in the referenced table.
|
469
|
+
def foreign_keys(table_name, name = nil)
|
470
|
+
sql = <<-SQL
|
471
|
+
SELECT
|
472
|
+
confrelid::regclass AS referenced_table_name,
|
473
|
+
a.attname AS foreign_key,
|
474
|
+
af.attname AS referenced_column
|
475
|
+
FROM
|
476
|
+
pg_attribute af,
|
477
|
+
pg_attribute a,
|
478
|
+
pg_class c, (
|
479
|
+
SELECT
|
480
|
+
conrelid,
|
481
|
+
confrelid,
|
482
|
+
conkey[i] AS conkey,
|
483
|
+
confkey[i] AS confkey
|
484
|
+
FROM (
|
485
|
+
SELECT
|
486
|
+
conrelid,
|
487
|
+
confrelid,
|
488
|
+
conkey,
|
489
|
+
confkey,
|
490
|
+
generate_series(1, array_upper(conkey, 1)) AS i
|
491
|
+
FROM
|
492
|
+
pg_constraint
|
493
|
+
WHERE
|
494
|
+
contype = 'f'
|
495
|
+
) ss
|
496
|
+
) ss2
|
497
|
+
WHERE
|
498
|
+
c.oid = conrelid
|
499
|
+
AND
|
500
|
+
c.relname = #{quote(table_name)}
|
501
|
+
AND
|
502
|
+
af.attnum = confkey
|
503
|
+
AND
|
504
|
+
af.attrelid = confrelid
|
505
|
+
AND
|
506
|
+
a.attnum = conkey
|
507
|
+
AND
|
508
|
+
a.attrelid = conrelid
|
509
|
+
SQL
|
510
|
+
|
511
|
+
query(sql, name).inject([]) do |memo, (tbl, column, referenced_column)|
|
512
|
+
memo.tap {
|
513
|
+
memo << {
|
514
|
+
:table => tbl,
|
515
|
+
:column => column,
|
516
|
+
:referenced_column => referenced_column
|
517
|
+
}
|
518
|
+
}
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
# Returns an Array of foreign keys that point to a particular
|
523
|
+
# table. The Array itself is an Array of Arrays, where each
|
524
|
+
# particular Array contains the referencing table, the foreign key
|
525
|
+
# and the name of the column in the referenced table.
|
526
|
+
def referenced_foreign_keys(table_name, name = nil)
|
527
|
+
sql = <<-SQL
|
528
|
+
SELECT
|
529
|
+
c2.relname AS table_name,
|
530
|
+
a.attname AS foreign_key,
|
531
|
+
af.attname AS referenced_column
|
532
|
+
FROM
|
533
|
+
pg_attribute af,
|
534
|
+
pg_attribute a,
|
535
|
+
pg_class c1,
|
536
|
+
pg_class c2, (
|
537
|
+
SELECT
|
538
|
+
conrelid,
|
539
|
+
confrelid,
|
540
|
+
conkey[i] AS conkey,
|
541
|
+
confkey[i] AS confkey
|
542
|
+
FROM (
|
543
|
+
SELECT
|
544
|
+
conrelid,
|
545
|
+
confrelid,
|
546
|
+
conkey,
|
547
|
+
confkey,
|
548
|
+
generate_series(1, array_upper(conkey, 1)) AS i
|
549
|
+
FROM
|
550
|
+
pg_constraint
|
551
|
+
WHERE
|
552
|
+
contype = 'f'
|
553
|
+
) ss
|
554
|
+
) ss2
|
555
|
+
WHERE
|
556
|
+
confrelid = c1.oid
|
557
|
+
AND
|
558
|
+
conrelid = c2.oid
|
559
|
+
AND
|
560
|
+
c1.relname = #{quote(table_name)}
|
561
|
+
AND
|
562
|
+
af.attnum = confkey
|
563
|
+
AND
|
564
|
+
af.attrelid = confrelid
|
565
|
+
AND
|
566
|
+
a.attnum = conkey
|
567
|
+
AND
|
568
|
+
a.attrelid = conrelid
|
569
|
+
SQL
|
570
|
+
|
571
|
+
query(sql, name).inject([]) do |memo, (tbl, column, referenced_column)|
|
572
|
+
memo.tap {
|
573
|
+
memo << {
|
574
|
+
:table => tbl,
|
575
|
+
:column => column,
|
576
|
+
:referenced_column => referenced_column
|
577
|
+
}
|
578
|
+
}
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
def add_column_options_with_expression!(sql, options) #:nodoc:
|
583
|
+
if options_include_default?(options) &&
|
584
|
+
options[:default].is_a?(Hash) &&
|
585
|
+
options[:default].has_key?(:expression)
|
586
|
+
|
587
|
+
expression = options.delete(:default)
|
588
|
+
sql << " DEFAULT #{expression[:expression]}"
|
589
|
+
end
|
590
|
+
add_column_options_without_expression!(sql, options)
|
591
|
+
end
|
592
|
+
alias_method_chain :add_column_options!, :expression
|
593
|
+
|
594
|
+
def change_column_default_with_expression(table_name, column_name, default) #:nodoc:
|
595
|
+
if default.is_a?(Hash) && default.has_key?(:expression)
|
596
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{default[:expression]}"
|
597
|
+
else
|
598
|
+
change_column_default_without_expression(table_name, column_name, default)
|
599
|
+
end
|
600
|
+
end
|
601
|
+
alias_method_chain :change_column_default, :expression
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
module ActiveRecord
|
607
|
+
class Base
|
608
|
+
class << self
|
609
|
+
def with_schema(schema)
|
610
|
+
self.connection.with_schema(schema) { |*block_args|
|
611
|
+
yield(*block_args)
|
612
|
+
}
|
613
|
+
end
|
614
|
+
|
615
|
+
def ignore_schema
|
616
|
+
self.connection.ignore_schema { |*block_args|
|
617
|
+
yield(*block_args)
|
618
|
+
}
|
619
|
+
end
|
620
|
+
|
621
|
+
def scoped_schemas
|
622
|
+
self.connection.scope_schemas
|
623
|
+
end
|
624
|
+
|
625
|
+
def current_schema
|
626
|
+
self.connection.current_schema
|
627
|
+
end
|
628
|
+
|
629
|
+
def sequence_exists?
|
630
|
+
!!(connection.sequence_exists?(sequence_name) if connection.respond_to?(:sequence_exists?))
|
631
|
+
end
|
632
|
+
|
633
|
+
def view_exists?
|
634
|
+
connection.view_exists?(table_name)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
ActiveRecord::Base.class_eval do
|
641
|
+
# Enable extended query logging
|
642
|
+
cattr_accessor :enable_extended_logging
|
643
|
+
|
644
|
+
# Enable REALLY extended query logging
|
645
|
+
cattr_accessor :enable_really_extended_logging
|
646
|
+
end
|