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,219 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
class InvalidIndexColumnDefinition < ActiveRecordError #:nodoc:
|
4
|
+
def initialize(msg, column)
|
5
|
+
super("#{msg} - #{column.inspect}")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidIndexFillFactory < ActiveRecordError #:nodoc:
|
10
|
+
def initialize(fill_factor)
|
11
|
+
super("Invalid index fill factor - #{fill_factor}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ConnectionAdapters
|
16
|
+
class PostgreSQLAdapter < AbstractAdapter
|
17
|
+
# Creates an index. This method is an alternative to the standard
|
18
|
+
# ActiveRecord add_index method and includes PostgreSQL-specific
|
19
|
+
# options.
|
20
|
+
#
|
21
|
+
# === Differences to add_index
|
22
|
+
#
|
23
|
+
# * With the standard ActiveRecord add_index method, ActiveRecord
|
24
|
+
# will automatically generate an index name. With create_index,
|
25
|
+
# you need to supply a name yourself. This is due to the fact
|
26
|
+
# that PostgreSQL's indexes can include things like expressions
|
27
|
+
# and special index types, so we're not going to try and parse
|
28
|
+
# your expressions for you. You'll have to supply your own
|
29
|
+
# index name.
|
30
|
+
# * Several PostgreSQL-specific options are included. See below
|
31
|
+
# for details.
|
32
|
+
# * The +columns+ argument supports Hashes to allow for
|
33
|
+
# expressions. See examples below.
|
34
|
+
#
|
35
|
+
# ==== Options
|
36
|
+
#
|
37
|
+
# * <tt>:unique</tt> - adds UNIQUE to the index definition.
|
38
|
+
# * <tt>:concurrently</tt> - adds CONCURRENTLY to the index
|
39
|
+
# definition. See the PostgreSQL documentation for a discussion
|
40
|
+
# on concurrently reindexing tables.
|
41
|
+
# * <tt>:using</tt> - the indexing method to use. PostgreSQL
|
42
|
+
# supports serveral indexing methods out of the box, the default
|
43
|
+
# being a binary tree method. For certain column types,
|
44
|
+
# alternative indexing methods produce better indexing results.
|
45
|
+
# In some cases, a btree index would be pointless given certain
|
46
|
+
# datatypes and queries. For instance, PostGIS' geometry
|
47
|
+
# datatypes should generally be indexed with GiST indexes, while
|
48
|
+
# the tsvector full text search datatype should generally be
|
49
|
+
# indexed with a GiN index. See the PostgreSQL documentation
|
50
|
+
# for details.
|
51
|
+
# * <tt>:fill_factor</tt> - sets the FILLFACTOR value for the
|
52
|
+
# index. This option tells PostgreSQL how to pack its index
|
53
|
+
# pages on disk. As indexes grow, they begin to get spread out
|
54
|
+
# over multiple disk pages, thus reducing efficiency. This option
|
55
|
+
# allows you to control some of that behaviour. The default
|
56
|
+
# value for btree indexes is 90, and any value from 10 to
|
57
|
+
# 100 can be used. See the PostgreSQL documentation for more
|
58
|
+
# details.
|
59
|
+
# * <tt>:tablespace</tt> - sets the tablespace for the index.
|
60
|
+
# * <tt>:conditions</tt> - adds an optional WHERE clause to the
|
61
|
+
# index. (You can alternatively use the option <tt>:where</tt>
|
62
|
+
# instead.)
|
63
|
+
#
|
64
|
+
# ==== Column Options
|
65
|
+
#
|
66
|
+
# You can specify a handful of options on each index
|
67
|
+
# column/expression definition by supplying a Hash for the
|
68
|
+
# definition rather than a Symbol/String.
|
69
|
+
#
|
70
|
+
# * <tt>:column</tt> or <tt>:expression</tt> - you can specify
|
71
|
+
# either <tt>:column</tt> or <tt>:expression</tt> in the column
|
72
|
+
# definition, but not both. When using <tt>:column</tt>, the
|
73
|
+
# column name is quoted properly using PostgreSQL's quoting
|
74
|
+
# rules, while using <tt>:expression</tt> leaves you on your
|
75
|
+
# own.
|
76
|
+
# * <tt>:opclass</tt> - an "opclass" (a.k.a. "operator class")
|
77
|
+
# provides hints to the PostgreSQL query planner that allow it
|
78
|
+
# to more effectively take advantage of indexes. An opclass
|
79
|
+
# effectively tells the planner what operators can be used by an
|
80
|
+
# index when searching a column or expression. When creating
|
81
|
+
# an index, PostgreSQL generally uses an opclass equivalent to
|
82
|
+
# the column datatype (i.e. +int4_ops+ for an integer column).
|
83
|
+
# You can override this behaviour when necessary. For instance,
|
84
|
+
# in queries involving the LIKE operator on a text column,
|
85
|
+
# PostgreSQL will usually only take advantage of an index if the
|
86
|
+
# database has been created in the C locale. You can override
|
87
|
+
# this behaviour by forcing the index to be created using the
|
88
|
+
# +text_pattern_ops+ opclass
|
89
|
+
# * <tt>:order</tt> - the order to index the column in. This can
|
90
|
+
# be one of <tt>:asc</tt> or <tt>:desc</tt>.
|
91
|
+
# * <tt>:nulls</tt> - specify whether NULL values should be placed
|
92
|
+
# <tt>:first</tt> or <tt>:last</tt> in the index.
|
93
|
+
#
|
94
|
+
# ==== Examples
|
95
|
+
#
|
96
|
+
# ### ruby
|
97
|
+
# # using multiple columns
|
98
|
+
# create_index('this_is_my_index', :foo, [ :id, :ref_id ], :using => :gin)
|
99
|
+
# # => CREATE INDEX "this_is_my_index" ON "foo"("id", "ref_id");
|
100
|
+
#
|
101
|
+
# # using expressions
|
102
|
+
# create_index('this_is_another_idx', :foo, { :expression => 'COALESCE(ref_id, 0)' })
|
103
|
+
# # => CREATE INDEX "this_is_another_idx" ON "foo"((COALESCE(ref_id, 0)));
|
104
|
+
#
|
105
|
+
# # additional options
|
106
|
+
# create_index('search_idx', :foo, :tsvector, :using => :gin)
|
107
|
+
# # => CREATE INDEX "search_idx" ON "foo" USING "gin"("tsvector");
|
108
|
+
def create_index(name, table, columns, options = {})
|
109
|
+
execute PostgreSQLIndexDefinition.new(self, name, table, columns, options).to_s
|
110
|
+
end
|
111
|
+
|
112
|
+
# PostgreSQL-specific version of the standard ActiveRecord
|
113
|
+
# remove_index method.
|
114
|
+
#
|
115
|
+
# Unlike remove_index, you'll have to specify an actual index
|
116
|
+
# name with drop_index. See create_index for the particulars on
|
117
|
+
# why.
|
118
|
+
#
|
119
|
+
# ==== Options
|
120
|
+
#
|
121
|
+
# * <tt>:if_exists</tt> - adds IF EXISTS.
|
122
|
+
# * <tt>:cascade</tt> - adds CASCADE.
|
123
|
+
def drop_index(name, options = {})
|
124
|
+
sql = 'DROP INDEX '
|
125
|
+
sql << 'IF EXISTS ' if options[:if_exists]
|
126
|
+
sql << Array(name).collect { |i| quote_generic(i) }.join(', ')
|
127
|
+
sql << ' CASCADE' if options[:cascade]
|
128
|
+
execute sql
|
129
|
+
end
|
130
|
+
|
131
|
+
# Renames an index.
|
132
|
+
def rename_index(name, new_name, options = {})
|
133
|
+
execute "ALTER INDEX #{quote_generic(name)} RENAME TO #{quote_generic(new_name)}"
|
134
|
+
end
|
135
|
+
|
136
|
+
# Changes an index's tablespace.
|
137
|
+
def alter_index_tablespace(name, tablespace, options = {})
|
138
|
+
execute "ALTER INDEX #{quote_generic(name)} SET TABLESPACE #{quote_tablespace(tablespace)}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Creates a PostgreSQL index definition. This class isn't really meant
|
143
|
+
# to be used directly. Instead, see PostgreSQLAdapter#create_index
|
144
|
+
# for usage.
|
145
|
+
class PostgreSQLIndexDefinition
|
146
|
+
attr_accessor :base, :name, :table, :columns, :options
|
147
|
+
|
148
|
+
def initialize(base, name, table, columns, options = {}) #:nodoc:
|
149
|
+
assert_valid_columns(columns)
|
150
|
+
assert_valid_fill_factor(options[:fill_factor])
|
151
|
+
|
152
|
+
@base, @name, @table, @columns, @options = base, name, table, columns, options
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_sql #:nodoc:
|
156
|
+
sql = 'CREATE '
|
157
|
+
sql << 'UNIQUE ' if options[:unique]
|
158
|
+
sql << 'INDEX '
|
159
|
+
sql << 'CONCURRENTLY ' if options[:concurrently]
|
160
|
+
sql << "#{base.quote_generic(name)} ON #{base.quote_table_name(table)}"
|
161
|
+
sql << " USING #{base.quote_generic(options[:using])}" if options[:using]
|
162
|
+
sql << '('
|
163
|
+
sql << [ columns ].flatten.collect do |column|
|
164
|
+
column_def = String.new
|
165
|
+
if column.is_a?(Hash)
|
166
|
+
column_def << if column[:column]
|
167
|
+
"#{base.quote_column_name(column[:column])}"
|
168
|
+
else
|
169
|
+
"(#{column[:expression]})"
|
170
|
+
end
|
171
|
+
|
172
|
+
column_def << " #{base.quote_generic(column[:opclass])}" if column[:opclass]
|
173
|
+
column_def << " #{column[:order].to_s.upcase}" if column[:order]
|
174
|
+
column_def << " NULLS #{column[:nulls].to_s.upcase}" if column[:nulls]
|
175
|
+
column_def
|
176
|
+
else
|
177
|
+
base.quote_column_name(column.to_s)
|
178
|
+
end
|
179
|
+
end.join(', ')
|
180
|
+
sql << ')'
|
181
|
+
sql << " WITH (FILLFACTOR = #{options[:fill_factor].to_i})" if options[:fill_factor]
|
182
|
+
sql << " TABLESPACE #{base.quote_tablespace(options[:tablespace])}" if options[:tablespace]
|
183
|
+
sql << " WHERE #{options[:conditions] || options[:where]}" if options[:conditions] || options[:where]
|
184
|
+
sql
|
185
|
+
end
|
186
|
+
alias :to_s :to_sql
|
187
|
+
|
188
|
+
private
|
189
|
+
def assert_valid_columns(columns) #:nodoc:
|
190
|
+
Array(columns).each do |column|
|
191
|
+
if column.is_a?(Hash)
|
192
|
+
if column.has_key?(:column) && column.has_key?(:expression)
|
193
|
+
raise ActiveRecord::InvalidIndexColumnDefinition.new("You can't specify both :column and :expression in a column definition", column)
|
194
|
+
elsif !(column.has_key?(:column) || column.has_key?(:expression))
|
195
|
+
raise ActiveRecord::InvalidIndexColumnDefinition.new("You must specify either :column or :expression in a column definition", column)
|
196
|
+
end
|
197
|
+
|
198
|
+
if ![ 'asc', 'desc' ].include?(column[:order].to_s.downcase)
|
199
|
+
raise ActiveRecord::InvalidIndexColumnDefinition.new("Invalid :order value", column)
|
200
|
+
end if column[:order]
|
201
|
+
|
202
|
+
if ![ 'first', 'last' ].include?(column[:nulls].to_s.downcase)
|
203
|
+
raise ActiveRecord::InvalidIndexColumnDefinition.new("Invalid :nulls value", column)
|
204
|
+
end if column[:nulls]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def assert_valid_fill_factor(fill_factor) #:nodoc:
|
210
|
+
if !fill_factor.nil?
|
211
|
+
ff = fill_factor.to_i
|
212
|
+
if ff < 0 || ff > 100
|
213
|
+
raise ActiveRecord::InvalidIndexFillFactor.new(fill_factor)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionAdapters
|
6
|
+
class PostgreSQLAdapter < AbstractAdapter
|
7
|
+
# Creates a PostgreSQL procedural language.
|
8
|
+
#
|
9
|
+
# Note that you can grant privileges on languages using the
|
10
|
+
# grant_language_privileges method and revoke them using
|
11
|
+
# revoke_language_privileges.
|
12
|
+
#
|
13
|
+
# ==== Options
|
14
|
+
#
|
15
|
+
# * <tt>:trusted</tt> - adds a TRUSTED clause. Trusted languages
|
16
|
+
# in PostgreSQL are given a couple of extra abilities that
|
17
|
+
# their untrusted counterparts lust for, such as the ability
|
18
|
+
# to touch the server's local file system. This can be rather
|
19
|
+
# important if you need to access external libraries in your
|
20
|
+
# language's functions, such as importing CPAN libraries in
|
21
|
+
# plperl. The default is untrusted.
|
22
|
+
# * <tt>:handler</tt> - this option is used to point the server
|
23
|
+
# in the direction of the procedural language's hooks and such.
|
24
|
+
# It's generally not required now unless you for some reason
|
25
|
+
# need to access a langauge that isn't currently held in the
|
26
|
+
# <tt>pg_pltemplate</tt> system table.
|
27
|
+
# * <tt>:validator</tt> - this option provides a previously
|
28
|
+
# declared test function that will be used to test the
|
29
|
+
# functionality of the newly-installed procedural language.
|
30
|
+
#
|
31
|
+
# You don't often see people using the <tt>:handler</tt> and
|
32
|
+
# <tt>:validator</tt> options, and they're really just kind of
|
33
|
+
# here for the sake of completeness.
|
34
|
+
def create_language language, options = {}
|
35
|
+
sql = 'CREATE '
|
36
|
+
sql << 'TRUSTED ' if options[:trusted]
|
37
|
+
sql << "PROCEDURAL LANGUAGE #{quote_language(language)}"
|
38
|
+
sql << " HANDLER #{quote_language(options[:call_handler])}" if options[:call_handler]
|
39
|
+
sql << " VALIDATOR #{options[:validator]}" if options[:validator]
|
40
|
+
execute sql
|
41
|
+
end
|
42
|
+
|
43
|
+
# Drops a language.
|
44
|
+
#
|
45
|
+
# ==== Options
|
46
|
+
#
|
47
|
+
# * <tt>:if_exists</tt> - adds IF EXISTS.
|
48
|
+
# * <tt>:cascade</tt> - adds CASCADE.
|
49
|
+
def drop_language language, options = {}
|
50
|
+
sql = 'DROP PROCEDURAL LANGUAGE '
|
51
|
+
sql << 'IF EXISTS ' if options[:if_exists]
|
52
|
+
sql << quote_language(language)
|
53
|
+
sql << ' CASCADE' if options[:cascade]
|
54
|
+
execute sql
|
55
|
+
end
|
56
|
+
|
57
|
+
# Renames a language.
|
58
|
+
def alter_language_name old_language, new_language, options = {}
|
59
|
+
execute "ALTER PROCEDURAL LANGUAGE #{quote_language(old_language)} RENAME TO #{quote_language(new_language)}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Changes a language's owner.
|
63
|
+
def alter_language_owner language, role, options = {}
|
64
|
+
execute "ALTER PROCEDURAL LANGUAGE #{quote_language(language)} OWNER TO #{quote_language(role)}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns an Array of available languages.
|
68
|
+
def languages(name = nil)
|
69
|
+
query(<<-SQL, name).map { |row| row[0] }
|
70
|
+
SELECT lanname
|
71
|
+
FROM pg_language;
|
72
|
+
SQL
|
73
|
+
end
|
74
|
+
|
75
|
+
def language_exists?(name)
|
76
|
+
languages.include?(name.to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
class InvalidPrivilegeTypes < ActiveRecordError #:nodoc:
|
6
|
+
def initialize(type, privileges)
|
7
|
+
super("Invalid privileges for #{type} - #{privileges.inspect}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ConnectionAdapters
|
12
|
+
class PostgreSQLAdapter < AbstractAdapter
|
13
|
+
# Grants privileges on tables. You can specify multiple tables,
|
14
|
+
# roles and privileges all at once using Arrays for each of the
|
15
|
+
# desired parameters. See PostgreSQLGrantPrivilege for
|
16
|
+
# usage.
|
17
|
+
def grant_table_privileges(tables, privileges, roles, options = {})
|
18
|
+
execute PostgreSQLGrantPrivilege.new(self, :table, tables, privileges, roles, options).to_sql
|
19
|
+
end
|
20
|
+
|
21
|
+
# Grants privileges on sequences. You can specify multiple
|
22
|
+
# sequences, roles and privileges all at once using Arrays for each
|
23
|
+
# of the desired parameters. See PostgreSQLGrantPrivilege for
|
24
|
+
# usage.
|
25
|
+
def grant_sequence_privileges(sequences, privileges, roles, options = {})
|
26
|
+
execute PostgreSQLGrantPrivilege.new(self, :sequence, sequences, privileges, roles, options).to_sql
|
27
|
+
end
|
28
|
+
|
29
|
+
# Grants privileges on databases. You can specify multiple
|
30
|
+
# databases, roles and privileges all at once using Arrays for
|
31
|
+
# each of the desired parameters. See PostgreSQLGrantPrivilege for
|
32
|
+
# usage.
|
33
|
+
def grant_database_privileges(databases, privileges, roles, options = {})
|
34
|
+
execute PostgreSQLGrantPrivilege.new(self, :database, databases, privileges, roles, options).to_sql
|
35
|
+
end
|
36
|
+
|
37
|
+
# Grants privileges on functions. You can specify multiple
|
38
|
+
# functions, roles and privileges all at once using Arrays for
|
39
|
+
# each of the desired parameters. See PostgreSQLGrantPrivilege for
|
40
|
+
# usage.
|
41
|
+
def grant_function_privileges(function_prototypes, privileges, roles, options = {})
|
42
|
+
execute PostgreSQLGrantPrivilege.new(self, :function, function_prototypes, privileges, roles, options, :quote_objects => false).to_sql
|
43
|
+
end
|
44
|
+
|
45
|
+
# Grants privileges on procedural languages. You can specify
|
46
|
+
# multiple languages, roles and privileges all at once using
|
47
|
+
# Arrays for each of the desired parameters. See
|
48
|
+
# PostgreSQLGrantPrivilege for usage.
|
49
|
+
def grant_language_privileges(languages, privileges, roles, options = {})
|
50
|
+
execute PostgreSQLGrantPrivilege.new(self, :language, languages, privileges, roles, options).to_sql
|
51
|
+
end
|
52
|
+
|
53
|
+
# Grants privileges on schemas. You can specify multiple schemas,
|
54
|
+
# roles and privileges all at once using Arrays for each of the
|
55
|
+
# desired parameters. See PostgreSQLGrantPrivilege for
|
56
|
+
# usage.
|
57
|
+
def grant_schema_privileges(schemas, privileges, roles, options = {})
|
58
|
+
execute PostgreSQLGrantPrivilege.new(self, :schema, schemas, privileges, roles, options, :ignore_schema => true).to_sql
|
59
|
+
end
|
60
|
+
|
61
|
+
# Grants privileges on tablespaces. You can specify multiple
|
62
|
+
# tablespaces, roles and privileges all at once using Arrays for
|
63
|
+
# each of the desired parameters. See PostgreSQLGrantPrivilege for
|
64
|
+
# usage.
|
65
|
+
def grant_tablespace_privileges(tablespaces, privileges, roles, options = {})
|
66
|
+
execute PostgreSQLGrantPrivilege.new(self, :tablespace, tablespaces, privileges, roles, options).to_sql
|
67
|
+
end
|
68
|
+
|
69
|
+
# Grants role membership to another role. You can specify multiple
|
70
|
+
# roles for both the roles and the role_names parameters using
|
71
|
+
# Arrays.
|
72
|
+
#
|
73
|
+
# ==== Options
|
74
|
+
#
|
75
|
+
# * <tt>:with_admin_option</tt> - adds the WITH ADMIN OPTION
|
76
|
+
# clause to the command.
|
77
|
+
def grant_role_membership(roles, role_names, options = {})
|
78
|
+
sql = "GRANT "
|
79
|
+
sql << Array(roles).collect { |r| quote_role(r) }.join(', ')
|
80
|
+
sql << ' TO '
|
81
|
+
sql << Array(role_names).collect { |r| quote_role(r) }.join(', ')
|
82
|
+
sql << ' WITH ADMIN OPTION' if options[:with_admin_option]
|
83
|
+
execute sql
|
84
|
+
end
|
85
|
+
|
86
|
+
# Revokes table privileges. You can specify multiple tables,
|
87
|
+
# roles and privileges all at once using Arrays for each of the
|
88
|
+
# desired parameters. See PostgreSQLRevokePrivilege for
|
89
|
+
# usage.
|
90
|
+
def revoke_table_privileges(tables, privileges, roles, options = {})
|
91
|
+
execute PostgreSQLRevokePrivilege.new(self, :table, tables, privileges, roles, options).to_sql
|
92
|
+
end
|
93
|
+
|
94
|
+
# Revokes sequence privileges. You can specify multiple sequences,
|
95
|
+
# roles and privileges all at once using Arrays for each of the
|
96
|
+
# desired parameters. See PostgreSQLRevokePrivilege for
|
97
|
+
# usage.
|
98
|
+
def revoke_sequence_privileges(sequences, privileges, roles, options = {})
|
99
|
+
execute PostgreSQLRevokePrivilege.new(self, :sequence, sequences, privileges, roles, options).to_sql
|
100
|
+
end
|
101
|
+
|
102
|
+
# Revokes database privileges. You can specify multiple databases,
|
103
|
+
# roles and privileges all at once using Arrays for each of the
|
104
|
+
# desired parameters. See PostgreSQLRevokePrivilege for
|
105
|
+
# usage.
|
106
|
+
def revoke_database_privileges(databases, privileges, roles, options = {})
|
107
|
+
execute PostgreSQLRevokePrivilege.new(self, :database, databases, privileges, roles, options).to_sql
|
108
|
+
end
|
109
|
+
|
110
|
+
# Revokes function privileges. You can specify multiple functions,
|
111
|
+
# roles and privileges all at once using Arrays for each of the
|
112
|
+
# desired parameters. See PostgreSQLRevokePrivilege for
|
113
|
+
# usage.
|
114
|
+
def revoke_function_privileges(function_prototypes, privileges, roles, options = {})
|
115
|
+
execute PostgreSQLRevokePrivilege.new(self, :function, function_prototypes, privileges, roles, options, :quote_objects => false).to_sql
|
116
|
+
end
|
117
|
+
|
118
|
+
# Revokes language privileges. You can specify multiple languages,
|
119
|
+
# roles and privileges all at once using Arrays for each of the
|
120
|
+
# desired parameters. See PostgreSQLRevokePrivilege for
|
121
|
+
# usage.
|
122
|
+
def revoke_language_privileges(languages, privileges, roles, options = {})
|
123
|
+
execute PostgreSQLRevokePrivilege.new(self, :language, languages, privileges, roles, options).to_sql
|
124
|
+
end
|
125
|
+
|
126
|
+
# Revokes schema privileges. You can specify multiple schemas,
|
127
|
+
# roles and privileges all at once using Arrays for each of the
|
128
|
+
# desired parameters. See PostgreSQLRevokePrivilege for
|
129
|
+
# usage.
|
130
|
+
def revoke_schema_privileges(schemas, privileges, roles, options = {})
|
131
|
+
execute PostgreSQLRevokePrivilege.new(self, :schema, schemas, privileges, roles, options, :ignore_schema => true).to_sql
|
132
|
+
end
|
133
|
+
|
134
|
+
# Revokes tablespace privileges. You can specify multiple
|
135
|
+
# tablespaces, roles and privileges all at once using Arrays for
|
136
|
+
# each of the desired parameters. See PostgreSQLRevokePrivilege for
|
137
|
+
# usage.
|
138
|
+
def revoke_tablespace_privileges(tablespaces, privileges, roles, options = {})
|
139
|
+
execute PostgreSQLRevokePrivilege.new(self, :tablespace, tablespaces, privileges, roles, options).to_sql
|
140
|
+
end
|
141
|
+
|
142
|
+
# Revokes role membership. You can specify multiple
|
143
|
+
# roles for both the roles and the role_names parameters using
|
144
|
+
# Arrays.
|
145
|
+
#
|
146
|
+
# ==== Options
|
147
|
+
#
|
148
|
+
# * <tt>:with_admin_option</tt> - adds the WITH ADMIN OPTION
|
149
|
+
# clause to the command.
|
150
|
+
# * <tt>:cascade</tt> - adds the CASCADE option to the command.
|
151
|
+
def revoke_role_membership(roles, role_names, options = {})
|
152
|
+
sql = 'REVOKE '
|
153
|
+
sql << 'ADMIN_OPTION_FOR ' if options[:admin_option_for]
|
154
|
+
sql << Array(roles).collect { |r| quote_role(r) }.join(', ')
|
155
|
+
sql << ' FROM '
|
156
|
+
sql << Array(role_names).collect { |r| quote_role(r) }.join(', ')
|
157
|
+
sql << ' CASCADE' if options[:cascade]
|
158
|
+
execute sql
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# This is a base class for PostgreSQLGrantPrivilege and
|
163
|
+
# PostgreSQLRevokePrivilege and is not meant to be used directly.
|
164
|
+
class PostgreSQLPrivilege
|
165
|
+
attr_accessor :base, :type, :objects, :privileges, :roles, :options, :query_options
|
166
|
+
|
167
|
+
def initialize(base, type, objects, privileges, roles, options = {}, query_options = {}) #:nodoc:
|
168
|
+
assert_valid_privileges type, privileges
|
169
|
+
@base, @type, @objects, @privileges, @roles, @options, @query_options =
|
170
|
+
base, type, objects, privileges, roles, options, query_options
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
PRIVILEGE_TYPES = {
|
175
|
+
:table => [ 'select', 'insert', 'update', 'delete', 'references', 'trigger', 'all' ],
|
176
|
+
:sequence => [ 'usage', 'select', 'update', 'all' ],
|
177
|
+
:database => [ 'create', 'connect', 'temporary', 'all' ],
|
178
|
+
:function => [ 'execute', 'all' ],
|
179
|
+
:language => [ 'usage', 'all' ],
|
180
|
+
:schema => [ 'create', 'usage', 'all' ],
|
181
|
+
:tablespace => [ 'create', 'all' ]
|
182
|
+
}.freeze
|
183
|
+
|
184
|
+
def assert_valid_privileges type, privileges
|
185
|
+
check_privileges = Array(privileges).collect(&:to_s) - PRIVILEGE_TYPES[type]
|
186
|
+
if !check_privileges.empty?
|
187
|
+
raise ActiveRecord::InvalidPrivilegeTypes.new(type, check_privileges)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Creates queries for granting PostgreSQL role privileges.
|
193
|
+
#
|
194
|
+
# This class is meant to be used by the grant_*_privileges methods
|
195
|
+
# in the PostgreSQLAdapter. Different database objects have
|
196
|
+
# different privileges that you can apply to a role. See the
|
197
|
+
# PostgreSQLPrivilege PRIVILEGE_TYPES constant for usage. Generally
|
198
|
+
# speaking, you usually don't want to use this class directly, but
|
199
|
+
# rather the aforementioned wrapped methods.
|
200
|
+
#
|
201
|
+
# When using the grant_*_privileges methods, you can specify multiple
|
202
|
+
# permissions, objects and roles by using Arrays for the appropriate
|
203
|
+
# argument.
|
204
|
+
#
|
205
|
+
# ==== Examples
|
206
|
+
#
|
207
|
+
# ### ruby
|
208
|
+
# grant_table_privileges([ :table1, :table2 ], :select, :joe)
|
209
|
+
# # => GRANT SELECT ON TABLE "table1", "table2" TO "joe"
|
210
|
+
#
|
211
|
+
# grant_sequence_privileges(:my_seq, [ :select, :update ], :public)
|
212
|
+
# # => GRANT SELECT, UPDATE ON SEQUENCE "my_seq" TO PUBLIC
|
213
|
+
#
|
214
|
+
# You can specify the <tt>:with_grant_option</tt> in any of the
|
215
|
+
# grant_*_privilege methods to add a WITH GRANT OPTION clause to
|
216
|
+
# the command.
|
217
|
+
class PostgreSQLGrantPrivilege < PostgreSQLPrivilege
|
218
|
+
def to_sql #:nodoc:
|
219
|
+
my_query_options = {
|
220
|
+
:quote_objects => true
|
221
|
+
}.merge query_options
|
222
|
+
|
223
|
+
sql = "GRANT #{Array(privileges).collect(&:to_s).collect(&:upcase).join(', ')} ON #{type.to_s.upcase} "
|
224
|
+
|
225
|
+
sql << Array(objects).collect do |t|
|
226
|
+
if my_query_options[:quote_objects]
|
227
|
+
if my_query_options[:ignore_schema]
|
228
|
+
base.quote_generic_ignore_schema(t)
|
229
|
+
else
|
230
|
+
base.quote_table_name(t)
|
231
|
+
end
|
232
|
+
else
|
233
|
+
t
|
234
|
+
end
|
235
|
+
end.join(', ')
|
236
|
+
|
237
|
+
sql << ' TO ' << Array(roles).collect do |r|
|
238
|
+
r = r.to_s
|
239
|
+
if r.upcase == 'PUBLIC'
|
240
|
+
'PUBLIC'
|
241
|
+
else
|
242
|
+
base.quote_role r
|
243
|
+
end
|
244
|
+
end.join(', ')
|
245
|
+
|
246
|
+
sql << ' WITH GRANT OPTION' if options[:with_grant_option]
|
247
|
+
sql
|
248
|
+
end
|
249
|
+
alias :to_s :to_sql
|
250
|
+
end
|
251
|
+
|
252
|
+
# Creates queries for revoking PostgreSQL role privileges.
|
253
|
+
#
|
254
|
+
# This class is meant to be used by the revoke_*_privileges methods
|
255
|
+
# in the PostgreSQLAdapter. Different database objects have
|
256
|
+
# different privileges that you can apply to a role. See the
|
257
|
+
# PostgreSQLPrivilege PRIVILEGE_TYPES constant for usage. Generally
|
258
|
+
# speaking, you usually don't want to use this class directly, but
|
259
|
+
# rather the aforementioned wrapped methods.
|
260
|
+
#
|
261
|
+
# When using the revoke_*_privileges methods, you can specify multiple
|
262
|
+
# permissions, objects and roles by using Arrays for the appropriate
|
263
|
+
# argument.
|
264
|
+
#
|
265
|
+
# ==== Examples
|
266
|
+
#
|
267
|
+
# ### ruby
|
268
|
+
# revoke_table_privileges([ :table1, :table2 ], :select, :joe)
|
269
|
+
# # => REVOKE SELECT ON TABLE "table1", "table2" FROM "joe"
|
270
|
+
#
|
271
|
+
# revoke_sequence_privileges(:my_seq, [ :select, :update ], :public)
|
272
|
+
# # => REVOKE SELECT, UPDATE ON SEQUENCE "my_seq" FROM PUBLIC
|
273
|
+
#
|
274
|
+
# You can specify the <tt>:grant_option_for</tt> in any of the
|
275
|
+
# revoke_*_privilege methods to add a GRANT OPTION FOR clause to
|
276
|
+
# the command. Note that this option removes the role's ability to
|
277
|
+
# grant the privilege to other roles, but does not remove the
|
278
|
+
# privilege itself.
|
279
|
+
#
|
280
|
+
# You can also specify the <tt>:cascade</tt> option to cause the
|
281
|
+
# privilege revocation to cascade down to depedent privileges.
|
282
|
+
#
|
283
|
+
# The cascading stuff is pretty crazy, so you may want to consult the
|
284
|
+
# PostgreSQL docs on the subject.
|
285
|
+
class PostgreSQLRevokePrivilege < PostgreSQLPrivilege
|
286
|
+
def to_sql #:nodoc:
|
287
|
+
my_query_options = {
|
288
|
+
:quote_objects => true
|
289
|
+
}.merge query_options
|
290
|
+
|
291
|
+
sql = 'REVOKE '
|
292
|
+
sql << 'GRANT OPTION FOR ' if options[:grant_option_for]
|
293
|
+
sql << "#{Array(privileges).collect(&:to_s).collect(&:upcase).join(', ')} ON #{type.to_s.upcase} "
|
294
|
+
|
295
|
+
sql << Array(objects).collect do |t|
|
296
|
+
if my_query_options[:quote_objects]
|
297
|
+
if my_query_options[:ignore_schema]
|
298
|
+
base.quote_generic_ignore_schema(t)
|
299
|
+
else
|
300
|
+
base.quote_table_name(t)
|
301
|
+
end
|
302
|
+
else
|
303
|
+
t
|
304
|
+
end
|
305
|
+
end.join(', ')
|
306
|
+
|
307
|
+
sql << ' FROM ' << Array(roles).collect do |r|
|
308
|
+
r = r.to_s
|
309
|
+
if r.upcase == 'PUBLIC'
|
310
|
+
'PUBLIC'
|
311
|
+
else
|
312
|
+
base.quote_role r
|
313
|
+
end
|
314
|
+
end.join(', ')
|
315
|
+
|
316
|
+
sql << ' CASCADE' if options[:cascade]
|
317
|
+
sql
|
318
|
+
end
|
319
|
+
alias :to_s :to_sql
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|