rudb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +2 -0
- data/lib/rdb.rb +35 -0
- data/lib/rdb/collection.rb +72 -0
- data/lib/rdb/document.rb +27 -0
- data/lib/rdb/error.rb +4 -0
- data/lib/rdb/index.rb +11 -0
- data/lib/rdb/indexes/btree.rb +235 -0
- data/lib/rdb/indexes/hash.rb +6 -0
- data/lib/rdb/orm.rb +85 -0
- data/lib/rdb/storage_engine.rb +7 -0
- data/lib/rdb/storage_engines/json.rb +25 -0
- data/lib/rdb/version.rb +3 -0
- data/rdb.gemspec +24 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0d0953b3633a41451309300ae686f6a33257dff864c996c85b852475e5f7b7d7
|
4
|
+
data.tar.gz: 87017f8c90398f9f47c99385b671fbb365b47b09a61b8f774aeb57190a0aba62
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1459916d56817268764af91728bab8d9bb3f2eedab4b0b62cea19aed47e7b5accc75df8a13acf2064dd0f95b2ae00bc61b7c58340eb60679b6ac2e5792c0d1e
|
7
|
+
data.tar.gz: 65e881d04a43ece48dfd4812bf05f3279af6a2e48be47e472c1ca32f8eb6537db12b3ffe1da8d2fc0d7178bec075a6713b64f7bbae261d442614fee669ff5269
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Rustam Galeev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Rustik db XD
|
2
|
+
|
3
|
+
Rustik DB is a Ruby implementation of simple files database, with Btree indexes.
|
4
|
+
Btree is described in Introduction to Algorithms by Cormen, Leiserson, Rivest and Stein, Chapter 18.
|
5
|
+
It's bundled with the `ORM mapper` which behaves like a Rails `find_by` and `save` methods on your classes.
|
6
|
+
Please note, this gem is not purposed for production use, the only reason I've written it is to demonstrate how to write your own database, and use it with your own data mapper, like `ActiveRecord`.
|
7
|
+
|
8
|
+
|
9
|
+
##Features
|
10
|
+
|
11
|
+
- Create Btree of arbitrary degree (4 by default)
|
12
|
+
- Insert and search by object attributes, like Rails framework does
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'rdb'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install rdb
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
```
|
33
|
+
|
34
|
+
require_relative 'rdb'
|
35
|
+
require_relative 'rdb/orm'
|
36
|
+
|
37
|
+
class Plane
|
38
|
+
include Rdb::Orm
|
39
|
+
rdb_mount 'planes', {id: Integer, code: String, weight: Integer}, {id: Rdb::Indexes::Btree}, '/home/rustam/rdata'
|
40
|
+
end
|
41
|
+
|
42
|
+
names = %w(rose velvet pink blue charley delta bravo)
|
43
|
+
|
44
|
+
300000.times do |x|
|
45
|
+
code = "%s%d%s" % [names.sample, rand(1000), names.sample]
|
46
|
+
Plane.create(code: code, weight: rand(1000))
|
47
|
+
end
|
48
|
+
|
49
|
+
Plane.find_by(id: 1123)
|
50
|
+
|
51
|
+
x = Plane.new
|
52
|
+
x.code = "superjet3000"
|
53
|
+
x.weight = 2572
|
54
|
+
x.save
|
55
|
+
|
56
|
+
Plane.find_by(id: x.id)
|
57
|
+
|
58
|
+
```
|
59
|
+
|
60
|
+
## To Do
|
61
|
+
|
62
|
+
The features in development are:
|
63
|
+
|
64
|
+
- Hash index
|
65
|
+
- Multiple indexes
|
66
|
+
|
67
|
+
## License
|
68
|
+
|
69
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/lib/rdb.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Top level namespace Rdb
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Rdb
|
5
|
+
|
6
|
+
### alias for Collection#open
|
7
|
+
def self::open *args
|
8
|
+
Rdb::Collection.new *args
|
9
|
+
end
|
10
|
+
|
11
|
+
def self::logger
|
12
|
+
@logger ||= default_logger
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_writer :logger
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def self.default_logger
|
22
|
+
logger = Logger.new(STDERR)
|
23
|
+
logger.level = Logger::ERROR
|
24
|
+
logger
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
29
|
+
|
30
|
+
require 'rdb/storage_engine'
|
31
|
+
require 'rdb/index'
|
32
|
+
require 'rdb/document'
|
33
|
+
require 'rdb/collection'
|
34
|
+
require 'rdb/error'
|
35
|
+
require 'rdb/version'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# the collection of documents
|
2
|
+
module Rdb
|
3
|
+
class Collection
|
4
|
+
|
5
|
+
attr_accessor :name, :fields, :indexes, :path_to_database
|
6
|
+
|
7
|
+
PRIMARY_INDEX_NAME = :id
|
8
|
+
PRIMARY_INDEX_START_VAL = 0
|
9
|
+
|
10
|
+
# initialize collection with given attributes:
|
11
|
+
# @param name
|
12
|
+
# @param fields {id: Integer, name: String}
|
13
|
+
# @param indexes {id: Rdb::Indexes::Btree, name: Rdb::Indexes::Hash}
|
14
|
+
def initialize *args
|
15
|
+
@name = args.first[:name]
|
16
|
+
@fields = args.first[:fields]
|
17
|
+
@indexes = args.first[:indexes]
|
18
|
+
@path_to_database = args.first[:path_to_database]
|
19
|
+
setup_dirs
|
20
|
+
end
|
21
|
+
|
22
|
+
# insert document into collection
|
23
|
+
def << attrs
|
24
|
+
primary_index_id = get_last_id(PRIMARY_INDEX_NAME) + 1
|
25
|
+
document = Rdb::Document.new attrs.merge(PRIMARY_INDEX_NAME => primary_index_id), {
|
26
|
+
fields: fields,
|
27
|
+
indexes: indexes,
|
28
|
+
collection_name: name,
|
29
|
+
path_to_database: path_to_database,
|
30
|
+
last_id: primary_index_id}
|
31
|
+
raise ::Error, "Not a Document type" unless document.is_a? Rdb::Document
|
32
|
+
transaction do
|
33
|
+
document.store!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias_method :insert, :<<
|
37
|
+
|
38
|
+
# fetch document from collection
|
39
|
+
def [] key, value
|
40
|
+
index = indexes[key].new(key, {},
|
41
|
+
collection_name: name,
|
42
|
+
path_to_database: path_to_database)
|
43
|
+
index.search value
|
44
|
+
end
|
45
|
+
alias_method :select, :[]
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
# for future atomicity
|
50
|
+
def transaction
|
51
|
+
yield if block_given?
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_last_id index_name
|
55
|
+
Rdb::Indexes::Btree.get_last_id(File.join(path_to_database, name, index_name.to_s)) || PRIMARY_INDEX_START_VAL
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup_dirs
|
59
|
+
raise ::Error, "Database dir not found" unless Dir.exist? path_to_database
|
60
|
+
paths = []
|
61
|
+
path_to_collection = File.join(path_to_database, name)
|
62
|
+
paths << path_to_collection unless Dir.exist? path_to_collection
|
63
|
+
indexes.keys.each do |index_name|
|
64
|
+
path_to_index = File.join(path_to_database, name, index_name.to_s)
|
65
|
+
paths << path_to_index unless Dir.exist? path_to_index
|
66
|
+
end
|
67
|
+
|
68
|
+
paths.each { |path| Dir.mkdir(path, 0700) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
data/lib/rdb/document.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# the set of attributes
|
2
|
+
module Rdb
|
3
|
+
class Document
|
4
|
+
|
5
|
+
attr_accessor :attributes, :fields, :indexes, :collection_name, :path_to_database#, :last_id
|
6
|
+
|
7
|
+
def initialize attrs, options = {}
|
8
|
+
@attributes = attrs
|
9
|
+
@fields = options[:fields]
|
10
|
+
@indexes = options[:indexes]
|
11
|
+
@collection_name = options[:collection_name]
|
12
|
+
@path_to_database = options[:path_to_database]
|
13
|
+
end
|
14
|
+
|
15
|
+
def store!
|
16
|
+
indexes.inject({}) do |result, index_hash|
|
17
|
+
field_name, index_class = index_hash.flatten
|
18
|
+
index = index_class.new field_name, attributes,
|
19
|
+
collection_name: collection_name,
|
20
|
+
path_to_database: path_to_database
|
21
|
+
|
22
|
+
result[field_name] = index.create!
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rdb/error.rb
ADDED
data/lib/rdb/index.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Rdb
|
4
|
+
module Indexes
|
5
|
+
class Btree < Rdb::Index
|
6
|
+
|
7
|
+
attr_accessor :key, :attributes, :collection_name, :min_degree, :path_to_database
|
8
|
+
|
9
|
+
def initialize key, attributes, opts
|
10
|
+
@key = key
|
11
|
+
@attributes = attributes
|
12
|
+
@collection_name = opts[:collection_name]
|
13
|
+
@path_to_database = opts[:path_to_database]
|
14
|
+
@min_degree = 4
|
15
|
+
Rdb.logger.info "Btree#initialize: key %s val %s path %s" % [@key.to_s, @attributes[@key], path_to_index]
|
16
|
+
end
|
17
|
+
|
18
|
+
def create!
|
19
|
+
insert!
|
20
|
+
end
|
21
|
+
|
22
|
+
def insert!
|
23
|
+
root_node = Node.new min_degree, path_to_index, attributes, true
|
24
|
+
if root_node.full?
|
25
|
+
Rdb.logger.debug "Btree#insert! root node full"
|
26
|
+
new_root_node = Node.new min_degree, path_to_index, attributes, true
|
27
|
+
root_node.move_to! new_root_node
|
28
|
+
new_root_node.split_child 0, root_node
|
29
|
+
index = 0
|
30
|
+
Rdb.logger.debug "Btree#insert! attributes[key] %s" % attributes[key].to_s
|
31
|
+
Rdb.logger.debug "Btree#insert! new root node keys %s" % new_root_node.keys.join(' ')
|
32
|
+
if new_root_node.keys[0] < attributes[key]
|
33
|
+
index += 1
|
34
|
+
end
|
35
|
+
Rdb.logger.debug "Btree#insert! index for new el %d" % index
|
36
|
+
Rdb.logger.debug "Btree#insert! new root node children %s" % new_root_node.children_paths(true).join(' ')
|
37
|
+
child_node = new_root_node.children[index]
|
38
|
+
child_node.data = attributes
|
39
|
+
child_node.insert_non_full attributes[key]
|
40
|
+
else
|
41
|
+
Rdb.logger.debug "Btree#insert! insert into root node"
|
42
|
+
root_node.insert_non_full attributes[key]
|
43
|
+
end
|
44
|
+
attributes[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
def search key
|
48
|
+
root_node = Node.new min_degree, path_to_index, attributes, true
|
49
|
+
root_node.search key
|
50
|
+
end
|
51
|
+
|
52
|
+
def path_to_index
|
53
|
+
File.join(path_to_database, collection_name, key.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self::get_last_id path
|
57
|
+
return unless Dir.exist? path
|
58
|
+
children_and_keys = Dir.children(path)
|
59
|
+
children = children_and_keys.select { |fname| File.directory?(File.join(path, fname))}.collect(&:to_i).sort
|
60
|
+
if children.size > 0
|
61
|
+
get_last_id(File.join(path, children.last.to_s))
|
62
|
+
else
|
63
|
+
children_and_keys.select { |fname| fname.include? 'data_'}.
|
64
|
+
collect { |fname| fname.match(/\d+/)[0].to_i}.compact.max.to_i
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Node
|
69
|
+
|
70
|
+
attr_accessor :t, :path_to_node, :data, :is_root
|
71
|
+
|
72
|
+
def initialize t, path_to_node, data = nil, is_root = false
|
73
|
+
@path_to_node = path_to_node
|
74
|
+
@t = t
|
75
|
+
@data = data
|
76
|
+
@is_root = is_root
|
77
|
+
setup_dirs
|
78
|
+
end
|
79
|
+
|
80
|
+
def search key
|
81
|
+
index = 0
|
82
|
+
while index < num_of_keys && key > keys[index]
|
83
|
+
index += 1
|
84
|
+
end
|
85
|
+
|
86
|
+
if key == keys[index]
|
87
|
+
return Rdb::StorageEngines::Json.new({}, File.join(path_to_node,"data_#{key}.json")).read
|
88
|
+
end
|
89
|
+
|
90
|
+
return nil if is_leaf?
|
91
|
+
children[index].search key
|
92
|
+
end
|
93
|
+
|
94
|
+
def insert key, index
|
95
|
+
fname = File.join(path_to_node, "data_#{key}.json")
|
96
|
+
Rdb::StorageEngines::Json.new(data, fname).store!
|
97
|
+
end
|
98
|
+
|
99
|
+
def insert_non_full key
|
100
|
+
Rdb.logger.debug "Btree#insert_non_full key %s" % key.to_s
|
101
|
+
Rdb.logger.debug "Btree#insert_non_full all keys: %s" % keys.join(' ')
|
102
|
+
index = num_of_keys - 1
|
103
|
+
if is_leaf?
|
104
|
+
while index >= 0 && keys[index] > key do
|
105
|
+
index -= 1
|
106
|
+
end
|
107
|
+
insert key, index
|
108
|
+
else
|
109
|
+
while index >= 0 && keys[index] > key do
|
110
|
+
index -= 1
|
111
|
+
end
|
112
|
+
child_path = children_paths[index + 1]
|
113
|
+
child_node = Node.new t, child_path, data
|
114
|
+
Rdb.logger.debug "Btree#insert_non_full selected child node %s" % child_path
|
115
|
+
if child_node.num_of_keys == 2*t - 1
|
116
|
+
Rdb.logger.debug "Btree#insert_non_full going to split child node"
|
117
|
+
split_child index + 1, child_node
|
118
|
+
Rdb.logger.debug "Btree#insert_non_full keys %s index %d" % [keys.join(' '), index]
|
119
|
+
if keys[index + 1] < key
|
120
|
+
index += 1
|
121
|
+
end
|
122
|
+
child_path = children_paths(true)[index + 1]
|
123
|
+
child_node = Node.new t, child_path, data
|
124
|
+
end
|
125
|
+
child_node.insert_non_full key
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def split_child index, child_node
|
130
|
+
Rdb.logger.debug "Btree#split_child index %d" % index
|
131
|
+
Rdb.logger.debug "Btree#split_child child to split path %s" % child_node.path_to_node
|
132
|
+
new_child_path = File.join(path_to_node, (index + 1).to_s)
|
133
|
+
Rdb.logger.debug "Btree#split_child new child path %s" % new_child_path
|
134
|
+
new_child = Node.new t, new_child_path, data
|
135
|
+
Rdb.logger.debug "Btree#split_child child node keys %s" % child_node.keys.join(' ')
|
136
|
+
child_node.keys.last(t - 1).each do |child_key|
|
137
|
+
unless File.join(child_node.path_to_node, "data_%d.json" % child_key) == File.join(new_child.path_to_node, "data_%d.json" % child_key)
|
138
|
+
Rdb.logger.debug "Btree#split_child move keys from " + File.join(child_node.path_to_node, "data_%d.json" % child_key) + " to " +
|
139
|
+
File.join(new_child.path_to_node, "data_%d.json" % child_key)
|
140
|
+
FileUtils.mv File.join(child_node.path_to_node, "data_%d.json" % child_key),
|
141
|
+
File.join(new_child.path_to_node, "data_%d.json" % child_key)
|
142
|
+
else
|
143
|
+
Rdb.logger.debug "Btree#split_child move keys same path, skipped " + File.join(child_node.path_to_node, "data_%d.json" % child_key) + " to " +
|
144
|
+
File.join(new_child.path_to_node, "data_%d.json" % child_key)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
Rdb.logger.debug "Btree#split_child going to move children to %s" % new_child_path
|
149
|
+
Rdb.logger.debug "Btree#split_child %s" % child_node.children(true).map(&:path_to_node).join(' ')
|
150
|
+
Rdb.logger.debug "Btree#split_child last t of them %d" % t
|
151
|
+
child_node.children(true).last(t).each do |child|
|
152
|
+
child.move_to! new_child
|
153
|
+
end
|
154
|
+
|
155
|
+
child_key = child_node.keys[t - 1]
|
156
|
+
unless child_node.path_to_node == path_to_node
|
157
|
+
Rdb.logger.debug "Btree#split_child finaly move from " + File.join(child_node.path_to_node, "data_%d.json" % child_key) + " to " +
|
158
|
+
File.join(path_to_node, "data_%d.json" % child_key)
|
159
|
+
FileUtils.mv File.join(child_node.path_to_node, "data_%d.json" % child_key),
|
160
|
+
File.join(path_to_node, "data_%d.json" % child_key)
|
161
|
+
else
|
162
|
+
Rdb.logger.debug "Btree#split_child finaly skipped same from " + File.join(child_node.path_to_node, "data_%d.json" % child_key) + " to " +
|
163
|
+
File.join(path_to_node, "data_%d.json" % child_key)
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
def children reload = true
|
169
|
+
Rdb.logger.debug "Btree#children init"
|
170
|
+
return @children if defined?(@children) && !reload
|
171
|
+
@children = children_paths(reload).collect { |path| Node.new(t, path)}
|
172
|
+
end
|
173
|
+
|
174
|
+
def children_paths reload = false
|
175
|
+
return @children_paths if defined?(@children_paths) && !reload
|
176
|
+
@children_paths = Dir.children(path_to_node).select { |fname| File.directory?(File.join(path_to_node, fname))}.sort_by(&:to_i).
|
177
|
+
collect { |fname| File.join(path_to_node, fname)}
|
178
|
+
end
|
179
|
+
|
180
|
+
def next_child_id
|
181
|
+
is_leaf? ? 1 : children_paths.last.to_i + 1
|
182
|
+
end
|
183
|
+
|
184
|
+
def move_to! node
|
185
|
+
next_child_id = (node.is_root || node.is_leaf?) ? 0 : node.children_paths.size
|
186
|
+
Rdb.logger.debug "Btree#move_to! next child id children lookup %s " % node.children_paths.join(' ')
|
187
|
+
path_to_child_tmp = File.join(node.path_to_node, next_child_id.to_s + ".tmp")
|
188
|
+
path_to_child = File.join(node.path_to_node, next_child_id.to_s)
|
189
|
+
Rdb.logger.debug "Btree#move_to! of %s to %s" % [path_to_node, path_to_child]
|
190
|
+
Dir.mkdir(path_to_child_tmp, 0700) unless Dir.exist? path_to_child_tmp
|
191
|
+
Rdb.logger.debug "Btree#move_to! move all children and keys to new dir"
|
192
|
+
keys.each do |child_key|
|
193
|
+
Rdb.logger.debug "Btree#move_to! move keys from " + File.join(path_to_node, "data_%d.json" % child_key) + " to " +
|
194
|
+
File.join(path_to_child_tmp, "data_%d.json" % child_key)
|
195
|
+
FileUtils.mv File.join(path_to_node, "data_%d.json" % child_key),
|
196
|
+
File.join(path_to_child_tmp, "data_%d.json" % child_key)
|
197
|
+
end
|
198
|
+
children_paths.each do |child_path|
|
199
|
+
Rdb.logger.debug "Btree#move_to! move child from " + child_path + " to " +
|
200
|
+
path_to_child_tmp
|
201
|
+
FileUtils.mv child_path,
|
202
|
+
path_to_child_tmp
|
203
|
+
end
|
204
|
+
FileUtils.mv path_to_child_tmp, path_to_child
|
205
|
+
Dir.rmdir(path_to_node) rescue nil
|
206
|
+
Rdb.logger.debug "Btree#move_to! set path_to_node %s" % path_to_child
|
207
|
+
self.path_to_node = path_to_child
|
208
|
+
end
|
209
|
+
|
210
|
+
def keys
|
211
|
+
Dir.children(path_to_node).select { |fname| fname.include? 'data_'}.
|
212
|
+
collect { |fname| fname.match(/\d+/)[0].to_i}.sort
|
213
|
+
end
|
214
|
+
|
215
|
+
def full?
|
216
|
+
num_of_keys == 2*t - 1
|
217
|
+
end
|
218
|
+
|
219
|
+
def num_of_keys
|
220
|
+
keys.size
|
221
|
+
end
|
222
|
+
|
223
|
+
def is_leaf?
|
224
|
+
children_paths(true).size.zero?
|
225
|
+
end
|
226
|
+
|
227
|
+
def setup_dirs
|
228
|
+
Rdb.logger.debug "Btree#setup_dirs setup dir %s" % path_to_node unless Dir.exist? path_to_node
|
229
|
+
Dir.mkdir(path_to_node, 0700) unless Dir.exist? path_to_node
|
230
|
+
children_paths
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
data/lib/rdb/orm.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# the ORM mapper, it's not included in main code
|
2
|
+
#
|
3
|
+
# Example of usage:
|
4
|
+
#
|
5
|
+
=begin
|
6
|
+
|
7
|
+
require_relative 'rdb'
|
8
|
+
require_relative 'rdb/orm'
|
9
|
+
|
10
|
+
class Plane
|
11
|
+
include Rdb::Orm
|
12
|
+
rdb_mount 'planes', {id: Integer, code: String, weight: Integer}, {id: Rdb::Indexes::Btree}, '/home/rustam/rdata'
|
13
|
+
end
|
14
|
+
|
15
|
+
names = %w(rose velvet pink blue charley delta bravo)
|
16
|
+
|
17
|
+
300000.times do |x|
|
18
|
+
code = "%s%d%s" % [names.sample, rand(1000), names.sample]
|
19
|
+
Plane.create(code: code, weight: rand(1000))
|
20
|
+
end
|
21
|
+
|
22
|
+
Plane.find_by(id: 1123)
|
23
|
+
|
24
|
+
x = Plane.new
|
25
|
+
x.code = "superjet3000"
|
26
|
+
x.weight = 2572
|
27
|
+
x.save
|
28
|
+
|
29
|
+
Plane.find_by(id: x.id)
|
30
|
+
|
31
|
+
=end
|
32
|
+
|
33
|
+
module Rdb
|
34
|
+
module Orm
|
35
|
+
|
36
|
+
def self.included base
|
37
|
+
base.include InstanceMethods
|
38
|
+
base.extend ClassMethods
|
39
|
+
end
|
40
|
+
|
41
|
+
module InstanceMethods
|
42
|
+
def save
|
43
|
+
result = self.class.create attributes
|
44
|
+
result.each do |key, value|
|
45
|
+
self.send("#{key}=", value)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def attributes
|
50
|
+
field_names.inject({}) do |result, key|
|
51
|
+
result[key] = self.send(key)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
def rdb_mount name, fields, indexes, path_to_database
|
59
|
+
@rdb = Rdb.open(
|
60
|
+
name: name,
|
61
|
+
fields: fields,
|
62
|
+
indexes: indexes,
|
63
|
+
path_to_database: path_to_database)
|
64
|
+
attr_accessor *fields.keys
|
65
|
+
define_method(:field_names) { fields.keys }
|
66
|
+
end
|
67
|
+
|
68
|
+
# search by key field only
|
69
|
+
def find_by condition
|
70
|
+
if result = @rdb[*condition.flatten]
|
71
|
+
instance = new()
|
72
|
+
result.each do |key, value|
|
73
|
+
instance.send("#{key}=", value)
|
74
|
+
end
|
75
|
+
instance
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def create attributes
|
80
|
+
@rdb << attributes
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rdb
|
4
|
+
module StorageEngines
|
5
|
+
class Json < Rdb::StorageEngine
|
6
|
+
|
7
|
+
def initialize data, filename
|
8
|
+
@data = data
|
9
|
+
@filename = filename
|
10
|
+
end
|
11
|
+
|
12
|
+
def content
|
13
|
+
@data.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
def store!
|
17
|
+
File.open(@filename, 'w') { |file| file.write(content) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def read
|
21
|
+
JSON.parse File.read(@filename)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rdb/version.rb
ADDED
data/rdb.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "rdb/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rudb"
|
8
|
+
spec.version = Rdb::VERSION
|
9
|
+
spec.authors = ["rustik"]
|
10
|
+
spec.email = ["vasil.brazil@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Rustik db}
|
13
|
+
spec.description = %q{Ruby implementation of database with btree and ORM mapper}
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
required_ruby_version = '~> 2.5'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rudb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- rustik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Ruby implementation of database with btree and ORM mapper
|
42
|
+
email:
|
43
|
+
- vasil.brazil@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- lib/rdb.rb
|
55
|
+
- lib/rdb/collection.rb
|
56
|
+
- lib/rdb/document.rb
|
57
|
+
- lib/rdb/error.rb
|
58
|
+
- lib/rdb/index.rb
|
59
|
+
- lib/rdb/indexes/btree.rb
|
60
|
+
- lib/rdb/indexes/hash.rb
|
61
|
+
- lib/rdb/orm.rb
|
62
|
+
- lib/rdb/storage_engine.rb
|
63
|
+
- lib/rdb/storage_engines/json.rb
|
64
|
+
- lib/rdb/version.rb
|
65
|
+
- rdb.gemspec
|
66
|
+
homepage:
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.7.6
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Rustik db
|
90
|
+
test_files: []
|