postgres_ext 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +15 -5
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +2 -2
  5. data/README.md +4 -0
  6. data/docs/type_casting.md +19 -0
  7. data/lib/postgres_ext/active_record/connection_adapters/postgres_adapter.rb +149 -3
  8. data/lib/postgres_ext/active_record/relation/predicate_builder.rb +1 -1
  9. data/lib/postgres_ext/version.rb +1 -1
  10. data/spec/columns/array_spec.rb +3 -4
  11. data/spec/columns/ranges/daterange_spec.rb +37 -0
  12. data/spec/columns/ranges/int4range_spec.rb +38 -0
  13. data/spec/columns/ranges/int8range_spec.rb +38 -0
  14. data/spec/columns/ranges/numrange_spec.rb +37 -0
  15. data/spec/columns/ranges/tsrange_spec.rb +37 -0
  16. data/spec/dummy/app/models/person.rb +1 -1
  17. data/spec/dummy/config/application.rb +1 -1
  18. data/spec/dummy/db/migrate/20120501163758_create_people.rb +1 -0
  19. data/spec/dummy/db/schema.rb +9 -8
  20. data/spec/migrations/array_spec.rb +20 -0
  21. data/spec/migrations/ranges/daterange_spec.rb +27 -0
  22. data/spec/migrations/ranges/int4range_spec.rb +27 -0
  23. data/spec/migrations/ranges/int8range_spec.rb +27 -0
  24. data/spec/migrations/ranges/numrange_spec.rb +27 -0
  25. data/spec/migrations/ranges/tsrange_spec.rb +27 -0
  26. data/spec/migrations/ranges/tstzrange_spec.rb +27 -0
  27. data/spec/models/ranges/daterange_spec.rb +88 -0
  28. data/spec/models/ranges/int4range_spec.rb +85 -0
  29. data/spec/models/ranges/int8range_spec.rb +85 -0
  30. data/spec/models/ranges/numrange_spec.rb +85 -0
  31. data/spec/models/ranges/tsrange_spec.rb +89 -0
  32. data/spec/models/ranges/tstzrange_spec.rb +89 -0
  33. data/spec/queries/sanity_spec.rb +1 -0
  34. data/spec/schema_dumper/array_spec.rb +1 -1
  35. data/spec/schema_dumper/ranges/daterange_spec.rb +18 -0
  36. data/spec/schema_dumper/ranges/int4range_spec.rb +18 -0
  37. data/spec/schema_dumper/ranges/int8range_spec.rb +18 -0
  38. data/spec/schema_dumper/ranges/numrange_spec.rb +18 -0
  39. data/spec/schema_dumper/ranges/tsrange_spec.rb +18 -0
  40. data/spec/schema_dumper/ranges/tstzrange_spec.rb +17 -0
  41. metadata +53 -27
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'int8range column' do
4
+ let!(:adapter) { ActiveRecord::Base.connection }
5
+ let!(:int8_range_column) { ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new 'field', nil, 'int8range'}
6
+
7
+ describe '#type_class' do
8
+ it 'converts an end-inclusive PostgreSQL integer range to a Ruby range' do
9
+ int8_range_column.type_cast('[0,4]').should eq 0..4
10
+ end
11
+
12
+ it 'converts an end-exclusive PostgreSQL integer range to a Ruby range' do
13
+ int8_range_column.type_cast('[0,4)').should eq 0...4
14
+ end
15
+
16
+ it 'converts an infinite PostgreSQL integer range to a Ruby range' do
17
+ int8_range_column.type_cast('(,4)').should eq -(1.0/0.0)...4
18
+ int8_range_column.type_cast('[0,)').should eq 0..(1.0/0.0)
19
+ end
20
+ end
21
+
22
+ describe 'int8 range to SQL statment conversion' do
23
+ it 'returns an end-inclusive PostgreSQL range' do
24
+ value = int8_range_column.type_cast('[0,4]')
25
+ adapter.type_cast(value, int8_range_column).should eq '[0,4]'
26
+ end
27
+ it 'returns an end-exclusive PostgreSQL range' do
28
+ value = int8_range_column.type_cast('[0,4)')
29
+ adapter.type_cast(value, int8_range_column).should eq '[0,4)'
30
+ end
31
+ it 'converts an infinite PostgreSQL integer range to a Ruby range' do
32
+ value = int8_range_column.type_cast('(,4)')
33
+ adapter.type_cast(value, int8_range_column).should eq '(,4)'
34
+ value = int8_range_column.type_cast('[0,)')
35
+ adapter.type_cast(value, int8_range_column).should eq '[0,)'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'numrange column' do
4
+ let!(:adapter) { ActiveRecord::Base.connection }
5
+ let!(:numeric_range_column) { ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new 'field', nil, 'numrange'}
6
+ describe '#type_class' do
7
+ it 'converts an end-inclusive PostgreSQL integer range to a Ruby range' do
8
+ numeric_range_column.type_cast('[0,4.1]').should eq 0.0..4.1
9
+ end
10
+
11
+ it 'converts an end-exclusive PostgreSQL integer range to a Ruby range' do
12
+ numeric_range_column.type_cast('[0,4)').should eq 0.0...4.0
13
+ end
14
+
15
+ it 'converts an infinite PostgreSQL integer range to a Ruby range' do
16
+ numeric_range_column.type_cast('(,4)').should eq -(1.0/0.0)...4.0
17
+ numeric_range_column.type_cast('[0,)').should eq 0.0..(1.0/0.0)
18
+ end
19
+ end
20
+
21
+ describe 'Numeric range to SQL statment conversion' do
22
+ it 'returns an end-inclusive PostgreSQL range' do
23
+ value = numeric_range_column.type_cast('[0,4.0]')
24
+ adapter.type_cast(value, numeric_range_column).should eq '[0.0,4.0]'
25
+ end
26
+ it 'returns an end-exclusive PostgreSQL range' do
27
+ value = numeric_range_column.type_cast('[0,4.0)')
28
+ adapter.type_cast(value, numeric_range_column).should eq '[0.0,4.0)'
29
+ end
30
+ it 'converts an infinite PostgreSQL integer range to a Ruby range' do
31
+ value = numeric_range_column.type_cast('(,4.0)')
32
+ adapter.type_cast(value, numeric_range_column).should eq '(,4.0)'
33
+ value = numeric_range_column.type_cast('[0,)')
34
+ adapter.type_cast(value, numeric_range_column).should eq '[0.0,)'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'tsrange column' do
4
+ let!(:adapter) { ActiveRecord::Base.connection }
5
+ let!(:ts_range_column) { ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new 'field', nil, 'tsrange'}
6
+
7
+ describe '#type_class' do
8
+ it 'converts an end-inclusive PostgreSQL integer range to a Ruby range' do
9
+ ts_range_column.type_cast('[2011-01-01 12:34:00,2012-01-31 08:00:01]').should eq Time.new(2011, 01, 01, 12, 34)..Time.new(2012, 01, 31, 8, 0, 1)
10
+ end
11
+
12
+ it 'converts an end-exclusive PostgreSQL integer range to a Ruby range' do
13
+ ts_range_column.type_cast('[2011-01-01 12:34:00,2012-01-31 08:00:01)').should eq Time.new(2011, 01, 01, 12, 34)...Time.new(2012, 01, 31, 8, 0, 1)
14
+ end
15
+
16
+ #it 'converts an infinite PostgreSQL integer range to a Ruby range' do
17
+ ## Cannot have a range from -Infinity to a ts
18
+ ## ts_range_column.type_cast('(,2011-01-01)').should eq -(1.0/0.0)...4
19
+ #ts_range_column.type_cast('[2011-01-01 12:34:00,)').should eq Time.new(2011,01,01, 12, 34)..(1.0/0.0)
20
+ #end
21
+ end
22
+
23
+ describe 'Time range to SQL statment conversion' do
24
+ it 'returns an end-inclusive PostgreSQL range' do
25
+ value = ts_range_column.type_cast('[2011-01-01 12:34:00,2012-01-31 08:00:01]')
26
+ adapter.type_cast(value, ts_range_column).should eq '[2011-01-01 12:34:00,2012-01-31 08:00:01]'
27
+ end
28
+ it 'returns an end-exclusive PostgreSQL range' do
29
+ value = ts_range_column.type_cast('[2011-01-01 12:34:00,2012-01-31 08:00:01)')
30
+ adapter.type_cast(value, ts_range_column).should eq '[2011-01-01 12:34:00,2012-01-31 08:00:01)'
31
+ end
32
+ #it 'converts an infinite PostgreSQL integer range to a Ruby range' do
33
+ #value = ts_range_column.type_cast('[2011-01-01 12:34:00,)')
34
+ #adapter.type_cast(value, ts_range_column).should eq '[2011-01-01 12:34:00,)'
35
+ #end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  class Person < ActiveRecord::Base
2
- attr_accessible :ip, :tags
2
+ attr_accessible :ip, :tags, :tag_ids, :biography, :lucky_number, :int_range
3
3
  end
