diff --git a/ext/pg_tuple.c b/ext/pg_tuple.c index 07ddd2fb5..d0edff439 100644 --- a/ext/pg_tuple.c +++ b/ext/pg_tuple.c @@ -547,14 +547,30 @@ pg_tuple_load(VALUE self, VALUE a) return self; } +/* + * call-seq: + * res.freeze + * + * Freeze the PG::Tuple object and materialize all fields, so that it can be safely shared across ractors. + * + */ +static VALUE +pg_tuple_freeze(VALUE self) +{ + pg_tuple_materialize(self); + return rb_call_super(0, NULL); +} + void init_pg_tuple(void) { rb_cPG_Tuple = rb_define_class_under( rb_mPG, "Tuple", rb_cObject ); rb_define_alloc_func( rb_cPG_Tuple, pg_tuple_s_allocate ); + rb_undef_method(rb_singleton_class(rb_cPG_Tuple), "new"); rb_include_module(rb_cPG_Tuple, rb_mEnumerable); rb_define_method(rb_cPG_Tuple, "fetch", pg_tuple_fetch, -1); + rb_define_method(rb_cPG_Tuple, "freeze", pg_tuple_freeze, 0 ); rb_define_method(rb_cPG_Tuple, "[]", pg_tuple_aref, 1); rb_define_method(rb_cPG_Tuple, "each", pg_tuple_each, 0); rb_define_method(rb_cPG_Tuple, "each_value", pg_tuple_each_value, 0); diff --git a/spec/pg/tuple_spec.rb b/spec/pg/tuple_spec.rb index 12ddaf50a..ffa2ca0a4 100644 --- a/spec/pg/tuple_spec.rb +++ b/spec/pg/tuple_spec.rb @@ -26,7 +26,12 @@ let!(:tuple2) { result2x3cast.tuple(0).freeze } let!(:tuple2sym) { result2x3symcast.tuple(0).freeze } let!(:tuple3) { str = Marshal.dump(result2x3cast.tuple(1).freeze); Marshal.load(str) } - let!(:tuple_empty) { PG::Tuple.new.freeze } + + describe "new" do + it "is not defined" do + expect { PG::Tuple.new }.to raise_error(NoMethodError) + end + end describe "[]" do it "returns nil for invalid keys" do @@ -35,7 +40,6 @@ expect( tuple0[2] ).to be_nil expect( tuple0[-3] ).to be_nil expect( tuple2[-4] ).to be_nil - expect{ tuple_empty[0] }.to raise_error(TypeError) end it "supports array like access" do @@ -114,7 +118,6 @@ expect{ tuple0.fetch(-3) }.to raise_error(IndexError) expect{ tuple0.fetch(-3) }.to raise_error(IndexError) expect{ tuple2.fetch(-4) }.to raise_error(IndexError) - expect{ tuple_empty[0] }.to raise_error(TypeError) end it "supports array like access" do @@ -158,7 +161,6 @@ expect( tuple1.each.to_a ).to eq( [["column1", "2"], ["column2", "b"]] ) expect( tuple2.each.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] ) expect( tuple3.each.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] ) - expect{ tuple_empty.each }.to raise_error(TypeError) end it "can be used as an enumerator with symbols" do @@ -183,7 +185,6 @@ expect( tuple1.each_value.to_a ).to eq( ["2", "b"] ) expect( tuple2.each_value.to_a ).to eq( [1, true, "3"] ) expect( tuple3.each_value.to_a ).to eq( [2, false, "4"] ) - expect{ tuple_empty.each_value }.to raise_error(TypeError) end it "can be used with block" do @@ -198,7 +199,6 @@ it "responds to values" do expect( tuple0.values ).to eq( ["1", "a"] ) expect( tuple3.values ).to eq( [2, false, "4"] ) - expect{ tuple_empty.values }.to raise_error(TypeError) end it "responds to key?" do @@ -294,10 +294,6 @@ expect( t2.instance_variable_get("@a") ).to eq( 4711 ) end - it "can't be marshaled when empty" do - expect{ Marshal.dump(tuple_empty) }.to raise_error(TypeError) - end - it "should be shareable for Ractor", :ractor do res = @conn.exec("SELECT 1").tuple(0) Ractor.make_shareable(res) @@ -322,14 +318,12 @@ it "should give account about memory usage" do expect( ObjectSpace.memsize_of(tuple0) ).to be > DATA_OBJ_MEMSIZE - expect( ObjectSpace.memsize_of(tuple_empty) ).to be > 0 end it "should override #inspect" do expect( tuple1.inspect ).to eq('#') expect( tuple2.inspect ).to eq('#') expect( tuple2sym.inspect ).to eq('#') - expect{ tuple_empty.inspect }.to raise_error(TypeError) end context "with cleared result" do