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,345 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ class InvalidFunctionBehavior < ActiveRecordError #:nodoc:
6
+ def initialize(behavior)
7
+ super("Invalid function behavior - #{behavior}")
8
+ end
9
+ end
10
+
11
+ class InvalidFunctionOnNullInputValue < ActiveRecordError #:nodoc:
12
+ def initialize(option)
13
+ super("Invalid function ON NULL INPUT behavior - #{option}")
14
+ end
15
+ end
16
+
17
+ class InvalidFunctionSecurityValue < ActiveRecordError #:nodoc:
18
+ def initialize(option)
19
+ super("Invalid function SECURITY value - #{option}")
20
+ end
21
+ end
22
+
23
+ class InvalidFunctionAction < ActiveRecordError #:nodoc:
24
+ def initialize(option)
25
+ super("Invalid function SECURITY value - #{option}")
26
+ end
27
+ end
28
+
29
+ module ConnectionAdapters
30
+ class PostgreSQLAdapter < AbstractAdapter
31
+ # Creates a PostgreSQL function/stored procedure.
32
+ #
33
+ # +args+ is a simple String that you can use to represent the
34
+ # function arguments.
35
+ #
36
+ # +returns+ is the return type for the function.
37
+ #
38
+ # +language+ is the procedural language the function is written
39
+ # in. The possible values for this argument will depend on your
40
+ # database set up. See create_language for details on adding
41
+ # new languages to your database.
42
+ #
43
+ # +body+ is the actual function body. When the function language is
44
+ # C, this will be an Array containing two items: the object file
45
+ # the function is found in and the link symbol for the function.
46
+ # In all other cases, this argument will be a String containing
47
+ # the actual function code.
48
+ #
49
+ # ==== Options
50
+ #
51
+ # * <tt>:force</tt> - add an <tt>OR REPLACE</tt> clause to the
52
+ # statement, thus overwriting any existing function definition
53
+ # of the same name and arguments.
54
+ # * <tt>:behavior</tt> - one of <tt>:immutable</tt>,
55
+ # <tt>:stable</tt> or <tt>:volatile</tt>. This option helps
56
+ # the server when making planning estimates when the function
57
+ # is called. The default is <tt>:volatile</tt>.
58
+ # * <tt>:on_null_inputs</tt> - one of <tt>:called</tt>,
59
+ # <tt>:returns</tt> or <tt>:strict</tt>. This indicates to the
60
+ # server how the function should be called when it receives
61
+ # NULL inputs. When <tt>:called</tt> is used, the function is
62
+ # executed even when one or more of its arguments is NULL.
63
+ # For <tt>:returns</tt> and <tt>:strict</tt> (which are actually
64
+ # just aliases), function execution is skipped and NULL is
65
+ # returned immediately.
66
+ # * <tt>:security</tt> - one of <tt>:invoker</tt> or
67
+ # <tt>:definer</tt>. This option determines what privileges the
68
+ # function should used when called. The values are pretty
69
+ # self explanatory. The default is <tt>:invoker</tt>.
70
+ # * <tt>:delimiter</tt> - the delimiter to use for the function
71
+ # body. The default is '$$'.
72
+ # * <tt>:cost</tt> - a number that determines the approximate
73
+ # overhead the server can expect when calling this function. This
74
+ # is used when calculating execution costs in the planner.
75
+ # * <tt>:rows</tt> - a number indicating the estimated number
76
+ # of rows the function will return. This is used when
77
+ # calculating execution costs in the planner and only affects
78
+ # functions that return result sets.
79
+ # * <tt>:set</tt> - allows you to set parameters temporarily
80
+ # during function execution. This would include things like
81
+ # +search_path+ or <tt>time zone</tt> and such. This option
82
+ # can either be a String with the set fragment or a Hash
83
+ # with the parameters as keys and the values to set as values.
84
+ # When using a Hash, the value <tt>:from_current</tt> can be
85
+ # used to specify the actual <tt>FROM CURRENT</tt> clause.
86
+ #
87
+ # You should definitely check out the PostgreSQL documentation
88
+ # on creating stored procedures, because it can get pretty
89
+ # convoluted as evidenced by the plethora of options we're
90
+ # handling here.
91
+ #
92
+ # ==== Example
93
+ #
94
+ # ### ruby
95
+ # create_function('tester_function', 'integer',
96
+ # 'integer', 'sql', :behavior => :immutable, :set => { :search_path => :from_current }, :force => true) do
97
+ # "select $1;"
98
+ # end
99
+ #
100
+ # # Produces
101
+ # #
102
+ # # CREATE OR REPLACE FUNCTION "tester_function"(integer) RETURNS integer AS $$
103
+ # # select $1;
104
+ # # $$
105
+ # # LANGUAGE "sql"
106
+ # # IMMUTABLE
107
+ # # SET "search_path" FROM CURRENT;
108
+ def create_function(name, args, returns, language, options = {})
109
+ body = yield.to_s
110
+ execute PostgreSQLFunctionDefinition.new(self, name, args, returns, language, body, options).to_s
111
+ end
112
+
113
+ # Drops a function.
114
+ #
115
+ # ==== Options
116
+ #
117
+ # * <tt>:if_exists</tt> - adds an <tt>IF EXISTS</tt> clause.
118
+ # * <tt>:cascade</tt> - cascades the operation on to any objects
119
+ # referring to the function.
120
+ def drop_function(name, args, options = {})
121
+ sql = 'DROP FUNCTION '
122
+ sql << 'IF EXISTS ' if options[:if_exists]
123
+ sql << "#{quote_function(name)}(#{args})"
124
+ sql << ' CASCADE' if options[:cascade]
125
+ execute sql
126
+ end
127
+
128
+ # Renames a function.
129
+ def rename_function(name, args, rename_to, options = {})
130
+ execute PostgreSQLFunctionAlterer.new(self, name, args, :rename_to => rename_to).to_s
131
+ end
132
+
133
+ # Changes the function's owner.
134
+ def alter_function_owner(name, args, owner_to, options = {})
135
+ execute PostgreSQLFunctionAlterer.new(self, name, args, :owner_to => owner_to).to_s
136
+ end
137
+
138
+ # Changes the function's schema.
139
+ def alter_function_schema(name, args, set_schema, options = {})
140
+ execute PostgreSQLFunctionAlterer.new(self, name, args, :set_schema => set_schema).to_s
141
+ end
142
+
143
+ # Alters a function. There's a ton of stuff you can do here, and
144
+ # there's two ways to do it: with a block or with an options Hash.
145
+ #
146
+ # In both cases, you're going to be using the same options as
147
+ # defined in <tt>create_function</tt> with the exception of
148
+ # <tt>:force</tt> and <tt>:delimiter</tt> and with the addition
149
+ # of <tt>:reset</tt>. The <tt>:reset</tt> option allows you
150
+ # to reset the values of parameters used with <tt>:set</tt> either
151
+ # on an individual basis using an Array or by using <tt>:all</tt>
152
+ # to reset all of them.
153
+ #
154
+ # ==== Examples
155
+ #
156
+ # Both of the following examples should produce the same output.
157
+ #
158
+ # ### ruby
159
+ # # with options Hash
160
+ # alter_function('my_function', 'integer', :rename_to => 'another_function')
161
+ # alter_function('another_function', 'integer', :owner_to => 'jdoe')
162
+ #
163
+ # # block mode
164
+ # alter_function('my_function', 'integer') do |f|
165
+ # f.rename_to 'another_function'
166
+ # f.owner_to 'jdoe'
167
+ # end
168
+ #
169
+ # # Produces
170
+ # #
171
+ # # ALTER FUNCTION "my_function"(integer) OWNER TO "jdoe";
172
+ # # ALTER FUNCTION "my_function"(integer) RENAME TO "another_function";
173
+ def alter_function(name, args, options = {})
174
+ if block_given?
175
+ alterer = PostgreSQLFunctionAlterer.new(self, name, args)
176
+ yield alterer
177
+ execute alterer.to_s unless alterer.empty?
178
+ else
179
+ execute PostgreSQLFunctionAlterer.new(self, name, args, options).to_s
180
+ end
181
+ end
182
+ end
183
+
184
+ # This is a base class for our PostgreSQL function classes. You don't
185
+ # want to be accessing this class directly.
186
+ class PostgreSQLFunction
187
+ attr_accessor :base, :name, :args, :options
188
+
189
+ def initialize(base, name, args, options = {}) #:nodoc:
190
+ @base, @name, @args, @options = base, name, args, options
191
+ end
192
+
193
+ private
194
+ BEHAVIORS = %w{ immutable stable volatile }.freeze
195
+ ON_NULL_INPUTS = %w{ called returns strict }.freeze
196
+ SECURITIES = %w{ invoker definer }.freeze
197
+
198
+ def assert_valid_behavior(option) #:nodoc:
199
+ if !BEHAVIORS.include? option.to_s.downcase
200
+ raise ActiveRecord::InvalidFunctionBehavior.new(option)
201
+ end unless option.nil?
202
+ end
203
+
204
+ def assert_valid_on_null_input(option) #:nodoc:
205
+ if !ON_NULL_INPUTS.include? option.to_s.downcase
206
+ raise ActiveRecord::InvalidFunctionOnNullInputValue.new(option)
207
+ end unless option.nil?
208
+ end
209
+
210
+ def assert_valid_security(option) #:nodoc:
211
+ if !SECURITIES.include? option.to_s.downcase
212
+ raise ActiveRecord::InvalidFunctionSecurityValue.new(option)
213
+ end unless option.nil?
214
+ end
215
+
216
+ def set_options(opts) #:nodoc:
217
+ sql = Array.new
218
+ if opts.is_a?(Hash)
219
+ opts.each do |k, v|
220
+ set_me = if k.to_s.upcase == 'TIME ZONE'
221
+ "SET TIME ZONE #{base.quote_generic(v)}"
222
+ else
223
+ "SET #{base.quote_generic(k)}" + if (v == :from_current)
224
+ " FROM CURRENT"
225
+ else
226
+ " TO #{base.quote_generic(v)}"
227
+ end
228
+ end
229
+
230
+ sql << set_me
231
+ end
232
+ else
233
+ sql << Array(opts).collect do |s|
234
+ "SET #{s.to_s}"
235
+ end
236
+ end
237
+ sql
238
+ end
239
+ end
240
+
241
+ # Creates a PostgreSQL function definition. You're generally going to
242
+ # want to use the +create_function+ method instead of accessing this
243
+ # class directly.
244
+ class PostgreSQLFunctionDefinition < PostgreSQLFunction
245
+ attr_accessor :language, :returns, :body
246
+
247
+ def initialize(base, name, args, returns, language, body, options = {}) #:nodoc:
248
+ assert_valid_behavior(options[:behavior])
249
+ assert_valid_on_null_input(options[:on_null_input])
250
+ assert_valid_security(options[:security])
251
+
252
+ @language, @returns, @body = language, returns, body
253
+ options = {
254
+ :delimiter => '$$'
255
+ }.merge options
256
+ super(base, name, args, options)
257
+ end
258
+
259
+ def to_sql #:nodoc:
260
+ sql = 'CREATE '
261
+ sql << 'OR REPLACE ' if options[:force]
262
+ sql << "FUNCTION #{base.quote_function(name)}(#{args}) RETURNS #{returns} AS "
263
+ if language == 'C'
264
+ "#{base.quote_function(body[0])}, #{base.quote_generic(body[1])}"
265
+ else
266
+ sql << "#{options[:delimiter]}\n#{body}\n#{options[:delimiter]}\n"
267
+ end
268
+ sql << "LANGUAGE #{base.quote_language(language)}\n"
269
+ sql << " #{options[:behavior].to_s.upcase}\n" if options[:behavior]
270
+ sql << " #{options[:on_null_input].to_s.upcase}\n" if options[:on_null_input]
271
+ sql << " COST #{options[:cost].to_i}\n" if options[:cost]
272
+ sql << " ROWS #{options[:rows].to_i}\n" if options[:rows]
273
+ sql << " " << (set_options(options[:set]) * "\n ") if options[:set]
274
+ sql.strip
275
+ end
276
+ alias :to_s :to_sql
277
+ end
278
+
279
+ # Alters a function. You'll generally want to be calling the
280
+ # PostgreSQLAdapter#alter_function method rather than risk messing
281
+ # with this class directly. It's a finicky, delicate flower.
282
+ class PostgreSQLFunctionAlterer < PostgreSQLFunction
283
+ def initialize(base, name, args, options = {}) #:nodoc:
284
+ super(base, name, args, options)
285
+
286
+ @sql = options.collect { |k, v| build_statement(k, v) }
287
+ end
288
+
289
+ def empty? #:nodoc:
290
+ @sql.empty?
291
+ end
292
+
293
+ def to_sql #:nodoc:
294
+ @sql.join(";\n")
295
+ end
296
+ alias :to_s :to_sql
297
+
298
+ [ :rename_to, :owner_to, :set_schema, :behavior,
299
+ :on_null_input, :security, :cost, :rows, :set,
300
+ :reset
301
+ ].each do |f|
302
+ self.class_eval <<-EOF
303
+ def #{f}(v)
304
+ @sql << build_statement(:#{f}, v)
305
+ end
306
+ EOF
307
+ end
308
+
309
+ private
310
+ def build_statement(k, v) #:nodoc:
311
+ sql = "ALTER FUNCTION #{base.quote_function(@new_name || name)}(#{args}) "
312
+ sql << case k
313
+ when :rename_to
314
+ "RENAME TO #{base.quote_generic_ignore_schema(v)}".tap { @new_name = v }
315
+ when :owner_to
316
+ "OWNER TO #{base.quote_role(v)}"
317
+ when :set_schema
318
+ "SET SCHEMA #{base.quote_schema(v)}"
319
+ when :behavior
320
+ assert_valid_behavior(v)
321
+ v.to_s.upcase
322
+ when :on_null_input
323
+ assert_valid_on_null_input(v)
324
+ v.to_s.upcase
325
+ when :security
326
+ assert_valid_security(v)
327
+ "SECURITY #{v.to_s.upcase}"
328
+ when :cost
329
+ "COST #{v.to_i}"
330
+ when :rows
331
+ "ROWS #{v.to_i}"
332
+ when :set
333
+ set_options(v) * "\n"
334
+ when :reset
335
+ if v.is_a?(Array)
336
+ v.collect { |vv| "RESET #{base.quote_generic(vv)}" }.join(" ")
337
+ elsif v == :all
338
+ 'RESET ALL'
339
+ end
340
+ end
341
+ sql
342
+ end
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,212 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ class InvalidGeometryType < ActiveRecordError #:nodoc:
6
+ def initialize(type)
7
+ super("Invalid PostGIS geometry type - #{type}")
8
+ end
9
+ end
10
+
11
+ class InvalidGeometryDimensions < ActiveRecordError #:nodoc:
12
+ end
13
+
14
+ module ConnectionAdapters
15
+ class PostgreSQLAdapter < AbstractAdapter
16
+ def native_database_types_with_geometry #:nodoc:
17
+ native_database_types_without_geometry.merge({
18
+ :geometry => { :name => 'geometry' }
19
+ })
20
+ end
21
+ alias_method_chain :native_database_types, :geometry
22
+ end
23
+
24
+ class PostgreSQLTableDefinition < TableDefinition
25
+ attr_reader :geometry_columns
26
+
27
+ # This is a special geometry type for the PostGIS extension's
28
+ # geometry data type. It is used in a table definition to define
29
+ # a geometry column.
30
+ #
31
+ # Essentially this method works like a wrapper around the PostGIS
32
+ # AddGeometryColumn function. It can create the geometry column,
33
+ # add CHECK constraints and create a GiST index on the column
34
+ # all in one go.
35
+ #
36
+ # ==== Options
37
+ #
38
+ # * <tt>:geometry_type</tt> - set the geometry type. The actual
39
+ # data type is always "geometry"; this option is used in the
40
+ # <tt>geometry_columns</tt> table and on the CHECK constraints
41
+ # to enforce the geometry type allowed in the field. The default
42
+ # is "GEOMETRY". See the PostGIS documentation for valid types,
43
+ # or check out the GEOMETRY_TYPES constant in this extension.
44
+ # * <tt>:add_constraints</tt> - automatically creates the CHECK
45
+ # constraints used to enforce ndims, srid and geometry type.
46
+ # The default is true.
47
+ # * <tt>:add_geometry_columns_entry</tt> - automatically adds
48
+ # an entry to the <tt>geometry_columns</tt> table. We will
49
+ # try to delete any existing match in <tt>geometry_columns</tt>
50
+ # before inserting. The default is true.
51
+ # * <tt>:create_gist_index</tt> - automatically creates a GiST
52
+ # index for the new geometry column. This option accepts either
53
+ # a true/false expression or a String. If the value is a String,
54
+ # we'll use it as the index name. The default is true.
55
+ # * <tt>:ndims</tt> - the number of dimensions to allow in the
56
+ # geometry. This value is either 2 or 3 by default depending on
57
+ # the value of the <tt>:geometry_type</tt> option. If the
58
+ # <tt>:geometry_type</tt> ends in an "m" (for "measured
59
+ # geometries" the default is 3); for everything else, it is 2.
60
+ # * <tt>:srid</tt> - the SRID, a.k.a. the Spatial Reference
61
+ # Identifier. The default is -1, which is a special SRID
62
+ # PostGIS in lieu of a real SRID.
63
+ def geometry(column_name, opts = {})
64
+ opts = {
65
+ :geometry_type => :geometry,
66
+ :add_constraints => true,
67
+ :add_geometry_columns_entry => true,
68
+ :create_gist_index => true,
69
+ :srid => -1
70
+ }.merge(opts)
71
+
72
+ if opts[:ndims].blank?
73
+ opts[:ndims] = if opts[:geometry_type].to_s.upcase =~ /M$/
74
+ 3
75
+ else
76
+ 2
77
+ end
78
+ end
79
+
80
+ assert_valid_geometry_type(opts[:geometry_type])
81
+ assert_valid_ndims(opts[:ndims], opts[:geometry_type])
82
+
83
+ column = self[column_name] || ColumnDefinition.new(base, column_name, :geometry)
84
+ column.default = opts[:default]
85
+ column.null = opts[:default]
86
+
87
+ unless @columns.include?(column)
88
+ @columns << column
89
+ if opts[:add_constraints]
90
+ @table_constraints << PostgreSQLCheckConstraint.new(
91
+ base,
92
+ "srid(#{base.quote_column_name(column_name)}) = (#{opts[:srid].to_i})",
93
+ :name => "enforce_srid_#{column_name}"
94
+ )
95
+
96
+ @table_constraints << PostgreSQLCheckConstraint.new(
97
+ base,
98
+ "ndims(#{base.quote_column_name(column_name)}) = #{opts[:ndims].to_i}",
99
+ :name => "enforce_dims_#{column_name}"
100
+ )
101
+
102
+ if opts[:geometry_type].to_s.upcase != 'GEOMETRY'
103
+ @table_constraints << PostgreSQLCheckConstraint.new(
104
+ base,
105
+ "geometrytype(#{base.quote_column_name(column_name)}) = '#{opts[:geometry_type].to_s.upcase}'::text OR #{base.quote_column_name(column_name)} IS NULL",
106
+ :name => "enforce_geotype_#{column_name}"
107
+ )
108
+ end
109
+ end
110
+ end
111
+
112
+ # We want to split up the schema and the table name for the
113
+ # upcoming geometry_columns rows and GiST index.
114
+ current_schema, current_table_name = if self.table_name.is_a?(Hash)
115
+ [ self.table_name.keys.first, self.table_name.values.first ]
116
+ elsif base.current_schema
117
+ [ base.current_schema, self.table_name ]
118
+ else
119
+ schema, table_name = base.extract_schema_and_table_names(self.table_name)
120
+ [ schema || 'public', table_name ]
121
+ end
122
+
123
+ @post_processing ||= Array.new
124
+
125
+ if opts[:add_geometry_columns_entry]
126
+ @post_processing << sprintf(
127
+ "DELETE FROM \"geometry_columns\" WHERE f_table_catalog = '' AND " +
128
+ "f_table_schema = %s AND " +
129
+ "f_table_name = %s AND " +
130
+ "f_geometry_column = %s",
131
+ base.quote(current_schema.to_s),
132
+ base.quote(current_table_name.to_s),
133
+ base.quote(column_name.to_s)
134
+ )
135
+
136
+ @post_processing << sprintf(
137
+ "INSERT INTO \"geometry_columns\" VALUES ('', %s, %s, %s, %d, %d, %s)",
138
+ base.quote(current_schema.to_s),
139
+ base.quote(current_table_name.to_s),
140
+ base.quote(column_name.to_s),
141
+ opts[:ndims].to_i,
142
+ opts[:srid].to_i,
143
+ base.quote(opts[:geometry_type].to_s)
144
+ )
145
+ end
146
+
147
+ if opts[:create_gist_index]
148
+ index_name = if opts[:create_gist_index].is_a?(String)
149
+ opts[:create_gist_index]
150
+ else
151
+ "#{current_table_name}_#{column_name}_gist_index"
152
+ end
153
+
154
+ @post_processing << PostgreSQLIndexDefinition.new(
155
+ base,
156
+ index_name,
157
+ current_table_name,
158
+ column_name,
159
+ :using => :gist
160
+ ).to_s
161
+ end
162
+
163
+ self
164
+ end
165
+
166
+ private
167
+ GEOMETRY_TYPES = [
168
+ 'GEOMETRY',
169
+ 'GEOMETRYCOLLECTION',
170
+ 'POINT',
171
+ 'MULTIPOINT',
172
+ 'POLYGON',
173
+ 'MULTIPOLYGON',
174
+ 'LINESTRING',
175
+ 'MULTILINESTRING',
176
+ 'GEOMETRYCOLLECTIONM',
177
+ 'POINTM',
178
+ 'MULTIPOINTM',
179
+ 'POLYGONM',
180
+ 'MULTIPOLYGONM',
181
+ 'LINESTRINGM',
182
+ 'MULTILINESTRINGM',
183
+ 'CIRCULARSTRING',
184
+ 'CIRCULARSTRINGM',
185
+ 'COMPOUNDCURVE',
186
+ 'COMPOUNDCURVEM',
187
+ 'CURVEPOLYGON',
188
+ 'CURVEPOLYGONM',
189
+ 'MULTICURVE',
190
+ 'MULTICURVEM',
191
+ 'MULTISURFACE',
192
+ 'MULTISURFACEM'
193
+ ].freeze
194
+
195
+ def assert_valid_geometry_type(type)
196
+ if !GEOMETRY_TYPES.include?(type.to_s.upcase)
197
+ raise ActiveRecord::InvalidGeometryType.new(type)
198
+ end unless type.nil?
199
+ end
200
+
201
+ def assert_valid_ndims(ndims, type)
202
+ if !ndims.blank?
203
+ if type.to_s.upcase =~ /([A-Z]+M)$/ && ndims != 3
204
+ raise ActiveRecord::InvalidGeometryDimensions.new("Invalid PostGIS geometry dimensions (#{$1} requires 3 dimensions)")
205
+ elsif ndims < 0 || ndims > 4
206
+ ralse ActiveRecord::InvalidGeometryDimensions.new("Invalid PostGIS geometry dimensions (should be between 0 and 4 inclusive) - #{ndims}")
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end