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.
Files changed (34) hide show
  1. data/MIT-LICENSE +23 -0
  2. data/README.rdoc +32 -0
  3. data/Rakefile +42 -0
  4. data/VERSION +1 -0
  5. data/lib/activerecord-postgresql-extensions.rb +30 -0
  6. data/lib/postgresql_extensions/foreign_key_associations.rb +367 -0
  7. data/lib/postgresql_extensions/postgresql_adapter_extensions.rb +646 -0
  8. data/lib/postgresql_extensions/postgresql_constraints.rb +579 -0
  9. data/lib/postgresql_extensions/postgresql_functions.rb +345 -0
  10. data/lib/postgresql_extensions/postgresql_geometry.rb +212 -0
  11. data/lib/postgresql_extensions/postgresql_indexes.rb +219 -0
  12. data/lib/postgresql_extensions/postgresql_languages.rb +80 -0
  13. data/lib/postgresql_extensions/postgresql_permissions.rb +322 -0
  14. data/lib/postgresql_extensions/postgresql_rules.rb +112 -0
  15. data/lib/postgresql_extensions/postgresql_schemas.rb +49 -0
  16. data/lib/postgresql_extensions/postgresql_sequences.rb +222 -0
  17. data/lib/postgresql_extensions/postgresql_tables.rb +308 -0
  18. data/lib/postgresql_extensions/postgresql_triggers.rb +131 -0
  19. data/lib/postgresql_extensions/postgresql_types.rb +17 -0
  20. data/lib/postgresql_extensions/postgresql_views.rb +103 -0
  21. data/postgresql-extensions.gemspec +50 -0
  22. data/test/adapter_test.rb +45 -0
  23. data/test/constraints_test.rb +98 -0
  24. data/test/functions_test.rb +112 -0
  25. data/test/geometry_test.rb +43 -0
  26. data/test/index_test.rb +68 -0
  27. data/test/languages_test.rb +48 -0
  28. data/test/permissions_test.rb +163 -0
  29. data/test/rules_test.rb +32 -0
  30. data/test/schemas_test.rb +43 -0
  31. data/test/sequences_test.rb +90 -0
  32. data/test/tables_test.rb +49 -0
  33. data/test/test_helper.rb +64 -0
  34. 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