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.
@@ -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
@@ -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
@@ -5,8 +5,8 @@ gemspec
5
5
  unless ENV['CI']
6
6
  if RUBY_PLATFORM =~ /java/
7
7
  gem 'ruby-debug'
8
- elsif RUBY_VERSION == '1.9.3'
9
- gem 'debugger', '~> 1.1.2'
8
+ elsif RUBY_VERSION == '1.9.3' || RUBY_VERSION == '2.0.0'
9
+ gem 'debugger'
10
10
  end
11
11
  end
12
12
  gem 'fivemat'
@@ -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, :index_type => :gin
10
- add_index :table_name, :column, :index_type => :gin, :index_opclass => :gin_trgm_ops
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
+ ```
@@ -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
+ ````
@@ -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 :index_type, :where, :index_opclass
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
- return IPAddr.new(string)
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[:index_type] ? " USING #{options[:index_type]} " : ""
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
- index_type = inddef.scan(/USING (.+?) /).flatten[0].to_sym
343
- if index_type
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.index_type = index_type if index_type && index_type != :btree
351
- index_def.index_opclass = index_op if index_type && index_type != :btree && index_op
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
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
 
10
10
  def overlap(opts)
11
11
  opts.each do |key, value|
12
- @scope = @scope.where(arel_table[key].array_overlap(value))
12
+ @scope = @scope.where(arel_table[key].overlap(value))
13
13
  end
14
14
  @scope
15
15
  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 << (':index_type => ' + index.index_type.inspect) if index.index_type
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
 
@@ -1 +1,2 @@
1
+ require 'postgres_ext/arel/nodes/array_nodes'
1
2
  require 'postgres_ext/arel/nodes/contained_within'
@@ -0,0 +1,9 @@
1
+ require 'arel/nodes/binary'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class Overlap < Arel::Nodes::Binary
6
+ def operator; '&&' end
7
+ end
8
+ end
9
+ end
@@ -6,7 +6,7 @@ module Arel
6
6
  end
7
7
 
8
8
  class ContainedWithinEquals < Arel::Nodes::Binary
9
- def operator; '<<='.symbolize end
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; '>>='.symbolize end
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
@@ -18,8 +18,8 @@ module Arel
18
18
  Nodes::ContainsEquals.new self, other
19
19
  end
20
20
 
21
- def array_overlap(other)
22
- Nodes::ArrayOverlap.new self, other
21
+ def overlap(other)
22
+ Nodes::Overlap.new self, other
23
23
  end
24
24
  end
25
25
  end
@@ -13,19 +13,21 @@ module Arel
13
13
  end
14
14
 
15
15
  def visit_Arel_Nodes_Contains o
16
- "#{visit o.left} >> #{visit o.right}"
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 visit_Arel_Nodes_ArrayOverlap o
24
- if Array === o.right
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
@@ -1,3 +1,3 @@
1
1
  module PostgresExt
2
- VERSION = '0.2.2'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -1,57 +1,77 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'Array Column Predicates' do
4
- let!(:adapter) { ActiveRecord::Base.connection }
4
+ describe 'Array Overlap' do
5
+ it 'converts Arel overlap statment' do
6
+ arel_table = Person.arel_table
5
7
 
6
- before do
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
- class ArelArray < ActiveRecord::Base
13
- attr_accessible :tags
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
- after do
18
- adapter.drop_table :arel_arrays
19
- Object.send(:remove_const, :ArelArray)
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 Overlap' do
23
- it 'converts Arel array_overlap statment' do
24
- arel_table = ArelArray.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].array_overlap(['tag','tag 2'])).to_sql.should match /&& '\{"tag","tag 2"\}'/
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 array_overlap statment' do
30
- arel_table = ArelArray.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].array_overlap([1,2])).to_sql.should match /&& '\{1,2\}'/
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 = ArelArray.arel_table
54
+ arel_table = Person.arel_table
37
55
 
38
- ArelArray.where(arel_table[:tag_ids].array_overlap([1,2])).count.should eq 0
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 = ArelArray.create!(:tags => ['one'])
43
- two = ArelArray.create!(:tags => ['two'])
44
- arel_table = ArelArray.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].array_overlap(['one'])).project(Arel.sql('*'))
47
- ArelArray.find_by_sql(query.to_sql).should include(one)
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].array_overlap(['two'])).project(Arel.sql('*'))
50
- ArelArray.find_by_sql(query.to_sql).should include(two)
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].array_overlap(['two','one'])).project(Arel.sql('*'))
53
- ArelArray.find_by_sql(query.to_sql).should include(two)
54
- ArelArray.find_by_sql(query.to_sql).should include(one)
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
@@ -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
- IpAddress.where(:address => IPAddr.new('127.0.0.1')).to_sql.should include("'127.0.0.1/32'")
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 = IpAddress.arel_table
12
+ arel_table = Person.arel_table
29
13
 
30
- arel_table.where(arel_table[:address].contained_within(IPAddr.new('127.0.0.1/24'))).to_sql.should match /<< '127.0.0.0\/24'/
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 = IpAddress.arel_table
20
+ arel_table = Person.arel_table
37
21
 
38
- arel_table.where(arel_table[:address].contained_within_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.should match /<<= '127.0.0.0\/24'/
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 = IpAddress.arel_table
27
+ arel_table = Person.arel_table
44
28
 
45
- arel_table.where(arel_table[:address].contains(IPAddr.new('127.0.0.1/24'))).to_sql.should match />> '127.0.0.0\/24'/
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 = IpAddress.arel_table
35
+ arel_table = Person.arel_table
52
36
 
53
- arel_table.where(arel_table[:address].contains_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.should match />>= '127.0.0.0\/24'/
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,3 +1,3 @@
1
1
  class Person < ActiveRecord::Base
2
- attr_accessible :ip
2
+ attr_accessible :ip, :tags
3
3
  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 :ip
5
- t.cidr :subnet
6
- t.inet :tag_ids, :array => true
7
- t.string :tags, :array => true
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
@@ -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.inet "tag_ids", :array => true
24
- t.string "tags", :array => true
25
- t.datetime "created_at", :null => false
26
- t.datetime "updated_at", :null => false
27
- end
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
- connection.create_table :index_types do |t|
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 { connection.drop_table :index_types }
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(:index_types, :col1, :index_type => :gin)
21
+ connection.add_index(:people, :tag_ids, :using => :gin)
19
22
  end.should_not raise_exception
20
23
 
21
- indexes = connection.indexes(:index_types)
22
- index_1 = indexes.detect { |c| c.columns.map(&:to_s) == ['col1']}
24
+ indexes = connection.indexes(:people)
25
+ index_1 = indexes.detect { |c| c.columns.map(&:to_s) == ['tag_ids']}
23
26
 
24
- index_1.index_type.to_s.should eq 'gin'
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(:index_types, :col2, :where => '(col2 > 50)')
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
- indexes = connection.indexes(:index_types)
33
- index_2 = indexes.detect { |c| c.columns.map(&:to_s) == ['col2']}
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
- index_2.where.should match /col2 > 50/
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(:index_types, :col3, :index_type => :gin, :index_opclass => :gin_trgm_ops)
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(:index_types)
44
- index_3 = indexes.detect { |c| c.columns.map(&:to_s) == ['col3']}
61
+ indexes = connection.indexes(:people)
62
+ index_3 = indexes.detect { |c| c.columns.map(&:to_s) == ['biography']}
45
63
 
46
- index_3.index_type.should eq :gin
47
- index_3.index_opclass.should eq :gin_trgm_ops
64
+ index_3.using.should eq :gin
65
+ index_3.index_opclass.should eq :gin_trgm_ops
48
66
  end
49
67
  end
@@ -222,14 +222,14 @@ describe 'Models with array columns' do
222
222
  end
223
223
  end
224
224
 
225
- describe 'array_overlap' do
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].array_overlap(["s'o{m}e"]))
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
@@ -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
- before do
7
- connection.create_table :index_types do |t|
8
- t.integer :col1, :array => true
9
- t.integer :col2
10
- t.text :col3
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(:index_types, :col1, :index_type => :gin)
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 /:index_type => :gin/
24
- output.should_not match /:index_type => :btree/
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(:index_types, :col2, :where => '(col2 > 50)')
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 => "\(col2 > 50\)"/
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(:index_types, :col3, :index_type => :gin, :index_opclass => :gin_trgm_ops)
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 /:index_type => :gin,\s+:index_opclass => :gin_trgm_ops/
44
+ output.should match /:using => :gin,\s+:index_opclass => :gin_trgm_ops/
46
45
  end
47
46
  end
@@ -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.2.2
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-02-14 00:00:00.000000000 Z
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: 1.8.23
233
+ rubygems_version: 2.0.3
253
234
  signing_key:
254
- specification_version: 3
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