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,112 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ class InvalidRuleEvent < ActiveRecordError #:nodoc:
6
+ def initialize(event)
7
+ super("Invalid rule event - #{event}")
8
+ end
9
+ end
10
+
11
+ class InvalidRuleAction < ActiveRecordError #:nodoc:
12
+ def initialize(action)
13
+ super("Invalid rule action - #{action}")
14
+ end
15
+ end
16
+
17
+ module ConnectionAdapters
18
+ class PostgreSQLAdapter < AbstractAdapter
19
+ # Creates a PostgreSQL rule.
20
+ #
21
+ # +event+ can be one of <tt>:select</tt>, <tt>:insert</tt>,
22
+ # <tt>:update</tt> or <tt>:delete</tt>.
23
+ #
24
+ # +action+ can be one of <tt>:instead</tt> or <tt>:also</tt>.
25
+ #
26
+ # +commands+ is the actual query to rewrite to. commands can
27
+ # actually be "+NOTHING+", a String representing the commands
28
+ # or an Array of Strings if you have multiple commands you want to
29
+ # fire.
30
+ #
31
+ # ==== Options
32
+ #
33
+ # * <tt>:force</tt> - add an <tt>OR REPLACE</tt> clause to the
34
+ # command.
35
+ # * <tt>:conditions</tt> - a <tt>WHERE</tt> clause to limit the
36
+ # rule.
37
+ #
38
+ # ==== Examples
39
+ #
40
+ # ### ruby
41
+ # create_rule(
42
+ # 'check_it_out_rule',
43
+ # :select,
44
+ # :child,
45
+ # :instead,
46
+ # 'select * from public.another', :conditions => 'id = 1'
47
+ # )
48
+ # # => CREATE RULE "check_it_out_rule" AS ON SELECT TO "child" WHERE id = 1 DO INSTEAD select * from public.another;
49
+ def create_rule(name, event, table, action, commands, options = {})
50
+ execute PostgreSQLRuleDefinition.new(self, name, event, table, action, commands, options).to_s
51
+ end
52
+
53
+ # Drops a PostgreSQL rule.
54
+ def drop_rule(name, table)
55
+ execute "DROP RULE #{quote_rule(name)} ON #{quote_table_name(table)}"
56
+ end
57
+ end
58
+
59
+ # Creates a PostgreSQL rule.
60
+ #
61
+ # The PostgreSQL rule system is basically a query-rewriter. You should
62
+ # take a look at the PostgreSQL documentation for more details, but the
63
+ # basic idea is that a rule can be set to fire on certain query events
64
+ # and will force the query to be rewritten before it is even sent to
65
+ # the query planner and executor.
66
+ #
67
+ # Generally speaking, you're probably going to want to stick to
68
+ # create_rule and drop_rule when working with rules.
69
+ class PostgreSQLRuleDefinition
70
+ attr_accessor :base, :name, :event, :table, :action, :commands, :options
71
+
72
+ def initialize(base, name, event, table, action, commands, options = {}) #:nodoc:
73
+ assert_valid_event(event)
74
+ assert_valid_action(action)
75
+ @base, @name, @event, @table, @action, @commands, @options =
76
+ base, name, event, table, action, commands, options
77
+ end
78
+
79
+ def to_sql #:nodoc:
80
+ sql = 'CREATE '
81
+ sql << ' OR REPLACE ' if options[:force]
82
+ sql << "RULE #{base.quote_rule(name)} AS ON #{event.to_s.upcase} TO #{base.quote_table_name(table)} "
83
+ sql << "WHERE #{options[:conditions]} " if options[:conditions]
84
+ sql << "DO #{action.to_s.upcase} "
85
+ sql << if commands.to_s.upcase == 'NOTHING'
86
+ 'NOTHING'
87
+ elsif commands.is_a?(Array)
88
+ '(' << commands.collect(&:to_s).join(';') << ')'
89
+ else
90
+ commands.to_s
91
+ end
92
+ end
93
+ alias :to_s :to_sql
94
+
95
+ private
96
+ EVENTS = [ 'select', 'insert', 'update', 'delete' ].freeze
97
+ ACTIONS = [ 'instead', 'also' ].freeze
98
+
99
+ def assert_valid_event(event) #:nodoc:
100
+ if !EVENTS.include? event.to_s
101
+ raise ActiveRecord::InvalidRuleEvent.new(event)
102
+ end
103
+ end
104
+
105
+ def assert_valid_action(action) #:nodoc:
106
+ if !ACTIONS.include? action.to_s
107
+ raise ActiveRecord::InvalidRuleAction.new(action)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,49 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ class PostgreSQLAdapter < AbstractAdapter
7
+ # Creates a new PostgreSQL schema.
8
+ #
9
+ # Note that you can grant privileges on schemas using the
10
+ # grant_schema_privileges method and revoke them using
11
+ # revoke_schema_privileges.
12
+ #
13
+ # ==== Options
14
+ #
15
+ # * <tt>:authorization</tt> - adds an AUTHORIZATION clause. This is
16
+ # used to set the owner of the schema. This can be changed with
17
+ # alter_schema_owner as necessary.
18
+ def create_schema schema, options = {}
19
+ sql = "CREATE SCHEMA #{quote_schema(schema)}"
20
+ sql << " AUTHORIZATION #{quote_role(options[:authorization])}" if options[:authorization]
21
+ execute sql
22
+ end
23
+
24
+ # Drops a schema.
25
+ #
26
+ # ==== Options
27
+ #
28
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
29
+ # * <tt>:cascade</tt> - adds CASCADE.
30
+ def drop_schema schemas, options = {}
31
+ sql = 'DROP SCHEMA '
32
+ sql << 'IF EXISTS ' if options[:if_exists]
33
+ sql << Array(schemas).collect { |s| quote_schema(s) }.join(', ')
34
+ sql << ' CASCADE' if options[:cascade]
35
+ execute sql
36
+ end
37
+
38
+ # Alter's a schema's name.
39
+ def alter_schema_name old_schema, new_schema
40
+ execute "ALTER SCHEMA #{quote_schema(old_schema)} RENAME TO #{quote_schema(new_schema)}"
41
+ end
42
+
43
+ # Changes a schema's owner.
44
+ def alter_schema_owner schema, role
45
+ execute "ALTER SCHEMA #{quote_schema(schema)} OWNER TO #{quote_schema(role)}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,222 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ class InvalidSequenceAction < ActiveRecordError #:nodoc:
6
+ def initialize(action)
7
+ super("Invalid sequence action - #{action}")
8
+ end
9
+ end
10
+
11
+ class InvalidSequenceOptions < ActiveRecordError #:nodoc:
12
+ end
13
+
14
+ module ConnectionAdapters
15
+ class PostgreSQLAdapter < AbstractAdapter
16
+ # Creates a sequence.
17
+ #
18
+ # Note that you can grant privileges on sequences using the
19
+ # grant_sequence_privileges method and revoke them using
20
+ # revoke_sequence_privileges.
21
+ #
22
+ # ==== Options
23
+ #
24
+ # * <tt>:temporary</tt> - creates a temporary sequence.
25
+ # * <tt>:incement</tt> - sets the sequence increment value.
26
+ # * <tt>:min_value</tt> - sets a minimum value for the sequence.
27
+ # If this value is <tt>nil</tt> or <tt>false</tt>, we'll go with
28
+ # "NO MINVALUE".
29
+ # * <tt>:max_value</tt> - same as <tt>:min_value</tt> but for
30
+ # maximum values. Mindblowing.
31
+ # * <tt>:start</tt> - the initial value of the sequence.
32
+ # * <tt>:cache</tt> - the number of future values to cache in
33
+ # the sequence. This is generally dangerous to mess with, so be
34
+ # sure to refer to the PostgreSQL documentation for reasons why.
35
+ # * <tt>:cycle</tt> - whether or not the sequence should cycle.
36
+ # * <tt>:owned_by</tt> - this refers to the table and column that
37
+ # a sequence is owned by. If that column/table were to be
38
+ # dropped in the future, for instance, the sequence would be
39
+ # automatically dropped with it. This option can be set using
40
+ # an Array (as in <tt>[ table, column ]</tt>) or a Hash
41
+ # (as in <tt>{ :table => 'foo', :column => 'bar' }</tt>).
42
+ #
43
+ # ==== Example
44
+ #
45
+ # ### ruby
46
+ # create_sequence(
47
+ # 'what_a_sequence_of_events',
48
+ # :increment => 2,
49
+ # :cache => 2,
50
+ # :min_value => nil,
51
+ # :max_value => 10,
52
+ # :owned_by => [ :foo, :id ]
53
+ # )
54
+ # # => CREATE SEQUENCE "what_a_sequence_of_events" INCREMENT BY 2
55
+ # # NO MINVALUE MAXVALUE 10 CACHE 2 OWNED BY "foo"."id";
56
+ def create_sequence(name, options = {})
57
+ execute PostgreSQLSequenceDefinition.new(self, :create, name, options).to_s
58
+ end
59
+
60
+ # Drops a sequence.
61
+ #
62
+ # ==== Options
63
+ #
64
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
65
+ # * <tt>:cascade</tt> - cascades the operation down to objects
66
+ # referring to the sequence.
67
+ def drop_sequence(name, options = {})
68
+ sql = 'DROP SEQUENCE '
69
+ sql << 'IF EXISTS ' if options[:if_exists]
70
+ sql << Array(name).collect { |s| quote_sequence(s) }.join(', ')
71
+ sql << ' CASCADE' if options[:cascade]
72
+ execute sql
73
+ end
74
+
75
+ # Renames the sequence.
76
+ def rename_sequence(name, rename, options = {})
77
+ execute "ALTER SEQUENCE #{quote_sequence(name)} RENAME TO #{quote_generic_ignore_schema(rename)}"
78
+ end
79
+
80
+ # Alters the sequence's schema.
81
+ def alter_sequence_schema(name, schema, options = {})
82
+ execute "ALTER SEQUENCE #{quote_sequence(name)} SET SCHEMA #{quote_schema(schema)}"
83
+ end
84
+
85
+ # Alters any of the various options for a sequence. See
86
+ # create_sequence for details on the available options. In addition
87
+ # to the options provided by create_sequence, there is also the
88
+ # <tt>:restart_with</tt> option, which resets the sequence to
89
+ # a new starting value and sets the <tt>is_called</tt> flag to
90
+ # false, which would be the equivalent of calling the PostgreSQL
91
+ # function <tt>setval</tt> with a false value in the third
92
+ # parameter.
93
+ def alter_sequence(name, options = {})
94
+ execute PostgreSQLSequenceDefinition.new(self, :alter, name, options).to_s
95
+ end
96
+
97
+ # Calls the <tt>setval</tt> function on the sequence.
98
+ #
99
+ # ==== Options
100
+ #
101
+ # * <tt>:is_called</tt> - the value to set in the third argument
102
+ # to the function call, which is, appropriately enough, the
103
+ # <tt>is_called</tt> argument. The default value is true.
104
+ def set_sequence_value(name, value, options = {})
105
+ options = {
106
+ :is_called => true
107
+ }.merge(options)
108
+
109
+ execute "SELECT setval(#{quote(name)}, #{value.to_i}, " <<
110
+ if options[:is_called]
111
+ 'true'
112
+ else
113
+ 'false'
114
+ end <<
115
+ ')'
116
+ end
117
+
118
+ # Returns an Array of available sequences.
119
+ def sequences(name = nil)
120
+ query(<<-SQL, name).map { |row| row[0] }
121
+ SELECT c.relname AS sequencename
122
+ FROM pg_class c
123
+ WHERE c.relkind = 'S'::"char";
124
+ SQL
125
+ end
126
+
127
+ def sequence_exists?(name)
128
+ sequences.include?(name.to_s)
129
+ end
130
+ end
131
+
132
+ # Class used to create or alter sequences. Generally you should be
133
+ # using PostgreSQLAdapter#create_sequence and its various sequence
134
+ # manipulation functions rather than using this class directly.
135
+ class PostgreSQLSequenceDefinition
136
+ attr_accessor :base, :action, :name, :options
137
+
138
+ def initialize(base, action, name, options = {}) #:nodoc:
139
+ assert_valid_owned_by(options)
140
+ assert_valid_action(action)
141
+
142
+ @base, @action, @name, @options = base, action, name, options
143
+ end
144
+
145
+ def to_sql #:nodoc:
146
+ sql = Array.new
147
+ if action == :create
148
+ sql << 'CREATE'
149
+ sql << 'TEMPORARY' if options[:temporary]
150
+ else
151
+ sql << 'ALTER'
152
+ end
153
+ sql << "SEQUENCE #{base.quote_sequence(name)}"
154
+ sql << "INCREMENT BY #{options[:increment].to_i}" if options[:increment]
155
+ if options.has_key?(:min_value)
156
+ sql << case options[:min_value]
157
+ when NilClass, FalseClass
158
+ 'NO MINVALUE'
159
+ else
160
+ "MINVALUE #{options[:min_value].to_i}"
161
+ end
162
+ end
163
+
164
+ if options.has_key?(:max_value)
165
+ sql << case options[:max_value]
166
+ when NilClass, FalseClass
167
+ 'NO MAXVALUE'
168
+ else
169
+ "MAXVALUE #{options[:max_value].to_i}"
170
+ end
171
+ end
172
+ sql << "START WITH #{options[:start].to_i}" if options[:start]
173
+ sql << "CACHE #{options[:cache].to_i}" if options[:cache]
174
+
175
+ if options.has_key?(:cycle)
176
+ sql << (options[:cycle] ? 'CYCLE' : 'NO CYCLE')
177
+ end
178
+
179
+ if options.has_key?(:owned_by)
180
+ table_column = if options[:owned_by].is_a?(Hash)
181
+ [ options[:owned_by][:table], options[:owned_by][:column] ]
182
+ elsif options[:owned_by].is_a?(Array)
183
+ options[:owned_by]
184
+ end
185
+
186
+ sql << 'OWNED BY ' + if options[:owned_by] == :none
187
+ 'NONE'
188
+ else
189
+ "#{base.quote_table_name(table_column.first)}.#{base.quote_column_name(table_column.last)}"
190
+ end
191
+ end
192
+
193
+ if action != :create && options.has_key?(:restart_with)
194
+ sql << "RESTART WITH #{options[:restart_with].to_i}"
195
+ end
196
+ sql.join(' ')
197
+ end
198
+ alias :to_s :to_sql
199
+
200
+ private
201
+ def assert_valid_owned_by(options) #:nodoc:
202
+ if options.has_key?(:owned_by)
203
+ begin
204
+ if options[:owned_by].is_a?(Hash)
205
+ raise if !(options[:owned_by].keys.sort == [ :column, :table ])
206
+ elsif options[:owned_by].is_a?(Array)
207
+ raise if options[:owned_by].length != 2
208
+ end
209
+ rescue
210
+ raise ActiveRecord::InvalidSequenceOptions.new("Invalid :owned_by options")
211
+ end
212
+ end
213
+ end
214
+
215
+ def assert_valid_action(action) #:nodoc:
216
+ if ![ :create, :alter ].include?(action)
217
+ raise ActiveRecord::InvalidSequenceAction.new(action)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,308 @@
1
+
2
+ module ActiveRecord
3
+ class InvalidLikeTypes < ActiveRecordError #:nodoc:
4
+ def initialize(likes)
5
+ super("Invalid LIKE INCLUDING/EXCLUDING types - #{likes.inspect}")
6
+ end
7
+ end
8
+
9
+ class InvalidTableOptions < ActiveRecordError #:nodoc:
10
+ end
11
+
12
+ module ConnectionAdapters
13
+ class PostgreSQLAdapter < AbstractAdapter
14
+ # Set the schema of a table.
15
+ def alter_table_schema table_name, schema, options = {}
16
+ execute "ALTER TABLE #{quote_schema(table_name)} SET SCHEMA #{quote_schema(schema)}"
17
+ end
18
+
19
+ alias :original_create_table :create_table
20
+ # Creates a new table. We've expanded the capabilities of the
21
+ # standard ActiveRecord create_table method to included a host of
22
+ # PostgreSQL-specific functionality.
23
+ #
24
+ # === PostgreSQL-specific Do-dads
25
+ #
26
+ # PostgreSQL allows for a couple of nifty table creation options
27
+ # that ActiveRecord usually doesn't account for, so we're filling
28
+ # in the blanks here.
29
+ #
30
+ # * <tt>:inherits</tt> - PostgreSQL allows you to create tables
31
+ # that inherit the properties of another. PostgreSQL is
32
+ # sometimes referred to as an Object-Relational DBMS rather
33
+ # than a straight-up RDBMS because of stuff like this.
34
+ # * <tt>:on_commit</tt> - allows you to define the behaviour of
35
+ # temporary tables. Allowed values are <tt>:preserve_rows</tt>
36
+ # (the default, which causes the temporary table to retain its
37
+ # rows at the end of a transaction), <tt>:delete_rows</tt>
38
+ # (which truncates the table at the end of a transaction) and
39
+ # <tt>:drop</tt> (which drops the table at the end of a
40
+ # transaction).
41
+ # * <tt>:tablespace</tt> - allows you to set the tablespace of a
42
+ # table.
43
+ # * <tt>:force</tt> - force a table to be dropped before trying to
44
+ # create it. This will pass <tt>:if_exists => true</tt> to the
45
+ # drop_table method.
46
+ # * <tt>:cascade_drop</tt> - when using the <tt>:force</tt>, this
47
+ # Jedi mindtrick will pass along the :cascade option to
48
+ # drop_table.
49
+ #
50
+ # We're expanding the doors of table definition perception with
51
+ # this exciting new addition to the world of ActiveRecord
52
+ # PostgreSQL adapters.
53
+ #
54
+ # create_table generally behaves like the standard ActiveRecord
55
+ # create_table method with a couple of notable exceptions:
56
+ #
57
+ # * you can add column constraints.
58
+ # * you can add constraints to the table itself.
59
+ # * you can add LIKE and INHERITS clauses to the definition.
60
+ #
61
+ # See the PostgreSQL documentation for more detailed on these
62
+ # sorts of things. Odds are that you'll probably recognize what
63
+ # we're referring to here if you're bothering to use this
64
+ # plugin, eh?
65
+ #
66
+ # Also, do note that you can grant privileges on tables using the
67
+ # grant_table_privileges method and revoke them using
68
+ # revoke_table_privileges.
69
+ #
70
+ # ==== Examples
71
+ #
72
+ # ### ruby
73
+ # create_table(:foo, :inherits => :parent) do |t|
74
+ # t.integer :bar_id, :references => :bar
75
+ # t.like :base, :including => [ :defaults, :indexes ], :excluding => :constraints
76
+ # t.check_constraint "bar_id < 100"
77
+ # t.unique_constraint :bar_id
78
+ # end
79
+ #
80
+ # # Produces:
81
+ # #
82
+ # # CREATE TABLE "foo" (
83
+ # # "id" serial primary key,
84
+ # # "bar_id" integer DEFAULT NULL NULL,
85
+ # # LIKE "base" INCLUDING DEFAULTS INCLUDING INDEXES EXCLUDING CONSTRAINTS,
86
+ # # FOREIGN KEY ("bar_id") REFERENCES "bar",
87
+ # # CHECK (bar_id < 100),
88
+ # # UNIQUE ("bar_id")
89
+ # # ) INHERITS ("parent");
90
+ #
91
+ # This is a fairly convoluted example, but there you have it.
92
+ #
93
+ # Beyond these differences, create_table acts like the original
94
+ # ActiveRecord create_table, which you can actually still access
95
+ # using the original_create_table method if you really, really want
96
+ # to.
97
+ #
98
+ # Be sure to refer to the PostgreSQL documentation for details on
99
+ # data definition and such.
100
+ def create_table(table_name, options = {})
101
+ if options[:force]
102
+ drop_table(table_name, { :if_exists => true, :cascade => options[:cascade_drop] })
103
+ end
104
+
105
+ table_definition = PostgreSQLTableDefinition.new(self, table_name, options)
106
+ yield table_definition
107
+
108
+ execute table_definition.to_s
109
+ unless table_definition.post_processing.blank?
110
+ table_definition.post_processing.each do |pp|
111
+ execute pp
112
+ end
113
+ end
114
+ end
115
+
116
+ alias :original_drop_table :drop_table
117
+ # Drops a table. This method is expanded beyond the standard
118
+ # ActiveRecord drop_table method to allow for a couple of
119
+ # PostgreSQL-specific options:
120
+ #
121
+ # * <tt>:if_exists</tt> - adds an IF EXISTS clause to the query.
122
+ # In absence of this option, an exception will be raised if you
123
+ # try to drop a table that doesn't exist.
124
+ # * <tt>:cascade</tt> - adds a CASCADE clause to the query. This
125
+ # will cause references to this table like foreign keys to be
126
+ # dropped as well. See the PostgreSQL documentation for details.
127
+ #
128
+ # You can still access the original method via original_drop_table.
129
+ def drop_table(tables, options = {})
130
+ sql = 'DROP TABLE '
131
+ sql << 'IF EXISTS ' if options[:if_exists]
132
+ sql << Array(tables).collect { |t| quote_table_name(t) }.join(', ')
133
+ sql << ' CASCADE' if options[:cascade]
134
+ execute sql
135
+ end
136
+
137
+ alias :original_rename_table :rename_table
138
+ # Renames a table. We're overriding the original rename_table so
139
+ # that we can take advantage of our super schema quoting
140
+ # capabilities. You can still access the original method via
141
+ # original_rename_table.
142
+ def rename_table(name, new_name, options = {})
143
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_generic_ignore_schema(new_name)}"
144
+ end
145
+
146
+ private
147
+ ON_COMMIT_VALUES = [ 'preserve_rows', 'delete_rows', 'drop' ].freeze
148
+
149
+ def assert_valid_on_commit(temp, on_commit)
150
+ unless on_commit.nil?
151
+ if !ON_COMMIT_VALUES.include?(on_commit.to_s.downcase)
152
+ raise ActiveRecord::InvalidTableOptions.new("Invalid ON COMMIT value - #{on_commit}")
153
+ elsif !temp
154
+ raise ActiveRecord::InvalidTableOptions.new("ON COMMIT can only be used with temporary tables")
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ # Creates a PostgreSQL table definition. This class isn't really meant
161
+ # to be used directly. Instead, see PostgreSQLAdapter#create_table
162
+ # for usage.
163
+ #
164
+ # Beyond our various PostgreSQL-specific extensions, we've also added
165
+ # the <tt>post_processing</tt> member, which allows you to tack on
166
+ # some SQL statements to run after creating the table. This member
167
+ # should be an Array of SQL statements to run once the table has
168
+ # been created. See the source code for PostgreSQLAdapter#create_table
169
+ # and PostgreSQLTableDefinition#geometry for an example of its use.
170
+ class PostgreSQLTableDefinition < TableDefinition
171
+ attr_accessor :base, :table_name, :options, :post_processing
172
+
173
+ def initialize(base, table_name, options = {}) #:nodoc:
174
+ @table_constraints = Array.new
175
+ @table_name, @options = table_name, options
176
+ super(base)
177
+
178
+ self.primary_key(
179
+ options[:primary_key] || Base.get_primary_key(table_name)
180
+ ) unless options[:id] == false
181
+ end
182
+
183
+ def to_sql #:nodoc:
184
+ sql = 'CREATE '
185
+ sql << 'TEMPORARY ' if options[:temporary]
186
+ sql << "TABLE #{base.quote_table_name(table_name)} (\n "
187
+
188
+ ary = @columns.collect(&:to_sql)
189
+ ary << @like if @like
190
+ ary << @table_constraints unless @table_constraints.empty?
191
+ sql << ary * ",\n "
192
+ sql << "\n)"
193
+
194
+ sql << "INHERITS (" << Array(options[:inherits]).collect { |i| base.quote_table_name(i) }.join(', ') << ')' if options[:inherits]
195
+ sql << "ON COMMIT #{options[:on_commit].to_s.upcase}" if options[:on_commit]
196
+ sql << "#{options[:options]}" if options[:options]
197
+ sql << "TABLESPACE #{base.quote_tablespace(options[:tablespace])}" if options[:tablespace]
198
+ sql
199
+ end
200
+ alias :to_s :to_sql
201
+
202
+ # Creates a LIKE statement for use in a table definition.
203
+ #
204
+ # ==== Options
205
+ #
206
+ # * <tt>:including</tt> and <tt>:excluding</tt> - set options for
207
+ # the INCLUDING and EXCLUDING clauses in a LIKE statement. Valid
208
+ # values are <tt>:constraints</tt>, <tt>:defaults</tt> and
209
+ # <tt>:indexes</tt>. You can set one or more by using an Array.
210
+ #
211
+ # See the PostgreSQL documentation for details on how to use
212
+ # LIKE. Be sure to take note as to how it differs from INHERITS.
213
+ #
214
+ # Also, be sure to note that, like, this LIKE isn't, like, the
215
+ # LIKE you use in a WHERE condition. This is, PostgreSQL's
216
+ # own special LIKE clause for table definitions. Like.
217
+ def like(parent_table, options = {})
218
+ assert_valid_like_types(options[:includes])
219
+ assert_valid_like_types(options[:excludes])
220
+
221
+ # Huh? Whyfor I dun this?
222
+ # @like = base.with_schema(@schema) { "LIKE #{base.quote_table_name(parent_table)}" }
223
+ @like = "LIKE #{@base.quote_table_name(parent_table)}"
224
+
225
+ if options[:including]
226
+ @like << Array(options[:including]).collect { |l| " INCLUDING #{l.to_s.upcase}" }.join
227
+ end
228
+
229
+ if options[:excluding]
230
+ @like << Array(options[:excluding]).collect { |l| " EXCLUDING #{l.to_s.upcase}" }.join
231
+ end
232
+ @like
233
+ end
234
+
235
+ # Add a CHECK constraint to the table. See
236
+ # PostgreSQLCheckConstraint for more details.
237
+ def check_constraint(expression, options = {})
238
+ @table_constraints << PostgreSQLCheckConstraint.new(@base, expression, options)
239
+ end
240
+
241
+ # Add a UNIQUE constraint to the table. See
242
+ # PostgreSQLUniqueConstraint for more details.
243
+ def unique_constraint(columns, options = {})
244
+ @table_constraints << PostgreSQLUniqueConstraint.new(@base, columns, options)
245
+ end
246
+
247
+ # Add a FOREIGN KEY constraint to the table. See
248
+ # PostgreSQLForeignKeyConstraint for more details.
249
+ def foreign_key(columns, ref_table, *args)
250
+ @table_constraints << PostgreSQLForeignKeyConstraint.new(@base, columns, ref_table, *args)
251
+ end
252
+
253
+ def column_with_constraints(name, type, *args) #:nodoc:
254
+ options = args.extract_options!
255
+ check = options.delete(:check)
256
+ references = options.delete(:references)
257
+ unique = options.delete(:unique)
258
+ column_without_constraints(name, type, options)
259
+
260
+ if check
261
+ @table_constraints << Array(check).collect do |c|
262
+ if c.is_a?(Hash)
263
+ PostgreSQLCheckConstraint.new(@base, c.delete(:expression), c)
264
+ else
265
+ PostgreSQLCheckConstraint.new(@base, c)
266
+ end
267
+ end
268
+ end
269
+
270
+ if references
271
+ ref_table, ref_options = if references.is_a?(Hash)
272
+ [ references.delete(:table), references ]
273
+ else
274
+ [ references, {} ]
275
+ end
276
+
277
+ @table_constraints << PostgreSQLForeignKeyConstraint.new(
278
+ @base,
279
+ name,
280
+ ref_table,
281
+ ref_options
282
+ )
283
+ end
284
+
285
+ if unique
286
+ unless unique.is_a?(Hash)
287
+ unique = {}
288
+ end
289
+ @table_constraints << PostgreSQLUniqueConstraint.new(@base, name, unique)
290
+ end
291
+ self
292
+ end
293
+ alias_method_chain :column, :constraints
294
+
295
+ private
296
+ LIKE_TYPES = [ 'defaults', 'constraints', 'indexes' ].freeze
297
+
298
+ def assert_valid_like_types(likes) #:nodoc:
299
+ unless likes.blank?
300
+ check_likes = Array(likes).collect(&:to_s) - LIKE_TYPES
301
+ if !check_likes.empty?
302
+ raise ActiveRecord::InvalidLikeTypes.new(check_likes)
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end