palsy 0.0.3 → 0.0.4

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: 60fb2ba51b992f7b83ce4d94f0999bbdd57ba8a7
4
+ data.tar.gz: b52c06df6875bade0c18b0261c19ee2c1c27c51a
5
+ SHA512:
6
+ metadata.gz: ba0b46d264bd49e24ed3780827705ab7a17a7cf1145a6e81beb903ec2fcfcd79f986ea7c5e564b48b5ac26fc8183cd2f2b8d42a7bccb2a83cb4fce63d395a84a
7
+ data.tar.gz: aa00113a932da3527a871e940493d099698c42d025b8d6093f27ccb1b1a95397c67705465c649964494ce5c44354e6cbdb3781a66aed4973eaf2b9b4d16abdf9
@@ -1,3 +1,6 @@
1
+ * 0.0.4 - April 6, 2013
2
+ * This takes the changes in 0.0.3 and improves robustness over high-volume
3
+ concurrency and improves transaction isolation for single operations.
1
4
  * 0.0.3 - April 2, 2013
2
5
  * Ensure exclusive transactions are being used for all operations. This
3
6
  should make it a little more viable for non-MRI things.
@@ -1,6 +1,7 @@
1
1
  require 'sqlite3'
2
2
  require 'singleton'
3
3
  require 'delegate'
4
+ require 'thread'
4
5
  require "palsy/version"
5
6
 
6
7
  # Present ruby core data structures in a manner similar to perl's tie backed by a
@@ -67,6 +68,7 @@ class Palsy < DelegateClass(SQLite3::Database)
67
68
  # working with the rest of the library.
68
69
  #
69
70
  def initialize
71
+ @palsy_lock = Mutex.new
70
72
  super(connect)
71
73
  end
72
74
 
@@ -88,15 +90,72 @@ class Palsy < DelegateClass(SQLite3::Database)
88
90
  end
89
91
  end
90
92
 
93
+ # Override the explicit lock for the current thread. For things that generate
94
+ # queries, will not attempt to acquire a mutex, will not attempt to run a
95
+ # transaction. Will just run the block. See #with_t for information on lock
96
+ # semantics.
97
+ #
98
+ # For Ruby 2.0 users and above: If you wrap your palsy operations in #no_lock
99
+ # on the current thread, #with_t will not try to use a mutex lock or
100
+ # transaction.
101
+ #
102
+ # This is useful if you want to use palsy in a signal handler:
103
+ #
104
+ # trap("INFO") do
105
+ # Palsy.instance.no_lock do
106
+ # some_palsy_thing
107
+ # end
108
+ # end
109
+ #
110
+ # The caveat is that any writes done in this region will not be isolated and
111
+ # will likely cause problems.
112
+ #
113
+ def no_lock
114
+ retval = nil
115
+ Thread.current[:palsy_no_lock] = true
116
+ retval = yield
117
+ Thread.current[:palsy_no_lock] = false
118
+ return retval
119
+ end
120
+
121
+ #
122
+ # Start a mutex-wrapped exclusive transaction, then execute the block. If
123
+ # we're already the owner of the transaction, just execute.
124
+ #
125
+ # This is used by most things in Palsy. If you want to step around the lock
126
+ # (necessary for specific situations, and has certain caveats), take a look
127
+ # at #no_lock.
128
+ #
129
+ def with_t
130
+ result = nil
131
+ tc = Thread.current
132
+
133
+ if tc[:lock] or tc[:palsy_no_lock]
134
+ result = yield
135
+ else
136
+ @palsy_lock.synchronize do
137
+ begin
138
+ tc[:lock] = @palsy_lock
139
+ transaction(:exclusive) { result = yield }
140
+ rescue StandardError => e
141
+ rollback if transaction_active?
142
+ raise e
143
+ ensure
144
+ tc[:lock] = nil
145
+ end
146
+ end
147
+ end
148
+
149
+ return result
150
+ end
151
+
91
152
  #
92
153
  # Execute this query in an exclusive transaction.
93
154
  #
94
155
  def execute_t(query, args=[], &block)
95
156
  result = nil
96
157
 
97
- transaction(:exclusive) do
98
- result = execute(query, args, &block)
99
- end
158
+ with_t { result = execute(query, args, &block) }
100
159
 
101
160
  return result
102
161
  end
