mem_db 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mem_db/out"
4
+ require "mem_db/index"
5
+ require "mem_db/index/bucket"
6
+ require "mem_db/bucket"
7
+
8
+ class MemDB
9
+ module Index
10
+ class PrefixTree
11
+ include MemDB::Index
12
+
13
+ class Bucket
14
+ include MemDB::Index::Bucket
15
+
16
+ def initialize(idx:, bucket: MemDB::Bucket)
17
+ @idx = idx
18
+ @bucket = bucket
19
+ end
20
+
21
+ def new
22
+ MemDB::Index::PrefixTree.new(idx: @idx, bucket: @bucket)
23
+ end
24
+ end
25
+
26
+ class Root
27
+ MAX_LENGTH_DEFAULT = 2 ^ 64
28
+
29
+ def initialize(bucket:)
30
+ @item = Item.new(bucket: bucket)
31
+ @min_length = MAX_LENGTH_DEFAULT
32
+ end
33
+
34
+ def get(contents, query:, result:)
35
+ contents.each do |content|
36
+ next if @min_length > content.length
37
+
38
+ @item.select_values(content, 0, query: query, out: result)
39
+ end
40
+
41
+ result
42
+ end
43
+
44
+ def add(prefixes, obj, value)
45
+ prefixes.each do |prefix|
46
+ @min_length = prefix.length if @min_length > prefix.length
47
+ @item.add(prefix, 0, obj, value)
48
+ end
49
+ end
50
+ end
51
+
52
+ class Item
53
+ attr_reader :value
54
+
55
+ def initialize(bucket:)
56
+ @bucket = bucket
57
+ end
58
+
59
+ def select_values(content, i, query:, out:)
60
+ @value&.query(query, out: out)
61
+
62
+ return if content.length == i
63
+
64
+ return unless @children
65
+
66
+ if (item = @children[content[i]])
67
+ item.select_values(content, i + 1, query: query, out: out)
68
+ end
69
+ end
70
+
71
+ def add(prefix, i, obj, value)
72
+ if prefix.length == i
73
+ set_value(obj, value)
74
+ else
75
+ item = fetch_children(prefix[i])
76
+ item.add(prefix, i + 1, obj, value)
77
+ end
78
+ end
79
+
80
+ def set_value(obj, value)
81
+ @value ||= @bucket.new
82
+ @value.add(obj, value)
83
+ end
84
+
85
+ def fetch_children(idx)
86
+ @children ||= {}
87
+ @children[idx] ||= Item.new(bucket: @bucket)
88
+ end
89
+ end
90
+
91
+ attr_reader :idx, :bucket
92
+
93
+ def initialize(idx:, bucket: MemDB::Bucket)
94
+ @idx = idx
95
+ @bucket = bucket
96
+ @root = Root.new(bucket: bucket)
97
+ end
98
+
99
+ def add(obj, value)
100
+ @root.add(obj.idx_value(@idx), obj, value)
101
+ end
102
+
103
+ def query(query, out: MemDB::Out.new)
104
+ @root.get(query.idx_value(@idx), query: query, result: out)
105
+
106
+ out
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mem_db/index"
4
+ require "mem_db/index/bucket"
5
+ require "mem_db/out"
6
+
7
+ class MemDB
8
+ module Index
9
+ class SequenceMatch
10
+ include MemDB::Index
11
+
12
+ class Bucket
13
+ include MemDB::Index::Bucket
14
+
15
+ def initialize(idx:, bucket: MemDB::Bucket)
16
+ @idx = idx
17
+ @bucket = bucket
18
+ end
19
+
20
+ def new
21
+ MemDB::Index::SequenceMatch.new(idx: @idx, bucket: @bucket)
22
+ end
23
+ end
24
+
25
+ # https://en.wikipedia.org/wiki/Boyer-Moore_string_search_algorithm
26
+ class SequenceIndex
27
+ def initialize(pattern)
28
+ @pattern = pattern
29
+ @pattern_length = pattern.length
30
+ @bad_char_skip = {}
31
+ @good_suffix_skip = Array.new(@pattern_length, 0)
32
+
33
+ init_tables
34
+ end
35
+
36
+ def index(seq)
37
+ i = @pattern_length - 1
38
+
39
+ while i < seq.length
40
+ j = @pattern_length - 1
41
+
42
+ while j >= 0 && seq[i] == @pattern[j]
43
+ i -= 1
44
+ j -= 1
45
+ end
46
+
47
+ return i + 1 if j.negative?
48
+
49
+ bad_skip = @bad_char_skip[seq[i]] || @pattern_length
50
+ good_skip = @good_suffix_skip[j]
51
+ i += good_skip > bad_skip ? good_skip : bad_skip
52
+ end
53
+
54
+ -1
55
+ end
56
+
57
+ def init_tables # rubocop:disable Metrics/AbcSize
58
+ last = @pattern_length - 1
59
+
60
+ i = 0
61
+ while i < last
62
+ @bad_char_skip[@pattern[i]] = last - i
63
+ i += 1
64
+ end
65
+
66
+ last_prefix = last
67
+
68
+ i = last
69
+ while i >= 0
70
+ last_prefix = i + 1 if pattern_suffix?(i + 1)
71
+
72
+ @good_suffix_skip[i] = last_prefix + last - i
73
+ i -= 1
74
+ end
75
+
76
+ i = 0
77
+ while i < last
78
+ len_suffix = longest_pattern_suffix(i)
79
+
80
+ if @pattern[i - len_suffix] != @pattern[last - len_suffix]
81
+ @good_suffix_skip[last - len_suffix] = len_suffix + last - i
82
+ end
83
+
84
+ i += 1
85
+ end
86
+ end
87
+
88
+ def pattern_suffix?(pos)
89
+ i = 0
90
+ while i + pos < @pattern.length
91
+ return false if @pattern[i] != @pattern[i + pos]
92
+
93
+ i += 1
94
+ end
95
+ true
96
+ end
97
+
98
+ def longest_pattern_suffix(pos)
99
+ i = 0
100
+
101
+ while i < @pattern.length && i < pos
102
+ break if @pattern[@pattern.length - 1 - i] != @pattern[pos - i]
103
+
104
+ i += 1
105
+ end
106
+
107
+ i
108
+ end
109
+ end
110
+
111
+ def initialize(idx:, bucket: MemDB::Bucket)
112
+ @idx = idx
113
+ @bucket = bucket
114
+ @patterns = {}
115
+ @matchers = {}
116
+ end
117
+
118
+ def add(obj, value)
119
+ obj.idx_value(@idx).each do |pattern|
120
+ @patterns[pattern] ||= @bucket.new
121
+ @patterns[pattern].add(obj, value)
122
+
123
+ @matchers[pattern] ||= SequenceIndex.new(pattern)
124
+ end
125
+ end
126
+
127
+ def query(query, out: MemDB::Out.new)
128
+ query.idx_value(@idx).each do |seq|
129
+ select_one(query, seq, out)
130
+ end
131
+
132
+ out
133
+ end
134
+
135
+ private
136
+
137
+ def select_one(query, seq, out)
138
+ @matchers.each do |pattern, sequence|
139
+ next if seq.length < pattern.length
140
+
141
+ @patterns[pattern].query(query, out: out) if sequence.index(seq) > -1
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mem_db/indexing_object"
4
+
5
+ class MemDB
6
+ class Indexation
7
+ def initialize(index)
8
+ @obj = MemDB::IndexingObject.new
9
+ @index = index
10
+ end
11
+
12
+ def add(raw, value)
13
+ @obj.assign!(raw)
14
+ @index.add(@obj, value)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mem_db/idx"
4
+
5
+ class MemDB
6
+ class IndexingObject
7
+ def initialize
8
+ @params = {}
9
+ @attrs = {}
10
+ @idx_value = {}
11
+ end
12
+
13
+ def assign!(params)
14
+ @params = params
15
+ @attrs.clear
16
+ @idx_value.clear
17
+
18
+ self
19
+ end
20
+
21
+ def [](attr)
22
+ if @attrs.key?(attr)
23
+ @attrs[attr]
24
+ else
25
+ @attrs[attr] ||= prepare_attr(attr)
26
+ end
27
+ end
28
+
29
+ def []=(param, value)
30
+ @params[param] = value
31
+ end
32
+
33
+ def delete(param)
34
+ @params.delete(param)
35
+ @attrs.delete(param)
36
+ end
37
+
38
+ def idx_value(idx)
39
+ if @idx_value.key?(idx)
40
+ @idx_value[idx]
41
+ else
42
+ @idx_value[idx] ||= idx.value(self)
43
+ end
44
+ end
45
+
46
+ def prepare_attr(attr)
47
+ v = @params[attr]
48
+
49
+ if v == MemDB::Idx::ANY
50
+ v
51
+ elsif v.nil? || v.is_a?(Array)
52
+ v
53
+ else
54
+ [v]
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/mem_db/out.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MemDB
4
+ class Out
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @arr = []
9
+ end
10
+
11
+ def add(res)
12
+ @arr.push(res)
13
+
14
+ true
15
+ end
16
+
17
+ def each(&block)
18
+ return to_enum unless block_given?
19
+
20
+ @arr.each do |values|
21
+ values.each(&block)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MemDB
4
+ class Query
5
+ def initialize(params)
6
+ @params = params
7
+ @attrs = {}
8
+ @idx_value = {}
9
+ end
10
+
11
+ def using(index)
12
+ index.query(self)
13
+ end
14
+
15
+ def [](attr)
16
+ if @attrs.key?(attr)
17
+ @attrs[attr]
18
+ else
19
+ @attrs[attr] ||= prepare_attr(attr)
20
+ end
21
+ end
22
+
23
+ def []=(param, value)
24
+ @params[param] = value
25
+ end
26
+
27
+ def delete(param)
28
+ @params.delete(param)
29
+ @attrs.delete(param)
30
+ end
31
+
32
+ def idx_value(idx)
33
+ if @idx_value.key?(idx)
34
+ @idx_value[idx]
35
+ else
36
+ @idx_value[idx] ||= idx.prepare_query(self)
37
+ end
38
+ end
39
+
40
+ def prepare_attr(attr)
41
+ v = @params[attr]
42
+ if v.is_a?(Array)
43
+ v
44
+ else
45
+ [v]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MemDB
4
+ VERSION = "0.1.0"
5
+ end
data/mem_db.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/mem_db/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mem_db"
7
+ spec.version = MemDB::VERSION
8
+ spec.authors = ["Dmitry Bochkarev"]
9
+ spec.email = ["dimabochkarev@gmail.com"]
10
+
11
+ spec.summary = "MemDB is embedded database"
12
+ spec.description = "MemDB is embedded database"
13
+ spec.homepage = "https://github.com/DmitryBochkarev/mem_db"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/DmitryBochkarev/mem_db"
19
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ end