postgres_ext 0.2.2 → 0.3.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.
@@ -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