@@ -49,16 +49,23 @@ class Palsy
49
49
  # Add a value to the head of the list.
50
50
  #
51
51
  def unshift(val)
52
- replace([val] + to_a)
52
+ @db.with_t do
53
+ replace([val] + to_a)
54
+ end
53
55
  end
54
56
 
55
57
  #
56
58
  # Helper method for mutators.
57
59
  #
58
60
  def mutate(meth)
59
- a = to_a
60
- val = a.send(meth)
61
- replace(a)
61
+ val = nil
62
+
63
+ @db.with_t do
64
+ a = to_a
65
+ val = a.send(meth)
66
+ replace(a)
67
+ end
68
+
62
69
  return val
63
70
  end
64
71
 
@@ -81,15 +88,17 @@ class Palsy
81
88
  # like an Enumerable.
82
89
  #
83
90
  def replace(ary)
84
- clear
85
- return if ary.count == 0
91
+ @db.with_t do
92
+ clear
93
+ return if ary.count == 0
86
94
 
87
- value_string = ("(?, ?)," * ary.count).chop
95
+ value_string = ("(?, ?)," * ary.count).chop
88
96
 
89
- @db.execute_t(
90
- "insert into #{@table_name} (name, value) values #{value_string}",
91
- ary.map { |x| [@object_name, Marshal.dump(x)] }.flatten
92
- )
97
+ @db.execute_t(
98
+ "insert into #{@table_name} (name, value) values #{value_string}",
99
+ ary.map { |x| [@object_name, Marshal.dump(x)] }.flatten
100
+ )
101
+ end
93
102
  end
94
103
 
95
104
  #
@@ -49,18 +49,37 @@ class Palsy
49
49
  # database before attempting to write the new pair.
50
50
  #
51
51
  def []=(key, value)
52
- delete(key)
53
- @db.execute_t("insert into #{@table_name} (name, key, value) values (?, ?, ?)", [@object_name, Marshal.dump(key), Marshal.dump(value)])
52
+ @db.with_t do
53
+ delete(key)
54
+ @db.execute_t("insert into #{@table_name} (name, key, value) values (?, ?, ?)", [@object_name, Marshal.dump(key), Marshal.dump(value)])
55
+ end
54
56
  value
55
57
  end
56
58
 
59
+ #
60
+ # replace this map with the contents of a Hash.
61
+ #
62
+ def replace(hash)
63
+ @db.with_t do
64
+ clear
65
+
66
+ return if hash.keys.empty?
67
+
68
+ value_string = ("(?, ?, ?)," * hash.keys.count).chop
69
+
70
+ @db.execute_t("insert into #{@table_name} (name, key, value) values #{value_string}", hash.map { |key, value| [@object_name, Marshal.dump(key), Marshal.dump(value)] }.flatten)
71
+ end
72
+ end
73
+
57
74
  #
58
75
  # Successively yields key and value for each item in the map. Modifications
59
76
  # to either the key or value yielded will not persist.
60
77
  #
61
78
  def each
62
- keys.each do |key|
63
- yield key, self[key]
79
+ @db.with_t do
80
+ keys.each do |key|
81
+ yield key, self[key]
82
+ end
64
83
  end
65
84
  end
66
85
 
@@ -27,7 +27,13 @@ class Palsy
27
27
  # Get an object by referencing its key. Returns nil unless it exists.
28
28
  #
29
29
  def [](key)
30
- value = @db.execute_t("select value from #{@table_name} where key=?", [key]).first.first rescue nil
30
+ value = begin
31
+ ret = @db.execute_t("select value from #{@table_name} where key=?", [key])
32
+ (ret && ret.first) ? ret.first.first : nil
33
+ rescue SQLite3::Exception
34
+ nil
35
+ end
36
+
31
37
  return value && Marshal.load(value)
32
38
  end
33
39
 
@@ -35,8 +41,10 @@ class Palsy
35
41
  # Set an object. Returns the object. The object must be able to be Marshalled.
36
42
  #
37
43
  def []=(key, value)
38
- delete(key)
39
- @db.execute_t("insert into #{@table_name} (key, value) values (?, ?)", [key, Marshal.dump(value)])
44
+ @db.with_t do
45
+ delete(key)
46
+ @db.execute_t("insert into #{@table_name} (key, value) values (?, ?)", [key, Marshal.dump(value)])
47
+ end
40
48
  value
