activerecord-postgresql-extensions 0.0.7
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.
- 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
|