From aa924bf01fbeac7bc5517305ab12d81016de72e5 Mon Sep 17 00:00:00 2001 From: uleelx Date: Mon, 6 Apr 2015 21:44:20 +0800 Subject: [PATCH] Initial commit --- .gitignore | 1 + README.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++- cli.lua | 80 +++++++++++++++++++++++++++++ flatdb.lua | 70 ++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 cli.lua create mode 100644 flatdb.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a19a59 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/db/* diff --git a/README.md b/README.md index 4b83a43..7162b1b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,142 @@ -# FlatDB -A simple NoSQL database written in Lua +FlatDB +=========== + +FlatDB is a lua library that implements a serverless, zero-configuration, NoSQL database engine.
+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.
+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.
+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. diff --git a/cli.lua b/cli.lua new file mode 100644 index 0000000..eda6768 --- /dev/null +++ b/cli.lua @@ -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() diff --git a/flatdb.lua b/flatdb.lua new file mode 100644 index 0000000..12416aa --- /dev/null +++ b/flatdb.lua @@ -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 +})