41
49
  end
42
50
 
@@ -41,8 +41,10 @@ class Palsy
41
41
  # avoid raising a constraint from sqlite.
42
42
  #
43
43
  def add(key)
44
- delete(key)
45
- @db.execute_t("insert into #{@table_name} (name, key) values (?, ?)", [@object_name, Marshal.dump(key)])
44
+ @db.with_t do
45
+ delete(key)
46
+ @db.execute_t("insert into #{@table_name} (name, key) values (?, ?)", [@object_name, Marshal.dump(key)])
47
+ end
46
48
  end
47
49
 
48
50
  #
@@ -73,13 +75,15 @@ class Palsy
73
75
  # set, or any Enumerable that has a set of unique values.
74
76
  #
75
77
  def replace(set)
76
- clear
78
+ @db.with_t do
79
+ clear
77
80
 
78
- return if set.empty?
81
+ return if set.empty?
79
82
 
80
- value_string = ("(?, ?)," * set.count).chop
83
+ value_string = ("(?, ?)," * set.count).chop
81
84
 
82
- @db.execute_t("insert into #{@table_name} (name, key) values #{value_string}", set.map { |x| [@object_name, Marshal.dump(x)] }.flatten)
85
+ @db.execute_t("insert into #{@table_name} (name, key) values #{value_string}", set.map { |x| [@object_name, Marshal.dump(x)] }.flatten)
86
+ end
83
87
  end
84
88
 
85
89
  #
@@ -1 +1 @@
1
- PalsyVersion = "0.0.3"
1
+ PalsyVersion = "0.0.4"
@@ -7,6 +7,7 @@ end
7
7
 
8
8
  require 'minitest/unit'
9
9
  require 'tempfile'
10
+ require 'timeout'
10
11
  require 'palsy'
11
12
 
12
13
  module Palsy::TestHelper
@@ -38,4 +38,19 @@ class TestInit < MiniTest::Unit::TestCase
38
38
  Palsy.reconnect
39
39
  assert_raises(NoMethodError) { Palsy.instance.closed? }
40
40
  end
41
+
42
+ def test_with_t
43
+ dbfile = new_dbfile
44
+ Palsy.change_db(dbfile.path)
45
+
46
+ (0..2).map do
47
+ Thread.new do
48
+ assert_raises(ArgumentError) do
49
+ Palsy.instance.with_t do
50
+ raise ArgumentError
51
+ end
52
+ end
53
+ end
54
+ end.map(&:join)
55
+ end
41
56
  end
@@ -15,6 +15,46 @@ class TestList < PalsyTypeTest
15
15
  t.each(&:join)
16
16
 
17
17
  assert_equal(syms.sort, @list.to_a.sort)
18
+
19
+ writer = Thread.new { loop { @list.push(rand.to_i) } }
20
+ reader = Thread.new { loop { @list.shift } }
21
+
22
+ begin
23
+ Timeout.timeout(10) do
24
+ writer.join
25
+ reader.join
26
+ end
27
+ rescue TimeoutError
28
+ pass "push and shift competed for 10 seconds without any exceptions"
29
+ writer.raise
30
+ reader.raise
31
+ writer.join rescue nil
32
+ reader.join rescue nil
33
+ end
34
+
35
+ replacer = Thread.new { loop { @list.replace(Set[1,2,3]) } }
36
+ clearer = Thread.new { loop { @list.clear } }
37
+
38
+ begin
39
+ Timeout.timeout(10) do
40
+ replacer.join
41
+ clearer.join
42
+ end
43
+ rescue TimeoutError
44
+ pass "replace and clear competed for 10 seconds without any exceptions"
45
+ replacer.raise
46
+ clearer.raise
47
+ replacer.join rescue nil
48
+ clearer.join rescue nil
49
+ end
50
+
51
+ refute(@list.db.transaction_active?)
52
+
53
+ # XXX there's no solid way of knowing what'll be in here without inserting
54
+ # locks which will actually defeat the point of the test. Check the type,
55
+ # which will cause an execution and ensure we don't have a stale
56
+ # transaction.
57
+ assert_kind_of(Array, @list.to_a)
18
58
  end
19
59
 
20
60
  def test_semantics
