activerecord-postgresql-extensions 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +23 -0
- data/README.rdoc +32 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/lib/activerecord-postgresql-extensions.rb +30 -0
- data/lib/postgresql_extensions/foreign_key_associations.rb +367 -0
- data/lib/postgresql_extensions/postgresql_adapter_extensions.rb +646 -0
- data/lib/postgresql_extensions/postgresql_constraints.rb +579 -0
- data/lib/postgresql_extensions/postgresql_functions.rb +345 -0
- data/lib/postgresql_extensions/postgresql_geometry.rb +212 -0
- data/lib/postgresql_extensions/postgresql_indexes.rb +219 -0
- data/lib/postgresql_extensions/postgresql_languages.rb +80 -0
- data/lib/postgresql_extensions/postgresql_permissions.rb +322 -0
- data/lib/postgresql_extensions/postgresql_rules.rb +112 -0
- data/lib/postgresql_extensions/postgresql_schemas.rb +49 -0
- data/lib/postgresql_extensions/postgresql_sequences.rb +222 -0
- data/lib/postgresql_extensions/postgresql_tables.rb +308 -0
- data/lib/postgresql_extensions/postgresql_triggers.rb +131 -0
- data/lib/postgresql_extensions/postgresql_types.rb +17 -0
- data/lib/postgresql_extensions/postgresql_views.rb +103 -0
- data/postgresql-extensions.gemspec +50 -0
- data/test/adapter_test.rb +45 -0
- data/test/constraints_test.rb +98 -0
- data/test/functions_test.rb +112 -0
- data/test/geometry_test.rb +43 -0
- data/test/index_test.rb +68 -0
- data/test/languages_test.rb +48 -0
- data/test/permissions_test.rb +163 -0
- data/test/rules_test.rb +32 -0
- data/test/schemas_test.rb +43 -0
- data/test/sequences_test.rb +90 -0
- data/test/tables_test.rb +49 -0
- data/test/test_helper.rb +64 -0
- 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
|