postgres_ext 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +2 -2
- data/docs/indexes.md +11 -2
- data/docs/migrations.md +16 -0
- data/docs/querying.md +32 -0
- data/lib/postgres_ext/active_record/connection_adapters/postgres_adapter.rb +16 -8
- data/lib/postgres_ext/active_record/relation/query_methods.rb +1 -1
- data/lib/postgres_ext/active_record/schema_dumper.rb +3 -2
- data/lib/postgres_ext/arel/nodes.rb +1 -0
- data/lib/postgres_ext/arel/nodes/array_nodes.rb +9 -0
- data/lib/postgres_ext/arel/nodes/contained_within.rb +2 -6
- data/lib/postgres_ext/arel/predications.rb +2 -2
- data/lib/postgres_ext/arel/visitors/visitor.rb +9 -7
- data/lib/postgres_ext/version.rb +1 -1
- data/spec/arel/array_spec.rb +50 -30
- data/spec/arel/inet_spec.rb +9 -25
- data/spec/dummy/app/models/person.rb +1 -1
- data/spec/dummy/db/migrate/20120501163758_create_people.rb +6 -4
- data/spec/dummy/db/schema.rb +6 -13
- data/spec/migrations/index_spec.rb +38 -20
- data/spec/models/array_spec.rb +2 -2
- data/spec/models/inet_spec.rb +9 -0
- data/spec/queries/array_queries_spec.rb +15 -0
- data/spec/schema_dumper/index_spec.rb +13 -14
- data/spec/spec_helper.rb +1 -1
- metadata +8 -27
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 78f29ab859ad31017d1173ec07e12a0b5603b823
|
4
|
+
data.tar.gz: 3ae3cbc58fc1cdf7018721d01078855cc4fcc089
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 995ffd20fefb645371140742c7742cee1ebb9bc1cdbc9bd9bd7f9b32fa8057ac83fc661daed70bffb311700cb9c5e1b87ad64102008509ddf93ec6a7e2f11d14
|
7
|
+
data.tar.gz: ae873dde5e07a41dee3b5ccccbbdd6f06443f7a0ccb0e65ff3f2678a7a8fcb91d5323afb6bbcbf17be03a30dd9278ff6fee71b96b5e0bc7e863ef9274615d992
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## 0.3.0
|
2
|
+
|
3
|
+
* Adds support to create indexes concurrently - Dan McClain
|
4
|
+
* Changes using syntax, updates specs - Dan McClain
|
5
|
+
* Empty strings are converted to nil by string_to_cidr_address - Dan McClain
|
6
|
+
* Replaced .symbolize with .to_sym in arel nodes. - OMCnet Development Team
|
7
|
+
* Removes array_contains in favor of a column aware contains - Dan McClain
|
8
|
+
* Renames Arel array_overlap to overlap - Dan McClain
|
9
|
+
* Merge pull request #67 from jagregory/array_contains Array contains operator support - Dan McClain
|
10
|
+
* Update querying doc to include array_contains - James Gregory
|
11
|
+
* Array contains operator ( @> ) support - James Gregory
|
12
|
+
* how to use SQL to convert string-delimited arrays in docs - Turadg Aleahmad
|
13
|
+
* Check if connection responds to #support_extensions? before invoking it - Dirk von Grünigen
|
14
|
+
|
1
15
|
## 0.2.2
|
2
16
|
|
3
17
|
* Fixes issue with visit_Array monkey patch - Dan McClain (@danmcclain)
|
data/Gemfile
CHANGED
data/docs/indexes.md
CHANGED
@@ -6,8 +6,8 @@ Postgres\_ext allows you to specify index type and index operator
|
|
6
6
|
class at index creation.
|
7
7
|
|
8
8
|
```ruby
|
9
|
-
add_index :table_name, :column, :
|
10
|
-
add_index :table_name, :column, :
|
9
|
+
add_index :table_name, :column, :using => :gin
|
10
|
+
add_index :table_name, :column, :using => :gin, :index_opclass => :gin_trgm_ops
|
11
11
|
```
|
12
12
|
|
13
13
|
## Where clauses
|
@@ -17,3 +17,12 @@ Postgres\_ext allows you to specify a where clause at index creation.
|
|
17
17
|
```ruby
|
18
18
|
add_index :table_name, :column, :where => 'column < 50'
|
19
19
|
```
|
20
|
+
|
21
|
+
## Concurrent Indexes
|
22
|
+
|
23
|
+
Postgres\_ext allows you to create indexes concurrently using the
|
24
|
+
`:algorithm` option
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
add_index :table_name, :column, :algorithm => :concurrently
|
28
|
+
```
|
data/docs/migrations.md
CHANGED
@@ -74,3 +74,19 @@ create_table :testing do |t|
|
|
74
74
|
# char varying(30)[]
|
75
75
|
end
|
76
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/querying.md
CHANGED
@@ -42,6 +42,38 @@ User.where(user_arel[:tags].array_overlap(['one','two']))
|
|
42
42
|
# => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"tags\" && '{one,two}'
|
43
43
|
```
|
44
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
|
+
|
45
77
|
### ANY or ALL functions
|
46
78
|
|
47
79
|
When querying array columns, you have the ability to see if a predicate
|
@@ -5,7 +5,7 @@ require 'pg_array_parser'
|
|
5
5
|
module ActiveRecord
|
6
6
|
module ConnectionAdapters
|
7
7
|
class IndexDefinition
|
8
|
-
attr_accessor :
|
8
|
+
attr_accessor :using, :where, :index_opclass
|
9
9
|
end
|
10
10
|
|
11
11
|
class PostgreSQLColumn
|
@@ -92,7 +92,10 @@ module ActiveRecord
|
|
92
92
|
class << self
|
93
93
|
def string_to_cidr_address(string)
|
94
94
|
return string unless String === string
|
95
|
-
|
95
|
+
|
96
|
+
if string.present?
|
97
|
+
IPAddr.new(string)
|
98
|
+
end
|
96
99
|
end
|
97
100
|
end
|
98
101
|
|
@@ -217,11 +220,16 @@ module ActiveRecord
|
|
217
220
|
def add_index(table_name, column_name, options = {})
|
218
221
|
index_name, unique, index_columns, _ = add_index_options(table_name, column_name, options)
|
219
222
|
if options.is_a? Hash
|
220
|
-
index_type = options[:
|
223
|
+
index_type = options[:using] ? " USING #{options[:using]} " : ""
|
221
224
|
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
|
222
225
|
index_opclass = options[:index_opclass]
|
226
|
+
index_algorithm = options[:algorithm] == :concurrently ? ' CONCURRENTLY' : ''
|
227
|
+
|
228
|
+
if options[:algorithm].present? && options[:algorithm] != :concurrently
|
229
|
+
raise ArgumentError.new 'Algorithm must be one of the following: :concurrently'
|
230
|
+
end
|
223
231
|
end
|
224
|
-
execute "CREATE #{unique} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}#{index_type}(#{index_columns} #{index_opclass})#{index_options}"
|
232
|
+
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}"
|
225
233
|
end
|
226
234
|
|
227
235
|
def add_extension(extension_name, options={})
|
@@ -339,16 +347,16 @@ module ActiveRecord
|
|
339
347
|
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
340
348
|
#changed from rails 3.2
|
341
349
|
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
342
|
-
|
343
|
-
if
|
350
|
+
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
351
|
+
if using
|
344
352
|
index_op = inddef.scan(/USING .+? \(.+? (#{opclasses.join('|')})\)/).flatten
|
345
353
|
index_op = index_op[0].to_sym if index_op.present?
|
346
354
|
end
|
347
355
|
if column_names.present?
|
348
356
|
index_def = IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
349
357
|
index_def.where = where
|
350
|
-
index_def.
|
351
|
-
index_def.index_opclass = index_op if
|
358
|
+
index_def.using = using if using && using != :btree
|
359
|
+
index_def.index_opclass = index_op if using && using != :btree && index_op
|
352
360
|
index_def
|
353
361
|
# else nil
|
354
362
|
end
|
@@ -10,7 +10,8 @@ module ActiveRecord
|
|
10
10
|
def dump(stream)
|
11
11
|
header(stream)
|
12
12
|
# added
|
13
|
-
extensions(stream) if @connection.supports_extensions?
|
13
|
+
extensions(stream) if @connection.respond_to?(:supports_extensions?) &&
|
14
|
+
@connection.supports_extensions?
|
14
15
|
# /added
|
15
16
|
tables(stream)
|
16
17
|
trailer(stream)
|
@@ -118,7 +119,7 @@ module ActiveRecord
|
|
118
119
|
|
119
120
|
# changed from rails 2.3
|
120
121
|
statement_parts << (':where => ' + index.where.inspect) if index.where
|
121
|
-
statement_parts << (':
|
122
|
+
statement_parts << (':using => ' + index.using.inspect) if index.using
|
122
123
|
statement_parts << (':index_opclass => ' + index.index_opclass.inspect) if index.index_opclass.present?
|
123
124
|
# /changed
|
124
125
|
|
@@ -6,7 +6,7 @@ module Arel
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class ContainedWithinEquals < Arel::Nodes::Binary
|
9
|
-
def operator; '<<='.
|
9
|
+
def operator; '<<='.to_sym end
|
10
10
|
end
|
11
11
|
|
12
12
|
class Contains < Arel::Nodes::Binary
|
@@ -14,11 +14,7 @@ module Arel
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class ContainsEquals < Arel::Nodes::Binary
|
17
|
-
def operator; '>>='.
|
18
|
-
end
|
19
|
-
|
20
|
-
class ArrayOverlap < Arel::Nodes::Binary
|
21
|
-
def operator; '&&' end
|
17
|
+
def operator; '>>='.to_sym end
|
22
18
|
end
|
23
19
|
end
|
24
20
|
end
|
@@ -13,19 +13,21 @@ module Arel
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def visit_Arel_Nodes_Contains o
|
16
|
-
|
16
|
+
left_column = o.left.relation.engine.columns.find { |col| col.name == o.left.name.to_s }
|
17
|
+
|
18
|
+
if left_column && left_column.respond_to?(:array) && left_column.array
|
19
|
+
"#{visit o.left} @> #{visit o.right}"
|
20
|
+
else
|
21
|
+
"#{visit o.left} >> #{visit o.right}"
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def visit_Arel_Nodes_ContainsEquals o
|
20
26
|
"#{visit o.left} >>= #{visit o.right}"
|
21
27
|
end
|
22
28
|
|
23
|
-
def
|
24
|
-
|
25
|
-
"#{visit o.left} && #{visit o.right}"
|
26
|
-
else
|
27
|
-
"#{visit o.left} && #{visit o.right}"
|
28
|
-
end
|
29
|
+
def visit_Arel_Nodes_Overlap o
|
30
|
+
"#{visit o.left} && #{visit o.right}"
|
29
31
|
end
|
30
32
|
|
31
33
|
def visit_IPAddr value
|
data/lib/postgres_ext/version.rb
CHANGED
data/spec/arel/array_spec.rb
CHANGED
@@ -1,57 +1,77 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Array Column Predicates' do
|
4
|
-
|
4
|
+
describe 'Array Overlap' do
|
5
|
+
it 'converts Arel overlap statment' do
|
6
|
+
arel_table = Person.arel_table
|
5
7
|
|
6
|
-
|
7
|
-
adapter.create_table :arel_arrays, :force => true do |t|
|
8
|
-
t.string :tags, :array => true
|
9
|
-
t.integer :tag_ids, :array => true
|
8
|
+
arel_table.where(arel_table[:tags].overlap(['tag','tag 2'])).to_sql.should match /&& '\{"tag","tag 2"\}'/
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
it 'converts Arel overlap statment' do
|
12
|
+
arel_table = Person.arel_table
|
13
|
+
|
14
|
+
arel_table.where(arel_table[:tag_ids].overlap([1,2])).to_sql.should match /&& '\{1,2\}'/
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'works with count (and other predicates)' do
|
18
|
+
arel_table = Person.arel_table
|
19
|
+
|
20
|
+
Person.where(arel_table[:tag_ids].overlap([1,2])).count.should eq 0
|
14
21
|
end
|
15
|
-
end
|
16
22
|
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
it 'returns matched records' do
|
24
|
+
one = Person.create!(:tags => ['one'])
|
25
|
+
two = Person.create!(:tags => ['two'])
|
26
|
+
arel_table = Person.arel_table
|
27
|
+
|
28
|
+
query = arel_table.where(arel_table[:tags].overlap(['one'])).project(Arel.sql('*'))
|
29
|
+
Person.find_by_sql(query.to_sql).should include(one)
|
30
|
+
|
31
|
+
query = arel_table.where(arel_table[:tags].overlap(['two'])).project(Arel.sql('*'))
|
32
|
+
Person.find_by_sql(query.to_sql).should include(two)
|
33
|
+
|
34
|
+
query = arel_table.where(arel_table[:tags].overlap(['two','one'])).project(Arel.sql('*'))
|
35
|
+
Person.find_by_sql(query.to_sql).should include(two)
|
36
|
+
Person.find_by_sql(query.to_sql).should include(one)
|
37
|
+
end
|
20
38
|
end
|
21
39
|
|
22
|
-
describe 'Array
|
23
|
-
it 'converts Arel
|
24
|
-
arel_table =
|
40
|
+
describe 'Array Contains' do
|
41
|
+
it 'converts Arel contains statement and escapes strings' do
|
42
|
+
arel_table = Person.arel_table
|
25
43
|
|
26
|
-
arel_table.where(arel_table[:tags].
|
44
|
+
arel_table.where(arel_table[:tags].contains(['tag','tag 2'])).to_sql.should match /@> '\{"tag","tag 2"\}'/
|
27
45
|
end
|
28
46
|
|
29
|
-
it 'converts Arel
|
30
|
-
arel_table =
|
47
|
+
it 'converts Arel contains statement with numbers' do
|
48
|
+
arel_table = Person.arel_table
|
31
49
|
|
32
|
-
arel_table.where(arel_table[:tag_ids].
|
50
|
+
arel_table.where(arel_table[:tag_ids].contains([1,2])).to_sql.should match /@> '\{1,2\}'/
|
33
51
|
end
|
34
52
|
|
35
53
|
it 'works with count (and other predicates)' do
|
36
|
-
arel_table =
|
54
|
+
arel_table = Person.arel_table
|
37
55
|
|
38
|
-
|
56
|
+
Person.where(arel_table[:tag_ids].contains([1,2])).count.should eq 0
|
39
57
|
end
|
40
58
|
|
41
59
|
it 'returns matched records' do
|
42
|
-
one =
|
43
|
-
two =
|
44
|
-
arel_table =
|
60
|
+
one = Person.create!(:tags => ['one', 'two', 'three'])
|
61
|
+
two = Person.create!(:tags => ['one', 'three'])
|
62
|
+
arel_table = Person.arel_table
|
45
63
|
|
46
|
-
query = arel_table.where(arel_table[:tags].
|
47
|
-
|
64
|
+
query = arel_table.where(arel_table[:tags].contains(['one', 'two'])).project(Arel.sql('*'))
|
65
|
+
Person.find_by_sql(query.to_sql).should include one
|
66
|
+
Person.find_by_sql(query.to_sql).should_not include two
|
48
67
|
|
49
|
-
query = arel_table.where(arel_table[:tags].
|
50
|
-
|
68
|
+
query = arel_table.where(arel_table[:tags].contains(['one', 'three'])).project(Arel.sql('*'))
|
69
|
+
Person.find_by_sql(query.to_sql).should include one
|
70
|
+
Person.find_by_sql(query.to_sql).should include two
|
51
71
|
|
52
|
-
query = arel_table.where(arel_table[:tags].
|
53
|
-
|
54
|
-
|
72
|
+
query = arel_table.where(arel_table[:tags].contains(['two'])).project(Arel.sql('*'))
|
73
|
+
Person.find_by_sql(query.to_sql).should include one
|
74
|
+
Person.find_by_sql(query.to_sql).should_not include two
|
55
75
|
end
|
56
76
|
end
|
57
77
|
end
|
data/spec/arel/inet_spec.rb
CHANGED
@@ -1,56 +1,40 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'INET related AREL functions' do
|
4
|
-
let!(:adapter) { ActiveRecord::Base.connection }
|
5
|
-
before do
|
6
|
-
adapter.create_table :ip_addresses, :force => true do |t|
|
7
|
-
t.inet :address
|
8
|
-
end
|
9
|
-
|
10
|
-
class IpAddress < ActiveRecord::Base
|
11
|
-
attr_accessible :address
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
after do
|
16
|
-
adapter.drop_table :ip_addresses
|
17
|
-
Object.send(:remove_const, :IpAddress)
|
18
|
-
end
|
19
|
-
|
20
4
|
describe 'quoting IPAddr in sql statement' do
|
21
5
|
it 'properly converts IPAddr to quoted strings when passed as an argument to a where clause' do
|
22
|
-
|
6
|
+
Person.where(:ip => IPAddr.new('127.0.0.1')).to_sql.should include("'127.0.0.1/32'")
|
23
7
|
end
|
24
8
|
end
|
25
9
|
|
26
10
|
describe 'contained with (<<) operator' do
|
27
11
|
it 'converts Arel contained_within statements to <<' do
|
28
|
-
arel_table =
|
12
|
+
arel_table = Person.arel_table
|
29
13
|
|
30
|
-
arel_table.where(arel_table[:
|
14
|
+
arel_table.where(arel_table[:ip].contained_within(IPAddr.new('127.0.0.1/24'))).to_sql.should match /<< '127.0.0.0\/24'/
|
31
15
|
end
|
32
16
|
end
|
33
17
|
|
34
18
|
describe 'contained within or equals (<<=) operator' do
|
35
19
|
it 'converts Arel contained_within_or_equals statements to <<=' do
|
36
|
-
arel_table =
|
20
|
+
arel_table = Person.arel_table
|
37
21
|
|
38
|
-
arel_table.where(arel_table[:
|
22
|
+
arel_table.where(arel_table[:ip].contained_within_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.should match /<<= '127.0.0.0\/24'/
|
39
23
|
end
|
40
24
|
end
|
41
25
|
describe 'contains (>>) operator' do
|
42
26
|
it 'converts Arel contains statements to >>' do
|
43
|
-
arel_table =
|
27
|
+
arel_table = Person.arel_table
|
44
28
|
|
45
|
-
arel_table.where(arel_table[:
|
29
|
+
arel_table.where(arel_table[:ip].contains(IPAddr.new('127.0.0.1/24'))).to_sql.should match />> '127.0.0.0\/24'/
|
46
30
|
end
|
47
31
|
end
|
48
32
|
|
49
33
|
describe 'contains or equals (>>=) operator' do
|
50
34
|
it 'converts Arel contains_or_equals statements to >>=' do
|
51
|
-
arel_table =
|
35
|
+
arel_table = Person.arel_table
|
52
36
|
|
53
|
-
arel_table.where(arel_table[:
|
37
|
+
arel_table.where(arel_table[:ip].contains_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.should match />>= '127.0.0.0\/24'/
|
54
38
|
end
|
55
39
|
end
|
56
40
|
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
class CreatePeople < ActiveRecord::Migration
|
2
2
|
def change
|
3
3
|
create_table :people do |t|
|
4
|
-
t.inet
|
5
|
-
t.cidr
|
6
|
-
t.
|
7
|
-
t.string
|
4
|
+
t.inet :ip
|
5
|
+
t.cidr :subnet
|
6
|
+
t.integer :tag_ids, :array => true
|
7
|
+
t.string :tags, :array => true
|
8
|
+
t.text :biography
|
9
|
+
t.integer :lucky_number
|
8
10
|
|
9
11
|
t.timestamps
|
10
12
|
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -13,22 +13,15 @@
|
|
13
13
|
|
14
14
|
ActiveRecord::Schema.define(:version => 20120501163758) do
|
15
15
|
|
16
|
-
add_extension "hstore"
|
17
|
-
add_extension "pg_trgm"
|
18
|
-
add_extension "citext"
|
19
|
-
|
20
16
|
create_table "people", :force => true do |t|
|
21
17
|
t.inet "ip"
|
22
18
|
t.cidr "subnet"
|
23
|
-
t.
|
24
|
-
t.string "tags",
|
25
|
-
t.
|
26
|
-
t.
|
27
|
-
|
28
|
-
|
29
|
-
create_table "sanity_tests", :force => true do |t|
|
30
|
-
t.string "tags", :array => true
|
31
|
-
t.integer "tag_ids", :array => true
|
19
|
+
t.integer "tag_ids", :array => true
|
20
|
+
t.string "tags", :array => true
|
21
|
+
t.text "biography"
|
22
|
+
t.integer "lucky_number"
|
23
|
+
t.datetime "created_at", :null => false
|
24
|
+
t.datetime "updated_at", :null => false
|
32
25
|
end
|
33
26
|
|
34
27
|
end
|
@@ -2,48 +2,66 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe 'Index migrations' do
|
4
4
|
let!(:connection) { ActiveRecord::Base.connection }
|
5
|
-
|
5
|
+
let!(:stream) { StringIO.new }
|
6
6
|
before do
|
7
|
-
|
8
|
-
t.integer :col1, :array => true
|
9
|
-
t.integer :col2
|
10
|
-
t.text :col3
|
11
|
-
end
|
7
|
+
ActiveRecord::Base.logger = ActiveSupport::TaggedLogging.new(Logger.new stream)
|
12
8
|
end
|
13
9
|
|
14
|
-
after
|
10
|
+
after do
|
11
|
+
[:tag_ids, :lucky_number, :biography].each do |column|
|
12
|
+
begin
|
13
|
+
connection.remove_index :people, column
|
14
|
+
rescue ArgumentError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
15
18
|
|
16
19
|
it 'creates special index' do
|
17
20
|
lambda do
|
18
|
-
connection.add_index(:
|
21
|
+
connection.add_index(:people, :tag_ids, :using => :gin)
|
19
22
|
end.should_not raise_exception
|
20
23
|
|
21
|
-
indexes = connection.indexes(:
|
22
|
-
index_1 = indexes.detect { |c| c.columns.map(&:to_s) == ['
|
24
|
+
indexes = connection.indexes(:people)
|
25
|
+
index_1 = indexes.detect { |c| c.columns.map(&:to_s) == ['tag_ids']}
|
23
26
|
|
24
|
-
index_1.
|
27
|
+
index_1.using.should eq :gin
|
25
28
|
end
|
26
29
|
|
27
30
|
it 'creates indexes with where clauses' do
|
28
31
|
lambda do
|
29
|
-
connection.add_index(:
|
32
|
+
connection.add_index(:people, :lucky_number, :where => '(lucky_number > 50)')
|
33
|
+
end.should_not raise_exception
|
34
|
+
|
35
|
+
indexes = connection.indexes(:people)
|
36
|
+
index_2 = indexes.detect { |c| c.columns.map(&:to_s) == ['lucky_number']}
|
37
|
+
|
38
|
+
index_2.where.should match /lucky_number > 50/
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'creates index concurrently' do
|
42
|
+
lambda do
|
43
|
+
connection.add_index(:people, :lucky_number, :algorithm => :concurrently)
|
30
44
|
end.should_not raise_exception
|
31
45
|
|
32
|
-
|
33
|
-
|
46
|
+
output = stream.string
|
47
|
+
output.should match /CREATE INDEX CONCURRENTLY "index_people_on_lucky_number" ON "people"\(\"lucky_number\" \)/
|
48
|
+
end
|
34
49
|
|
35
|
-
|
50
|
+
it 'rejects bad algorithm arguments' do
|
51
|
+
lambda do
|
52
|
+
connection.add_index(:people, :lucky_number, :algorithm => :conurrently)
|
53
|
+
end.should raise_exception
|
36
54
|
end
|
37
55
|
|
38
56
|
it 'creates indexes with operator classes', :if => ActiveRecord::Base.connection.supports_extensions? do
|
39
57
|
lambda do
|
40
|
-
connection.add_index(:
|
58
|
+
connection.add_index(:people, :biography, :using => :gin, :index_opclass => :gin_trgm_ops)
|
41
59
|
end.should_not raise_exception
|
42
60
|
|
43
|
-
indexes = connection.indexes(:
|
44
|
-
index_3 = indexes.detect { |c| c.columns.map(&:to_s) == ['
|
61
|
+
indexes = connection.indexes(:people)
|
62
|
+
index_3 = indexes.detect { |c| c.columns.map(&:to_s) == ['biography']}
|
45
63
|
|
46
|
-
index_3.
|
47
|
-
index_3.index_opclass.should
|
64
|
+
index_3.using.should eq :gin
|
65
|
+
index_3.index_opclass.should eq :gin_trgm_ops
|
48
66
|
end
|
49
67
|
end
|
data/spec/models/array_spec.rb
CHANGED
@@ -222,14 +222,14 @@ describe 'Models with array columns' do
|
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
225
|
-
describe '
|
225
|
+
describe 'overlap' do
|
226
226
|
it "works" do
|
227
227
|
arel = User.arel_table
|
228
228
|
User.create(:nick_names => ['this'])
|
229
229
|
x = User.create
|
230
230
|
x.nick_names = ["s'o{m}e", 'thing']
|
231
231
|
x.save
|
232
|
-
u = User.where(arel[:nick_names].
|
232
|
+
u = User.where(arel[:nick_names].overlap(["s'o{m}e"]))
|
233
233
|
u.first.should_not be_nil
|
234
234
|
u.first.nick_names.should eq ["s'o{m}e", 'thing']
|
235
235
|
end
|
data/spec/models/inet_spec.rb
CHANGED
@@ -50,6 +50,15 @@ describe 'Models with inet columns' do
|
|
50
50
|
address.ip_address.should eq IPAddr.new('192.168.1.2')
|
51
51
|
end
|
52
52
|
|
53
|
+
it 'converts empty strings to nil' do
|
54
|
+
address = Address.create
|
55
|
+
address.ip_address = ''
|
56
|
+
address.save
|
57
|
+
|
58
|
+
address.reload
|
59
|
+
address.ip_address.should eq nil
|
60
|
+
end
|
61
|
+
|
53
62
|
it 'updates an address with an IPAddr' do
|
54
63
|
ip_addr_1 = IPAddr.new('192.168.0.1')
|
55
64
|
ip_addr_2 = IPAddr.new('192.168.1.2')
|
@@ -3,6 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe 'Array queries' do
|
4
4
|
let(:equality_regex) { %r{\"people\"\.\"tags\" = '\{\"working\"\}'} }
|
5
5
|
let(:overlap_regex) { %r{\"people\"\.\"tag_ids\" && '\{1,2\}'} }
|
6
|
+
let(:contains_regex) { %r{\"people\"\.\"tag_ids\" @> '\{1,2\}'} }
|
6
7
|
let(:any_regex) { %r{2 = ANY\(\"people\"\.\"tag_ids\"\)} }
|
7
8
|
let(:all_regex) { %r{2 = ALL\(\"people\"\.\"tag_ids\"\)} }
|
8
9
|
|
@@ -27,6 +28,20 @@ describe 'Array queries' do
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
31
|
+
describe '.where.contains(:column => value)' do
|
32
|
+
it 'generates the appropriate where clause' do
|
33
|
+
query = Person.where.contains(:tag_ids => [1,2])
|
34
|
+
query.to_sql.should match contains_regex
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows chaining' do
|
38
|
+
query = Person.where.contains(:tag_ids => [1,2]).where(:tags => ['working']).to_sql
|
39
|
+
|
40
|
+
query.should match contains_regex
|
41
|
+
query.should match equality_regex
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
30
45
|
describe '.where.any(:column => value)' do
|
31
46
|
it 'generates the appropriate where clause' do
|
32
47
|
query = Person.where.any(:tag_ids => 2)
|
@@ -3,45 +3,44 @@ require 'spec_helper'
|
|
3
3
|
describe 'Index schema dumper' do
|
4
4
|
let!(:connection) { ActiveRecord::Base.connection }
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
after do
|
7
|
+
[:tag_ids, :lucky_number, :biography].each do |column|
|
8
|
+
begin
|
9
|
+
connection.remove_index :people, column
|
10
|
+
rescue ArgumentError
|
11
|
+
end
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
after { connection.drop_table :index_types }
|
15
|
-
|
16
15
|
it 'handles index type parameters' do
|
17
|
-
connection.add_index(:
|
16
|
+
connection.add_index(:people, :tag_ids, :using => :gin)
|
18
17
|
|
19
18
|
stream = StringIO.new
|
20
19
|
ActiveRecord::SchemaDumper.dump(connection, stream)
|
21
20
|
output = stream.string
|
22
21
|
|
23
|
-
output.should match /:
|
24
|
-
output.should_not match /:
|
22
|
+
output.should match /:using => :gin/
|
23
|
+
output.should_not match /:using => :btree/
|
25
24
|
output.should_not match /:index_opclass =>/
|
26
25
|
end
|
27
26
|
|
28
27
|
it 'handles index where clauses' do
|
29
|
-
connection.add_index(:
|
28
|
+
connection.add_index(:people, :lucky_number, :where => '(lucky_number > 50)')
|
30
29
|
|
31
30
|
stream = StringIO.new
|
32
31
|
ActiveRecord::SchemaDumper.dump(connection, stream)
|
33
32
|
output = stream.string
|
34
33
|
|
35
|
-
output.should match /:where => "\(
|
34
|
+
output.should match /:where => "\(lucky_number > 50\)"/
|
36
35
|
end
|
37
36
|
|
38
37
|
it 'dumps index operator classes', :if => ActiveRecord::Base.connection.supports_extensions? do
|
39
|
-
connection.add_index(:
|
38
|
+
connection.add_index(:people, :biography, :using => :gin, :index_opclass => :gin_trgm_ops)
|
40
39
|
|
41
40
|
stream = StringIO.new
|
42
41
|
ActiveRecord::SchemaDumper.dump(connection, stream)
|
43
42
|
output = stream.string
|
44
43
|
|
45
|
-
output.should match /:
|
44
|
+
output.should match /:using => :gin,\s+:index_opclass => :gin_trgm_ops/
|
46
45
|
end
|
47
46
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,7 +5,7 @@ require File.expand_path('../dummy/config/environment.rb', __FILE__)
|
|
5
5
|
require 'rspec/rails'
|
6
6
|
require 'rspec/autorun'
|
7
7
|
require 'bourne'
|
8
|
-
require 'debugger' unless ENV['CI'] || (RUBY_PLATFORM =~ /java/)
|
8
|
+
#require 'debugger' unless ENV['CI'] || (RUBY_PLATFORM =~ /java/)
|
9
9
|
|
10
10
|
ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../')
|
11
11
|
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: postgres_ext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Dan McClain
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-03-26 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: activerecord
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ~>
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ~>
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,7 +27,6 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: pg_array_parser
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ~>
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ~>
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,7 +41,6 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rails
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
45
|
- - ~>
|
52
46
|
- !ruby/object:Gem::Version
|
@@ -54,7 +48,6 @@ dependencies:
|
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
52
|
- - ~>
|
60
53
|
- !ruby/object:Gem::Version
|
@@ -62,7 +55,6 @@ dependencies:
|
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: rspec-rails
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
59
|
- - ~>
|
68
60
|
- !ruby/object:Gem::Version
|
@@ -70,7 +62,6 @@ dependencies:
|
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
66
|
- - ~>
|
76
67
|
- !ruby/object:Gem::Version
|
@@ -78,7 +69,6 @@ dependencies:
|
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: bourne
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
73
|
- - ~>
|
84
74
|
- !ruby/object:Gem::Version
|
@@ -86,7 +76,6 @@ dependencies:
|
|
86
76
|
type: :development
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
80
|
- - ~>
|
92
81
|
- !ruby/object:Gem::Version
|
@@ -94,7 +83,6 @@ dependencies:
|
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: pg
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
87
|
- - ~>
|
100
88
|
- !ruby/object:Gem::Version
|
@@ -102,7 +90,6 @@ dependencies:
|
|
102
90
|
type: :development
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
94
|
- - ~>
|
108
95
|
- !ruby/object:Gem::Version
|
@@ -138,6 +125,7 @@ files:
|
|
138
125
|
- lib/postgres_ext/active_record/schema_dumper.rb
|
139
126
|
- lib/postgres_ext/arel.rb
|
140
127
|
- lib/postgres_ext/arel/nodes.rb
|
128
|
+
- lib/postgres_ext/arel/nodes/array_nodes.rb
|
141
129
|
- lib/postgres_ext/arel/nodes/contained_within.rb
|
142
130
|
- lib/postgres_ext/arel/predications.rb
|
143
131
|
- lib/postgres_ext/arel/visitors.rb
|
@@ -225,33 +213,26 @@ files:
|
|
225
213
|
- spec/spec_helper.rb
|
226
214
|
homepage: ''
|
227
215
|
licenses: []
|
216
|
+
metadata: {}
|
228
217
|
post_install_message:
|
229
218
|
rdoc_options: []
|
230
219
|
require_paths:
|
231
220
|
- lib
|
232
221
|
required_ruby_version: !ruby/object:Gem::Requirement
|
233
|
-
none: false
|
234
222
|
requirements:
|
235
|
-
- -
|
223
|
+
- - '>='
|
236
224
|
- !ruby/object:Gem::Version
|
237
225
|
version: '0'
|
238
|
-
segments:
|
239
|
-
- 0
|
240
|
-
hash: 3137999377489191858
|
241
226
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
|
-
none: false
|
243
227
|
requirements:
|
244
|
-
- -
|
228
|
+
- - '>='
|
245
229
|
- !ruby/object:Gem::Version
|
246
230
|
version: '0'
|
247
|
-
segments:
|
248
|
-
- 0
|
249
|
-
hash: 3137999377489191858
|
250
231
|
requirements: []
|
251
232
|
rubyforge_project:
|
252
|
-
rubygems_version:
|
233
|
+
rubygems_version: 2.0.3
|
253
234
|
signing_key:
|
254
|
-
specification_version:
|
235
|
+
specification_version: 4
|
255
236
|
summary: Extends ActiveRecord to handle native PostgreSQL data types
|
256
237
|
test_files:
|
257
238
|
- spec/arel/arel_spec.rb
|