activerecord-postgresql-extensions 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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