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,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