activerecord-postgis-array 0.3.4

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 (97) hide show
  1. data/.gitignore +21 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +18 -0
  4. data/CHANGELOG.md +99 -0
  5. data/CONTRIBUTING.md +35 -0
  6. data/Gemfile +12 -0
  7. data/LICENSE +22 -0
  8. data/README.md +87 -0
  9. data/Rakefile +33 -0
  10. data/activerecord-postgis-array.gemspec +30 -0
  11. data/docs/indexes.md +28 -0
  12. data/docs/migrations.md +92 -0
  13. data/docs/querying.md +170 -0
  14. data/docs/type_casting.md +51 -0
  15. data/lib/activerecord-postgis-array.rb +3 -0
  16. data/lib/activerecord-postgis-array/active_record.rb +4 -0
  17. data/lib/activerecord-postgis-array/active_record/connection_adapters.rb +1 -0
  18. data/lib/activerecord-postgis-array/active_record/connection_adapters/postgres_adapter.rb +346 -0
  19. data/lib/activerecord-postgis-array/active_record/relation.rb +2 -0
  20. data/lib/activerecord-postgis-array/active_record/relation/predicate_builder.rb +71 -0
  21. data/lib/activerecord-postgis-array/active_record/relation/query_methods.rb +84 -0
  22. data/lib/activerecord-postgis-array/active_record/sanitization.rb +30 -0
  23. data/lib/activerecord-postgis-array/active_record/schema_dumper.rb +157 -0
  24. data/lib/activerecord-postgis-array/arel.rb +3 -0
  25. data/lib/activerecord-postgis-array/arel/nodes.rb +2 -0
  26. data/lib/activerecord-postgis-array/arel/nodes/array_nodes.rb +9 -0
  27. data/lib/activerecord-postgis-array/arel/nodes/contained_within.rb +20 -0
  28. data/lib/activerecord-postgis-array/arel/predications.rb +25 -0
  29. data/lib/activerecord-postgis-array/arel/visitors.rb +2 -0
  30. data/lib/activerecord-postgis-array/arel/visitors/to_sql.rb +15 -0
  31. data/lib/activerecord-postgis-array/arel/visitors/visitor.rb +38 -0
  32. data/lib/activerecord-postgis-array/version.rb +3 -0
  33. data/spec/arel/arel_spec.rb +30 -0
  34. data/spec/arel/array_spec.rb +77 -0
  35. data/spec/columns/array_spec.rb +120 -0
  36. data/spec/dummy/.gitignore +15 -0
  37. data/spec/dummy/README.rdoc +261 -0
  38. data/spec/dummy/Rakefile +7 -0
  39. data/spec/dummy/app/assets/images/rails.png +0 -0
  40. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  41. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  42. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  43. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  44. data/spec/dummy/app/mailers/.gitkeep +0 -0
  45. data/spec/dummy/app/models/.gitkeep +0 -0
  46. data/spec/dummy/app/models/person.rb +3 -0
  47. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  48. data/spec/dummy/config.ru +4 -0
  49. data/spec/dummy/config/application.rb +59 -0
  50. data/spec/dummy/config/boot.rb +6 -0
  51. data/spec/dummy/config/database.yml.example +14 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +38 -0
  54. data/spec/dummy/config/environments/production.rb +67 -0
  55. data/spec/dummy/config/environments/test.rb +37 -0
  56. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/spec/dummy/config/initializers/inflections.rb +15 -0
  58. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  59. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  60. data/spec/dummy/config/initializers/session_store.rb +8 -0
  61. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/spec/dummy/config/locales/en.yml +5 -0
  63. data/spec/dummy/config/routes.rb +58 -0
  64. data/spec/dummy/db/migrate/20120501163758_create_people.rb +12 -0
  65. data/spec/dummy/db/schema.rb +25 -0
  66. data/spec/dummy/db/seeds.rb +7 -0
  67. data/spec/dummy/lib/assets/.gitkeep +0 -0
  68. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  69. data/spec/dummy/log/.gitkeep +0 -0
  70. data/spec/dummy/public/404.html +26 -0
  71. data/spec/dummy/public/422.html +26 -0
  72. data/spec/dummy/public/500.html +25 -0
  73. data/spec/dummy/public/favicon.ico +0 -0
  74. data/spec/dummy/public/index.html +241 -0
  75. data/spec/dummy/public/robots.txt +5 -0
  76. data/spec/dummy/script/rails +6 -0
  77. data/spec/dummy/spec/factories/people.rb +7 -0
  78. data/spec/dummy/test/fixtures/.gitkeep +0 -0
  79. data/spec/dummy/test/functional/.gitkeep +0 -0
  80. data/spec/dummy/test/integration/.gitkeep +0 -0
  81. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  82. data/spec/dummy/test/test_helper.rb +13 -0
  83. data/spec/dummy/test/unit/.gitkeep +0 -0
  84. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  85. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  86. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  87. data/spec/migrations/active_record_migration_spec.rb +29 -0
  88. data/spec/migrations/array_spec.rb +136 -0
  89. data/spec/migrations/index_spec.rb +67 -0
  90. data/spec/models/array_spec.rb +285 -0
  91. data/spec/queries/array_queries_spec.rb +72 -0
  92. data/spec/queries/sanity_spec.rb +16 -0
  93. data/spec/schema_dumper/array_spec.rb +17 -0
  94. data/spec/schema_dumper/extension_spec.rb +14 -0
  95. data/spec/schema_dumper/index_spec.rb +46 -0
  96. data/spec/spec_helper.rb +29 -0
  97. metadata +318 -0
