diff options
Diffstat (limited to 'tex/context/base/util-sql.lua')
-rw-r--r-- | tex/context/base/util-sql.lua | 886 |
1 files changed, 443 insertions, 443 deletions
diff --git a/tex/context/base/util-sql.lua b/tex/context/base/util-sql.lua index 1c1766edf..cd2c4c2e2 100644 --- a/tex/context/base/util-sql.lua +++ b/tex/context/base/util-sql.lua @@ -1,443 +1,443 @@ -if not modules then modules = { } end modules ['util-sql'] = { - version = 1.001, - comment = "companion to m-sql.mkiv", - author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "see context related readme files" -} - --- todo: templates as table (saves splitting) - --- Of course we could use a library but we don't want another depedency and there is --- a bit of flux in these libraries. Also, we want the data back in a way that we --- like. --- --- This is the first of set of sql related modules that are providing functionality --- for a web based framework that we use for typesetting (related) services. We're --- talking of session management, job ticket processing, storage, (xml) file processing --- and dealing with data from databases (often ambitiously called database publishing). --- --- There is no generic solution for such services, but from our perspective, as we use --- context in a regular tds tree (the standard distribution) it makes sense to put shared --- code in the context distribution. That way we don't need to reinvent wheels every time. - --- We use the template mechanism from util-tpl which inturn is just using the dos cq --- windows convention of %whatever% variables that I've used for ages. - --- util-sql-imp-client.lua --- util-sql-imp-library.lua --- util-sql-imp-swiglib.lua --- util-sql-imp-lmxsql.lua - --- local sql = require("util-sql") --- --- local converter = sql.makeconverter { --- { name = "id", type = "number" }, --- { name = "data",type = "string" }, --- } --- --- local execute = sql.methods.swiglib.execute --- -- local execute = sql.methods.library.execute --- -- local execute = sql.methods.client.execute --- -- local execute = sql.methods.lmxsql.execute --- --- result = execute { --- presets = { --- host = "localhost", --- username = "root", --- password = "test", --- database = "test", --- id = "test", -- forces persistent session --- }, --- template = "select * from `test` where `id` > %criterium% ;", --- variables = { --- criterium = 2, --- }, --- converter = converter --- } --- --- inspect(result) - -local format, match = string.format, string.match -local random = math.random -local rawset, setmetatable, getmetatable, load, type = rawset, setmetatable, getmetatable, load, type -local P, S, V, C, Cs, Ct, Cc, Cg, Cf, patterns, lpegmatch = lpeg.P, lpeg.S, lpeg.V, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.patterns, lpeg.match -local concat = table.concat - -local osuuid = os.uuid -local osclock = os.clock or os.time -local ostime = os.time -local setmetatableindex = table.setmetatableindex - -local trace_sql = false trackers.register("sql.trace", function(v) trace_sql = v end) -local trace_queries = false trackers.register("sql.queries",function(v) trace_queries = v end) -local report_state = logs.reporter("sql") - --- trace_sql = true --- trace_queries = true - -utilities.sql = utilities.sql or { } -local sql = utilities.sql - -local replacetemplate = utilities.templates.replace -local loadtemplate = utilities.templates.load - -local methods = { } -sql.methods = methods - -local helpers = { } -sql.helpers = helpers - -local serialize = table.fastserialize -local deserialize = table.deserialize - -sql.serialize = serialize -sql.deserialize = deserialize - -helpers.serialize = serialize -- bonus -helpers.deserialize = deserialize -- bonus - -local defaults = { __index = - { - resultfile = "result.dat", - templatefile = "template.sql", - queryfile = "query.sql", - variables = { }, - username = "default", - password = "default", - host = "localhost", - port = 3306, - database = "default", - }, -} - -setmetatableindex(sql.methods,function(t,k) - report_state("start loading method %a",k) - require("util-sql-imp-"..k) - report_state("loading method %a done",k) - return rawget(t,k) -end) - --- converters - -local converters = { } -sql.converters = converters - -local function makeconverter(entries,celltemplate,wraptemplate) - local shortcuts = { } - local assignments = { } - local key = false - for i=1,#entries do - local entry = entries[i] - local name = entry.name - local kind = entry.type or entry.kind - local value = format(celltemplate,i,i) - if kind == "boolean" then - assignments[#assignments+1] = format("[%q] = booleanstring(%s),",name,value) - elseif kind == "number" then - assignments[#assignments+1] = format("[%q] = tonumber(%s),",name,value) - elseif type(kind) == "function" then - local c = #converters + 1 - converters[c] = kind - shortcuts[#shortcuts+1] = format("local fun_%s = converters[%s]",c,c) - assignments[#assignments+1] = format("[%q] = fun_%s(%s),",name,c,value) - elseif type(kind) == "table" then - local c = #converters + 1 - converters[c] = kind - shortcuts[#shortcuts+1] = format("local tab_%s = converters[%s]",c,c) - assignments[#assignments+1] = format("[%q] = tab_%s[%s],",name,#converters,value) - elseif kind == "deserialize" then - assignments[#assignments+1] = format("[%q] = deserialize(%s),",name,value) - elseif kind == "key" then - -- hashed instead of indexed - key = value - elseif kind == "entry" then - -- so we can (efficiently) extend the hashed table - local default = entry.default or "" - if type(default) == "string" then - assignments[#assignments+1] = format("[%q] = %q,",name,default) - else - assignments[#assignments+1] = format("[%q] = %s,",name,tostring(default)) - end - else - assignments[#assignments+1] = format("[%q] = %s,",name,value) - end - end - local code = format(wraptemplate,concat(shortcuts,"\n"),key and "{ }" or "data",key or "i",concat(assignments,"\n ")) - -- print(code) - local func = load(code) - return func and func() -end - -function sql.makeconverter(entries) - local fields = { } - for i=1,#entries do - fields[i] = format("`%s`",entries[i].name) - end - fields = concat(fields, ", ") - local converter = { - fields = fields - } - setmetatableindex(converter, function(t,k) - local sqlmethod = methods[k] - local v = makeconverter(entries,sqlmethod.celltemplate,sqlmethod.wraptemplate) - t[k] = v - return v - end) - return converter, fields -end - --- helper for libraries: - -local function validspecification(specification) - local presets = specification.presets - if type(presets) == "string" then - presets = dofile(presets) - end - if type(presets) == "table" then - setmetatable(presets,defaults) - setmetatable(specification,{ __index = presets }) - else - setmetatable(specification,defaults) - end - return true -end - -helpers.validspecification = validspecification - -local whitespace = patterns.whitespace^0 -local eol = patterns.eol -local separator = P(";") -local escaped = patterns.escaped -local dquote = patterns.dquote -local squote = patterns.squote -local dsquote = squote * squote ----- quoted = patterns.quoted -local quoted = dquote * (escaped + (1-dquote))^0 * dquote - + squote * (escaped + dsquote + (1-squote))^0 * squote -local comment = P("--") * (1-eol) / "" -local query = whitespace - * Cs((quoted + comment + 1 - separator)^1 * Cc(";")) - * whitespace -local splitter = Ct(query * (separator * query)^0) - -helpers.querysplitter = splitter - --- I will add a bit more checking. - -local function validspecification(specification) - local presets = specification.presets - if type(presets) == "string" then - presets = dofile(presets) - end - if type(presets) == "table" then - local m = getmetatable(presets) - if m then - setmetatable(m,defaults) - else - setmetatable(presets,defaults) - end - setmetatable(specification,{ __index = presets }) - else - setmetatable(specification,defaults) - end - local templatefile = specification.templatefile or "query" - local queryfile = specification.queryfile or presets.queryfile or file.nameonly(templatefile) .. "-temp.sql" - local resultfile = specification.resultfile or presets.resultfile or file.nameonly(templatefile) .. "-temp.dat" - specification.queryfile = queryfile - specification.resultfile = resultfile - if trace_sql then - report_state("template file: %s",templatefile or "<none>") - report_state("query file: %s",queryfile) - report_state("result file: %s",resultfile) - end - return true -end - -local function preparetemplate(specification) - local template = specification.template - if template then - local query = replacetemplate(template,specification.variables,'sql') - if not query then - report_state("error in template: %s",template) - elseif trace_queries then - report_state("query from template: %s",query) - end - return query - end - local templatefile = specification.templatefile - if templatefile then - local query = loadtemplate(templatefile,specification.variables,'sql') - if not query then - report_state("error in template file %a",templatefile) - elseif trace_queries then - report_state("query from template file %a: %s",templatefile,query) - end - return query - end - report_state("no query template or templatefile") -end - -helpers.preparetemplate = preparetemplate - --- -- -- we delay setting this -- -- -- - -local currentmethod - -local function firstexecute(...) - local execute = methods[currentmethod].execute - sql.execute = execute - return execute(...) -end - -function sql.setmethod(method) - currentmethod = method - sql.execute = firstexecute -end - -sql.setmethod("library") - --- helper: - -function sql.usedatabase(presets,datatable) - local name = datatable or presets.datatable - if name then - local method = presets.method and sql.methods[presets.method] or sql.methods.client - local base = presets.database or "test" - local basename = format("`%s`.`%s`",base,name) - local execute = nil - local m_execute = method.execute - if method.usesfiles then - local queryfile = presets.queryfile or format("%s-temp.sql",name) - local resultfile = presets.resultfile or format("%s-temp.dat",name) - execute = function(specification) -- variables template - if not specification.presets then specification.presets = presets end - if not specification.queryfile then specification.queryfile = queryfile end - if not specification.resultfile then specification.resultfile = queryfile end - return m_execute(specification) - end - else - execute = function(specification) -- variables template - if not specification.presets then specification.presets = presets end - return m_execute(specification) - end - end - local function unpackdata(records,name) - if records then - name = name or "data" - for i=1,#records do - local record = records[i] - local data = record[name] - if data then - record[name] = deserialize(data) - end - end - end - end - return { - presets = preset, - base = base, - name = name, - basename = basename, - execute = execute, - serialize = serialize, - deserialize = deserialize, - unpackdata = unpackdata, - } - else - report_state("missing name in usedatabase specification") - end -end - --- local data = utilities.sql.prepare { --- templatefile = "test.sql", --- variables = { }, --- host = "...", --- username = "...", --- password = "...", --- database = "...", --- } - --- local presets = { --- host = "...", --- username = "...", --- password = "...", --- database = "...", --- } --- --- local data = utilities.sql.prepare { --- templatefile = "test.sql", --- variables = { }, --- presets = presets, --- } - --- local data = utilities.sql.prepare { --- templatefile = "test.sql", --- variables = { }, --- presets = dofile(...), --- } - --- local data = utilities.sql.prepare { --- templatefile = "test.sql", --- variables = { }, --- presets = "...", --- } - --- for i=1,10 do --- local dummy = uuid() -- else same every time, don't ask --- end - -sql.tokens = { - length = 42, -- but in practice we will reserve some 50 characters - new = function() - return format("%s-%x06",osuuid(),random(0xFFFFF)) -- 36 + 1 + 6 = 42 - end, -} - --- -- -- - --- local func, code = sql.makeconverter { --- { name = "a", type = "number" }, --- { name = "b", type = "string" }, --- { name = "c", type = "boolean" }, --- { name = "d", type = { x = "1" } }, --- { name = "e", type = os.fulltime }, --- } --- --- print(code) - --- -- -- - -if tex and tex.systemmodes then - - local droptable = table.drop - local threshold = 16 * 1024 -- use slower but less memory hungry variant - - function sql.prepare(specification,tag) - -- could go into tuc if needed - -- todo: serialize per column - local tag = tag or specification.tag or "last" - local filename = format("%s-sql-result-%s.tuc",tex.jobname,tag) - if tex.systemmodes["first"] then - local data, keys = sql.execute(specification) - if not data then - data = { } - end - if not keys then - keys = { } - end - io.savedata(filename,droptable({ data = data, keys = keys },#keys*#data>threshold)) - return data, keys - else - local result = table.load(filename) - return result.data, result.keys - end - end - -else - - sql.prepare = sql.execute - -end - -return sql +if not modules then modules = { } end modules ['util-sql'] = {
+ version = 1.001,
+ comment = "companion to m-sql.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- todo: templates as table (saves splitting)
+
+-- Of course we could use a library but we don't want another depedency and there is
+-- a bit of flux in these libraries. Also, we want the data back in a way that we
+-- like.
+--
+-- This is the first of set of sql related modules that are providing functionality
+-- for a web based framework that we use for typesetting (related) services. We're
+-- talking of session management, job ticket processing, storage, (xml) file processing
+-- and dealing with data from databases (often ambitiously called database publishing).
+--
+-- There is no generic solution for such services, but from our perspective, as we use
+-- context in a regular tds tree (the standard distribution) it makes sense to put shared
+-- code in the context distribution. That way we don't need to reinvent wheels every time.
+
+-- We use the template mechanism from util-tpl which inturn is just using the dos cq
+-- windows convention of %whatever% variables that I've used for ages.
+
+-- util-sql-imp-client.lua
+-- util-sql-imp-library.lua
+-- util-sql-imp-swiglib.lua
+-- util-sql-imp-lmxsql.lua
+
+-- local sql = require("util-sql")
+--
+-- local converter = sql.makeconverter {
+-- { name = "id", type = "number" },
+-- { name = "data",type = "string" },
+-- }
+--
+-- local execute = sql.methods.swiglib.execute
+-- -- local execute = sql.methods.library.execute
+-- -- local execute = sql.methods.client.execute
+-- -- local execute = sql.methods.lmxsql.execute
+--
+-- result = execute {
+-- presets = {
+-- host = "localhost",
+-- username = "root",
+-- password = "test",
+-- database = "test",
+-- id = "test", -- forces persistent session
+-- },
+-- template = "select * from `test` where `id` > %criterium% ;",
+-- variables = {
+-- criterium = 2,
+-- },
+-- converter = converter
+-- }
+--
+-- inspect(result)
+
+local format, match = string.format, string.match
+local random = math.random
+local rawset, setmetatable, getmetatable, load, type = rawset, setmetatable, getmetatable, load, type
+local P, S, V, C, Cs, Ct, Cc, Cg, Cf, patterns, lpegmatch = lpeg.P, lpeg.S, lpeg.V, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.patterns, lpeg.match
+local concat = table.concat
+
+local osuuid = os.uuid
+local osclock = os.clock or os.time
+local ostime = os.time
+local setmetatableindex = table.setmetatableindex
+
+local trace_sql = false trackers.register("sql.trace", function(v) trace_sql = v end)
+local trace_queries = false trackers.register("sql.queries",function(v) trace_queries = v end)
+local report_state = logs.reporter("sql")
+
+-- trace_sql = true
+-- trace_queries = true
+
+utilities.sql = utilities.sql or { }
+local sql = utilities.sql
+
+local replacetemplate = utilities.templates.replace
+local loadtemplate = utilities.templates.load
+
+local methods = { }
+sql.methods = methods
+
+local helpers = { }
+sql.helpers = helpers
+
+local serialize = table.fastserialize
+local deserialize = table.deserialize
+
+sql.serialize = serialize
+sql.deserialize = deserialize
+
+helpers.serialize = serialize -- bonus
+helpers.deserialize = deserialize -- bonus
+
+local defaults = { __index =
+ {
+ resultfile = "result.dat",
+ templatefile = "template.sql",
+ queryfile = "query.sql",
+ variables = { },
+ username = "default",
+ password = "default",
+ host = "localhost",
+ port = 3306,
+ database = "default",
+ },
+}
+
+setmetatableindex(sql.methods,function(t,k)
+ report_state("start loading method %a",k)
+ require("util-sql-imp-"..k)
+ report_state("loading method %a done",k)
+ return rawget(t,k)
+end)
+
+-- converters
+
+local converters = { }
+sql.converters = converters
+
+local function makeconverter(entries,celltemplate,wraptemplate)
+ local shortcuts = { }
+ local assignments = { }
+ local key = false
+ for i=1,#entries do
+ local entry = entries[i]
+ local name = entry.name
+ local kind = entry.type or entry.kind
+ local value = format(celltemplate,i,i)
+ if kind == "boolean" then
+ assignments[#assignments+1] = format("[%q] = booleanstring(%s),",name,value)
+ elseif kind == "number" then
+ assignments[#assignments+1] = format("[%q] = tonumber(%s),",name,value)
+ elseif type(kind) == "function" then
+ local c = #converters + 1
+ converters[c] = kind
+ shortcuts[#shortcuts+1] = format("local fun_%s = converters[%s]",c,c)
+ assignments[#assignments+1] = format("[%q] = fun_%s(%s),",name,c,value)
+ elseif type(kind) == "table" then
+ local c = #converters + 1
+ converters[c] = kind
+ shortcuts[#shortcuts+1] = format("local tab_%s = converters[%s]",c,c)
+ assignments[#assignments+1] = format("[%q] = tab_%s[%s],",name,#converters,value)
+ elseif kind == "deserialize" then
+ assignments[#assignments+1] = format("[%q] = deserialize(%s),",name,value)
+ elseif kind == "key" then
+ -- hashed instead of indexed
+ key = value
+ elseif kind == "entry" then
+ -- so we can (efficiently) extend the hashed table
+ local default = entry.default or ""
+ if type(default) == "string" then
+ assignments[#assignments+1] = format("[%q] = %q,",name,default)
+ else
+ assignments[#assignments+1] = format("[%q] = %s,",name,tostring(default))
+ end
+ else
+ assignments[#assignments+1] = format("[%q] = %s,",name,value)
+ end
+ end
+ local code = format(wraptemplate,concat(shortcuts,"\n"),key and "{ }" or "data",key or "i",concat(assignments,"\n "))
+ -- print(code)
+ local func = load(code)
+ return func and func()
+end
+
+function sql.makeconverter(entries)
+ local fields = { }
+ for i=1,#entries do
+ fields[i] = format("`%s`",entries[i].name)
+ end
+ fields = concat(fields, ", ")
+ local converter = {
+ fields = fields
+ }
+ setmetatableindex(converter, function(t,k)
+ local sqlmethod = methods[k]
+ local v = makeconverter(entries,sqlmethod.celltemplate,sqlmethod.wraptemplate)
+ t[k] = v
+ return v
+ end)
+ return converter, fields
+end
+
+-- helper for libraries:
+
+local function validspecification(specification)
+ local presets = specification.presets
+ if type(presets) == "string" then
+ presets = dofile(presets)
+ end
+ if type(presets) == "table" then
+ setmetatable(presets,defaults)
+ setmetatable(specification,{ __index = presets })
+ else
+ setmetatable(specification,defaults)
+ end
+ return true
+end
+
+helpers.validspecification = validspecification
+
+local whitespace = patterns.whitespace^0
+local eol = patterns.eol
+local separator = P(";")
+local escaped = patterns.escaped
+local dquote = patterns.dquote
+local squote = patterns.squote
+local dsquote = squote * squote
+---- quoted = patterns.quoted
+local quoted = dquote * (escaped + (1-dquote))^0 * dquote
+ + squote * (escaped + dsquote + (1-squote))^0 * squote
+local comment = P("--") * (1-eol) / ""
+local query = whitespace
+ * Cs((quoted + comment + 1 - separator)^1 * Cc(";"))
+ * whitespace
+local splitter = Ct(query * (separator * query)^0)
+
+helpers.querysplitter = splitter
+
+-- I will add a bit more checking.
+
+local function validspecification(specification)
+ local presets = specification.presets
+ if type(presets) == "string" then
+ presets = dofile(presets)
+ end
+ if type(presets) == "table" then
+ local m = getmetatable(presets)
+ if m then
+ setmetatable(m,defaults)
+ else
+ setmetatable(presets,defaults)
+ end
+ setmetatable(specification,{ __index = presets })
+ else
+ setmetatable(specification,defaults)
+ end
+ local templatefile = specification.templatefile or "query"
+ local queryfile = specification.queryfile or presets.queryfile or file.nameonly(templatefile) .. "-temp.sql"
+ local resultfile = specification.resultfile or presets.resultfile or file.nameonly(templatefile) .. "-temp.dat"
+ specification.queryfile = queryfile
+ specification.resultfile = resultfile
+ if trace_sql then
+ report_state("template file: %s",templatefile or "<none>")
+ report_state("query file: %s",queryfile)
+ report_state("result file: %s",resultfile)
+ end
+ return true
+end
+
+local function preparetemplate(specification)
+ local template = specification.template
+ if template then
+ local query = replacetemplate(template,specification.variables,'sql')
+ if not query then
+ report_state("error in template: %s",template)
+ elseif trace_queries then
+ report_state("query from template: %s",query)
+ end
+ return query
+ end
+ local templatefile = specification.templatefile
+ if templatefile then
+ local query = loadtemplate(templatefile,specification.variables,'sql')
+ if not query then
+ report_state("error in template file %a",templatefile)
+ elseif trace_queries then
+ report_state("query from template file %a: %s",templatefile,query)
+ end
+ return query
+ end
+ report_state("no query template or templatefile")
+end
+
+helpers.preparetemplate = preparetemplate
+
+-- -- -- we delay setting this -- -- --
+
+local currentmethod
+
+local function firstexecute(...)
+ local execute = methods[currentmethod].execute
+ sql.execute = execute
+ return execute(...)
+end
+
+function sql.setmethod(method)
+ currentmethod = method
+ sql.execute = firstexecute
+end
+
+sql.setmethod("library")
+
+-- helper:
+
+function sql.usedatabase(presets,datatable)
+ local name = datatable or presets.datatable
+ if name then
+ local method = presets.method and sql.methods[presets.method] or sql.methods.client
+ local base = presets.database or "test"
+ local basename = format("`%s`.`%s`",base,name)
+ local execute = nil
+ local m_execute = method.execute
+ if method.usesfiles then
+ local queryfile = presets.queryfile or format("%s-temp.sql",name)
+ local resultfile = presets.resultfile or format("%s-temp.dat",name)
+ execute = function(specification) -- variables template
+ if not specification.presets then specification.presets = presets end
+ if not specification.queryfile then specification.queryfile = queryfile end
+ if not specification.resultfile then specification.resultfile = queryfile end
+ return m_execute(specification)
+ end
+ else
+ execute = function(specification) -- variables template
+ if not specification.presets then specification.presets = presets end
+ return m_execute(specification)
+ end
+ end
+ local function unpackdata(records,name)
+ if records then
+ name = name or "data"
+ for i=1,#records do
+ local record = records[i]
+ local data = record[name]
+ if data then
+ record[name] = deserialize(data)
+ end
+ end
+ end
+ end
+ return {
+ presets = preset,
+ base = base,
+ name = name,
+ basename = basename,
+ execute = execute,
+ serialize = serialize,
+ deserialize = deserialize,
+ unpackdata = unpackdata,
+ }
+ else
+ report_state("missing name in usedatabase specification")
+ end
+end
+
+-- local data = utilities.sql.prepare {
+-- templatefile = "test.sql",
+-- variables = { },
+-- host = "...",
+-- username = "...",
+-- password = "...",
+-- database = "...",
+-- }
+
+-- local presets = {
+-- host = "...",
+-- username = "...",
+-- password = "...",
+-- database = "...",
+-- }
+--
+-- local data = utilities.sql.prepare {
+-- templatefile = "test.sql",
+-- variables = { },
+-- presets = presets,
+-- }
+
+-- local data = utilities.sql.prepare {
+-- templatefile = "test.sql",
+-- variables = { },
+-- presets = dofile(...),
+-- }
+
+-- local data = utilities.sql.prepare {
+-- templatefile = "test.sql",
+-- variables = { },
+-- presets = "...",
+-- }
+
+-- for i=1,10 do
+-- local dummy = uuid() -- else same every time, don't ask
+-- end
+
+sql.tokens = {
+ length = 42, -- but in practice we will reserve some 50 characters
+ new = function()
+ return format("%s-%x06",osuuid(),random(0xFFFFF)) -- 36 + 1 + 6 = 42
+ end,
+}
+
+-- -- --
+
+-- local func, code = sql.makeconverter {
+-- { name = "a", type = "number" },
+-- { name = "b", type = "string" },
+-- { name = "c", type = "boolean" },
+-- { name = "d", type = { x = "1" } },
+-- { name = "e", type = os.fulltime },
+-- }
+--
+-- print(code)
+
+-- -- --
+
+if tex and tex.systemmodes then
+
+ local droptable = table.drop
+ local threshold = 16 * 1024 -- use slower but less memory hungry variant
+
+ function sql.prepare(specification,tag)
+ -- could go into tuc if needed
+ -- todo: serialize per column
+ local tag = tag or specification.tag or "last"
+ local filename = format("%s-sql-result-%s.tuc",tex.jobname,tag)
+ if tex.systemmodes["first"] then
+ local data, keys = sql.execute(specification)
+ if not data then
+ data = { }
+ end
+ if not keys then
+ keys = { }
+ end
+ io.savedata(filename,droptable({ data = data, keys = keys },#keys*#data>threshold))
+ return data, keys
+ else
+ local result = table.load(filename)
+ return result.data, result.keys
+ end
+ end
+
+else
+
+ sql.prepare = sql.execute
+
+end
+
+return sql
|