lithos 0.1.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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lithos
4
+ VERSION = "0.1.0"
5
+ end
data/lib/lithos.rb ADDED
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lithos/version"
4
+ require "lithos/lithos" # native extension: defines Lithos::DB + Lithos::Error
5
+
6
+ # lithos — a small embedded, ordered, crash-safe key-value store.
7
+ #
8
+ # Lithos.open("data/db") do |db|
9
+ # db["alpha"] = "1"
10
+ # db.put("\x00bin\xff", "raw") # binary keys/values welcome
11
+ # db["alpha"] # => "1"
12
+ # db.scan(gte: "a", lt: "m") { |k, v| } # ordered range
13
+ # end # auto-closed (flushes + fsyncs)
14
+ module Lithos
15
+ # Open a store at `path` (a directory, created if missing). Options:
16
+ # sync: durably fsync each write before it returns (default true)
17
+ # memtable_bytes: flush the in-memory table to an SSTable past this size
18
+ #
19
+ # With a block: yields the store, closes it when the block returns (even on
20
+ # error), and returns the block's value. Without a block: returns the store,
21
+ # and the caller must #close it.
22
+ def self.open(path, **opts)
23
+ db = DB.new(path, **opts)
24
+ return db unless block_given?
25
+
26
+ begin
27
+ yield db
28
+ ensure
29
+ db.close
30
+ end
31
+ end
32
+
33
+ class DB
34
+ include Enumerable
35
+
36
+ # --- mutators (forbidden while an each/scan is in progress) --------------
37
+ # The merge iterator holds raw pointers into memory-mapped SSTables and live
38
+ # memtable iterators; mutating mid-scan would free them underneath it. We
39
+ # refuse here with a plain Ruby exception (which unwinds safely), rather than
40
+ # let it reach the C engine during iteration.
41
+
42
+ def put(key, value)
43
+ forbid_during_iteration!
44
+ _put(key, value)
45
+ end
46
+
47
+ def delete(key)
48
+ forbid_during_iteration!
49
+ _delete(key)
50
+ end
51
+
52
+ def flush
53
+ forbid_during_iteration!
54
+ _flush
55
+ end
56
+
57
+ def compact
58
+ forbid_during_iteration!
59
+ _compact
60
+ end
61
+
62
+ def close
63
+ forbid_during_iteration!
64
+ _close
65
+ end
66
+
67
+ # Hash-like access. `db[k]` => value or nil; `db[k] = v` stores v.
68
+ def [](key)
69
+ get(key)
70
+ end
71
+
72
+ def []=(key, value)
73
+ put(key, value)
74
+ value
75
+ end
76
+
77
+ # Like Hash#fetch: return the value, else the block result, else the default,
78
+ # else raise KeyError. (Stored values are never nil, so nil == absent.)
79
+ def fetch(key, *default)
80
+ if default.size > 1
81
+ raise ArgumentError, "wrong number of arguments (given #{default.size + 1}, expected 1..2)"
82
+ end
83
+
84
+ value = get(key)
85
+ return value unless value.nil?
86
+ return yield(key) if block_given?
87
+ return default.first unless default.empty?
88
+
89
+ raise KeyError, "key not found: #{key.inspect}"
90
+ end
91
+
92
+ alias include? key?
93
+ alias has_key? key?
94
+ alias member? key?
95
+
96
+ # Iterate all pairs in ascending (unsigned-byte) key order.
97
+ def each(&block)
98
+ return enum_for(:each) unless block
99
+
100
+ bounded_each(nil, false, nil, false, &block)
101
+ end
102
+ alias each_pair each
103
+
104
+ # Iterate keys only, ascending.
105
+ def each_key
106
+ return enum_for(:each_key) unless block_given?
107
+
108
+ bounded_each(nil, false, nil, false) { |k, _| yield k }
109
+ end
110
+
111
+ # Ordered range scan. Bounds are optional; gt/lt are exclusive, gte/lte
112
+ # inclusive. Returns an Enumerator without a block.
113
+ def scan(gte: nil, gt: nil, lte: nil, lt: nil, &block)
114
+ lower, lower_incl = gt ? [gt, false] : (gte ? [gte, true] : [nil, false])
115
+ upper, upper_incl = lt ? [lt, false] : (lte ? [lte, true] : [nil, false])
116
+ bounded_each(lower, lower_incl, upper, upper_incl, &block)
117
+ end
118
+
119
+ # Half-open range [from, to) in ascending order.
120
+ def range(from, to, &block)
121
+ bounded_each(from, true, to, false, &block)
122
+ end
123
+
124
+ # Number of live keys. O(n) — counts via an ordered scan.
125
+ def size
126
+ n = 0
127
+ each_key { n += 1 }
128
+ n
129
+ end
130
+ alias length size
131
+
132
+ def empty?
133
+ each { return false }
134
+ true
135
+ end
136
+
137
+ def to_h
138
+ h = {}
139
+ each { |k, v| h[k] = v }
140
+ h
141
+ end
142
+
143
+ def inspect
144
+ "#<Lithos::DB path=#{path.inspect}#{closed? ? ' (closed)' : ''}>"
145
+ end
146
+
147
+ private
148
+
149
+ def forbid_during_iteration!
150
+ return unless (@iterating || 0).positive?
151
+
152
+ raise Error, "lithos: cannot modify the store during iteration"
153
+ end
154
+
155
+ # Shared range driver; positional args so enum_for forwards cleanly. The
156
+ # @iterating depth lets reads nest while mutations are blocked, and the
157
+ # ensure guarantees the count is restored even on break/exception.
158
+ def bounded_each(lower, lower_incl, upper, upper_incl, &block)
159
+ return enum_for(:bounded_each, lower, lower_incl, upper, upper_incl) unless block
160
+
161
+ @iterating = (@iterating || 0) + 1
162
+ begin
163
+ _each_range(lower, lower_incl, upper, upper_incl, &block)
164
+ ensure
165
+ @iterating -= 1
166
+ end
167
+ self
168
+ end
169
+ end
170
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lithos
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ned
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake-compiler
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.2'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: vcvars
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.1'
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 0.1.1
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '0.1'
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.1.1
74
+ description: |
75
+ lithos is a self-contained embedded key-value store written from scratch as a
76
+ native extension — no external database dependency. It uses a log-structured
77
+ merge (LSM) tree: a write-ahead log makes every write durable, an in-memory
78
+ sorted memtable flushes to immutable SSTables (with bloom filters), and
79
+ compaction merges them. Keys and values are arbitrary binary strings; keys
80
+ are kept in sorted order so you get ordered iteration and range scans, plus
81
+ crash recovery via WAL replay. Windows MSVC (mswin) Ruby only.
82
+ executables: []
83
+ extensions:
84
+ - ext/lithos/extconf.rb
85
+ extra_rdoc_files: []
86
+ files:
87
+ - CHANGELOG.md
88
+ - LICENSE.txt
89
+ - README.md
90
+ - ext/lithos/extconf.rb
91
+ - ext/lithos/lithos.cpp
92
+ - lib/lithos.rb
93
+ - lib/lithos/version.rb
94
+ homepage: https://github.com/main-path/lithos
95
+ licenses:
96
+ - MIT
97
+ metadata:
98
+ homepage_uri: https://github.com/main-path/lithos
99
+ changelog_uri: https://github.com/main-path/lithos/blob/main/CHANGELOG.md
100
+ bug_tracker_uri: https://github.com/main-path/lithos/issues
101
+ rubygems_mfa_required: 'true'
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '3.0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.6.9
117
+ specification_version: 4
118
+ summary: A small embedded, ordered, crash-safe key-value store (LSM-tree) for Ruby.
119
+ test_files: []