@@ -27,7 +27,7 @@ module Dummy
27
27
 
28
28
  # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29
29
  # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
30
- # config.time_zone = 'Central Time (US & Canada)'
30
+ config.time_zone = 'Eastern Time (US & Canada)'
31
31
 
32
32
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
33
33
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
@@ -7,6 +7,7 @@ class CreatePeople < ActiveRecord::Migration
7
7
  t.string :tags, :array => true
8
8
  t.text :biography
9
9
  t.integer :lucky_number
10
+ t.numrange :num_range
10
11
 
11
12
  t.timestamps
12
13
  end
@@ -14,14 +14,15 @@
14
14
  ActiveRecord::Schema.define(:version => 20120501163758) do
15
15
 
16
16
  create_table "people", :force => true do |t|
17
- t.inet "ip"
18
- t.cidr "subnet"
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
17
+ t.inet "ip"
18
+ t.cidr "subnet"
19
+ t.integer "tag_ids", :array => true
20
+ t.string "tags", :array => true
21
+ t.text "biography"
22
+ t.integer "lucky_number"
23
+ t.num_rang "num_range"
24
+ t.datetime "created_at", :null => false
25
+ t.datetime "updated_at", :null => false
25
26
  end
26
27
 
27
28
  end
@@ -82,6 +82,26 @@ describe 'Array migrations' do
82
82
  end
