sequel 3.36.1 → 3.37.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.
- data/CHANGELOG +84 -0
- data/Rakefile +13 -0
- data/bin/sequel +12 -16
- data/doc/advanced_associations.rdoc +36 -67
- data/doc/association_basics.rdoc +11 -16
- data/doc/release_notes/3.37.0.txt +338 -0
- data/doc/schema_modification.rdoc +4 -0
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
- data/lib/sequel/adapters/mysql2.rb +4 -3
- data/lib/sequel/adapters/odbc/mssql.rb +2 -2
- data/lib/sequel/adapters/postgres.rb +4 -60
- data/lib/sequel/adapters/shared/mssql.rb +2 -1
- data/lib/sequel/adapters/shared/mysql.rb +0 -5
- data/lib/sequel/adapters/shared/postgres.rb +68 -2
- data/lib/sequel/adapters/shared/sqlite.rb +17 -1
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
- data/lib/sequel/adapters/utils/pg_types.rb +76 -0
- data/lib/sequel/core.rb +13 -0
- data/lib/sequel/database/misc.rb +41 -1
- data/lib/sequel/database/schema_generator.rb +23 -10
- data/lib/sequel/database/schema_methods.rb +26 -4
- data/lib/sequel/dataset/graph.rb +2 -1
- data/lib/sequel/dataset/query.rb +62 -2
- data/lib/sequel/extensions/_pretty_table.rb +7 -3
- data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
- data/lib/sequel/extensions/blank.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +13 -2
- data/lib/sequel/extensions/core_extensions.rb +6 -0
- data/lib/sequel/extensions/eval_inspect.rb +158 -0
- data/lib/sequel/extensions/inflector.rb +4 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -4
- data/lib/sequel/extensions/migration.rb +4 -1
- data/lib/sequel/extensions/named_timezones.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +4 -0
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +219 -168
- data/lib/sequel/extensions/pg_array_ops.rb +7 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
- data/lib/sequel/extensions/pg_hstore.rb +3 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
- data/lib/sequel/extensions/pg_inet.rb +28 -3
- data/lib/sequel/extensions/pg_interval.rb +192 -0
- data/lib/sequel/extensions/pg_json.rb +21 -9
- data/lib/sequel/extensions/pg_range.rb +487 -0
- data/lib/sequel/extensions/pg_range_ops.rb +122 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
- data/lib/sequel/extensions/pretty_table.rb +12 -1
- data/lib/sequel/extensions/query.rb +4 -0
- data/lib/sequel/extensions/query_literals.rb +6 -6
- data/lib/sequel/extensions/schema_dumper.rb +39 -38
- data/lib/sequel/extensions/select_remove.rb +4 -0
- data/lib/sequel/extensions/server_block.rb +3 -2
- data/lib/sequel/extensions/split_array_nil.rb +65 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
- data/lib/sequel/extensions/to_dot.rb +4 -0
- data/lib/sequel/model/associations.rb +150 -91
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/list.rb +1 -0
- data/lib/sequel/plugins/many_through_many.rb +33 -32
- data/lib/sequel/plugins/nested_attributes.rb +11 -3
- data/lib/sequel/plugins/rcte_tree.rb +2 -2
- data/lib/sequel/plugins/schema.rb +1 -1
- data/lib/sequel/sql.rb +14 -14
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mysql_spec.rb +25 -0
- data/spec/adapters/postgres_spec.rb +572 -28
- data/spec/adapters/sqlite_spec.rb +16 -1
- data/spec/core/database_spec.rb +61 -2
- data/spec/core/dataset_spec.rb +92 -0
- data/spec/core/expression_filters_spec.rb +12 -0
- data/spec/extensions/arbitrary_servers_spec.rb +1 -1
- data/spec/extensions/boolean_readers_spec.rb +25 -25
- data/spec/extensions/eval_inspect_spec.rb +58 -0
- data/spec/extensions/json_serializer_spec.rb +0 -6
- data/spec/extensions/list_spec.rb +1 -1
- data/spec/extensions/looser_typecasting_spec.rb +7 -7
- data/spec/extensions/many_through_many_spec.rb +81 -0
- data/spec/extensions/nested_attributes_spec.rb +21 -4
- data/spec/extensions/pg_array_ops_spec.rb +1 -11
- data/spec/extensions/pg_array_spec.rb +181 -90
- data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
- data/spec/extensions/pg_hstore_spec.rb +1 -3
- data/spec/extensions/pg_inet_spec.rb +6 -1
- data/spec/extensions/pg_interval_spec.rb +73 -0
- data/spec/extensions/pg_json_spec.rb +5 -9
- data/spec/extensions/pg_range_ops_spec.rb +49 -0
- data/spec/extensions/pg_range_spec.rb +372 -0
- data/spec/extensions/pg_statement_cache_spec.rb +1 -2
- data/spec/extensions/query_literals_spec.rb +1 -2
- data/spec/extensions/schema_dumper_spec.rb +48 -89
- data/spec/extensions/serialization_spec.rb +1 -5
- data/spec/extensions/server_block_spec.rb +2 -2
- data/spec/extensions/spec_helper.rb +12 -2
- data/spec/extensions/split_array_nil_spec.rb +24 -0
- data/spec/integration/associations_test.rb +4 -4
- data/spec/integration/database_test.rb +2 -2
- data/spec/integration/dataset_test.rb +4 -4
- data/spec/integration/eager_loader_test.rb +6 -6
- data/spec/integration/plugin_test.rb +2 -2
- data/spec/integration/spec_helper.rb +2 -2
- data/spec/model/association_reflection_spec.rb +5 -0
- data/spec/model/associations_spec.rb +156 -49
- data/spec/model/eager_loading_spec.rb +137 -2
- data/spec/model/model_spec.rb +10 -10
- metadata +15 -2
|
@@ -5,7 +5,8 @@ describe "pg_inet extension" do
|
|
|
5
5
|
before do
|
|
6
6
|
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
|
7
7
|
@db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
|
|
8
|
-
@db.
|
|
8
|
+
@db.extension(:pg_array)
|
|
9
|
+
@db.extension(:pg_inet)
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
it "should literalize IPAddr v4 instances to strings correctly" do
|
|
@@ -29,6 +30,10 @@ describe "pg_inet extension" do
|
|
|
29
30
|
@db.bound_variable_arg(IPAddr.new('127.0.0.1'), nil).should == '127.0.0.1/32'
|
|
30
31
|
end
|
|
31
32
|
|
|
33
|
+
it "should support using IPAddr instances in array types in bound variables" do
|
|
34
|
+
@db.bound_variable_arg([IPAddr.new('127.0.0.1')].pg_array, nil).should == '{"127.0.0.1/32"}'
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
it "should parse inet/cidr type from the schema correctly" do
|
|
33
38
|
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'inet'}, {:name=>'c', :db_type=>'cidr'}]
|
|
34
39
|
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :ipaddr, :ipaddr]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'active_support/duration'
|
|
5
|
+
rescue LoadError => e
|
|
6
|
+
skip_warn "pg_interval plugin: can't load active_support/duration (#{e.class}: #{e})"
|
|
7
|
+
else
|
|
8
|
+
describe "pg_interval extension" do
|
|
9
|
+
before do
|
|
10
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
|
11
|
+
@db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
|
|
12
|
+
@db.extension(:pg_array, :pg_interval)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should literalize ActiveSupport::Duration instances to strings correctly" do
|
|
16
|
+
@db.literal(ActiveSupport::Duration.new(0, [])).should == "'0'::interval"
|
|
17
|
+
@db.literal(ActiveSupport::Duration.new(0, [[:seconds, 0]])).should == "'0'::interval"
|
|
18
|
+
@db.literal(ActiveSupport::Duration.new(0, [[:seconds, 10], [:minutes, 20], [:days, 3], [:months, 4], [:years, 6]])).should == "'6 years 4 months 3 days 20 minutes 10 seconds '::interval"
|
|
19
|
+
@db.literal(ActiveSupport::Duration.new(0, [[:seconds, -10.000001], [:minutes, -20], [:days, -3], [:months, -4], [:years, -6]])).should == "'-6 years -4 months -3 days -20 minutes -10.000001 seconds '::interval"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "should literalize ActiveSupport::Duration instances with repeated parts correctly" do
|
|
23
|
+
@db.literal(ActiveSupport::Duration.new(0, [[:seconds, 2], [:seconds, 1]])).should == "'3 seconds '::interval"
|
|
24
|
+
@db.literal(ActiveSupport::Duration.new(0, [[:seconds, 2], [:seconds, 1], [:days, 1], [:days, 4]])).should == "'5 days 3 seconds '::interval"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should not affect literalization of custom objects" do
|
|
28
|
+
o = Object.new
|
|
29
|
+
def o.sql_literal(ds) 'v' end
|
|
30
|
+
@db.literal(o).should == 'v'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should support using ActiveSupport::Duration instances as bound variables" do
|
|
34
|
+
@db.bound_variable_arg(1, nil).should == 1
|
|
35
|
+
@db.bound_variable_arg(ActiveSupport::Duration.new(0, [[:seconds, 0]]), nil).should == '0'
|
|
36
|
+
@db.bound_variable_arg(ActiveSupport::Duration.new(0, [[:seconds, -10.000001], [:minutes, -20], [:days, -3], [:months, -4], [:years, -6]]), nil).should == '-6 years -4 months -3 days -20 minutes -10.000001 seconds '
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should support using ActiveSupport::Duration instances in array types in bound variables" do
|
|
40
|
+
@db.bound_variable_arg([ActiveSupport::Duration.new(0, [[:seconds, 0]])].pg_array, nil).should == '{"0"}'
|
|
41
|
+
@db.bound_variable_arg([ActiveSupport::Duration.new(0, [[:seconds, -10.000001], [:minutes, -20], [:days, -3], [:months, -4], [:years, -6]])].pg_array, nil).should == '{"-6 years -4 months -3 days -20 minutes -10.000001 seconds "}'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "should parse interval type from the schema correctly" do
|
|
45
|
+
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'interval'}]
|
|
46
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :interval]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should support typecasting for the interval type" do
|
|
50
|
+
ip = IPAddr.new('127.0.0.1')
|
|
51
|
+
d = ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
|
|
52
|
+
@db.typecast_value(:interval, d).object_id.should == d.object_id
|
|
53
|
+
|
|
54
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 05:06:07").is_a?(ActiveSupport::Duration).should be_true
|
|
55
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 05:06:07").should == d
|
|
56
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 05:06:07").parts.sort_by{|k,v| k.to_s}.should == d.parts.sort_by{|k,v| k.to_s}
|
|
57
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 05:06:07.0").parts.sort_by{|k,v| k.to_s}.should == d.parts.sort_by{|k,v| k.to_s}
|
|
58
|
+
|
|
59
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 5 hours 6 mins 7 secs").is_a?(ActiveSupport::Duration).should be_true
|
|
60
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 5 hours 6 mins 7 secs").should == d
|
|
61
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 5 hours 6 mins 7 secs").parts.sort_by{|k,v| k.to_s}.should == d.parts.sort_by{|k,v| k.to_s}
|
|
62
|
+
@db.typecast_value(:interval, "1 year 2 mons 25 days 5 hours 6 mins 7.0 secs").parts.sort_by{|k,v| k.to_s}.should == d.parts.sort_by{|k,v| k.to_s}
|
|
63
|
+
|
|
64
|
+
d2 = ActiveSupport::Duration.new(1, [[:seconds, 1]])
|
|
65
|
+
@db.typecast_value(:interval, 1).is_a?(ActiveSupport::Duration).should be_true
|
|
66
|
+
@db.typecast_value(:interval, 1).should == d2
|
|
67
|
+
@db.typecast_value(:interval, 1).parts.sort_by{|k,v| k.to_s}.should == d2.parts.sort_by{|k,v| k.to_s}
|
|
68
|
+
|
|
69
|
+
proc{@db.typecast_value(:interval, 'foo')}.should raise_error(Sequel::InvalidValue)
|
|
70
|
+
proc{@db.typecast_value(:interval, Object.new)}.should raise_error(Sequel::InvalidValue)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
2
|
|
|
3
|
-
begin
|
|
4
|
-
Sequel.extension :pg_json
|
|
5
|
-
rescue LoadError => e
|
|
6
|
-
skip_warn "can't load pg_json extension (#{e.class}: #{e})"
|
|
7
|
-
else
|
|
8
3
|
describe "pg_json extension" do
|
|
9
4
|
before(:all) do
|
|
10
5
|
m = Sequel::Postgres
|
|
@@ -26,6 +21,7 @@ describe "pg_json extension" do
|
|
|
26
21
|
before do
|
|
27
22
|
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
|
28
23
|
@db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
|
|
24
|
+
@db.extension(:pg_array, :pg_json)
|
|
29
25
|
end
|
|
30
26
|
|
|
31
27
|
it "should parse json strings correctly" do
|
|
@@ -70,20 +66,21 @@ describe "pg_json extension" do
|
|
|
70
66
|
end
|
|
71
67
|
|
|
72
68
|
it "should support using JSONHash and JSONArray as bound variables" do
|
|
73
|
-
@db.extend @m
|
|
74
69
|
@db.bound_variable_arg(1, nil).should == 1
|
|
75
70
|
@db.bound_variable_arg([1].pg_json, nil).should == '[1]'
|
|
76
71
|
@db.bound_variable_arg({'a'=>'b'}.pg_json, nil).should == '{"a":"b"}'
|
|
77
72
|
end
|
|
78
73
|
|
|
74
|
+
it "should support using json[] types in bound variables" do
|
|
75
|
+
@db.bound_variable_arg([[{"a"=>1}].pg_json, {"b"=>[1, 2]}.pg_json].pg_array, nil).should == '{"[{\\"a\\":1}]","{\\"b\\":[1,2]}"}'
|
|
76
|
+
end
|
|
77
|
+
|
|
79
78
|
it "should parse json type from the schema correctly" do
|
|
80
|
-
@db.extend @m
|
|
81
79
|
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'json'}]
|
|
82
80
|
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :json]
|
|
83
81
|
end
|
|
84
82
|
|
|
85
83
|
it "should support typecasting for the json type" do
|
|
86
|
-
@db.extend @m
|
|
87
84
|
h = {1=>2}.pg_json
|
|
88
85
|
a = [1].pg_json
|
|
89
86
|
@db.typecast_value(:json, h).should equal(h)
|
|
@@ -98,4 +95,3 @@ describe "pg_json extension" do
|
|
|
98
95
|
proc{@db.typecast_value(:json, 1)}.should raise_error(Sequel::InvalidValue)
|
|
99
96
|
end
|
|
100
97
|
end
|
|
101
|
-
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
|
+
|
|
3
|
+
describe "Sequel::Postgres::RangeOp" do
|
|
4
|
+
before do
|
|
5
|
+
@ds = Sequel.connect('mock://postgres', :quote_identifiers=>false).dataset
|
|
6
|
+
@h = :h.pg_range
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "#pg_range should return self" do
|
|
10
|
+
@h.pg_range.should equal(@h)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "#pg_range should return a RangeOp for symbols, literal strings, and expressions" do
|
|
14
|
+
@ds.literal(:h.pg_range.lower).should == "lower(h)"
|
|
15
|
+
@ds.literal(Sequel.function(:b, :h).pg_range.lower).should == "lower(b(h))"
|
|
16
|
+
@ds.literal(Sequel.lit('h').pg_range.lower).should == "lower(h)"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "PGRange#op should return a RangeOp" do
|
|
20
|
+
@ds.literal((1..2).pg_range(:numrange).op.lower).should == "lower('[1,2]'::numrange)"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should define methods for all of the the PostgreSQL range operators" do
|
|
24
|
+
@ds.literal(@h.contains(@h)).should == "(h @> h)"
|
|
25
|
+
@ds.literal(@h.contained_by(@h)).should == "(h <@ h)"
|
|
26
|
+
@ds.literal(@h.overlaps(@h)).should == "(h && h)"
|
|
27
|
+
@ds.literal(@h.left_of(@h)).should == "(h << h)"
|
|
28
|
+
@ds.literal(@h.right_of(@h)).should == "(h >> h)"
|
|
29
|
+
@ds.literal(@h.starts_before(@h)).should == "(h &< h)"
|
|
30
|
+
@ds.literal(@h.ends_after(@h)).should == "(h &> h)"
|
|
31
|
+
@ds.literal(@h.adjacent_to(@h)).should == "(h -|- h)"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "should define methods for all of the the PostgreSQL range functions" do
|
|
35
|
+
@ds.literal(@h.lower).should == "lower(h)"
|
|
36
|
+
@ds.literal(@h.upper).should == "upper(h)"
|
|
37
|
+
@ds.literal(@h.isempty).should == "isempty(h)"
|
|
38
|
+
@ds.literal(@h.lower_inc).should == "lower_inc(h)"
|
|
39
|
+
@ds.literal(@h.upper_inc).should == "upper_inc(h)"
|
|
40
|
+
@ds.literal(@h.lower_inf).should == "lower_inf(h)"
|
|
41
|
+
@ds.literal(@h.upper_inf).should == "upper_inf(h)"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "+ - * operators should be defined and return a RangeOp" do
|
|
45
|
+
@ds.literal((@h + @h).lower).should == "lower((h + h))"
|
|
46
|
+
@ds.literal((@h * @h).lower).should == "lower((h * h))"
|
|
47
|
+
@ds.literal((@h - @h).lower).should == "lower((h - h))"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
|
+
|
|
3
|
+
describe "pg_range extension" do
|
|
4
|
+
before do
|
|
5
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
|
6
|
+
@R = Sequel::Postgres::PGRange
|
|
7
|
+
@db.extend(Module.new{def get_conversion_procs(conn) {} end; def bound_variable_arg(arg, conn) arg end})
|
|
8
|
+
@db.extend_datasets(Module.new{def supports_timestamp_timezones?; false; end; def supports_timestamp_usecs?; false; end})
|
|
9
|
+
@db.extension(:pg_array)
|
|
10
|
+
@db.extension(:pg_range)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should literalize Range instances to strings correctly" do
|
|
14
|
+
@db.literal(Date.new(2011, 1, 2)...Date.new(2011, 3, 2)).should == "'[2011-01-02,2011-03-02)'"
|
|
15
|
+
@db.literal(Time.local(2011, 1, 2, 10, 20, 30)...Time.local(2011, 2, 3, 10, 20, 30)).should == "'[2011-01-02 10:20:30,2011-02-03 10:20:30)'"
|
|
16
|
+
@db.literal(DateTime.new(2011, 1, 2, 10, 20, 30)...DateTime.new(2011, 2, 3, 10, 20, 30)).should == "'[2011-01-02 10:20:30,2011-02-03 10:20:30)'"
|
|
17
|
+
@db.literal(DateTime.new(2011, 1, 2, 10, 20, 30)...DateTime.new(2011, 2, 3, 10, 20, 30)).should == "'[2011-01-02 10:20:30,2011-02-03 10:20:30)'"
|
|
18
|
+
@db.literal(1..2).should == "'[1,2]'"
|
|
19
|
+
@db.literal(1.0..2.0).should == "'[1.0,2.0]'"
|
|
20
|
+
@db.literal(BigDecimal.new('1.0')..BigDecimal.new('2.0')).should == "'[1.0,2.0]'"
|
|
21
|
+
@db.literal(Sequel.lit('a')..Sequel.lit('z')).should == "'[a,z]'"
|
|
22
|
+
@db.literal(''..'()[]",\\2').should == "'[\"\",\\(\\)\\[\\]\\\"\\,\\\\2]'"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should literalize PGRange instances to strings correctly" do
|
|
26
|
+
@db.literal(@R.new(1, 2)).should == "'[1,2]'"
|
|
27
|
+
@db.literal(@R.new(true, false)).should == "'[true,false]'"
|
|
28
|
+
@db.literal(@R.new(1, 2, :exclude_begin=>true)).should == "'(1,2]'"
|
|
29
|
+
@db.literal(@R.new(1, 2, :exclude_end=>true)).should == "'[1,2)'"
|
|
30
|
+
@db.literal(@R.new(nil, 2)).should == "'[,2]'"
|
|
31
|
+
@db.literal(@R.new(1, nil)).should == "'[1,]'"
|
|
32
|
+
@db.literal(@R.new(1, 2, :db_type=>'int8range')).should == "'[1,2]'::int8range"
|
|
33
|
+
@db.literal(@R.new(nil, nil, :empty=>true)).should == "'empty'"
|
|
34
|
+
@db.literal(@R.new("", 2)).should == "'[\"\",2]'"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "should not affect literalization of custom objects" do
|
|
38
|
+
o = Object.new
|
|
39
|
+
def o.sql_literal(ds) 'v' end
|
|
40
|
+
@db.literal(o).should == 'v'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should support using Range instances as bound variables" do
|
|
44
|
+
@db.bound_variable_arg(1..2, nil).should == "[1,2]"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should support using PGRange instances as bound variables" do
|
|
48
|
+
@db.bound_variable_arg(@R.new(1, 2), nil).should == "[1,2]"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should support using arrays of Range instances as bound variables" do
|
|
52
|
+
@db.bound_variable_arg([1..2,2...3], nil).should == '{"[1,2]","[2,3)"}'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should support using PGRange instances as bound variables" do
|
|
56
|
+
@db.bound_variable_arg([@R.new(1, 2),@R.new(2, 3)], nil).should == '{"[1,2]","[2,3]"}'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should parse range types from the schema correctly" do
|
|
60
|
+
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i4', :db_type=>'int4range'}, {:name=>'i8', :db_type=>'int8range'}, {:name=>'n', :db_type=>'numrange'}, {:name=>'d', :db_type=>'daterange'}, {:name=>'ts', :db_type=>'tsrange'}, {:name=>'tz', :db_type=>'tstzrange'}]
|
|
61
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :int4range, :int8range, :numrange, :daterange, :tsrange, :tstzrange]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should parse arrays of range types from the schema correctly" do
|
|
65
|
+
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i4', :db_type=>'int4range[]'}, {:name=>'i8', :db_type=>'int8range[]'}, {:name=>'n', :db_type=>'numrange[]'}, {:name=>'d', :db_type=>'daterange[]'}, {:name=>'ts', :db_type=>'tsrange[]'}, {:name=>'tz', :db_type=>'tstzrange[]'}]
|
|
66
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :int4range_array, :int8range_array, :numrange_array, :daterange_array, :tsrange_array, :tstzrange_array]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "database typecasting" do
|
|
70
|
+
before do
|
|
71
|
+
@o = @R.new(1, 2, :db_type=>'int4range')
|
|
72
|
+
@o2 = @R.new(1, 2, :db_type=>'int8range')
|
|
73
|
+
@eo = @R.new(nil, nil, :empty=>true, :db_type=>'int4range')
|
|
74
|
+
@eo2 = @R.new(nil, nil, :empty=>true, :db_type=>'int8range')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "should handle multiple range types" do
|
|
78
|
+
%w'int4 int8 num date ts tstz'.each do |i|
|
|
79
|
+
@db.typecast_value(:"#{i}range", @R.new(1, 2, :db_type=>"#{i}range")).should == @R.new(1, 2, :db_type=>"#{i}range")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should handle multiple array range types" do
|
|
84
|
+
%w'int4 int8 num date ts tstz'.each do |i|
|
|
85
|
+
@db.typecast_value(:"#{i}range_array", [@R.new(1, 2, :db_type=>"#{i}range")]).should be_a_kind_of(Sequel::Postgres::PGArray)
|
|
86
|
+
@db.typecast_value(:"#{i}range_array", [@R.new(1, 2, :db_type=>"#{i}range")]).should == [@R.new(1, 2, :db_type=>"#{i}range")]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "should return PGRange value as is if they have the same subtype" do
|
|
91
|
+
@db.typecast_value(:int4range, @o).should equal(@o)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "should return new PGRange value as is if they have a different subtype" do
|
|
95
|
+
@db.typecast_value(:int8range, @o).should_not equal(@o)
|
|
96
|
+
@db.typecast_value(:int8range, @o).should == @o2
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should return new PGRange value as is if they have a different subtype and value is empty" do
|
|
100
|
+
@db.typecast_value(:int8range, @eo).should == @eo2
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "should return new PGRange value if given a Range" do
|
|
104
|
+
@db.typecast_value(:int4range, 1..2).should == @o
|
|
105
|
+
@db.typecast_value(:int4range, 1..2).should_not == @o2
|
|
106
|
+
@db.typecast_value(:int8range, 1..2).should == @o2
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "should parse a string argument as the PostgreSQL output format" do
|
|
110
|
+
@db.typecast_value(:int4range, '[1,2]').should == @o
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "should raise errors for unparsable formats" do
|
|
114
|
+
proc{@db.typecast_value(:int8range, 'foo')}.should raise_error(Sequel::InvalidValue)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "should raise errors for unhandled values" do
|
|
118
|
+
proc{@db.typecast_value(:int4range, 1)}.should raise_error(Sequel::InvalidValue)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "should support registering custom range types" do
|
|
123
|
+
@R.register('foorange')
|
|
124
|
+
@db.typecast_value(:foorange, 1..2).should be_a_kind_of(@R)
|
|
125
|
+
@db.fetch = [{:name=>'id', :db_type=>'foorange'}]
|
|
126
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:foorange]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "should support using a block as a custom conversion proc given as block" do
|
|
130
|
+
@R.register('foo2range'){|s| (s*2).to_i}
|
|
131
|
+
@db.typecast_value(:foo2range, '[1,2]').should == (11..22)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "should support using a block as a custom conversion proc given as :converter option" do
|
|
135
|
+
@R.register('foo3range', :converter=>proc{|s| (s*2).to_i})
|
|
136
|
+
@db.typecast_value(:foo3range, '[1,2]').should == (11..22)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "should support using an existing scaler conversion proc via the :subtype_oid option" do
|
|
140
|
+
@R.register('foo4range', :subtype_oid=>16)
|
|
141
|
+
@db.typecast_value(:foo4range, '[t,f]').should == @R.new(true, false, :db_type=>'foo4range')
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "should raise an error if using :subtype_oid option with unexisting scalar conversion proc" do
|
|
145
|
+
proc{@R.register('fooirange', :subtype_oid=>0)}.should raise_error(Sequel::Error)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "should raise an error if using :converter option and a block argument" do
|
|
149
|
+
proc{@R.register('fooirange', :converter=>proc{}){}}.should raise_error(Sequel::Error)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "should raise an error if using :subtype_oid option and a block argument" do
|
|
153
|
+
proc{@R.register('fooirange', :subtype_oid=>16){}}.should raise_error(Sequel::Error)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "should support registering custom types with :oid option" do
|
|
157
|
+
@R.register('foo5range', :oid=>331)
|
|
158
|
+
Sequel::Postgres::PG_TYPES[331].call('[1,3)').should be_a_kind_of(@R)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe "parser" do
|
|
162
|
+
before do
|
|
163
|
+
@p = Sequel::Postgres::PG_TYPES[3904]
|
|
164
|
+
@sp = @R::Parser.new(nil)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "should have db_type method to return the database type string" do
|
|
168
|
+
@p.db_type.should == 'int4range'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "should have converter method which returns a callable used for conversion" do
|
|
172
|
+
@p.converter.call('1').should == 1
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "should have call parse input string argument into PGRange instance" do
|
|
176
|
+
@p.call('[1,2]').should == @R.new(1, 2, :db_type=>'int4range')
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "should handle empty ranges" do
|
|
180
|
+
@p.call('empty').should == @R.new(nil, nil, :empty=>true, :db_type=>'int4range')
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "should handle exclusive beginnings and endings" do
|
|
184
|
+
@p.call('(1,3]').should == @R.new(1, 3, :exclude_begin=>true, :db_type=>'int4range')
|
|
185
|
+
@p.call('[1,3)').should == @R.new(1, 3, :exclude_end=>true, :db_type=>'int4range')
|
|
186
|
+
@p.call('(1,3)').should == @R.new(1, 3, :exclude_begin=>true, :exclude_end=>true, :db_type=>'int4range')
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "should handle unbounded beginnings and endings" do
|
|
190
|
+
@p.call('[,2]').should == @R.new(nil, 2, :db_type=>'int4range')
|
|
191
|
+
@p.call('[1,]').should == @R.new(1, nil, :db_type=>'int4range')
|
|
192
|
+
@p.call('[,]').should == @R.new(nil, nil, :db_type=>'int4range')
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "should unescape quoted beginnings and endings" do
|
|
196
|
+
@sp.call('["\\\\ \\"","\\" \\\\"]').should == @R.new("\\ \"", "\" \\")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "should treat empty quoted string not as unbounded" do
|
|
200
|
+
@sp.call('["","z"]').should == @R.new("", "z")
|
|
201
|
+
@sp.call('["a",""]').should == @R.new("a", "")
|
|
202
|
+
@sp.call('["",""]').should == @R.new("", "")
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "should set appropriate timestamp range conversion procs when getting conversion procs" do
|
|
207
|
+
procs = @db.send(:get_conversion_procs, nil)
|
|
208
|
+
procs[3908].call('[2011-10-20 11:12:13,2011-10-20 11:12:14]').should == (Time.local(2011, 10, 20, 11, 12, 13)..(Time.local(2011, 10, 20, 11, 12, 14)))
|
|
209
|
+
procs[3910].call('[2011-10-20 11:12:13,2011-10-20 11:12:14]').should == (Time.local(2011, 10, 20, 11, 12, 13)..(Time.local(2011, 10, 20, 11, 12, 14)))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "should set appropriate timestamp range array conversion procs when getting conversion procs" do
|
|
213
|
+
procs = @db.send(:get_conversion_procs, nil)
|
|
214
|
+
procs[3909].call('{"[2011-10-20 11:12:13,2011-10-20 11:12:14]"}').should == [Time.local(2011, 10, 20, 11, 12, 13)..Time.local(2011, 10, 20, 11, 12, 14)]
|
|
215
|
+
procs[3911].call('{"[2011-10-20 11:12:13,2011-10-20 11:12:14]"}').should == [Time.local(2011, 10, 20, 11, 12, 13)..Time.local(2011, 10, 20, 11, 12, 14)]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe "a PGRange instance" do
|
|
219
|
+
before do
|
|
220
|
+
@r1 = @R.new(1, 2)
|
|
221
|
+
@r2 = @R.new(3, nil, :exclude_begin=>true, :db_type=>'int4range')
|
|
222
|
+
@r3 = @R.new(nil, 4, :exclude_end=>true, :db_type=>'int8range')
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "should have #begin return the beginning of the range" do
|
|
226
|
+
@r1.begin.should == 1
|
|
227
|
+
@r2.begin.should == 3
|
|
228
|
+
@r3.begin.should == nil
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "should have #end return the end of the range" do
|
|
232
|
+
@r1.end.should == 2
|
|
233
|
+
@r2.end.should == nil
|
|
234
|
+
@r3.end.should == 4
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "should have #db_type return the range's database type" do
|
|
238
|
+
@r1.db_type.should == nil
|
|
239
|
+
@r2.db_type.should == 'int4range'
|
|
240
|
+
@r3.db_type.should == 'int8range'
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "should be able to be created by Range#pg_range" do
|
|
244
|
+
(1..2).pg_range.should == @r1
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it "should have #initialize raise if requesting an empty range with beginning or ending" do
|
|
248
|
+
proc{@R.new(1, nil, :empty=>true)}.should raise_error(Sequel::Error)
|
|
249
|
+
proc{@R.new(nil, 2, :empty=>true)}.should raise_error(Sequel::Error)
|
|
250
|
+
proc{@R.new(nil, nil, :empty=>true, :exclude_begin=>true)}.should raise_error(Sequel::Error)
|
|
251
|
+
proc{@R.new(nil, nil, :empty=>true, :exclude_end=>true)}.should raise_error(Sequel::Error)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "should quack like a range" do
|
|
255
|
+
if RUBY_VERSION >= '1.9'
|
|
256
|
+
@r1.cover?(1.5).should be_true
|
|
257
|
+
@r1.cover?(2.5).should be_false
|
|
258
|
+
@r1.first(1).should == [1]
|
|
259
|
+
@r1.last(1).should == [2]
|
|
260
|
+
end
|
|
261
|
+
@r1.to_a.should == [1, 2]
|
|
262
|
+
@r1.first.should == 1
|
|
263
|
+
@r1.last.should == 2
|
|
264
|
+
a = []
|
|
265
|
+
@r1.step{|x| a << x}
|
|
266
|
+
a.should == [1, 2]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it "should only consider PGRanges equal if they have the same db_type" do
|
|
270
|
+
@R.new(1, 2, :db_type=>'int4range').should == @R.new(1, 2, :db_type=>'int4range')
|
|
271
|
+
@R.new(1, 2, :db_type=>'int8range').should_not == @R.new(1, 2, :db_type=>'int4range')
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it "should only consider empty PGRanges equal with other empty PGRanges" do
|
|
275
|
+
@R.new(nil, nil, :empty=>true).should == @R.new(nil, nil, :empty=>true)
|
|
276
|
+
@R.new(nil, nil, :empty=>true).should_not == @R.new(nil, nil)
|
|
277
|
+
@R.new(nil, nil).should_not == @R.new(nil, nil, :empty=>true)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "should only consider empty PGRanges equal if they have the same bounds" do
|
|
281
|
+
@R.new(1, 2).should == @R.new(1, 2)
|
|
282
|
+
@R.new(1, 2).should_not == @R.new(1, 3)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it "should only consider empty PGRanges equal if they have the same bound exclusions" do
|
|
286
|
+
@R.new(1, 2, :exclude_begin=>true).should == @R.new(1, 2, :exclude_begin=>true)
|
|
287
|
+
@R.new(1, 2, :exclude_end=>true).should == @R.new(1, 2, :exclude_end=>true)
|
|
288
|
+
@R.new(1, 2, :exclude_begin=>true).should_not == @R.new(1, 2, :exclude_end=>true)
|
|
289
|
+
@R.new(1, 2, :exclude_end=>true).should_not == @R.new(1, 2, :exclude_begin=>true)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it "should consider PGRanges equal with a Range they represent" do
|
|
293
|
+
@R.new(1, 2).should == (1..2)
|
|
294
|
+
@R.new(1, 2, :exclude_end=>true).should == (1...2)
|
|
295
|
+
@R.new(1, 3).should_not == (1..2)
|
|
296
|
+
@R.new(1, 2, :exclude_end=>true).should_not == (1..2)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it "should not consider a PGRange equal with a Range if it can't be expressed as a range" do
|
|
300
|
+
@R.new(nil, nil).should_not == (1..2)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it "should not consider a PGRange equal to other objects" do
|
|
304
|
+
@R.new(nil, nil).should_not == 1
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
it "should have #=== be true if given an equal PGRange" do
|
|
308
|
+
@R.new(1, 2).should === @R.new(1, 2)
|
|
309
|
+
@R.new(1, 2).should_not === @R.new(1, 3)
|
|
310
|
+
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "should have #=== be true if it would be true for the Range represented by the PGRange" do
|
|
314
|
+
@R.new(1, 2).should === 1.5
|
|
315
|
+
@R.new(1, 2).should_not === 2.5
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it "should have #=== be false if the PGRange cannot be represented by a Range" do
|
|
319
|
+
@R.new(nil, nil).should_not === 1.5
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it "should have #empty? indicate whether the range is empty" do
|
|
323
|
+
@R.empty.should be_empty
|
|
324
|
+
@R.new(1, 2).should_not be_empty
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it "should have #exclude_begin? and #exclude_end indicate whether the beginning or ending of the range is excluded" do
|
|
328
|
+
@r1.exclude_begin?.should be_false
|
|
329
|
+
@r1.exclude_end?.should be_false
|
|
330
|
+
@r2.exclude_begin?.should be_true
|
|
331
|
+
@r2.exclude_end?.should be_false
|
|
332
|
+
@r3.exclude_begin?.should be_false
|
|
333
|
+
@r3.exclude_end?.should be_true
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it "should have #to_range raise an exception if the PGRange cannot be represented by a Range" do
|
|
337
|
+
proc{@R.new(nil, 1).to_range}.should raise_error(Sequel::Error)
|
|
338
|
+
proc{@R.new(1, nil).to_range}.should raise_error(Sequel::Error)
|
|
339
|
+
proc{@R.new(0, 1, :exclude_begin=>true).to_range}.should raise_error(Sequel::Error)
|
|
340
|
+
proc{@R.empty.to_range}.should raise_error(Sequel::Error)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it "should have #to_range return the represented range" do
|
|
344
|
+
@r1.to_range.should == (1..2)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it "should have #to_range cache the returned value" do
|
|
348
|
+
@r1.to_range.should equal(@r1.to_range)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it "should have #unbounded_begin? and #unbounded_end indicate whether the beginning or ending of the range is unbounded" do
|
|
352
|
+
@r1.unbounded_begin?.should be_false
|
|
353
|
+
@r1.unbounded_end?.should be_false
|
|
354
|
+
@r2.unbounded_begin?.should be_false
|
|
355
|
+
@r2.unbounded_end?.should be_true
|
|
356
|
+
@r3.unbounded_begin?.should be_true
|
|
357
|
+
@r3.unbounded_end?.should be_false
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
it "should have #valid_ruby_range? return true if the PGRange can be represented as a Range" do
|
|
361
|
+
@r1.valid_ruby_range?.should be_true
|
|
362
|
+
@R.new(1, 2, :exclude_end=>true).valid_ruby_range?.should be_true
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it "should have #valid_ruby_range? return false if the PGRange cannot be represented as a Range" do
|
|
366
|
+
@R.new(nil, 1).valid_ruby_range?.should be_false
|
|
367
|
+
@R.new(1, nil).valid_ruby_range?.should be_false
|
|
368
|
+
@R.new(0, 1, :exclude_begin=>true).valid_ruby_range?.should be_false
|
|
369
|
+
@R.empty.valid_ruby_range?.should be_false
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|