From 677f05d69c506d9de58613130a501e7f6cfefa0f Mon Sep 17 00:00:00 2001 From: Robin Malley Date: Sun, 4 Jun 2023 21:33:02 +0000 Subject: [PATCH] Documentation Document various methods in db.lua and cache.lua --- src/lua/cache.lua | 143 +++++++++++++++++++++++++++++++++++++++++++--- src/lua/db.lua | 81 +++++++++++++++++--------- 2 files changed, 190 insertions(+), 34 deletions(-) diff --git a/src/lua/cache.lua b/src/lua/cache.lua index 2f79e0d..86e5e3f 100644 --- a/src/lua/cache.lua +++ b/src/lua/cache.lua @@ -1,8 +1,11 @@ ---[[ -Implements a simple in-memory cache. The cache has no upper size limit, and -may cause out-of-memory errors. When this happens, the OS will kill the kore -worker process, and the kore parent process will restart with a fresh, empty -cache +--[[ md + +@name lua/cache + +Implements a simple in memory read through cache. +The cache has no upper size limit, and may cause out-of-memory errors. +When this happens, the OS will kill the kore worker process, +and the kore parent process will restart with a fresh, empty cache. ]] local sql = require("lsqlite3") @@ -15,6 +18,68 @@ local ret = {} local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache +--[[ cat +@name lua/cache +

Schema for Cache

+

The cache mechanism is a in-memeory sqlite3 database behind the scenes, it +can ensure consistency and atomic updates & dirtying, though it doesn't today.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
cache
AttributesFieldTypeDescription
Primary KeypathTEXT + The logical path this text was rendered at, + before browser-specific headers (like accept-encoding) + are applied +
dataBLOB + The returned result from the function passed into + cache.render(), the result must be a string, and + may contain nulls. +
updatedINTEGER + The time this item was rendered at, can be used to set + a minimum update frequency. This is used so that web + scrapers don't constantly trigger re-renders of the + index page. +
dirtyINTEGER + Does this page need to be re-rendered the next time it + is called? For example, an author's story could have + multiple hits, which would require rerendering their + author page to show the new hit count, but we don't + actually need to do it until someone requests the + author page. In this case, we keep the old page around + to save time trying to clear it and potentially hit + sqlite's garbage collector. +
+]] + local oldconfigure = configure function configure(...) ret.cache = db.sqlassert(sql.open_memory())-- Expose db for testing @@ -52,7 +117,40 @@ function configure(...) return oldconfigure(...) end ---Render a page, with cacheing. If you need to dirty a cache, call dirty_cache() +--[[ md +@name lua/cache + +### cache.render + +Render a page with cacheing. +The callback will be called with no arguments, and must return a string. + +Parameters: + +0. pagename - {{lua/string}} - A logical string to associate with this +rendered page, this must be passed exactly into render() in order +to (potentially) retrive the cached page. +0. callback - {{lua/function}} - A function that may be called, +if it is called, it is called with no arguments, and must return a string. +The returned string may have embedded nulls. + +Returns: + +0. {{lua/string}} - Either the return of the passed function, or the cached +string. + +Example: + + cache = require("cache") + func = function() + print("Called") + return "Hello, world!" + end + print(cache.render("/test",func)) -- prints "Called", then "Hello, world!" + print(cache.render("/test",func)) -- prints "Hello, world!" + print(cache.render("/test",func)) -- prints "Hello, world!" + +]] function ret.render(pagename,callback) stmnt_cache:bind_names{path=pagename} local err = db.do_sql(stmnt_cache) @@ -81,8 +179,37 @@ function ret.render(pagename,callback) return text end --- Dirty a cached page, causing it to be re-rendered the next time it's --- requested. Doesn't actually delete it or anything, just sets it's dirty bit +--[[ md +@name lua/cache + +### cache.dirty + +Dirty a cached page, causing it to be re-rendered the next time it's +requested. Doesn't actually delete it or free memory, just sets its dirty bit. +If the page does not exists or has not been rendered yet, this function does +not error. + +Parameters: + +0. url - {{lua/string}} - `pagename` from the render function, the logical +string associcated with this rendered page. + +No returns + +Example: + + cache = require("cache") + func = function() + print("Called") + return "Hello, world!" + end + print(cache.render("/test",func)) -- prints "Called", then "Hello, world!" + print(cache.render("/test",func)) -- prints "Hello, world!") + cache.dirty("/test") + print(cache.render("/test",func)) -- prints "Called", then "Hello, world!" + print(cache.render("/test",func)) -- prints "Hello, world!" +]] + function ret.dirty(url) stmnt_dirty_cache:bind_names{ path = url diff --git a/src/lua/db.lua b/src/lua/db.lua index 9592997..6b5a322 100644 --- a/src/lua/db.lua +++ b/src/lua/db.lua @@ -1,6 +1,10 @@ --[[ md @name lua/db + +## Overview + Does most of the database interaction. +Creates default empty database during configure() Notably, holds a connection to the open sqlite3 database in .conn ]] local sql = require("lsqlite3") @@ -11,7 +15,9 @@ local config = require("config") local db = {} --[[ md -@name lua/db/sqlassert +@name lua/db + +### db.sqlassert Runs an sql query and receives the 3 arguments back, prints a nice error message on fail, and returns true on success. @@ -36,8 +42,8 @@ that error checking and assignment can all be done on a single line. Example: -> db = require("db") -> query = db.sqlassert(db.conn:parepare("SELECT 'Hello, world!'")) + db = require("db") + query = db.sqlassert(db.conn:parepare("SELECT 'Hello, world!'")) ]] function db.sqlassert(r, errcode, err) if not r then @@ -51,7 +57,9 @@ function db.sqlassert(r, errcode, err) end --[[ md -@name lua/db/do_sql +@name lua/db + +### db.do_sql Continuously tries to perform an sql statement until it goes through. This function may call {{lua/coroutine/yield}} @@ -65,12 +73,12 @@ Returns: Example: -> sql = require("lsqlite3") -> configure = function(...) end -- Mock smr environment -> db = require("db") -> configure() -> query = db.conn:prepare("SELECT 'Hello, world!';") -> assert(db.do_sql(query)) + sql = require("lsqlite3") + configure = function(...) end -- Mock smr environment + db = require("db") + configure() + query = db.conn:prepare("SELECT 'Hello, world!';") + assert(db.do_sql(query)) ]] function db.do_sql(stmnt) if not stmnt then error("No statement",2) end @@ -88,7 +96,9 @@ function db.do_sql(stmnt) end --[[ md -@name lua/db/sql_rows +@name lua/db + +### db.sql_rows Provides an iterator that loops over results in an sql statement or throws an error, then resets the statement after the loop is done. @@ -107,12 +117,11 @@ Returns: Example: -> db = require("db") -> query = db.conn:prepare("SELECT 'Hello, world!';") -> for row in db.sql_rows(query) do -> print(row) -> end -Hello, world! + db = require("db") + query = db.conn:prepare("SELECT 'Hello, world!';") + for row in db.sql_rows(query) do + print(row) -- prints 'Hello, world!' + end ]] function db.sql_rows(stmnt) @@ -141,14 +150,24 @@ function db.sql_rows(stmnt) end end ---[[ -@name lua/db/sqlbind +--[[ md +@name lua/db -Binds an argument to as statement with nice error reporting on failure. Approximatly the same as {{lsqlite/stmt/bind_names}}, but with better error reporting. -stmnt :: sql.stmnt - the prepared sql statemnet -call :: string - a string "bind" or "bind_blob" -position :: number - the argument position to bind to -data :: string - The data to bind +### db.sqlbind + +Binds an argument to a prepared statement, +with nice error reporting on failure. +Wraps {{lsqlite/stmnt/bind_name}} +with better error reporting. + +Parameters: + +0. stmnt - {{lsqlite/stmnt}} - The prepared statement from {{sqlite/db/prepare}} +0. call - {{lua/string}} - Literal string, options are `bind` for most types, or `bind_blob` for strings that may contain embedded nulls +0. position - {{lua/number}} - The argument position to bind to, does not support named parameters +0. data - Any - the data to bind + +Returns nothing ]] function db.sqlbind(stmnt,call,position,data) assert(call == "bind" or call == "bind_blob","Bad bind call, call was:" .. call) @@ -166,8 +185,6 @@ function db.sqlbind(stmnt,call,position,data) end end - - local oldconfigure = configure db.conn = db.sqlassert(sql.open(config.db)) function configure(...) @@ -191,6 +208,18 @@ function configure(...) return oldconfigure(...) end +--[[ md + +### db.close() + +Closes the database connection. Not called during normal operation, used to +assist in unit testing. + +No parameters + +No returns +]] + function db.close() db.conn:close() end