jbe/bsw@0: #!/usr/bin/env lua jbe/bsw@0: jbe/bsw@0: local _G = _G jbe/bsw@0: local _VERSION = _VERSION jbe/bsw@0: local assert = assert jbe/bsw@0: local error = error jbe/bsw@0: local getmetatable = getmetatable jbe/bsw@0: local ipairs = ipairs jbe/bsw@0: local next = next jbe/bsw@0: local pairs = pairs jbe/bsw@0: local print = print jbe/bsw@0: local rawequal = rawequal jbe/bsw@0: local rawget = rawget jbe@64: local rawlen = rawlen jbe/bsw@0: local rawset = rawset jbe/bsw@0: local select = select jbe/bsw@0: local setmetatable = setmetatable jbe/bsw@0: local tonumber = tonumber jbe/bsw@0: local tostring = tostring jbe/bsw@0: local type = type jbe/bsw@0: jbe/bsw@0: local math = math jbe/bsw@0: local string = string jbe@64: local table = table jbe/bsw@0: jbe/bsw@0: local mondelefant = require("mondelefant") jbe/bsw@0: local atom = require("atom") jbe@177: local json = require("json") jbe/bsw@0: jbe@64: local _M = {} jbe@64: if _ENV then jbe@64: _ENV = _M jbe@64: else jbe@64: _G[...] = _M jbe@64: setfenv(1, _M) jbe@64: end jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: input_converters = setmetatable({}, { __mode = "k" }) jbe/bsw@0: jbe@549: input_converters["boolean"] = function(conn, value, rawtext_mode) jbe@549: if rawtext_mode then jbe@549: if value then return "t" else return "f" end jbe@549: else jbe@549: if value then return "TRUE" else return "FALSE" end jbe@549: end jbe/bsw@0: end jbe/bsw@0: jbe@549: input_converters["number"] = function(conn, value, rawtext_mode) jbe@490: if _VERSION == "Lua 5.2" then jbe@490: -- TODO: remove following compatibility hack to allow large integers (e.g. 1e14) in Lua 5.2 jbe@490: local integer_string = string.format("%i", value) jbe@490: if tonumber(integer_string) == value then jbe@490: return integer_string jbe@419: else jbe@490: local number_string = tostring(value) jbe@490: if string.find(number_string, "^[0-9.e+-]+$") then jbe@490: return number_string jbe@490: else jbe@549: if rawtext_mode then return "NaN" else return "'NaN'" end jbe@490: end jbe@419: end jbe/bsw@0: end jbe@490: local integer = math.tointeger(value) jbe@490: if integer then jbe@490: return tostring(integer) jbe@490: end jbe@490: local str = tostring(value) jbe@490: if string.find(str, "^[0-9.e+-]+$") then jbe@490: return str jbe@490: end jbe@549: if rawtext_mode then return "NaN" else return "'NaN'" end jbe/bsw@0: end jbe/bsw@0: jbe@549: input_converters[atom.fraction] = function(conn, value, rawtext_mode) jbe/bsw@0: if value.invalid then jbe@549: if rawtext_mode then return "NaN" else return "'NaN'" end jbe/bsw@0: else jbe/bsw@0: local n, d = tostring(value.numerator), tostring(value.denominator) jbe/bsw@0: if string.find(n, "^%-?[0-9]+$") and string.find(d, "^%-?[0-9]+$") then jbe@549: if rawtext_mode then jbe@549: return n .. "/" .. d jbe@549: else jbe@549: return "(" .. n .. "::numeric / " .. d .. "::numeric)" jbe@549: end jbe/bsw@0: else jbe@549: if rawtext_mode then return "NaN" else return "'NaN'" end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe@549: input_converters[atom.date] = function(conn, value, rawtext_mode) jbe@549: if rawtext_mode then jbe@549: return tostring(value) jbe@549: else jbe@549: return conn:quote_string(tostring(value)) .. "::date" jbe@549: end jbe/bsw@0: end jbe/bsw@0: jbe@549: input_converters[atom.timestamp] = function(conn, value, rawtext_mode) jbe@549: if rawtext_mode then jbe@549: return tostring(value) jbe@549: else jbe@549: return conn:quote_string(tostring(value)) -- don't define type jbe@549: end jbe/bsw@0: end jbe/bsw@0: jbe@549: input_converters[atom.time] = function(conn, value, rawtext_mode) jbe@549: if rawtext_mode then jbe@549: return tostring(value) jbe@549: else jbe@549: return conn:quote_string(tostring(value)) .. "::time" jbe@549: end jbe/bsw@0: end jbe/bsw@0: jbe@556: input_converters["rawtable"] = function(conn, value, rawtext_mode) jbe@549: -- treat tables as arrays jbe@548: local parts = { "{" } jbe@548: for i, v in ipairs(value) do jbe@548: if i > 1 then parts[#parts+1] = "," end jbe@549: local converter = jbe@549: input_converters[getmetatable(v)] or jbe@549: input_converters[type(v)] jbe@549: if converter then jbe@549: v = converter(conn, v, true) jbe@549: else jbe@549: v = tostring(v) jbe@549: end jbe@548: parts[#parts+1] = '"' jbe@549: parts[#parts+1] = string.gsub(v, '[\\"]', '\\%0') jbe@548: parts[#parts+1] = '"' jbe@548: end jbe@548: parts[#parts+1] = "}" jbe@548: return conn:quote_string(table.concat(parts)) jbe@548: end jbe@548: jbe/bsw@0: jbe/bsw@0: output_converters = setmetatable({}, { __mode = "k" }) jbe/bsw@0: jbe/bsw@0: output_converters.int8 = function(str) return atom.integer:load(str) end jbe/bsw@0: output_converters.int4 = function(str) return atom.integer:load(str) end jbe/bsw@0: output_converters.int2 = function(str) return atom.integer:load(str) end jbe/bsw@0: jbe/bsw@0: output_converters.numeric = function(str) return atom.number:load(str) end jbe/bsw@0: output_converters.float4 = function(str) return atom.number:load(str) end jbe/bsw@0: output_converters.float8 = function(str) return atom.number:load(str) end jbe/bsw@0: jbe/bsw@0: output_converters.bool = function(str) return atom.boolean:load(str) end jbe/bsw@0: jbe/bsw@0: output_converters.date = function(str) return atom.date:load(str) end jbe/bsw@0: jbe@548: local function timestamp_loader_func(str) jbe@1: local year, month, day, hour, minute, second = string.match( jbe/bsw@0: str, jbe@1: "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9]) ([0-9]?[0-9]):([0-9][0-9]):([0-9][0-9])" jbe/bsw@0: ) jbe@1: if year then jbe/bsw@0: return atom.timestamp{ jbe@1: year = tonumber(year), jbe@1: month = tonumber(month), jbe@1: day = tonumber(day), jbe/bsw@0: hour = tonumber(hour), jbe/bsw@0: minute = tonumber(minute), jbe/bsw@0: second = tonumber(second) jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return atom.timestamp.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: output_converters.timestamp = timestamp_loader_func jbe/bsw@0: output_converters.timestamptz = timestamp_loader_func jbe/bsw@0: jbe@548: local function time_loader_func(str) jbe@1: local hour, minute, second = string.match( jbe/bsw@0: str, jbe@1: "^([0-9]?[0-9]):([0-9][0-9]):([0-9][0-9])" jbe/bsw@0: ) jbe@1: if hour then jbe/bsw@0: return atom.time{ jbe/bsw@0: hour = tonumber(hour), jbe/bsw@0: minute = tonumber(minute), jbe/bsw@0: second = tonumber(second) jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return atom.time.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: output_converters.time = time_loader_func jbe/bsw@0: output_converters.timetz = time_loader_func jbe/bsw@0: jbe@548: local function json_loader_func(str) jbe@178: return assert(json.import(str)) jbe@178: end jbe@178: output_converters.json = json_loader_func jbe@178: output_converters.jsonb = json_loader_func jbe@176: jbe/bsw@0: mondelefant.postgresql_connection_prototype.type_mappings = { jbe/bsw@0: int8 = atom.integer, jbe/bsw@0: int4 = atom.integer, jbe/bsw@0: int2 = atom.integer, jbe/bsw@0: bool = atom.boolean, jbe/bsw@0: date = atom.date, jbe/bsw@0: timestamp = atom.timestamp, jbe/bsw@0: time = atom.time, jbe/bsw@0: text = atom.string, jbe/bsw@0: varchar = atom.string, jbe@176: json = json, jbe@176: jsonb = json, jbe/bsw@0: } jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: function mondelefant.postgresql_connection_prototype.input_converter(conn, value, info) jbe/bsw@0: if value == nil then jbe/bsw@0: return "NULL" jbe/bsw@0: else jbe@556: local mt = getmetatable(value) jbe@556: local converter = input_converters.mt jbe@556: if not converter then jbe@556: local t = type(value) jbe@556: if t == "table" and mt == nil then jbe@556: converter = input_converters.rawtable jbe@556: else jbe@557: converter = input_converters.t jbe@556: end jbe@556: end jbe/bsw@0: local converter = jbe/bsw@0: input_converters[getmetatable(value)] or jbe/bsw@0: input_converters[type(value)] jbe/bsw@0: if converter then jbe/bsw@0: return converter(conn, value) jbe/bsw@0: else jbe/bsw@0: return conn:quote_string(tostring(value)) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function mondelefant.postgresql_connection_prototype.output_converter(conn, value, info) jbe/bsw@0: if value == nil then jbe/bsw@0: return nil jbe/bsw@0: else jbe@552: local array_type = nil jbe@552: if info.type then jbe@552: array_type = string.match(info.type, "^(.*)_array$") jbe@552: end jbe@548: if array_type then jbe@548: local result = {} jbe@548: local count = 0 jbe@548: local inner_converter = output_converters[array_type] jbe@548: if not inner_converter then jbe@548: inner_converter = function(x) return x end jbe@548: end jbe@548: value = string.match(value, "^{(.*)}$") jbe@548: if not value then jbe@548: error("Could not parse database array") jbe@548: end jbe@548: local pos = 1 jbe@548: while pos <= #value do jbe@548: count = count + 1 jbe@548: if string.find(value, '^""$', pos) then jbe@548: result[count] = inner_converter("") jbe@548: pos = pos + 2 jbe@548: elseif string.find(value, '^"",', pos) then jbe@548: result[count] = inner_converter("") jbe@548: pos = pos + 3 jbe@548: elseif string.find(value, '^"', pos) then jbe@548: local p1, p2, entry = string.find(value, '^"(.-[^\\])",', pos) jbe@548: if not p1 then jbe@548: p1, p2, entry = string.find(value, '^"(.*[^\\])"$', pos) jbe@548: end jbe@548: if not entry then error("Could not parse database array") end jbe@548: entry = string.gsub(entry, "\\(.)", "%1") jbe@548: result[count] = inner_converter(entry) jbe@548: pos = p2 + 1 jbe@548: else jbe@548: local p1, p2, entry = string.find(value, '^(.-),', pos) jbe@548: if not p1 then p1, p2, entry = string.find(value, '^(.*)$', pos) end jbe@548: result[count] = inner_converter(entry) jbe@548: pos = p2 + 1 jbe@548: end jbe@548: end jbe@548: return result jbe/bsw@0: else jbe@548: local converter = output_converters[info.type] jbe@548: if converter then jbe@548: return converter(value) jbe@548: else jbe@548: return value jbe@548: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe@374: jbe@374: function mondelefant.save_mutability_state(value) jbe@374: local jsontype = json.type(value) jbe@374: if jsontype == "object" or jsontype == "array" then jbe@374: return tostring(value) jbe@374: end jbe@374: end jbe@374: jbe@374: function mondelefant.verify_mutability_state(value, state) jbe@375: return tostring(value) ~= state jbe@374: end jbe@374: jbe@375: jbe@64: return _M jbe@64: jbe/bsw@0: jbe/bsw@0: --[[ jbe/bsw@0: jbe/bsw@0: db = assert(mondelefant.connect{engine='postgresql', dbname='test'}) jbe/bsw@0: result = db:query{'SELECT ? + 1', atom.date{ year=1999, month=12, day=31}} jbe/bsw@0: print(result[1][1].year) jbe/bsw@0: jbe/bsw@0: --]]