83
83
  end
84
84
 
85
+ context 'Change Column' do
86
+ after { connection.drop_table :data_types }
87
+ it 'updates the column definitions' do
88
+ lambda do
89
+ connection.create_table :data_types do |t|
90
+ t.integer :array_1, :array => true, :default => []
91
+ end
92
+
93
+ connection.change_column :data_types, :array_1, :integer, :array => true, :default => [], :null => false
94
+ end.should_not raise_exception
95
+
96
+ columns = connection.columns(:data_types)
97
+
98
+ array_1 = columns.detect { |c| c.name == 'array_1'}
99
+ array_1.sql_type.should eq 'integer[]'
100
+ array_1.default.should eq []
101
+ array_1.null.should be_false
102
+ end
103
+ end
104
+
85
105
  context 'Default Values' do
86
106
  describe 'String defaults' do
87
107
  after { connection.drop_table :default_strings }
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'daterange migrations' do
4
+ let!(:connection) { ActiveRecord::Base.connection }
5
+ after { connection.drop_table :data_types }
6
+
7
+ it 'creates an daterange column' do
8
+ lambda do
9
+ connection.create_table :data_types do |t|
10
+ t.daterange :date_range_1
11
+ t.daterange :date_range_2, :date_range_3
12
+ t.column :date_range_4, :daterange
13
+ end
14
+ end.should_not raise_exception
15
+
16
+ columns = connection.columns(:data_types)
17
+ date_range_1 = columns.detect { |c| c.name == 'date_range_1'}
18
+ date_range_2 = columns.detect { |c| c.name == 'date_range_2'}
19
+ date_range_3 = columns.detect { |c| c.name == 'date_range_3'}
20
+ date_range_4 = columns.detect { |c| c.name == 'date_range_4'}
21
+
22
+ date_range_1.sql_type.should eq 'daterange'
23
+ date_range_2.sql_type.should eq 'daterange'
24
+ date_range_3.sql_type.should eq 'daterange'
25
+ date_range_4.sql_type.should eq 'daterange'
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'int4range migrations' do
4
+ let!(:connection) { ActiveRecord::Base.connection }
5
+ after { connection.drop_table :data_types }
6
+
7
+ it 'creates an numrange column' do
8
+ lambda do
9
+ connection.create_table :data_types do |t|
10
+ t.int4range :range_1
11
+ t.int4range :range_2, :range_3
12
+ t.column :range_4, :int4range
13
+ end
14
+ end.should_not raise_exception
15
+
16
+ columns = connection.columns(:data_types)
17
+ range_1 = columns.detect { |c| c.name == 'range_1'}
18
+ range_2 = columns.detect { |c| c.name == 'range_2'}
19
+ range_3 = columns.detect { |c| c.name == 'range_3'}
20
+ range_4 = columns.detect { |c| c.name == 'range_4'}
21
+
22
+ range_1.sql_type.should eq 'int4range'
23
+ range_2.sql_type.should eq 'int4range'
24
+ range_3.sql_type.should eq 'int4range'
25
+ range_4.sql_type.should eq 'int4range'
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'int8range migrations' do
4
+ let!(:connection) { ActiveRecord::Base.connection }
5
+ after { connection.drop_table :data_types }
6
+
7
+ it 'creates an numrange column' do
8
+ lambda do
9
+ connection.create_table :data_types do |t|
10
+ t.int8range :range_1
11
+ t.int8range :range_2, :range_3
12
+ t.column :range_4, :int8range
13
+ end
14
+ end.should_not raise_exception
15
+
16
+ columns = connection.columns(:data_types)
17
+ range_1 = columns.detect { |c| c.name == 'range_1'}
18
+ range_2 = columns.detect { |c| c.name == 'range_2'}
19
+ range_3 = columns.detect { |c| c.name == 'range_3'}
20
+ range_4 = columns.detect { |c| c.name == 'range_4'}
21
+
22
+ range_1.sql_type.should eq 'int8range'
23
+ range_2.sql_type.should eq 'int8range'
24
+ range_3.sql_type.should eq 'int8range'
25
+ range_4.sql_type.should eq 'int8range'
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'numrange migrations' do
4
+ let!(:connection) { ActiveRecord::Base.connection }
5
+ after { connection.drop_table :data_types }
6
+
7
+ it 'creates an numrange column' do
8
+ lambda do
9
+ connection.create_table :data_types do |t|
10
+ t.numrange :num_range_1
11
+ t.numrange :num_range_2, :num_range_3
12
+ t.column :num_range_4, :numrange
13
+ end
14
+ end.should_not raise_exception
15
+
16
+ columns = connection.columns(:data_types)
17
+ num_range_1 = columns.detect { |c| c.name == 'num_range_1'}
18
+ num_range_2 = columns.detect { |c| c.name == 'num_range_2'}
19
+ num_range_3 = columns.detect { |c| c.name == 'num_range_3'}
20
+ num_range_4 = columns.detect { |c| c.name == 'num_range_4'}
21
+
22
+ num_range_1.sql_type.should eq 'numrange'
23
+ num_range_2.sql_type.should eq 'numrange'
24
+ num_range_3.sql_type.should eq 'numrange'
25
+ num_range_4.sql_type.should eq 'numrange'
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'tsrange migrations' do
4
+ let!(:connection) { ActiveRecord::Base.connection }
5
+ after { connection.drop_table :data_types }
6
+
7
+ it 'creates an tsrange column' do
8
+ lambda do
9
+ connection.create_table :data_types do |t|
10
+ t.tsrange :ts_range_1
11
+ t.tsrange :ts_range_2, :ts_range_3
12
+ t.column :ts_range_4, :tsrange
13
+ end
14
+ end.should_not raise_exception
15
+
16
+ columns = connection.columns(:data_types)
17
+ ts_range_1 = columns.detect { |c| c.name == 'ts_range_1'}
18
+ ts_range_2 = columns.detect { |c| c.name == 'ts_range_2'}
19
+ ts_range_3 = columns.detect { |c| c.name == 'ts_range_3'}
20
+ ts_range_4 = columns.detect { |c| c.name == 'ts_range_4'}
21
+
22
+ ts_range_1.sql_type.should eq 'tsrange'
23
+ ts_range_2.sql_type.should eq 'tsrange'
24
+ ts_range_3.sql_type.should eq 'tsrange'
25
+ ts_range_4.sql_type.should eq 'tsrange'
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'tstzrange migrations' do
4
+ let!(:connection) { ActiveRecord::Base.connection }
5
+ after { connection.drop_table :data_types }
6
+
7
+ it 'creates an tstzrange column' do
8
+ lambda do
9
+ connection.create_table :data_types do |t|
10
+ t.tstzrange :tstz_range_1
11
+ t.tstzrange :tstz_range_2, :tstz_range_3
12
+ t.column :tstz_range_4, :tstzrange
13
+ end
14
+ end.should_not raise_exception
15
+
16
+ columns = connection.columns(:data_types)
17
+ tstz_range_1 = columns.detect { |c| c.name == 'tstz_range_1'}
18
+ tstz_range_2 = columns.detect { |c| c.name == 'tstz_range_2'}
19
+ tstz_range_3 = columns.detect { |c| c.name == 'tstz_range_3'}
20
+ tstz_range_4 = columns.detect { |c| c.name == 'tstz_range_4'}
21
+
22
+ tstz_range_1.sql_type.should eq 'tstzrange'
23
+ tstz_range_2.sql_type.should eq 'tstzrange'
24
+ tstz_range_3.sql_type.should eq 'tstzrange'
25
+ tstz_range_4.sql_type.should eq 'tstzrange'
26
+ end
27
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Models with daterange columns' do
4
+ let!(:adapter) { ActiveRecord::Base.connection }
5
+
6
+ context 'no default value, range' do
7
+ before do
8
+ adapter.create_table :date_rangers, :force => true do |t|
9
+ t.daterange :best_estimate
10
+ end
11
+ class DateRanger < ActiveRecord::Base
12
+ attr_accessible :best_estimate
13
+ end
14
+ end
15
+
16
+ after do
17
+ adapter.drop_table :date_rangers
18
+ Object.send(:remove_const, :DateRanger)
19
+ end
20
+
21
+ describe '#create' do
22
+ it 'creates an record when there is no assignment' do
23
+ range = DateRanger.create()
24
+ range.reload
25
+ range.best_estimate.should eq nil
26
+ end
27
+
28
+ it 'creates an record with a range' do
29
+ date_range = Date.new(2011, 01, 01)..Date.new(2012, 01, 31)
30
+ range = DateRanger.create( :best_estimate => date_range)
31
+ range.reload
32
+ range.best_estimate.should eq Date.new(2011, 01, 01)...Date.new(2012, 02, 01)
33
+ end
34
+ end
35
+
36
+ describe 'range assignment' do
37
+ it 'updates an record with an range string' do
38
+ date_range = Date.new(2011, 01, 01)..Date.new(2012, 01, 31)
39
+ new_date_range = Date.new(2012, 01, 01)...Date.new(2012, 02, 01)
40
+ range = DateRanger.create( :best_estimate => date_range)
41
+ range.best_estimate = new_date_range
42
+ range.save
43
+
44
+ range.reload
45
+ range.best_estimate.should eq new_date_range
46
+ end
47
+
48
+ it 'converts empty strings to nil' do
49
+ range = DateRanger.create
50
+ range.best_estimate = ''
51
+ range.save
52
+
53
+ range.reload
54
+ range.best_estimate.should eq nil
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'default value, date range' do
60
+ before do
61
+ adapter.create_table :date_default_rangers, :force => true do |t|
62
+ t.daterange :best_estimate, :default => Date.new(2011, 01, 01)..Date.new(2011, 01, 31)
63
+ end
64
+ class DateDefaultRanger < ActiveRecord::Base
65
+ attr_accessible :best_estimate
66
+ end
67
+ end
68
+
69
+ after do
70
+ adapter.drop_table :date_default_rangers
71
+ Object.send(:remove_const, :DateDefaultRanger)
72
+ end
73
+
74
+ describe '#create' do
75
+ it 'creates an record when there is no assignment' do
76
+ range = DateDefaultRanger.create()
77
+ range.reload
78
+ range.best_estimate.should eq Date.new(2011, 01, 01)...Date.new(2011, 02, 01)
79
+ end
80
+
81
+ it 'creates an record with a range' do
82
+ range = DateDefaultRanger.create(:best_estimate => Date.new(2012, 01, 01)..Date.new(2012, 12, 31))
83
+ range.reload
84
+ range.best_estimate.should eq Date.new(2012, 01, 01)...Date.new(2013, 01, 01)
85
+ end
86
+ end
87
+ end
88
+ end