@@ -16,6 +16,46 @@ class TestMap < PalsyTypeTest
16
16
  t.each(&:join)
17
17
 
18
18
  assert_equal(@map.to_hash, args)
19
+
20
+ writer = Thread.new { loop { @map[rand.to_i] = 1 } }
21
+ reader = Thread.new { loop { @map.keys } }
22
+
23
+ begin
24
+ Timeout.timeout(10) do
25
+ writer.join
26
+ reader.join
27
+ end
28
+ rescue TimeoutError
29
+ pass "[] and keys competed for 10 seconds without any exceptions"
30
+ writer.raise
31
+ reader.raise
32
+ writer.join rescue nil
33
+ reader.join rescue nil
34
+ end
35
+
36
+ replacer = Thread.new { loop { @map.replace(Hash[[1,2,3].zip([2,3,4])]) } }
37
+ clearer = Thread.new { loop { @map.clear } }
38
+
39
+ begin
40
+ Timeout.timeout(10) do
41
+ replacer.join
42
+ clearer.join
43
+ end
44
+ rescue TimeoutError
45
+ pass "replace and clear competed for 10 seconds without any exceptions"
46
+ replacer.raise
47
+ clearer.raise
48
+ replacer.join rescue nil
49
+ clearer.join rescue nil
50
+ end
51
+
52
+ refute(@map.db.transaction_active?)
53
+
54
+ # XXX there's no solid way of knowing what'll be in here without inserting
55
+ # locks which will actually defeat the point of the test. Check the type,
56
+ # which will cause an execution and ensure we don't have a stale
57
+ # transaction.
58
+ assert_kind_of(Hash, @map.to_hash)
19
59
  end
20
60
 
21
61
  def test_inherits
@@ -86,4 +126,17 @@ class TestMap < PalsyTypeTest
86
126
  assert_equal(@map[k], v, "#each yields each value")
87
127
  end
88
128
  end
129
+
130
+ def test_replace
131
+ @map[1] = "foo"
132
+ assert_equal("foo", @map[1])
133
+
134
+ hash = {2 => "bar", 3 => "foo"}
135
+
136
+ @map.replace(hash)
137
+ refute_equal("foo", @map[1])
138
+ assert_equal("bar", @map[2])
139
+ assert_equal("foo", @map[3])
140
+ assert_equal(hash, @map.to_hash)
141
+ end
89
142
  end
@@ -14,6 +14,41 @@ class TestObject < PalsyTypeTest
14
14
  args.each do |k,v|
15
15
  assert_equal(obj[k], args[k])
16
16
  end
17
+
18
+ writer = Thread.new { loop { obj["1"] = "1" } }
19
+ reader = Thread.new { loop { obj["1"] } }
20
+
21
+ begin
22
+ Timeout.timeout(10) do
23
+ writer.join
24
+ reader.join
25
+ end
26
+ rescue TimeoutError
27
+ pass "[]= and [] competed for 10 seconds without any exceptions"
28
+ writer.raise
29
+ reader.raise
30
+ writer.join rescue nil
31
+ reader.join rescue nil
32
+ end
33
+
34
+ replacer = Thread.new { loop { obj[1] = 1 } }
35
+ clearer = Thread.new { loop { obj.delete(1) } }
36
+
37
+ begin
38
+ Timeout.timeout(10) do
39
+ replacer.join
40
+ clearer.join
41
+ end
42
+ rescue TimeoutError
43
+ pass "[]= and delete competed for 10 seconds without any exceptions"
44
+ replacer.raise
45
+ clearer.raise
46
+ replacer.join rescue nil
47
+ clearer.join rescue nil
48
+ end
49
+
50
+ refute(obj.db.transaction_active?)
51
+ obj.delete(1)
17
52
  end
18
53
 
19
54
  def test_index
@@ -15,6 +15,61 @@ class TestSet < PalsyTypeTest
15
15
  t.each(&:join)
16
16
 
17
17
  assert_equal(syms.sort, @set.to_a.sort)
