Initial commit
This commit is contained in:
parent
90aa47ceaf
commit
aa924bf01f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/db/*
|
144
README.md
144
README.md
|
@ -1,2 +1,142 @@
|
||||||
# FlatDB
|
FlatDB
|
||||||
A simple NoSQL database written in Lua
|
===========
|
||||||
|
|
||||||
|
FlatDB is a lua library that implements a serverless, zero-configuration, NoSQL database engine.<br>
|
||||||
|
It provides a key-value storage system using plain Lua tables.
|
||||||
|
|
||||||
|
When To Use FlatDB
|
||||||
|
===========
|
||||||
|
|
||||||
|
When you want to use SQLite to store data, just take a glance at FlatDB.<br>
|
||||||
|
When Lua acts in your program as the major language or the embedded scripting language, just try using FlatDB.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
==========
|
||||||
|
Copy *flatdb.lua* file to your project or where your lua libraries stored.<br>
|
||||||
|
Then write this in any Lua file where you want to use it:
|
||||||
|
```lua
|
||||||
|
local flatdb = require 'flatdb'
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Bind a directory as a database
|
||||||
|
```lua
|
||||||
|
local db = flatdb('./db')
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open or create a book
|
||||||
|
```lua
|
||||||
|
if not db.book then
|
||||||
|
db.book = {}
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Store key-value items
|
||||||
|
```lua
|
||||||
|
db.book.key = 'value'
|
||||||
|
-- equivalent to db.book['key'] = 'value'
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Retrieve items
|
||||||
|
```lua
|
||||||
|
print(db.book.key) -- prints 'value'
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Save to file
|
||||||
|
```lua
|
||||||
|
db:save()
|
||||||
|
-- 'book' will be saved to './db/book'
|
||||||
|
```
|
||||||
|
|
||||||
|
More usage can be found in the *cli.lua*(a Redis-like command line interface example using FlatDB).
|
||||||
|
|
||||||
|
Quick Look
|
||||||
|
==========
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local flatdb = require("flatdb")
|
||||||
|
|
||||||
|
-- open a directory as a database, 'db' is just a plain empty Lua table that can contain books
|
||||||
|
local db = flatdb("./db")
|
||||||
|
|
||||||
|
-- open or create a book named "default", it also a plain empty Lua table where key-value data stored in
|
||||||
|
if not db.default then
|
||||||
|
db.default = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- extend db methods for getting values from 'default' book
|
||||||
|
flatdb.hack.get = function(db, key)
|
||||||
|
return db.default[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- extend db methods for setting values to 'default' book
|
||||||
|
flatdb.hack.set = function(db, key, value)
|
||||||
|
db.default[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- extend db methods for watching new key
|
||||||
|
flatdb.hack.guard = function(db, f)
|
||||||
|
setmetatable(db.default, {__newindex = f})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get key-value data from 'default' book
|
||||||
|
print(db:get("hello"))
|
||||||
|
|
||||||
|
-- set key-value data to 'default' book
|
||||||
|
db:set("hello", "world")
|
||||||
|
|
||||||
|
-- get key-value data from 'default' book
|
||||||
|
print(db:get("hello"))
|
||||||
|
|
||||||
|
-- set guard function
|
||||||
|
db:guard(function(book, key, value)
|
||||||
|
print("CREATE KEY permission denied!")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- try creating new key-value data to 'default' book
|
||||||
|
db:set("key1", 1)
|
||||||
|
db:set("key2", 2)
|
||||||
|
|
||||||
|
-- update an existing key-value item
|
||||||
|
db:set("hello", "bye")
|
||||||
|
|
||||||
|
print(db:get("key1")) -- prints nil
|
||||||
|
print(db:get("key2")) -- prints nil
|
||||||
|
print(db:get("hello")) -- prints 'bye'
|
||||||
|
|
||||||
|
-- store 'default' book to './db/default' file
|
||||||
|
db:save()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
API
|
||||||
|
==========
|
||||||
|
|
||||||
|
- **Functions**
|
||||||
|
|
||||||
|
- **flatdb(dir) --> db**
|
||||||
|
|
||||||
|
Bind a directory as a database, returns nil if 'dir' doesn't exists. Otherwise, it returns a 'db' obeject.
|
||||||
|
|
||||||
|
- **db:save([book])**
|
||||||
|
|
||||||
|
Save all books or the given book(if specified) contained in db to file. The 'book' argument is a string, the book's name.
|
||||||
|
|
||||||
|
- **Tables**
|
||||||
|
|
||||||
|
- **flatdb.hack**
|
||||||
|
|
||||||
|
The 'hack' table contains db's methods. There is only one method 'save(db, book)' in it by default.
|
||||||
|
It is usually used to extend db methods.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
=======
|
||||||
|
|
||||||
|
- [pp](https://github.com/luapower/pp)
|
||||||
|
- [lfs](http://keplerproject.github.io/luafilesystem/)
|
||||||
|
|
||||||
|
All above libraries can be found in [LuaPower](https://luapower.com/).
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
FlatDB is distributed under the MIT license.
|
||||||
|
|
80
cli.lua
Normal file
80
cli.lua
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
local flatdb = require("flatdb")
|
||||||
|
local pp = require("pp")
|
||||||
|
|
||||||
|
local function split(s, sep, maxsplit, plain)
|
||||||
|
assert(sep and sep ~= "")
|
||||||
|
maxsplit = maxsplit or 1/0
|
||||||
|
local items = {}
|
||||||
|
if #s > 0 then
|
||||||
|
local init = 1
|
||||||
|
for i = 1, maxsplit do
|
||||||
|
local m, n = s:find(sep, init, plain)
|
||||||
|
if m and m <= n then
|
||||||
|
table.insert(items, s:sub(init, m - 1))
|
||||||
|
init = n + 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(items, s:sub(init))
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
local db = assert(flatdb("./db"))
|
||||||
|
local book = "default"
|
||||||
|
|
||||||
|
local function handle(input)
|
||||||
|
local c = split(input, " ", 2)
|
||||||
|
local cmd, key, value = string.upper(c[1]), c[2], c[3]
|
||||||
|
if cmd == "GET" then
|
||||||
|
print(pp.format(db[book][key]))
|
||||||
|
elseif cmd == "SET" then
|
||||||
|
local ok, tmp = pcall(load("return "..tostring(value), "=(load)", "t", db))
|
||||||
|
db[book][key] = ok and tmp or value
|
||||||
|
print("OK")
|
||||||
|
elseif cmd == "SAVE" then
|
||||||
|
print(db:save(key) and "OK" or "ERROR")
|
||||||
|
elseif cmd == "TOUCH" then
|
||||||
|
if not db[key] then
|
||||||
|
db[key] = {}
|
||||||
|
end
|
||||||
|
print("OK")
|
||||||
|
elseif cmd == "OPEN" then
|
||||||
|
if db[key] then
|
||||||
|
book = key
|
||||||
|
print("OK")
|
||||||
|
else
|
||||||
|
print("ERROR")
|
||||||
|
end
|
||||||
|
elseif cmd == "INCR" then
|
||||||
|
if not db[book][key] then
|
||||||
|
db[book][key] = 0
|
||||||
|
end
|
||||||
|
db[book][key] = db[book][key] + 1
|
||||||
|
print("OK")
|
||||||
|
elseif cmd == "DECR" then
|
||||||
|
if not db[book][key] then
|
||||||
|
db[book][key] = 0
|
||||||
|
end
|
||||||
|
db[book][key] = db[book][key] - 1
|
||||||
|
print("OK")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function main()
|
||||||
|
if not db.default then
|
||||||
|
db.default = {}
|
||||||
|
end
|
||||||
|
io.stdout:setvbuf("no")
|
||||||
|
while true do
|
||||||
|
io.write("> ")
|
||||||
|
local input = io.read("*l")
|
||||||
|
if string.upper(input) == "EXIT" then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
handle(input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
main()
|
70
flatdb.lua
Normal file
70
flatdb.lua
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
local lfs = require("lfs")
|
||||||
|
local pp = require("pp")
|
||||||
|
|
||||||
|
local function isFile(path)
|
||||||
|
return lfs.attributes(path, "mode") == "file"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isDir(path)
|
||||||
|
return lfs.attributes(path, "mode") == "directory"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_book(path)
|
||||||
|
return dofile(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function store_book(path, book)
|
||||||
|
if type(book) == "table" then
|
||||||
|
local f = io.open(path, "wb")
|
||||||
|
if f then
|
||||||
|
f:write("return ")
|
||||||
|
f:write(pp.format(book))
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local pool = {}
|
||||||
|
|
||||||
|
local db_funcs = {
|
||||||
|
save = function(db, bk)
|
||||||
|
if bk then
|
||||||
|
if type(bk) == "string" and type(db[bk]) == "table" then
|
||||||
|
return store_book(pool[db].."/"..bk, db[bk])
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for bk, book in pairs(db) do
|
||||||
|
store_book(pool[db].."/"..bk, book)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
local mt = {
|
||||||
|
__index = function(db, k)
|
||||||
|
if db_funcs[k] then return db_funcs[k] end
|
||||||
|
if isFile(pool[db].."/"..k) then
|
||||||
|
db[k] = load_book(pool[db].."/"..k)
|
||||||
|
end
|
||||||
|
return rawget(db, k)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.hack = db_funcs
|
||||||
|
|
||||||
|
return setmetatable(pool, {
|
||||||
|
__mode = "kv",
|
||||||
|
__call = function(pool, path)
|
||||||
|
if pool[path] then return pool[path] end
|
||||||
|
if not isDir(path) then return end
|
||||||
|
local db = {}
|
||||||
|
setmetatable(db, mt)
|
||||||
|
pool[path] = db
|
||||||
|
pool[db] = path
|
||||||
|
return db
|
||||||
|
end
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user