data/docs/querying.md ADDED
@@ -0,0 +1,170 @@
1
+ # Querying PostgreSQL datatypes
2
+
3
+ * [Arrays](#arrays)
4
+ * [INET/CIDR](#inetcidr-queries)
5
+
6
+ ## Arrays
7
+
8
+ * [&& - Array Overlap operator](#---array-overlap-operator)
9
+ * [ANY or ALL functions](#any-or-all-functions)
10
+
11
+ ### && - Array Overlap operator
12
+
13
+ PostgreSQL implements the `&&` operator, known as the overlap operator,
14
+ for arrays. The overlap operator returns `t` (true) when two arrays have
15
+ one or more elements in common.
16
+
17
+ ```sql
18
+ ARRAY[1,2,3] && ARRAY[4,5,6]
19
+ -- f
20
+
21
+ ARRAY[1,2,3] && ARRAY[3,5,6]
22
+ -- t
23
+ ```
24
+
25
+ Postgres\_ext extends the `ActiveRecord::Relation.where` method similar
26
+ to the Rails 4.0 not clause. The easiest way to make a overlap query
27
+ would be:
28
+
29
+ ```ruby
30
+ User.where.overlap(:nick_names => ['Bob', 'Fred'])
31
+ ```
32
+
33
+ Postgres\_ext defines `array_overlap`, an [Arel](https://github.com/rails/arel)
34
+ predicate for the `&&` operator. This is utilized by the `where.overlap`
35
+ call above.
36
+
37
+ ```ruby
38
+ user_arel = User.arel_table
39
+
40
+ # Execute the query
41
+ User.where(user_arel[:tags].array_overlap(['one','two']))
42
+ # => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"tags\" && '{one,two}'
43
+ ```
44
+
45
+ ### @> - Array Contains operator
46
+
47
+ PostgreSQL has a contains (`@>`) operator for querying whether all the
48
+ elements of an array are within another.
49
+
50
+ ```sql
51
+ ARRAY[1,2,3] @> ARRAY[3,4]
52
+ -- f
53
+
54
+ ARRAY[1,2,3] @> ARRAY[2,3]
55
+ -- t
56
+ ```
57
+
58
+ Postgres\_ext extends the `ActiveRecord::Relation.where` method by
59
+ adding a `contains` method. To make a contains query, you can do:
60
+
61
+ ```ruby
62
+ User.where.contains(:nick_names => ['Bob', 'Fred'])
63
+ ```
64
+
65
+ Postgres\_ext defines `array_contains`, an [Arel](https://github.com/rails/arel)
66
+ predicate for the `@>` operator. This is utilized by the
67
+ `where.array_contains` call above.
68
+
69
+ ```ruby
70
+ user_arel = User.arel_table
71
+
72
+ # Execute the query
73
+ User.where(user_arel[:tags].array_contains(['one','two']))
74
+ # => SELECT "users".* FROM "users" WHERE "users"."tags" @> '{"one","two"}'
75
+ ```
76
+
77
+ ### ANY or ALL functions
78
+
79
+ When querying array columns, you have the ability to see if a predicate
80
+ apply's to either *any* element in the array, or *all* elements of the
81
+ array. The syntax for these predicates are slightly different then the
82
+ normal `where` syntax in PostgreSQL. To see if an array contains the
83
+ string `'test'` in any location, you would write the following in SQL
84
+
85
+ ```sql
86
+ SELECT *
87
+ FROM users
88
+ WHERE 'test' = ANY(users.tags)
89
+ ```
90
+
91
+ Notice that the column is on the right hand side of the predicate,
92
+ instead of the left, because we have to call the `ANY` function on that
93
+ column.
94
+
95
+ Postgres\_ext provides a `ActiveRecord::Relation.where.any()` method. The
96
+ easiest way to make a ANY query would be:
97
+
98
+ ```ruby
99
+ User.where.any(:nick_names => 'Bob')
100
+ ```
101
+
102
+ There is also an `ActiveRecord::Relation.where.all()` call as well. This
103
+ method utilizes the following code to create the query:
104
+
105
+ We can generate the above query using [Arel](https://github.com/rails/arel)
106
+ and generating the Node manually. We would use the following to
107
+ accompish this:
108
+
109
+ ```ruby
110
+ user_arel = User.arel_table
111
+
112
+ any_tags_function = Arel::Nodes::NamedFunction.new('ANY', [user_arel[:tags]])
113
+ predicate = Arel::Nodes::Equality.new('test', any_tags_function)
114
+
115
+ # Execute the query
116
+ User.where(predicate)
117
+ #=> SELECT \"users\".* FROM \"users\" WHERE 'test' = ANY(\"users\".\"tags\")
118
+ ```
119
+
120
+ The ALL version of this same predicate can be generated by swap `'ANY'`
121
+ for `'ALL'` in the named function.
122
+
123
+ ## INET/CIDR Queries
124
+
125
+ PostgreSQL defines the `<<`, or contained within operator for INET and
126
+ CIDR datatypes. The `<<` operator returns `t` (true) if a INET or CIDR
127
+ address is contained within the given subnet.
128
+
129
+ ```sql
130
+ inet '192.168.1.6' << inet '10.0.0.0/24'
131
+ -- f
132
+
133
+ inet '192.168.1.6' << inet '192.168.1.0/24'
134
+ -- t
135
+ ```
136
+
137
+ In addition to contained within, there is also:
138
+
139
+ * `<<=` - Contained within or equals
140
+ * `>>` - Contains
141
+ * `>>=` - Contains or equals
142
+
143
+ Postgres\_ext extends the `ActiveRecord::Relation.where` method similar
144
+ to the Rails 4.0 not clause. The easiest way to make a overlap query
145
+ would be:
146
+
147
+ ```ruby
148
+ User.where.contained_within(:ip => '192.168.1.1/24')
149
+ User.where.contained_within_or_equals(:ip => '192.168.1.1/24')
150
+ User.where.contains(:ip => '192.168.1.14')
151
+ User.where.contains_or_equals(:ip => '192.168.1.14')
152
+ ```
153
+
154
+ Postgres\_ext defines `contained_within`, an [Arel](https://github.com/rails/arel)
155
+ predicate for the `<<` operator. This is utilized by the
156
+ methods above.
157
+
158
+ ```ruby
159
+ user_arel = User.arel_table
160
+
161
+ # Execute the query
162
+ User.where(user_arel[:ip_address].contained_within('127.0.0.1/24'))
163
+ # => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip_address\" << '127.0.0.1/24'
164
+ User.where(user_arel[:ip_address].contained_within_or_equals('127.0.0.1/24'))
165
+ # => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip_address\" <<= '127.0.0.1/24'
166
+ User.where(user_arel[:ip_address].contains('127.0.0.1'))
167
+ # => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip_address\" >> '127.0.0.1'
168
+ User.where(user_arel[:ip_address].contains_or_equals('127.0.0.1'))
169
+ # => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip_address\" >>= '127.0.0.1'
170
+ ```
@@ -0,0 +1,51 @@
1
+ # Type Casting support
2
+
3
+ ## INET and CIDR
4
+ INET and CIDR values are converted to
5
+ [IPAddr](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ipaddr/rdoc/IPAddr.html)
6
+ objects when retrieved from the database, or set as a string.
7
+
8
+ ```ruby
9
+ create_table :inet_examples do |t|
10
+ t.inet :ip_address
11
+ end
12
+
13
+ class InetExample < ActiveRecord::Base
14
+ end
15
+
16
+ inetExample = InetExample.new
17
+ inetExample.ip_address = '127.0.0.0/24'
18
+ inetExample.ip_address
19
+ # => #<IPAddr: IPv4:127.0.0.0/255.255.255.0>
20
+ inetExample.save
21
+
22
+ inet_2 = InetExample.first
23
+ inet_2.ip_address
24
+ # => #<IPAddr: IPv4:127.0.0.0/255.255.255.0>
25
+ ```
26
+
27
+ ## Arrays
28
+ Array values can be set with Array objects. Any array stored in the
29
+ database will be converted to a properly casted array of values on the
30
+ way out.
31
+
32
+ ```ruby
33
+ create_table :people do |t|
34
+ t.integer :favorite_numbers, :array => true
35
+ end
36
+
37
+ class Person < ActiveRecord::Base
38
+ end
39
+
40
+ person = Person.new
41
+ person.favorite_numbers = [1,2,3]
42
+ person.favorite_numbers
43
+ # => [1,2,3]
44
+ person.save
45
+
46
+ person_2 = Person.first
47
+ person_2.favorite_numbers
48
+ # => [1,2,3]
49
+ person_2.favorite_numbers.first.class
50
+ # => Fixnum
51
+ ```
@@ -0,0 +1,3 @@
1
+ require 'activerecord-postgis-array/version'
2
+ require 'activerecord-postgis-array/active_record'
3
+ require 'activerecord-postgis-array/arel'
@@ -0,0 +1,4 @@
1
+ require 'activerecord-postgis-array/active_record/sanitization'
2
+ require 'activerecord-postgis-array/active_record/connection_adapters'
3
+ require 'activerecord-postgis-array/active_record/schema_dumper'
4
+ require 'activerecord-postgis-array/active_record/relation'
@@ -0,0 +1 @@
1
+ require 'activerecord-postgis-array/active_record/connection_adapters/postgres_adapter'
@@ -0,0 +1,346 @@
1
+ require 'activerecord-postgis-adapter'
2
+ require 'ipaddr'
3
+ require 'pg_array_parser'
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGISAdapter
8
+ ::RGeo::ActiveRecord::SpatialIndexDefinition.class_eval do
9
+ attr_accessor :using, :index_opclass
10
+ end
11
+
12
+ SpatialColumn.class_eval do
13
+ include PgArrayParser
14
+ attr_accessor :array
15
+
16
+ def initialize_with_extended_types(name, default, sql_type = nil, null = true, opts = {})
17
+ if sql_type =~ /\[\]$/
18
+ @array = true
19
+ initialize_without_extended_types(name, default, sql_type[0..sql_type.length - 3], null, opts)
20
+ @sql_type = sql_type
21
+ else
22
+ initialize_without_extended_types(name,default, sql_type, null, opts)
23
+ end
24
+ end
25
+ alias_method_chain :initialize, :extended_types
26
+
27
+ def type_cast_with_extended_types(value)
28
+ return nil if value.nil?
29
+ return coder.load(value) if encoded?
30
+
31
+ klass = self.class
32
+ if self.array && String === value && value.start_with?('{') && value.end_with?('}')
33
+ string_to_array value
34
+ elsif self.array && Array === value
35
+ value
36
+ else
37
+ type_cast_without_extended_types(value)
38
+ end
39
+ end
40
+ alias_method_chain :type_cast, :extended_types
41
+
42
+ def string_to_array(value)
43
+ if Array === value
44
+ value
45
+ else
46
+ string_array = parse_pg_array value
47
+ if type == :string || type == :text
48
+ force_character_encoding(string_array)
49
+ else
50
+ type_cast_array(string_array)
51
+ end
52
+ end
53
+ end
54
+
55
+ def type_cast_array(array)
56
+ array.map do |value|
57
+ Array === value ? type_cast_array(value) : type_cast(value)
58
+ end
59
+ end
60
+
61
+ def number?
62
+ !self.array && super
63
+ end
64
+
65
+ def type_cast_code_with_extended_types(var_name)
66
+ klass = self.class.name
67
+
68
+ if self.array
69
+ "#{klass}.new('#{self.name}', #{self.default.nil? ? 'nil' : "'#{self.default}'"}, '#{self.sql_type}').string_to_array(#{var_name})"
70
+ else
71
+ type_cast_code_without_extended_types(var_name)
72
+ end
73
+ end
74
+ alias_method_chain :type_cast_code, :extended_types
75
+
76
+ private
77
+
78
+ def force_character_encoding(string_array)
79
+ string_array.map do |item|
80
+ item.respond_to?(:force_encoding) ? item.force_encoding(ActiveRecord::Base.connection.encoding_for_ruby) : item
81
+ end
82
+ end
83
+ end
84
+
85
+ MainAdapter.class_eval do
86
+ class UnsupportedFeature < Exception; end
87
+
88
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
89
+ attr_accessor :array
90
+ end
91
+
92
+ SpatialTableDefinition.class_eval do
93
+
94
+ def column(name_, type_, options_={})
95
+ if (info_ = @base.spatial_column_constructor(type_.to_sym))
96
+ type_ = options_[:type] || info_[:type] || type_
97
+ if type_.to_s == 'geometry' &&
98
+ (options_[:no_constraints] ||
99
+ options_[:limit].is_a?(::Hash) && options_[:limit][:no_constraints])
100
+ then
101
+ options_.delete(:limit)
102
+ else
103
+ options_[:type] = type_
104
+ type_ = :spatial
105
+ end
106
+ end
107
+ super(name_, type_, options_)
108
+ if type_ == :spatial
109
+ col_ = self[name_]
110
+ col_.extend(SpatialColumnDefinitionMethods) unless col_.respond_to?(:geographic?)
111
+ options_.merge!(col_.limit) if col_.limit.is_a?(::Hash)
112
+ col_.set_spatial_type(options_[:type])
113
+ col_.set_geographic(options_[:geographic])
114
+ col_.set_srid(options_[:srid])
115
+ col_.set_has_z(options_[:has_z])
116
+ col_.set_has_m(options_[:has_m])
117
+ end
118
+
119
+ column = self[name_]
120
+ column.array = options_[:array]
121
+
122
+ self
123
+ end
124
+
125
+ private
126
+
127
+ def new_column_definition(base, name, type)
128
+ definition = ColumnDefinition.new base, name, type
129
+ @columns << definition
130
+ @columns_hash[name] = definition
131
+ definition
132
+ end
133
+ end
134
+
135
+ # Translate from the current database encoding to the encoding we
136
+ # will force string array components into on retrievial.
137
+ def encoding_for_ruby
138
+ @database_encoding ||= case ActiveRecord::Base.connection.encoding
139
+ when 'UTF8'
140
+ 'UTF-8'
141
+ else
142
+ ActiveRecord::Base.connection.encoding
143
+ end
144
+ end
145
+
146
+ def supports_extensions?
147
+ postgresql_version > 90100
148
+ end
149
+
150
+ def add_column_options!(sql, options)
151
+ if options[:array] || options[:column].try(:array)
152
+ sql << '[]'
153
+ end
154
+ super
155
+ end
156
+
157
+ def add_index(table_name, column_name, options = {})
158
+ index_name, unique, index_columns, _ = add_index_options(table_name, column_name, options)
159
+ if options.is_a? Hash
160
+ index_type = options[:using] ? " USING #{options[:using]} " : ""
161
+ index_type = 'USING GIST' if options[:spatial]
162
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
163
+ index_opclass = options[:index_opclass]
164
+ index_algorithm = options[:algorithm] == :concurrently ? ' CONCURRENTLY' : ''
165
+
166
+ if options[:algorithm].present? && options[:algorithm] != :concurrently
167
+ raise ArgumentError.new 'Algorithm must be one of the following: :concurrently'
168
+ end
169
+ end
170
+ execute "CREATE #{unique} INDEX#{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}#{index_type}(#{index_columns} #{index_opclass})#{index_options}"
171
+ end
172
+
173
+ def add_extension(extension_name, options={})
174
+ raise UnsupportedFeature.new('Extensions are not support by this version of PostgreSQL') unless supports_extensions?
175
+ execute "CREATE extension IF NOT EXISTS \"#{extension_name}\""
176
+ end
177
+
178
+ def change_table(table_name, options = {})
179
+ if supports_bulk_alter? && options[:bulk]
180
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
181
+ yield Table.new(table_name, recorder)
182
+ bulk_change_table(table_name, recorder.commands)
183
+ else
184
+ yield Table.new(table_name, self)
185
+ end
186
+ end
187
+
188
+ if RUBY_PLATFORM =~ /java/
189
+ # The activerecord-jbdc-adapter implements PostgreSQLAdapter#add_column differently from the active-record version
190
+ # so we have to patch that version in JRuby, but not in MRI/YARV
191
+ def add_column(table_name, column_name, type, options = {})
192
+ default = options[:default]
193
+ notnull = options[:null] == false
194
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
195
+
196
+ if options[:array]
197
+ sql_type << '[]'
198
+ end
199
+
200
+ # Add the column.
201
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{sql_type}")
202
+
203
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
204
+ change_column_null(table_name, column_name, false, default) if notnull
205
+ end
206
+ end
207
+
208
+ def type_cast_extended(value, column, part_array = false)
209
+ case value
210
+ when NilClass
211
+ if column.array && part_array
212
+ 'NULL'
213
+ elsif column.array && !part_array
214
+ value
215
+ else
216
+ type_cast_without_extended_types(value, column)
217
+ end
218
+ when Array
219
+ if column.array
220
+ array_to_string(value, column)
221
+ else
222
+ type_cast_without_extended_types(value, column)
223
+ end
224
+ else
225
+ type_cast_without_extended_types(value, column)
226
+ end
227
+ end
228
+
229
+ def type_cast_with_extended_types(value, column)
230
+ type_cast_extended(value, column)
231
+ end
232
+ alias_method_chain :type_cast, :extended_types
233
+
234
+ def quote_with_extended_types(value, column = nil)
235
+ if value.is_a? Array
236
+ "'#{array_to_string(value, column, true)}'"
237
+ elsif column.respond_to?(:array) && column.array && value =~ /^\{.*\}$/
238
+ "'#{value}'"
239
+ else
240
+ quote_without_extended_types(value, column)
241
+ end
242
+ end
243
+ alias_method_chain :quote, :extended_types
244
+
245
+ def opclasses
246
+ @opclasses ||= select_rows('SELECT opcname FROM pg_opclass').flatten.uniq
247
+ end
248
+
249
+ # this is based upon rails 4 changes to include different index methods
250
+ # Returns an array of indexes for the given table.
251
+ def indexes(table_name_, name_ = nil)
252
+ opclasses
253
+ # FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS.
254
+ result_ = query(<<-SQL, 'SCHEMA')
255
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
256
+ FROM pg_class t
257
+ INNER JOIN pg_index d ON t.oid = d.indrelid
258
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
259
+ WHERE i.relkind = 'i'
260
+ AND d.indisprimary = 'f'
261
+ AND t.relname = '#{table_name_}'
262
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
263
+ ORDER BY i.relname
264
+ SQL
265
+
266
+ result_.map do |row_|
267
+ index_name_ = row_[0]
268
+ unique_ = row_[1] == 't'
269
+ indkey_ = row_[2].split(" ")
270
+ inddef_ = row_[3]
271
+ oid_ = row_[4]
272
+
273
+ columns_ = query(<<-SQL, "SCHEMA")
274
+ SELECT a.attnum, a.attname, t.typname
275
+ FROM pg_attribute a, pg_type t
276
+ WHERE a.attrelid = #{oid_}
277
+ AND a.attnum IN (#{indkey_.join(",")})
278
+ AND a.atttypid = t.oid
279
+ SQL
280
+ columns_ = columns_.inject({}){ |h_, r_| h_[r_[0].to_s] = [r_[1], r_[2]]; h_ }
281
+ column_names_ = columns_.values_at(*indkey_).compact.map{ |a_| a_[0] }
282
+
283
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
284
+ desc_order_columns_ = inddef_.scan(/(\w+) DESC/).flatten
285
+ orders_ = desc_order_columns_.any? ? Hash[desc_order_columns_.map {|order_column_| [order_column_, :desc]}] : {}
286
+ where_ = inddef_.scan(/WHERE (.+)$/).flatten[0]
287
+ spatial_ = inddef_ =~ /using\s+gist/i && columns_.size == 1 &&
288
+ (columns_.values.first[1] == 'geometry' || columns_.values.first[1] == 'geography')
289
+
290
+ using_ = inddef_.scan(/USING (.+?) /).flatten[0].to_sym
291
+ if using_
292
+ index_op_ = inddef_.scan(/USING .+? \(.+? (#{opclasses.join('|')})\)/).flatten
293
+ index_op_ = index_op_[0].to_sym if index_op_.present?
294
+ end
295
+
296
+ if column_names_.present?
297
+ index_def_ = ::RGeo::ActiveRecord::SpatialIndexDefinition.new(table_name_, index_name_, unique_, column_names_, [], orders_, where_, spatial_ ? true : false)
298
+ index_def_.using = using_ if using_ && using_ != :btree && !spatial_
299
+ index_def_.index_opclass = index_op_ if using_ && using_ != :btree && !spatial_ && index_op_
300
+ index_def_
301
+ else
302
+ nil
303
+ end
304
+ #/changed
305
+ end.compact
306
+ end
307
+
308
+ def extensions
309
+ select_rows('select extname from pg_extension', 'extensions').map { |row| row[0] }.delete_if {|name| name == 'plpgsql'}
310
+ end
311
+
312
+ private
313
+
314
+ def array_to_string(value, column, encode_single_quotes = false)
315
+ "{#{value.map { |val| item_to_string(val, column, encode_single_quotes) }.join(',')}}"
316
+ end
317
+
318
+ def item_to_string(value, column, encode_single_quotes = false)
319
+ return 'NULL' if value.nil?
320
+
321
+ casted_value = type_cast_extended(value, column, true)
322
+
323
+ if casted_value.is_a?(String) && value.is_a?(String)
324
+ casted_value = casted_value.dup
325
+ # Encode backslashes. One backslash becomes 4 in the resulting SQL.
326
+ # (why 4, and not 2? Trial and error shows 4 works, 2 fails to parse.)
327
+ casted_value.gsub!('\\', '\\\\\\\\')
328
+ # Encode a bare " in the string as \"
329
+ casted_value.gsub!('"', '\\"')
330
+ # PostgreSQL parses the string values differently if they are quoted for
331
+ # use in a statement, or if it will be used as part of a bound argument.
332
+ # For directly-inserted values (UPDATE foo SET bar='{"array"}') we need to
333
+ # escape ' as ''. For bound arguments, do not escape them.
334
+ if encode_single_quotes
335
+ casted_value.gsub!("'", "''")
336
+ end
337
+
338
+ "\"#{casted_value}\""
339
+ else
340
+ casted_value
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end