18
+
19
+ writer = Thread.new { loop { @set.add(rand.to_i) } }
20
+ reader = Thread.new { loop { @set.keys } }
21
+
22
+ begin
23
+ Timeout.timeout(10) do
24
+ writer.join
25
+ reader.join
26
+ end
27
+ rescue TimeoutError
28
+ pass "add and keys competed for 10 seconds without any exceptions"
29
+ writer.raise
30
+ reader.raise
31
+ writer.join rescue nil
32
+ reader.join rescue nil
33
+ end
34
+
35
+ replacer = Thread.new { loop { @set.replace(Set[1,2,3]) } }
36
+ clearer = Thread.new { loop { @set.clear } }
37
+
38
+ begin
39
+ Timeout.timeout(10) do
40
+ replacer.join
41
+ clearer.join
42
+ end
43
+ rescue TimeoutError
44
+ pass "replace and clear competed for 10 seconds without any exceptions"
45
+ replacer.raise
46
+ clearer.raise
47
+ replacer.join rescue nil
48
+ clearer.join rescue nil
49
+ end
50
+
51
+ refute(@set.db.transaction_active?)
52
+
53
+ # XXX there's no solid way of knowing what'll be in here without inserting
54
+ # locks which will actually defeat the point of the test. Check the type,
55
+ # which will cause an execution and ensure we don't have a stale
56
+ # transaction.
57
+ assert_kind_of(Set, @set.to_set)
58
+ end
59
+
60
+ def test_enumerable_freebies
61
+ (0..10).each { |x| @set.add(x) }
62
+ real_set = @set.to_set
63
+ assert_kind_of(Set, real_set)
64
+ assert_equal(Set[*(0..10).to_a], real_set)
65
+ array = @set.to_a
66
+ assert_kind_of(Array, array)
67
+ assert_equal((0..10).to_a, array.sort)
68
+
69
+ t = (0..2).map { Thread.new { @set.to_set } }
70
+ t.each(&:join)
71
+
72
+ t = (0..2).map { Thread.new { @set.to_a } }
18
73
  end
19
74
 
20
75
  def test_semantics
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: palsy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
5
- prerelease:
4
+ version: 0.0.4
6
5
  platform: ruby
7
6
  authors:
8
7
  - Erik Hollensbe
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-02 00:00:00.000000000 Z
11
+ date: 2013-04-06 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: sqlite3
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: minitest
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: :development
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,23 +41,20 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
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
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rdoc
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,39 +69,34 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: simplecov
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ! '>='
73
+ - - '>='
84
74
  - !ruby/object:Gem::Version
85
75
  version: '0'
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
93
82
  version: '0'
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: guard-minitest
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0'
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
109
96
  version: '0'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: guard-rake
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
101
  - - ~>
116
102
  - !ruby/object:Gem::Version
@@ -118,7 +104,6 @@ dependencies:
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
108
  - - ~>
124
109
  - !ruby/object:Gem::Version
@@ -126,17 +111,15 @@ dependencies:
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: rb-fsevent
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
- - - ! '>='
115
+ - - '>='
132
116
  - !ruby/object:Gem::Version
133
117
  version: '0'
134
118
  type: :development
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
- - - ! '>='
122
+ - - '>='
140
123
  - !ruby/object:Gem::Version
141
124
  version: '0'
142
125
  description: An extremely simple marshalling persistence layer for SQLite based on
@@ -174,33 +157,26 @@ files:
174
157
  - test/test_set.rb
175
158
  homepage: ''
176
159
  licenses: []
160
+ metadata: {}
177
161
  post_install_message:
178
162
  rdoc_options: []
179
163
  require_paths:
180
164
  - lib
181
165
  required_ruby_version: !ruby/object:Gem::Requirement
182
- none: false
183
166
  requirements:
184
- - - ! '>='
167
+ - - '>='
185
168
  - !ruby/object:Gem::Version
186
169
  version: '0'
187
- segments:
188
- - 0
189
- hash: 3156010217290385344
190
170
  required_rubygems_version: !ruby/object:Gem::Requirement
191
- none: false
192
171
  requirements:
193
- - - ! '>='
172
+ - - '>='
194
173
  - !ruby/object:Gem::Version
195
174
  version: '0'
196
- segments:
197
- - 0
198
- hash: 3156010217290385344
199
175
  requirements: []
200
176
  rubyforge_project:
201
- rubygems_version: 1.8.25
177
+ rubygems_version: 2.0.0
202
178
  signing_key:
203
- specification_version: 3
179
+ specification_version: 4
204
180
  summary: An extremely simple marshalling persistence layer for SQLite based on perl's
205
181
  tie()
206
182
  test_files: