postgres_ext 1.0.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +6 -47
- data/lib/postgres_ext/active_record/relation/predicate_builder.rb +36 -55
- data/lib/postgres_ext/active_record/relation/query_methods.rb +0 -14
- data/lib/postgres_ext/active_record.rb +0 -3
- data/lib/postgres_ext/version.rb +1 -1
- data/postgres_ext.gemspec +6 -5
- data/spec/arel/arel_spec.rb +0 -1
- data/spec/dummy/app/models/person.rb +0 -1
- data/spec/dummy/config/application.rb +0 -3
- data/spec/dummy/config/environments/development.rb +2 -7
- data/spec/dummy/config/environments/production.rb +1 -0
- data/spec/dummy/config/environments/test.rb +1 -5
- data/spec/dummy/db/migrate/20120501163758_create_people.rb +3 -3
- data/spec/dummy/db/schema.rb +15 -12
- data/spec/queries/sanity_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -2
- metadata +21 -105
- data/docs/indexes.md +0 -28
- data/docs/migrations.md +0 -92
- data/docs/type_casting.md +0 -70
- data/lib/postgres_ext/active_record/connection_adapters/postgres_adapter.rb +0 -537
- data/lib/postgres_ext/active_record/connection_adapters.rb +0 -1
- data/lib/postgres_ext/active_record/sanitization.rb +0 -30
- data/lib/postgres_ext/active_record/schema_dumper.rb +0 -157
- data/spec/columns/array_spec.rb +0 -119
- data/spec/columns/inet_spec.rb +0 -25
- data/spec/columns/ranges/daterange_spec.rb +0 -37
- data/spec/columns/ranges/int4range_spec.rb +0 -38
- data/spec/columns/ranges/int8range_spec.rb +0 -38
- data/spec/columns/ranges/numrange_spec.rb +0 -37
- data/spec/columns/ranges/tsrange_spec.rb +0 -37
- data/spec/migrations/active_record_migration_spec.rb +0 -29
- data/spec/migrations/array_spec.rb +0 -214
- data/spec/migrations/cidr_spec.rb +0 -26
- data/spec/migrations/citext_spec.rb +0 -32
- data/spec/migrations/ean13_spec.rb +0 -27
- data/spec/migrations/index_spec.rb +0 -67
- data/spec/migrations/inet_spec.rb +0 -26
- data/spec/migrations/macaddr_spec.rb +0 -26
- data/spec/migrations/ranges/daterange_spec.rb +0 -27
- data/spec/migrations/ranges/int4range_spec.rb +0 -27
- data/spec/migrations/ranges/int8range_spec.rb +0 -27
- data/spec/migrations/ranges/numrange_spec.rb +0 -27
- data/spec/migrations/ranges/tsrange_spec.rb +0 -27
- data/spec/migrations/ranges/tstzrange_spec.rb +0 -27
- data/spec/migrations/uuid_spec.rb +0 -26
- data/spec/models/array_spec.rb +0 -285
- data/spec/models/ean13_spec.rb +0 -57
- data/spec/models/inet_spec.rb +0 -88
- data/spec/models/ranges/daterange_spec.rb +0 -88
- data/spec/models/ranges/int4range_spec.rb +0 -85
- data/spec/models/ranges/int8range_spec.rb +0 -85
- data/spec/models/ranges/numrange_spec.rb +0 -85
- data/spec/models/ranges/tsrange_spec.rb +0 -89
- data/spec/models/ranges/tstzrange_spec.rb +0 -89
- data/spec/schema_dumper/array_spec.rb +0 -17
- data/spec/schema_dumper/cidr_spec.rb +0 -17
- data/spec/schema_dumper/citext_spec.rb +0 -17
- data/spec/schema_dumper/ean13_spec.rb +0 -17
- data/spec/schema_dumper/extension_spec.rb +0 -14
- data/spec/schema_dumper/index_spec.rb +0 -46
- data/spec/schema_dumper/inet_spec.rb +0 -17
- data/spec/schema_dumper/macaddr_spec.rb +0 -17
- data/spec/schema_dumper/ranges/daterange_spec.rb +0 -18
- data/spec/schema_dumper/ranges/int4range_spec.rb +0 -18
- data/spec/schema_dumper/ranges/int8range_spec.rb +0 -18
- data/spec/schema_dumper/ranges/numrange_spec.rb +0 -18
- data/spec/schema_dumper/ranges/tsrange_spec.rb +0 -18
- data/spec/schema_dumper/ranges/tstzrange_spec.rb +0 -17
- data/spec/schema_dumper/uuid_spec.rb +0 -17
data/docs/migrations.md
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
# Migration/Schema.rb support
|
2
|
-
|
3
|
-
## INET
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
create_table :testing do |t|
|
7
|
-
t.inet :inet_column
|
8
|
-
# or
|
9
|
-
t.inet :inet_column_1, :inet_column_2
|
10
|
-
# or
|
11
|
-
t.column :inet_column, :inet
|
12
|
-
end
|
13
|
-
```
|
14
|
-
|
15
|
-
## CIDR
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
create_table :testing do |t|
|
19
|
-
t.cidr :cidr_column
|
20
|
-
# or
|
21
|
-
t.cidr :cidr_column_1, :cidr_column_2
|
22
|
-
# or
|
23
|
-
t.column :cidr_column, :cidr
|
24
|
-
end
|
25
|
-
```
|
26
|
-
|
27
|
-
## MACADDR
|
28
|
-
|
29
|
-
```ruby
|
30
|
-
create_table :testing do |t|
|
31
|
-
t.macaddr :macaddr_column
|
32
|
-
# or
|
33
|
-
t.macaddr :macaddr_column_1, :macaddr_column_2
|
34
|
-
# or
|
35
|
-
t.column :macaddr_column, :macaddr
|
36
|
-
end
|
37
|
-
```
|
38
|
-
|
39
|
-
## UUID
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
create_table :testing do |t|
|
43
|
-
t.uuid :uuid_column
|
44
|
-
# or
|
45
|
-
t.uuid :uuid_column_1, :uuid_column_2
|
46
|
-
# or
|
47
|
-
t.column :uuid_column, :uuid
|
48
|
-
end
|
49
|
-
```
|
50
|
-
|
51
|
-
## CITEXT
|
52
|
-
|
53
|
-
```ruby
|
54
|
-
create_table :testing do |t|
|
55
|
-
t.citext :citext_column
|
56
|
-
# or
|
57
|
-
t.citext :citext_column_1, :citext_column_2
|
58
|
-
# or
|
59
|
-
t.column :citext_column, :citext
|
60
|
-
end
|
61
|
-
```
|
62
|
-
|
63
|
-
## Arrays
|
64
|
-
Arrays are created from any ActiveRecord supported datatype (including
|
65
|
-
ones added by postgres\_ext), and respect length constraints
|
66
|
-
|
67
|
-
```ruby
|
68
|
-
create_table :testing do |t|
|
69
|
-
t.integer :int_array, :array => true
|
70
|
-
# integer[]
|
71
|
-
t.integer :int_array, :array => true, :limit => 2
|
72
|
-
# smallint[]
|
73
|
-
t.string :macaddr_column_1, :array => true, :limit => 30
|
74
|
-
# char varying(30)[]
|
75
|
-
end
|
76
|
-
```
|
77
|
-
|
78
|
-
### Converting to Arrays
|
79
|
-
|
80
|
-
If you have an existing column with a string-delimited array (e.g. 'val1 val2 val3') convert that data using SQL in your migration.
|
81
|
-
|
82
|
-
```ruby
|
83
|
-
class AddLinkedArticleIdsToLinkSet < ActiveRecord::Migration
|
84
|
-
def change
|
85
|
-
add_column :link_sets, :linked_article_ids, :integer, :array => true, :default => []
|
86
|
-
execute <<-eos
|
87
|
-
UPDATE link_sets
|
88
|
-
SET linked_article_ids = cast (string_to_array(linked_articles_string, ' ') as integer[])
|
89
|
-
eos
|
90
|
-
end
|
91
|
-
end
|
92
|
-
````
|
data/docs/type_casting.md
DELETED
@@ -1,70 +0,0 @@
|
|
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
|
-
```
|
52
|
-
|
53
|
-
## Ranges
|
54
|
-
Like array objects, postgres\_ext supports range types as well.
|
55
|
-
Numrange, in4range, int8range, daterange, tsrange, and tstzrange are all
|
56
|
-
supported, but there are some notable caveats.
|
57
|
-
|
58
|
-
### Int and Date ranges
|
59
|
-
As integers and days are discrete measurements, PostgreSQL will
|
60
|
-
normalize these ranges as they are store in the database. PostgreSQL
|
61
|
-
will convert end-inclusive ranges to end-exclusive, meaning that `0..4`
|
62
|
-
becomes `0...5`. Developers should be aware of this when using integer
|
63
|
-
and date ranges, since ruby will treat these ranges differently from
|
64
|
-
PostgreSQL.
|
65
|
-
|
66
|
-
### Timestamp with and without timezone
|
67
|
-
Ruby/Rails 3.2.x does not support datetime ranges that begin or end with
|
68
|
-
infinity. Rails 4 has patched datetime and time so that infinity
|
69
|
-
terminated ranges work, but currently postgres\_ext has not patched the
|
70
|
-
required methods.
|
@@ -1,537 +0,0 @@
|
|
1
|
-
require 'active_record/connection_adapters/postgresql_adapter'
|
2
|
-
require 'ipaddr'
|
3
|
-
require 'pg_array_parser'
|
4
|
-
|
5
|
-
module ActiveRecord
|
6
|
-
module ConnectionAdapters
|
7
|
-
class IndexDefinition
|
8
|
-
attr_accessor :using, :where, :index_opclass
|
9
|
-
end
|
10
|
-
|
11
|
-
class PostgreSQLColumn
|
12
|
-
include PgArrayParser
|
13
|
-
attr_accessor :array
|
14
|
-
|
15
|
-
def initialize_with_extended_types(name, default, sql_type = nil, null = true)
|
16
|
-
if sql_type =~ /\[\]$/
|
17
|
-
@array = true
|
18
|
-
initialize_without_extended_types(name, default, sql_type[0..sql_type.length - 3], null)
|
19
|
-
@sql_type = sql_type
|
20
|
-
else
|
21
|
-
initialize_without_extended_types(name,default, sql_type, null)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
alias_method_chain :initialize, :extended_types
|
25
|
-
|
26
|
-
def klass_with_extended_types
|
27
|
-
case type
|
28
|
-
when :inet, :cidr then IPAddr
|
29
|
-
else
|
30
|
-
klass_without_extended_types
|
31
|
-
end
|
32
|
-
end
|
33
|
-
alias_method_chain :klass, :extended_types
|
34
|
-
|
35
|
-
def type_cast_with_extended_types(value)
|
36
|
-
return nil if value.nil?
|
37
|
-
return coder.load(value) if encoded?
|
38
|
-
|
39
|
-
klass = self.class
|
40
|
-
if self.array && String === value && value.start_with?('{') && value.end_with?('}')
|
41
|
-
string_to_array value
|
42
|
-
elsif self.array && Array === value
|
43
|
-
value
|
44
|
-
else
|
45
|
-
case type
|
46
|
-
when :inet, :cidr then klass.string_to_cidr_address(value)
|
47
|
-
when :numrange,:int4range,:int8range then klass.string_to_numeric_range(value,type)
|
48
|
-
when :daterange then klass.string_to_date_range(value)
|
49
|
-
when :tsrange,:tstzrange then klass.string_to_datetime_range(value)
|
50
|
-
else
|
51
|
-
type_cast_without_extended_types(value)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
alias_method_chain :type_cast, :extended_types
|
56
|
-
|
57
|
-
def string_to_array(value)
|
58
|
-
if Array === value
|
59
|
-
value
|
60
|
-
else
|
61
|
-
string_array = parse_pg_array value
|
62
|
-
type_cast_array(string_array)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def type_cast_array(array)
|
67
|
-
array.map do |value|
|
68
|
-
Array === value ? type_cast_array(value) : type_cast(value)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def number?
|
73
|
-
!self.array && super
|
74
|
-
end
|
75
|
-
|
76
|
-
def type_cast_code_with_extended_types(var_name)
|
77
|
-
klass = self.class.name
|
78
|
-
|
79
|
-
if self.array
|
80
|
-
"#{klass}.new('#{self.name}', #{self.default.nil? ? 'nil' : "'#{self.default}'"}, '#{self.sql_type}').string_to_array(#{var_name})"
|
81
|
-
else
|
82
|
-
case type
|
83
|
-
when :inet, :cidr then "#{klass}.string_to_cidr_address(#{var_name})"
|
84
|
-
when :numrange,:int4range,:int8range then "#{klass}.string_to_numeric_range(#{var_name},#{type.inspect})"
|
85
|
-
when :daterange then "#{klass}.string_to_date_range(#{var_name})"
|
86
|
-
when :tsrange,:tstzrange then "#{klass}.string_to_datetime_range(#{var_name})"
|
87
|
-
else
|
88
|
-
type_cast_code_without_extended_types(var_name)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
alias_method_chain :type_cast_code, :extended_types
|
93
|
-
|
94
|
-
if RUBY_PLATFORM =~ /java/
|
95
|
-
def default_value_with_extended_types(default)
|
96
|
-
case default
|
97
|
-
when /\A'(.*)'::(?:(num|int[48]|date|ts(tz)?)range)\z/
|
98
|
-
$1
|
99
|
-
else
|
100
|
-
default_value_without_extended_types(default)
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
alias_method_chain :default_value, :extended_types
|
105
|
-
end
|
106
|
-
|
107
|
-
class << self
|
108
|
-
unless RUBY_PLATFORM =~ /java/
|
109
|
-
def extract_value_from_default_with_extended_types(default)
|
110
|
-
case default
|
111
|
-
when /\A'(.*)'::(?:(num|int[48]|date|ts(tz)?)range)\z/
|
112
|
-
$1
|
113
|
-
else
|
114
|
-
extract_value_from_default_without_extended_types(default)
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
alias_method_chain :extract_value_from_default, :extended_types
|
119
|
-
end
|
120
|
-
|
121
|
-
def string_to_cidr_address(string)
|
122
|
-
return string unless String === string
|
123
|
-
|
124
|
-
if string.present?
|
125
|
-
IPAddr.new(string)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def string_to_numeric_range(value, type)
|
130
|
-
if type == :numrange
|
131
|
-
extract_range(value) do |end_value|
|
132
|
-
end_value.to_f
|
133
|
-
end
|
134
|
-
else
|
135
|
-
extract_range(value) do |end_value|
|
136
|
-
end_value.to_i
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def string_to_date_range(value)
|
142
|
-
extract_range(value) do |end_value|
|
143
|
-
Date.parse(end_value)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def string_to_datetime_range(value)
|
148
|
-
extract_range(value) do |end_value|
|
149
|
-
Time.parse(end_value)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
private
|
154
|
-
|
155
|
-
def extract_range(value, &conversion)
|
156
|
-
if Range === value
|
157
|
-
value
|
158
|
-
else
|
159
|
-
# Until 1.8.7 support is dropped, must use group numbers instead of named groups
|
160
|
-
#range_regex = /\A(?<open>\[|\()(?<start>.*?),(?<end>.*?)(?<close>\]|\))\z/
|
161
|
-
range_regex = /\A(\[|\()(.*?),(.*?)(\]|\))\z/
|
162
|
-
if match = value.match(range_regex)
|
163
|
-
if match = value.match(range_regex)
|
164
|
-
start_value = match[2].empty? ? -(1.0/0.0) : conversion.call(match[2])
|
165
|
-
end_value = match[3].empty? ? (1.0/0.0) : conversion.call(match[3])
|
166
|
-
|
167
|
-
end_exclusive = end_value != (1.0/0.0) && match[4] == ')'
|
168
|
-
Range.new start_value, end_value, end_exclusive
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
private
|
176
|
-
|
177
|
-
def simplified_type_with_extended_types(field_type)
|
178
|
-
case field_type
|
179
|
-
when 'uuid'
|
180
|
-
:uuid
|
181
|
-
when 'citext'
|
182
|
-
:citext
|
183
|
-
when 'inet'
|
184
|
-
:inet
|
185
|
-
when 'cidr'
|
186
|
-
:cidr
|
187
|
-
when 'macaddr'
|
188
|
-
:macaddr
|
189
|
-
when 'ean13'
|
190
|
-
:ean13
|
191
|
-
when 'int4range'
|
192
|
-
:int4range
|
193
|
-
when 'int8range'
|
194
|
-
:int8range
|
195
|
-
when 'numrange'
|
196
|
-
:numrange
|
197
|
-
when 'daterange'
|
198
|
-
:daterange
|
199
|
-
when 'tsrange'
|
200
|
-
:tsrange
|
201
|
-
when 'tstzrange'
|
202
|
-
:tstzrange
|
203
|
-
else
|
204
|
-
simplified_type_without_extended_types field_type
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
alias_method_chain :simplified_type, :extended_types
|
209
|
-
end
|
210
|
-
|
211
|
-
class PostgreSQLAdapter
|
212
|
-
class UnsupportedFeature < Exception; end
|
213
|
-
|
214
|
-
EXTENDED_TYPES = { :inet => {:name => 'inet'}, :cidr => {:name => 'cidr'}, :macaddr => {:name => 'macaddr'},
|
215
|
-
:uuid => {:name => 'uuid'}, :citext => {:name => 'citext'}, :ean13 => {:name => 'ean13'}, :numrange => {:name => 'numrange'},
|
216
|
-
:daterange => {:name => 'daterange'}, :int4range => {:name => 'int4range'}, :int8range => {:name => 'int8range'}, :tsrange => {:name => 'tsrange'}, :tstzrange => {:name => 'tstzrange'} }
|
217
|
-
|
218
|
-
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
|
219
|
-
attr_accessor :array
|
220
|
-
end
|
221
|
-
|
222
|
-
class TableDefinition
|
223
|
-
EXTENDED_TYPES.keys.map(&:to_s).each do |column_type|
|
224
|
-
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
225
|
-
def #{column_type}(*args) # def string(*args)
|
226
|
-
options = args.extract_options! # options = args.extract_options!
|
227
|
-
column_names = args # column_names = args
|
228
|
-
type = :'#{column_type}' # type = :string
|
229
|
-
column_names.each { |name| column(name, type, options) } # column_names.each { |name| column(name, type, options) }
|
230
|
-
end # end
|
231
|
-
EOV
|
232
|
-
end
|
233
|
-
|
234
|
-
def column(name, type=nil, options = {})
|
235
|
-
super
|
236
|
-
|
237
|
-
column = self[name]
|
238
|
-
column.array = options[:array]
|
239
|
-
|
240
|
-
self
|
241
|
-
end
|
242
|
-
|
243
|
-
private
|
244
|
-
|
245
|
-
def new_column_definition(base, name, type)
|
246
|
-
definition = ColumnDefinition.new base, name, type
|
247
|
-
@columns << definition
|
248
|
-
@columns_hash[name] = definition
|
249
|
-
definition
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
class Table < ActiveRecord::ConnectionAdapters::Table
|
254
|
-
EXTENDED_TYPES.keys.map(&:to_s).each do |column_type|
|
255
|
-
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
256
|
-
def #{column_type}(*args) # def string(*args)
|
257
|
-
options = args.extract_options! # options = args.extract_options!
|
258
|
-
column_names = args # column_names = args
|
259
|
-
type = :'#{column_type}' # type = :string
|
260
|
-
column_names.each do |name| # column_names.each do |name|
|
261
|
-
column = ColumnDefinition.new(@base, name.to_s, type) # column = ColumnDefinition.new(@base, name, type)
|
262
|
-
if options[:limit] # if options[:limit]
|
263
|
-
column.limit = options[:limit] # column.limit = options[:limit]
|
264
|
-
elsif native[type].is_a?(Hash) # elsif native[type].is_a?(Hash)
|
265
|
-
column.limit = native[type][:limit] # column.limit = native[type][:limit]
|
266
|
-
end # end
|
267
|
-
column.precision = options[:precision] # column.precision = options[:precision]
|
268
|
-
column.scale = options[:scale] # column.scale = options[:scale]
|
269
|
-
column.default = options[:default] # column.default = options[:default]
|
270
|
-
column.null = options[:null] # column.null = options[:null]
|
271
|
-
@base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
|
272
|
-
end # end
|
273
|
-
end # end
|
274
|
-
EOV
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
NATIVE_DATABASE_TYPES.merge!(EXTENDED_TYPES)
|
279
|
-
|
280
|
-
def supports_extensions?
|
281
|
-
postgresql_version > 90100
|
282
|
-
end
|
283
|
-
|
284
|
-
def add_column_options!(sql, options)
|
285
|
-
if options[:array] || options[:column].try(:array)
|
286
|
-
sql << '[]'
|
287
|
-
end
|
288
|
-
super
|
289
|
-
end
|
290
|
-
|
291
|
-
def add_index(table_name, column_name, options = {})
|
292
|
-
index_name, unique, index_columns, _ = add_index_options(table_name, column_name, options)
|
293
|
-
if options.is_a? Hash
|
294
|
-
index_type = options[:using] ? " USING #{options[:using]} " : ""
|
295
|
-
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
|
296
|
-
index_opclass = options[:index_opclass]
|
297
|
-
index_algorithm = options[:algorithm] == :concurrently ? ' CONCURRENTLY' : ''
|
298
|
-
|
299
|
-
if options[:algorithm].present? && options[:algorithm] != :concurrently
|
300
|
-
raise ArgumentError.new 'Algorithm must be one of the following: :concurrently'
|
301
|
-
end
|
302
|
-
end
|
303
|
-
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}"
|
304
|
-
end
|
305
|
-
|
306
|
-
def add_extension(extension_name, options={})
|
307
|
-
raise UnsupportedFeature.new('Extensions are not support by this version of PostgreSQL') unless supports_extensions?
|
308
|
-
execute "CREATE extension IF NOT EXISTS \"#{extension_name}\""
|
309
|
-
end
|
310
|
-
|
311
|
-
def change_table(table_name, options = {})
|
312
|
-
if supports_bulk_alter? && options[:bulk]
|
313
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
314
|
-
yield Table.new(table_name, recorder)
|
315
|
-
bulk_change_table(table_name, recorder.commands)
|
316
|
-
else
|
317
|
-
yield Table.new(table_name, self)
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
if RUBY_PLATFORM =~ /java/
|
322
|
-
# The activerecord-jbdc-adapter implements PostgreSQLAdapter#add_column differently from the active-record version
|
323
|
-
# so we have to patch that version in JRuby, but not in MRI/YARV
|
324
|
-
def add_column(table_name, column_name, type, options = {})
|
325
|
-
default = options[:default]
|
326
|
-
notnull = options[:null] == false
|
327
|
-
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
|
328
|
-
|
329
|
-
if options[:array]
|
330
|
-
sql_type << '[]'
|
331
|
-
end
|
332
|
-
|
333
|
-
# Add the column.
|
334
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{sql_type}")
|
335
|
-
|
336
|
-
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
337
|
-
change_column_null(table_name, column_name, false, default) if notnull
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
def type_cast_extended(value, column, part_array = false)
|
342
|
-
case value
|
343
|
-
when NilClass
|
344
|
-
if column.array && part_array
|
345
|
-
'NULL'
|
346
|
-
elsif column.array && !part_array
|
347
|
-
value
|
348
|
-
else
|
349
|
-
type_cast_without_extended_types(value, column)
|
350
|
-
end
|
351
|
-
when Float
|
352
|
-
if [:numrange,:int4range,:int8range,:daterange].include?(column.type)&& value.abs == (1.0/0.0)
|
353
|
-
''
|
354
|
-
else
|
355
|
-
type_cast_without_extended_types(value, column)
|
356
|
-
end
|
357
|
-
when Date, DateTime, Time
|
358
|
-
if column.type == :tstzrange
|
359
|
-
quoted_date(value)
|
360
|
-
elsif column.type == :tsrange
|
361
|
-
value.to_s(:db)
|
362
|
-
else
|
363
|
-
type_cast_without_extended_types(value, column)
|
364
|
-
end
|
365
|
-
when Range
|
366
|
-
range_to_string(value, column)
|
367
|
-
when Array
|
368
|
-
if column.array
|
369
|
-
array_to_string(value, column)
|
370
|
-
else
|
371
|
-
type_cast_without_extended_types(value, column)
|
372
|
-
end
|
373
|
-
when IPAddr
|
374
|
-
ipaddr_to_string(value)
|
375
|
-
else
|
376
|
-
type_cast_without_extended_types(value, column)
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
def type_cast_with_extended_types(value, column)
|
381
|
-
type_cast_extended(value, column)
|
382
|
-
end
|
383
|
-
alias_method_chain :type_cast, :extended_types
|
384
|
-
|
385
|
-
def quote_with_extended_types(value, column = nil)
|
386
|
-
if value.is_a? IPAddr
|
387
|
-
"'#{type_cast(value, column)}'"
|
388
|
-
elsif value.is_a? Array
|
389
|
-
"'#{array_to_string(value, column, true)}'"
|
390
|
-
elsif column.respond_to?(:array) && column.array && value =~ /^\{.*\}$/
|
391
|
-
"'#{value}'"
|
392
|
-
elsif value.is_a? Range
|
393
|
-
"'#{type_cast(value, column)}'"
|
394
|
-
else
|
395
|
-
quote_without_extended_types(value, column)
|
396
|
-
end
|
397
|
-
end
|
398
|
-
alias_method_chain :quote, :extended_types
|
399
|
-
|
400
|
-
def opclasses
|
401
|
-
@opclasses ||= select_rows('SELECT opcname FROM pg_opclass').flatten.uniq
|
402
|
-
end
|
403
|
-
|
404
|
-
# this is based upon rails 4 changes to include different index methods
|
405
|
-
# Returns an array of indexes for the given table.
|
406
|
-
def indexes(table_name, name = nil)
|
407
|
-
opclasses
|
408
|
-
result = select_rows(<<-SQL, name)
|
409
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
410
|
-
FROM pg_class t
|
411
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
412
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
413
|
-
WHERE i.relkind = 'i'
|
414
|
-
AND d.indisprimary = 'f'
|
415
|
-
AND t.relname = '#{table_name}'
|
416
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
417
|
-
ORDER BY i.relname
|
418
|
-
SQL
|
419
|
-
result.map do |row|
|
420
|
-
index_name = row[0]
|
421
|
-
unique = row[1] == 't'
|
422
|
-
indkey = row[2].split(" ")
|
423
|
-
inddef = row[3]
|
424
|
-
oid = row[4]
|
425
|
-
|
426
|
-
columns = Hash[select_rows(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
|
427
|
-
SELECT a.attnum::text, a.attname
|
428
|
-
FROM pg_attribute a
|
429
|
-
WHERE a.attrelid = #{oid}
|
430
|
-
AND a.attnum IN (#{indkey.join(",")})
|
431
|
-
SQL
|
432
|
-
column_names = columns.values_at(*indkey).compact
|
433
|
-
|
434
|
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
435
|
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
436
|
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
437
|
-
#changed from rails 3.2
|
438
|
-
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
439
|
-
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
440
|
-
if using
|
441
|
-
index_op = inddef.scan(/USING .+? \(.+? (#{opclasses.join('|')})\)/).flatten
|
442
|
-
index_op = index_op[0].to_sym if index_op.present?
|
443
|
-
end
|
444
|
-
if column_names.present?
|
445
|
-
index_def = IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
446
|
-
index_def.where = where
|
447
|
-
index_def.using = using if using && using != :btree
|
448
|
-
index_def.index_opclass = index_op if using && using != :btree && index_op
|
449
|
-
index_def
|
450
|
-
# else nil
|
451
|
-
end
|
452
|
-
#/changed
|
453
|
-
end.compact
|
454
|
-
end
|
455
|
-
|
456
|
-
def extensions
|
457
|
-
select_rows('select extname from pg_extension', 'extensions').map { |row| row[0] }.delete_if {|name| name == 'plpgsql'}
|
458
|
-
end
|
459
|
-
|
460
|
-
def change_column_with_extended_types(table_name, column_name, type, options = {})
|
461
|
-
if options[:array]
|
462
|
-
clear_cache!
|
463
|
-
quoted_table_name = quote_table_name(table_name)
|
464
|
-
|
465
|
-
if type.to_s =~ /string|text/
|
466
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}[] USING string_to_array(#{quote_column_name(column_name)}, ',')"
|
467
|
-
else
|
468
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}[]"
|
469
|
-
end
|
470
|
-
|
471
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
472
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
473
|
-
else
|
474
|
-
change_column_without_extended_types(table_name, column_name, type, options)
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
alias_method_chain :change_column, :extended_types
|
479
|
-
|
480
|
-
private
|
481
|
-
|
482
|
-
def ipaddr_to_string(value)
|
483
|
-
"#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
|
484
|
-
end
|
485
|
-
|
486
|
-
def array_to_string(value, column, encode_single_quotes = false)
|
487
|
-
"{#{value.map { |val| item_to_string(val, column, encode_single_quotes) }.join(',')}}"
|
488
|
-
end
|
489
|
-
|
490
|
-
def range_to_string(value, column)
|
491
|
-
"#{range_lower_bound_character value}#{type_cast value.begin, column},#{type_cast value.end, column}#{range_upper_bound_character value}"
|
492
|
-
end
|
493
|
-
|
494
|
-
def range_lower_bound_character(value)
|
495
|
-
if value.begin == -(1.0/0.0)
|
496
|
-
'('
|
497
|
-
else
|
498
|
-
'['
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
def range_upper_bound_character(value)
|
503
|
-
if value.end == (1.0/0.0) || value.exclude_end?
|
504
|
-
')'
|
505
|
-
else
|
506
|
-
']'
|
507
|
-
end
|
508
|
-
end
|
509
|
-
|
510
|
-
def item_to_string(value, column, encode_single_quotes = false)
|
511
|
-
return 'NULL' if value.nil?
|
512
|
-
|
513
|
-
casted_value = type_cast_extended(value, column, true)
|
514
|
-
|
515
|
-
if casted_value.is_a?(String) && value.is_a?(String)
|
516
|
-
casted_value = casted_value.dup
|
517
|
-
# Encode backslashes. One backslash becomes 4 in the resulting SQL.
|
518
|
-
# (why 4, and not 2? Trial and error shows 4 works, 2 fails to parse.)
|
519
|
-
casted_value.gsub!('\\', '\\\\\\\\')
|
520
|
-
# Encode a bare " in the string as \"
|
521
|
-
casted_value.gsub!('"', '\\"')
|
522
|
-
# PostgreSQL parses the string values differently if they are quoted for
|
523
|
-
# use in a statement, or if it will be used as part of a bound argument.
|
524
|
-
# For directly-inserted values (UPDATE foo SET bar='{"array"}') we need to
|
525
|
-
# escape ' as ''. For bound arguments, do not escape them.
|
526
|
-
if encode_single_quotes
|
527
|
-
casted_value.gsub!("'", "''")
|
528
|
-
end
|
529
|
-
|
530
|
-
"\"#{casted_value}\""
|
531
|
-
else
|
532
|
-
casted_value
|
533
|
-
end
|
534
|
-
end
|
535
|
-
end
|
536
|
-
end
|
537
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
require 'postgres_ext/active_record/connection_adapters/postgres_adapter'
|