hoaxdb 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 +11 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +67 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/hoaxdb.gemspec +29 -0
- data/lib/hoaxdb.rb +265 -0
- data/lib/hoaxdb/table.rb +297 -0
- data/lib/hoaxdb/version.rb +3 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c7768d5652b3e5c33b611b48f182cd748b587e9fba398ba0fe4cc86242111f95
|
4
|
+
data.tar.gz: 3a714dd387f5ab77dcd334201921f49e237ce2ea5c551e3fce8230353a9fe488
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 18040158b426bcf35355d21eebaf5e14389bc1e67d5f41e228e10664c593c9b4f4716ca2b94a5b6fab5b9b61516434ceeaaecc827b77e349038deaea8a799e39
|
7
|
+
data.tar.gz: 2aad20d569865209d388575050034185c1f08ad0a34c8d124ec73a74af23c020b1c5ae215aa9b0a8bd81c50b1055d992e06bcca6724d72d039a5cf1a7f9e5244
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 njaneambrose
|
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,67 @@
|
|
1
|
+
# Hoaxdb
|
2
|
+
|
3
|
+
Hoaxdb is a simple implementation of a database written in 100% pure ruby. It offers an interface to insert,update,query and delete records like any other databse but does not offer complex features of a database. You set specific data types for your fields and you can also have default values on top of that it validates the records you enter. In terms of queries you can choose specific fields, limits and also sort according to a specific field which can be ascending or descending. Queries are written using ruby hence no external knowledge is needed.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'hoaxdb'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install hoaxdb
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Using the hoaxdb is very simple once you have installed it, the following are the basics:
|
24
|
+
#### Creating a table
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'hoaxdb'
|
28
|
+
db = Hoaxdb.new('products.db') #This reads an existing database or creates a database
|
29
|
+
db.create_table("products",{
|
30
|
+
"name"=>{"type"=>"String","default"=>""}, #setting default prevents nil values
|
31
|
+
"price"=>{"type"=>"Float","default"=>0.0}
|
32
|
+
})
|
33
|
+
```
|
34
|
+
|
35
|
+
#### Inserting a record
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
products.insert({"name"=>"Iphone","price"=>100.67}) #insert a record
|
39
|
+
```
|
40
|
+
|
41
|
+
#### Updating a record
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
products.update('this["name"].eql? "Iphone"',{"quantity"=>200})
|
45
|
+
```
|
46
|
+
|
47
|
+
#### Selecting records
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
products.select_if('this["name"].eql? "Iphone"')
|
51
|
+
```
|
52
|
+
|
53
|
+
#### Deleting records
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
products.del_if('this["name"].eql? "Iphone"')
|
57
|
+
```
|
58
|
+
|
59
|
+
More information is available via rdoc when the gem is installed
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/njaneambrose/hoaxdb.
|
64
|
+
|
65
|
+
## License
|
66
|
+
|
67
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hashdb"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/hoaxdb.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "hoaxdb/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hoaxdb"
|
8
|
+
spec.version = Hoaxdb::VERSION
|
9
|
+
spec.authors = ["njaneambrose"]
|
10
|
+
spec.email = ["njaneambrose@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{An implementation of a NoSQl database in pure ruby}
|
13
|
+
spec.description = %q{This is simple database based on a Hash that uses JSON for storage.It offers interfaces to create,update,delete and query records with a variety of options for queries}
|
14
|
+
spec.homepage = "https://github.com/njaneambrose/hoaxdb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
end
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
29
|
+
end
|
data/lib/hoaxdb.rb
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
require "hoaxdb/version"
|
2
|
+
require "hoaxdb/table"
|
3
|
+
require "zlib"
|
4
|
+
require "json"
|
5
|
+
require "thread"
|
6
|
+
|
7
|
+
module Hoaxdb
|
8
|
+
#=Introduction
|
9
|
+
#Hoaxdb is a small implementation of a database using a Hash and JSON for storage written in 100% pure ruby. It allows you to create, update, query and delete records.
|
10
|
+
#It also allows you to set specific data types for your fields, default values and validates input. In terms of queries you can set limits, select specific fields, sort by a field and also set a limit for the query.
|
11
|
+
#==Field rules
|
12
|
+
# Use string keys like ["products"] not [:products] due to JSON nature of storage
|
13
|
+
#==Data types
|
14
|
+
#The currently supported data types are
|
15
|
+
# Hash, DateTime, Date, Boolean, Float, Integer, Array & String
|
16
|
+
#==Saving your work
|
17
|
+
# #=> Never forget to call
|
18
|
+
# db.commit
|
19
|
+
# #=> to save the current changes
|
20
|
+
#==Querying rules:
|
21
|
+
# 1. "this" is used to refrence the current row e.g: this["price"] indicates the current row price
|
22
|
+
# 2. The syntax this[.. is used for queries if not * for all rows
|
23
|
+
# 3. For equality use .eql? not ==..
|
24
|
+
#check the querying section for examples
|
25
|
+
#==Updating rules:
|
26
|
+
# 1. All rules for querying above apply
|
27
|
+
# 2. To edit the current value use $ e.g an Array do: {"array"=>"$.push('value')"}, $ refrences the current value and must be called in a string as the first character
|
28
|
+
#check the updating section for examples
|
29
|
+
#
|
30
|
+
#====CAUTION: Do not use Hoaxdb for extreme loads due to speed
|
31
|
+
#
|
32
|
+
#==Creating a table
|
33
|
+
#Creating a table is very easy as first you need to connect to a database file then:
|
34
|
+
# require 'hoaxdb'
|
35
|
+
# db = Hoaxdb.new('products.db') #This reads an existing database or creates a database
|
36
|
+
# db.create_table("products",{
|
37
|
+
# "name"=>{"type"=>"String","default"=>""}, #setting default prevents nil values
|
38
|
+
# "price"=>{"type"=>"Float","default"=>0.0},
|
39
|
+
# "quantity"=>{"type"=>"Integer","default"=>100},
|
40
|
+
# "shipped"=>{"type"=>"Date"}
|
41
|
+
# })
|
42
|
+
#This creates a simple table with three fields
|
43
|
+
#==Inserting Records
|
44
|
+
#The next step is just a simple as the one above, this is a continuation from above:
|
45
|
+
# products = db.table("products") #this connects to the products table
|
46
|
+
# products.insert({"name"=>"Iphone","price"=>100.67,"shipped"=>Date.new(2018,6,30)}) #insert a record
|
47
|
+
# products.insert({"name"=>"Cable Tvs","price"=>390.67,"shipped"=>Date.new(2018,9,12),"quantity"=>400}) #insert another record
|
48
|
+
# p products.data #=> [{"name"=>"Iphone","price"=>100.67,"quantity"=>100,shipped=>"2018-6-30"},{"name"=>"Cable Tvs","price"=>390.67,"quantity"=>400,shipped=>"2018-9-12"}]
|
49
|
+
#==Querying records
|
50
|
+
# #=>select_if('condtion * for all',[array of the fields you want * for all],limit -1 for no limit,"sort field must be in the fields array",desc=true/false)
|
51
|
+
# #=> condition * indicates all rows
|
52
|
+
# products.select_if('*',["name","price"]) #=> [{"name"=>"Iphone","price"=>100.67},{"name"=>"Cable Tvs","price"=>390.67}]
|
53
|
+
# products.select_if('*',["name","price"],-1,"price") #=> [{"name"=>"Cable Tvs","price"=>390.67},{"name"=>"Iphone","price"=>100.67}]
|
54
|
+
# products.select_if('*',["name","price"],-1,"price",true) #=> [{"name"=>"Iphone","price"=>100.67},{"name"=>"Cable Tvs","price"=>390.67}]
|
55
|
+
# products.select_if('this["price"] > 300',['name','price']) #=> [{"name"=>"Cable Tvs","price"=>390.67}]
|
56
|
+
# products.max("price") #=> 390.67
|
57
|
+
# products.min("price") #=> 100.67
|
58
|
+
# products.avg("price") #=> 245.67
|
59
|
+
# products.sum("price") #=> 491.34
|
60
|
+
#==Updating records
|
61
|
+
# #=> table.update('condition * for all',{new values})
|
62
|
+
# #=> condition * indicates all rows
|
63
|
+
# #Incase of a 15% price increase in products do this to update
|
64
|
+
# products.update('*',{"price"=>"$ = this[\"price\"]*1.15"}) #=> Here you see how the $ symbol is used to update the existing value
|
65
|
+
# products.update('this["name"].eql? "Iphone"',{"quantity"=>200})
|
66
|
+
#==Deleting records
|
67
|
+
# #=> table.del_if('condition')
|
68
|
+
# products.del_if('this["name"].eql? "Iphone"')
|
69
|
+
# p products.data #=> [{"name"=>"Cable Tvs","price"=>390.67,"quantity"=>400,shipped=>"2018-9-12"}]
|
70
|
+
#==Adding fields
|
71
|
+
# products.add_column("remaining",{"type"=>"Integer","default"=>0})
|
72
|
+
# p products.base #=> [{"name"=>{"type"=>"String","default"=>""},"price"=>{"type"=>"Float","default"=>0.0},"quantity"=>{"type"=>Integer","default"=>100}, "shipped"=>{"type"=>"Date","default"=>nil},{"remaining"=>{"type"=>"Integer","default"=>0}}]
|
73
|
+
#==Deleting fields
|
74
|
+
# products.drop_column("remaining")
|
75
|
+
# p products.base #=> [{"name"=>{"type"=>"String","default"=>""},"price"=>{"type"=>"Float","default"=>0.0},"quantity"=>{"type"=>Integer","default"=>100}, "shipped"=>{"type"=>"Date","default"=>nil}]
|
76
|
+
class Db
|
77
|
+
def initialize(a)
|
78
|
+
@file = a
|
79
|
+
@a = {}
|
80
|
+
thr = Thread.new do
|
81
|
+
if File::exists? a
|
82
|
+
begin
|
83
|
+
Zlib::GzipReader.open(@file) do |gz|
|
84
|
+
x = JSON.parse(gz.read)
|
85
|
+
x.each{|k,v| @a.store(k,v)}
|
86
|
+
end
|
87
|
+
rescue Zlib::Error
|
88
|
+
raise "We could not read any keys from #{a} the database may be corrupted"
|
89
|
+
rescue JSON::ParserError
|
90
|
+
@a = {}
|
91
|
+
end
|
92
|
+
else
|
93
|
+
Zlib::GzipWriter.open(a).close
|
94
|
+
end
|
95
|
+
end
|
96
|
+
thr.join
|
97
|
+
end
|
98
|
+
def create_table(name,base)
|
99
|
+
if @a.has_key? name
|
100
|
+
raise "Table #{name} already exists"
|
101
|
+
end
|
102
|
+
tbase = name + "_base"
|
103
|
+
@a.store(tbase,{})
|
104
|
+
x = Table.new(name,base)
|
105
|
+
@a[tbase] = base
|
106
|
+
@a.store(name,[])
|
107
|
+
end
|
108
|
+
def drop_table(name)
|
109
|
+
tbase = name + "_base"
|
110
|
+
@a.delete(name)
|
111
|
+
@a.delete(tbase)
|
112
|
+
end
|
113
|
+
def table(name)
|
114
|
+
if @a.has_key? name and !name.match(/_base$/)
|
115
|
+
tbase = name + "_base"
|
116
|
+
x = Table.new(name,@a[tbase])
|
117
|
+
x.inject(@a[tbase],@a[name])
|
118
|
+
return x
|
119
|
+
elsif name.match(/_base$/)
|
120
|
+
raise "#{name} cannot be edited directly"
|
121
|
+
else
|
122
|
+
raise "Table #{name} does not exist"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
#Lists all the tables in the database
|
126
|
+
def tables
|
127
|
+
tables = []
|
128
|
+
@a.keys.each{|e| if !e.match(/_base$/) then tables.push(e) end}
|
129
|
+
tables
|
130
|
+
end
|
131
|
+
#Deletes all tables in the database
|
132
|
+
def clear
|
133
|
+
@a.clear
|
134
|
+
end
|
135
|
+
#This saves chages made in the current session
|
136
|
+
# db.commit #saves the database
|
137
|
+
def commit
|
138
|
+
thr = Thread.new do
|
139
|
+
Zlib::GzipWriter.open(@file) do |gz|
|
140
|
+
gz.write(JSON.generate(@a))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
thr.join
|
144
|
+
end
|
145
|
+
#This writes your database to a file in json format
|
146
|
+
# db >> "data.json"
|
147
|
+
def >>(a)
|
148
|
+
f = File.open(a,'w')
|
149
|
+
f << @a.to_json
|
150
|
+
f.close
|
151
|
+
end
|
152
|
+
#This returns the path to your database
|
153
|
+
# db.path
|
154
|
+
def path
|
155
|
+
@file
|
156
|
+
end
|
157
|
+
#This renames your database
|
158
|
+
# db.rename("hash.db")
|
159
|
+
def rename(a)
|
160
|
+
File.rename(@file,a)
|
161
|
+
@file = a
|
162
|
+
end
|
163
|
+
#This deletes your database
|
164
|
+
# db.del
|
165
|
+
def del
|
166
|
+
File.delete(@file)
|
167
|
+
end
|
168
|
+
#This returns JSON from selected table
|
169
|
+
# db.get_json("products")
|
170
|
+
def get_json(a)
|
171
|
+
x = {}
|
172
|
+
x.store(a,@a[a])
|
173
|
+
JSON.generate(x)
|
174
|
+
end
|
175
|
+
#This returns XML from a selected table
|
176
|
+
# db.get_xml("products")
|
177
|
+
def get_xml(a)
|
178
|
+
r = ""
|
179
|
+
if ishash(@a[a])
|
180
|
+
r = self.hash_to_xml(a,@a[a])
|
181
|
+
elsif isarray(@a[a])
|
182
|
+
r = self.array_to_xml(a,@a[a])
|
183
|
+
else
|
184
|
+
r = "<#{a}>#{@a[a]}</#{a}>"
|
185
|
+
end
|
186
|
+
r
|
187
|
+
end
|
188
|
+
#This offloads the hoaxdb to a file other than the current file and can also be used for backup pourposes
|
189
|
+
# db.offload("backup.db")
|
190
|
+
def offload(a)
|
191
|
+
Zlib::GzipWriter.open(a) do |gz|
|
192
|
+
gz.write(JSON.generate(@a))
|
193
|
+
end
|
194
|
+
end
|
195
|
+
#This reloads data from the file and discards unsaved changes
|
196
|
+
def reload
|
197
|
+
@a.clear
|
198
|
+
if File::exists? @file
|
199
|
+
begin
|
200
|
+
Zlib::GzipReader.open(@file) do |gz|
|
201
|
+
x = JSON.parse(gz.read)
|
202
|
+
x.each{|k,v| @a.store(k,v)}
|
203
|
+
end
|
204
|
+
rescue Zlib::Error
|
205
|
+
raise "We could not reload #{@file} it may be corrupted"
|
206
|
+
rescue JSON::ParserError
|
207
|
+
end
|
208
|
+
else
|
209
|
+
Zlib::GzipWriter.open(a).close
|
210
|
+
end
|
211
|
+
end
|
212
|
+
def isarray(e)
|
213
|
+
if e.class == Array
|
214
|
+
return true
|
215
|
+
else
|
216
|
+
return false
|
217
|
+
end
|
218
|
+
end
|
219
|
+
def ishash(e)
|
220
|
+
if e.class == Hash
|
221
|
+
return true
|
222
|
+
else
|
223
|
+
return false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
def array_to_xml(k,a)
|
227
|
+
sub = "<#{k}>"
|
228
|
+
a.each{|o|
|
229
|
+
if isarray(o)
|
230
|
+
sub << array_to_xml("array",o);
|
231
|
+
elsif ishash(o)
|
232
|
+
sub << hash_to_xml("hash",o)
|
233
|
+
else
|
234
|
+
sub << "<s#{k}>#{o}</s#{k}>"
|
235
|
+
end
|
236
|
+
}
|
237
|
+
sub << "</#{k}>"
|
238
|
+
sub
|
239
|
+
end
|
240
|
+
def hash_to_xml(x,k)
|
241
|
+
sub = "<#{x}>"
|
242
|
+
k.each do |q,v|
|
243
|
+
if isarray(v)
|
244
|
+
sub << array_to_xml(q,v);
|
245
|
+
elsif ishash(v)
|
246
|
+
sub << "<#{q}>"
|
247
|
+
v.each{|t,b|
|
248
|
+
if isarray(b)
|
249
|
+
sub << array_to_xml(t,b)
|
250
|
+
elsif ishash(b)
|
251
|
+
sub << hash_to_xml(t,b);
|
252
|
+
else
|
253
|
+
sub << "<#{t}>#{b}</#{t}>"
|
254
|
+
end
|
255
|
+
}
|
256
|
+
sub << "</#{q}>"
|
257
|
+
else
|
258
|
+
sub << "<#{q}>#{v}</#{q}>"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
sub << "</#{x}>"
|
262
|
+
sub
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
data/lib/hoaxdb/table.rb
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Hoaxdb
|
4
|
+
|
5
|
+
class Query
|
6
|
+
def parse(query)
|
7
|
+
query.strip!
|
8
|
+
if query.match(/row\[|@data|@base|[^\>\<]=/)
|
9
|
+
raise "This syntax is not allowed for querying"
|
10
|
+
else
|
11
|
+
if query.match(/this\[/)
|
12
|
+
query = query.gsub(/this\[/,"row[")
|
13
|
+
elsif query.eql? "*"
|
14
|
+
query = "true"
|
15
|
+
else
|
16
|
+
raise "Unfamiliar query used"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
query
|
20
|
+
end
|
21
|
+
def parse_complex(query)
|
22
|
+
query.strip!
|
23
|
+
if query.match(/@data|@base|;|row/)
|
24
|
+
raise "This syntax is not allowed for querying"
|
25
|
+
else
|
26
|
+
query = query.gsub(/this\[/,"row[")
|
27
|
+
end
|
28
|
+
query
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Table
|
33
|
+
def initialize(name,args)
|
34
|
+
@name = name
|
35
|
+
@base = {}
|
36
|
+
if !args.class.eql? Hash
|
37
|
+
raise "Incorrect base for your database"
|
38
|
+
else
|
39
|
+
args.each{|k,v|
|
40
|
+
if self.valid_type(v["type"])
|
41
|
+
self.validate(v)
|
42
|
+
@base.store(k,v)
|
43
|
+
else
|
44
|
+
raise "Invalid data type #{v}"
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
@data = []
|
49
|
+
end
|
50
|
+
def validate(v)
|
51
|
+
if v["type"].eql? 'Date' or v["type"].eql? 'DateTime' and v["default"]
|
52
|
+
v["default"] = "#{v["default"]}"
|
53
|
+
elsif v["type"].eql? 'Boolean' and !v["default"].nil?
|
54
|
+
if v["default"].class.eql? TrueClass or v["default"].class.eql? FalseClass
|
55
|
+
else
|
56
|
+
raise "Invalid default #{v["default"]} to #{v["type"]}"
|
57
|
+
end
|
58
|
+
else
|
59
|
+
if !v["default"].nil? and !v["default"].class.eql? eval(v["type"])
|
60
|
+
raise "Invalid default #{v["default"]} to #{v["type"]}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if v["default"].nil? then v["default"] = nil end
|
64
|
+
end
|
65
|
+
def data
|
66
|
+
@data.clone
|
67
|
+
end
|
68
|
+
def base
|
69
|
+
@base.clone
|
70
|
+
end
|
71
|
+
def fields
|
72
|
+
@base.keys
|
73
|
+
end
|
74
|
+
def valid_type(type)
|
75
|
+
types = ['String','Integer','Float','DateTime','Date','Boolean','Array','Hash']
|
76
|
+
found = false
|
77
|
+
types.each{|e| if e.eql? type then found = true;break end}
|
78
|
+
found
|
79
|
+
end
|
80
|
+
def load(data)
|
81
|
+
data.each do |row|
|
82
|
+
row.each do |k,v|
|
83
|
+
if !v.nil?
|
84
|
+
if @base[k]['type'].eql? 'Date'
|
85
|
+
row[k] = Date.parse(v)
|
86
|
+
elsif @base[k]['type'].eql? 'DateTime'
|
87
|
+
row[k] = DateTime.parse(v)
|
88
|
+
elsif !@base[k]['type'].eql? 'String' and v.class.eql? String
|
89
|
+
row[k] = eval(v)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
data
|
95
|
+
end
|
96
|
+
def load_hash(data)
|
97
|
+
data.each do |k,v|
|
98
|
+
if !v.nil?
|
99
|
+
if @base[k]['type'].eql? 'Date'
|
100
|
+
data[k] = Date.parse(v)
|
101
|
+
elsif @base[k]['type'].eql? 'DateTime'
|
102
|
+
data[k] = DateTime.parse(v)
|
103
|
+
elsif !@base[k]['type'].eql? 'String' and v.class.eql? String
|
104
|
+
data[k] = eval(v)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
def dump_array(data)
|
110
|
+
data.each do |row|
|
111
|
+
row.each do |k,v|
|
112
|
+
if @base[k]['type'].eql? 'Date' or @base[k]['type'].eql? 'DateTime'
|
113
|
+
row[k] = "#{v}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
data
|
118
|
+
end
|
119
|
+
def dump_hash(data)
|
120
|
+
data.each do |k,v|
|
121
|
+
if @base[k]['type'].eql? 'Date' or @base[k]['type'].eql? 'DateTime'
|
122
|
+
data[k] = "#{v}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
data
|
126
|
+
end
|
127
|
+
def insert(data)
|
128
|
+
data.each{|k,v|
|
129
|
+
if @base[k]
|
130
|
+
if @base[k]['type'].eql? 'Boolean'
|
131
|
+
if v.class.eql? FalseClass or v.class.eql? TrueClass
|
132
|
+
correct = true
|
133
|
+
else
|
134
|
+
raise "#{v} is not a Boolean value"
|
135
|
+
end
|
136
|
+
elsif !v.class.eql? eval(@base[k]['type'])
|
137
|
+
raise "Incorrect data type for #{v} to #{@base[k]}"
|
138
|
+
end
|
139
|
+
else
|
140
|
+
raise "This field #{k} is not in #{@name} table"
|
141
|
+
end
|
142
|
+
}
|
143
|
+
data = self.dump_hash(data)
|
144
|
+
dt = {}
|
145
|
+
@base.each_key{|k|
|
146
|
+
if !data[k] then dt.store(k,@base[k]['default'])
|
147
|
+
else
|
148
|
+
dt.store(k,data[k])
|
149
|
+
end
|
150
|
+
}
|
151
|
+
@data.push(dt)
|
152
|
+
end
|
153
|
+
def alter_default(col,default)
|
154
|
+
if default.class.eql? eval(@base[col]['type'])
|
155
|
+
@base[col]['default'] = default
|
156
|
+
else
|
157
|
+
raise "#{default} is not of data type #{@base[col]['type']}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
def add_column(col,type)
|
161
|
+
if @base.has_key? col
|
162
|
+
raise "Column #{col} already exists"
|
163
|
+
end
|
164
|
+
if valid_type(type['type'])
|
165
|
+
self.validate(type)
|
166
|
+
@base.store(col,type)
|
167
|
+
@data.each do |row|
|
168
|
+
row.store(col,@base[col]['default'])
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
def update(query,values)
|
173
|
+
@data = self.load(@data)
|
174
|
+
@data.each{|row|
|
175
|
+
if eval(Query.new.parse(query))
|
176
|
+
values.each{|k,v|
|
177
|
+
if v.class.eql? String and v[0].eql? "$"
|
178
|
+
f = Query.new.parse_complex(v.delete("$"))
|
179
|
+
temp = (row[k]).clone
|
180
|
+
eval("temp#{f}")
|
181
|
+
if temp.class.eql? eval(@base[k]['type'])
|
182
|
+
row[k] = temp
|
183
|
+
else
|
184
|
+
raise "Invalid data #{temp} to #{@base[k]['type']}"
|
185
|
+
end
|
186
|
+
else
|
187
|
+
if v.class.eql? eval(@base[k]['type'])
|
188
|
+
row[k] = v
|
189
|
+
else
|
190
|
+
raise "Invalid data #{v} to #{@base[k]['type']}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
}
|
194
|
+
end
|
195
|
+
}
|
196
|
+
@data = self.dump_array(@data)
|
197
|
+
end
|
198
|
+
def drop_column(col)
|
199
|
+
@base.delete(col)
|
200
|
+
@data.each do |row|
|
201
|
+
row.delete(col)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
def rename_column(col,ncol)
|
205
|
+
if @base.has_key? col
|
206
|
+
val = @base[col]
|
207
|
+
@base.delete(col)
|
208
|
+
@base.store(ncol,val)
|
209
|
+
@data.each do |row|
|
210
|
+
val = row[col]
|
211
|
+
row.delete(col)
|
212
|
+
row.store(ncol,val)
|
213
|
+
end
|
214
|
+
else
|
215
|
+
raise "Column #{col} does not exist"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
def del_if(query)
|
219
|
+
rm = []
|
220
|
+
@data = self.load(@data)
|
221
|
+
d = Query.new.parse(query)
|
222
|
+
for x in 0...@data.size
|
223
|
+
row = @data[x]
|
224
|
+
if eval(d)
|
225
|
+
rm.push(x)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
rm.reverse.each{|o| @data.delete_at(o)}
|
229
|
+
@data = self.dump_array(@data)
|
230
|
+
end
|
231
|
+
def max(k)
|
232
|
+
if !@base[k]['type'].eql? 'Array' or !@base[k]['type'].eql? 'Hash'
|
233
|
+
max = @data[0][k] || 0
|
234
|
+
@data.each{|row| if row[k] > max then max = row[k] end}
|
235
|
+
return max
|
236
|
+
end
|
237
|
+
end
|
238
|
+
def min(k)
|
239
|
+
if !@base[k]['type'].eql? 'Array' or !@base[k].eql? 'Hash'
|
240
|
+
min = @data[0][k] || 0
|
241
|
+
@data.each{|row| if row[k] < min then min = row[k] end}
|
242
|
+
return min
|
243
|
+
end
|
244
|
+
end
|
245
|
+
def avg(k)
|
246
|
+
if @base[k]['type'].eql? 'Integer' or @base[k]['type'].eql? 'Float'
|
247
|
+
sum = 0
|
248
|
+
@data.each{|row| sum += row[k]}
|
249
|
+
return (sum.to_f/@data.size)
|
250
|
+
else
|
251
|
+
raise "column #{k} is not of type Float or Integer"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
def sum(k)
|
255
|
+
if @base[k]['type'].eql? 'Integer' or @base[k]['type'].eql? 'Float'
|
256
|
+
sum = 0
|
257
|
+
@data.each{|row| sum += row[k]}
|
258
|
+
return sum
|
259
|
+
else
|
260
|
+
raise "column #{k} is not of type Float or Integer"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
def select_if(query,cols,limit=-1,sort=false,desc=false)
|
264
|
+
result = []
|
265
|
+
@data = self.load(@data)
|
266
|
+
x = Query.new.parse(query)
|
267
|
+
res = @data.select{|row| eval(x)}
|
268
|
+
g = false
|
269
|
+
if cols[0].strip.eql? "*" then g = true end
|
270
|
+
res.each do |row|
|
271
|
+
col = {}
|
272
|
+
if !g
|
273
|
+
cols.each do |e|
|
274
|
+
col.store(e,row[e])
|
275
|
+
end
|
276
|
+
result.push(col)
|
277
|
+
else
|
278
|
+
result.push(row)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
if sort
|
282
|
+
result = result.sort{|a,b| a[sort] <=> b[sort]}
|
283
|
+
end
|
284
|
+
if desc
|
285
|
+
result.reverse!
|
286
|
+
end
|
287
|
+
if limit != -1 then result = result[0...limit] end
|
288
|
+
@data = self.dump_array(@data)
|
289
|
+
result = self.load(result)
|
290
|
+
result
|
291
|
+
end
|
292
|
+
def inject(base,data) #:nodoc:#
|
293
|
+
@base = base
|
294
|
+
@data = data
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hoaxdb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- njaneambrose
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-18 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
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: This is simple database based on a Hash that uses JSON for storage.It
|
56
|
+
offers interfaces to create,update,delete and query records with a variety of options
|
57
|
+
for queries
|
58
|
+
email:
|
59
|
+
- njaneambrose@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- hoaxdb.gemspec
|
73
|
+
- lib/hoaxdb.rb
|
74
|
+
- lib/hoaxdb/table.rb
|
75
|
+
- lib/hoaxdb/version.rb
|
76
|
+
homepage: https://github.com/njaneambrose/hoaxdb
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.0.1
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: An implementation of a NoSQl database in pure ruby
|
99
|
+
test_files: []
|