webmcp

annotate libraries/mondelefant/mondelefant_atom_connector.lua @ 552:7e874b5227b6

Bugfix in array support: info.type may be nil
author jbe
date Mon Dec 09 16:09:14 2019 +0100 (2019-12-09)
parents 77355fe3b1cc
children c228db239964
rev   line source
jbe/bsw@0 1 #!/usr/bin/env lua
jbe/bsw@0 2
jbe/bsw@0 3 local _G = _G
jbe/bsw@0 4 local _VERSION = _VERSION
jbe/bsw@0 5 local assert = assert
jbe/bsw@0 6 local error = error
jbe/bsw@0 7 local getmetatable = getmetatable
jbe/bsw@0 8 local ipairs = ipairs
jbe/bsw@0 9 local next = next
jbe/bsw@0 10 local pairs = pairs
jbe/bsw@0 11 local print = print
jbe/bsw@0 12 local rawequal = rawequal
jbe/bsw@0 13 local rawget = rawget
jbe@64 14 local rawlen = rawlen
jbe/bsw@0 15 local rawset = rawset
jbe/bsw@0 16 local select = select
jbe/bsw@0 17 local setmetatable = setmetatable
jbe/bsw@0 18 local tonumber = tonumber
jbe/bsw@0 19 local tostring = tostring
jbe/bsw@0 20 local type = type
jbe/bsw@0 21
jbe/bsw@0 22 local math = math
jbe/bsw@0 23 local string = string
jbe@64 24 local table = table
jbe/bsw@0 25
jbe/bsw@0 26 local mondelefant = require("mondelefant")
jbe/bsw@0 27 local atom = require("atom")
jbe@177 28 local json = require("json")
jbe/bsw@0 29
jbe@64 30 local _M = {}
jbe@64 31 if _ENV then
jbe@64 32 _ENV = _M
jbe@64 33 else
jbe@64 34 _G[...] = _M
jbe@64 35 setfenv(1, _M)
jbe@64 36 end
jbe/bsw@0 37
jbe/bsw@0 38
jbe/bsw@0 39 input_converters = setmetatable({}, { __mode = "k" })
jbe/bsw@0 40
jbe@549 41 input_converters["boolean"] = function(conn, value, rawtext_mode)
jbe@549 42 if rawtext_mode then
jbe@549 43 if value then return "t" else return "f" end
jbe@549 44 else
jbe@549 45 if value then return "TRUE" else return "FALSE" end
jbe@549 46 end
jbe/bsw@0 47 end
jbe/bsw@0 48
jbe@549 49 input_converters["number"] = function(conn, value, rawtext_mode)
jbe@490 50 if _VERSION == "Lua 5.2" then
jbe@490 51 -- TODO: remove following compatibility hack to allow large integers (e.g. 1e14) in Lua 5.2
jbe@490 52 local integer_string = string.format("%i", value)
jbe@490 53 if tonumber(integer_string) == value then
jbe@490 54 return integer_string
jbe@419 55 else
jbe@490 56 local number_string = tostring(value)
jbe@490 57 if string.find(number_string, "^[0-9.e+-]+$") then
jbe@490 58 return number_string
jbe@490 59 else
jbe@549 60 if rawtext_mode then return "NaN" else return "'NaN'" end
jbe@490 61 end
jbe@419 62 end
jbe/bsw@0 63 end
jbe@490 64 local integer = math.tointeger(value)
jbe@490 65 if integer then
jbe@490 66 return tostring(integer)
jbe@490 67 end
jbe@490 68 local str = tostring(value)
jbe@490 69 if string.find(str, "^[0-9.e+-]+$") then
jbe@490 70 return str
jbe@490 71 end
jbe@549 72 if rawtext_mode then return "NaN" else return "'NaN'" end
jbe/bsw@0 73 end
jbe/bsw@0 74
jbe@549 75 input_converters[atom.fraction] = function(conn, value, rawtext_mode)
jbe/bsw@0 76 if value.invalid then
jbe@549 77 if rawtext_mode then return "NaN" else return "'NaN'" end
jbe/bsw@0 78 else
jbe/bsw@0 79 local n, d = tostring(value.numerator), tostring(value.denominator)
jbe/bsw@0 80 if string.find(n, "^%-?[0-9]+$") and string.find(d, "^%-?[0-9]+$") then
jbe@549 81 if rawtext_mode then
jbe@549 82 return n .. "/" .. d
jbe@549 83 else
jbe@549 84 return "(" .. n .. "::numeric / " .. d .. "::numeric)"
jbe@549 85 end
jbe/bsw@0 86 else
jbe@549 87 if rawtext_mode then return "NaN" else return "'NaN'" end
jbe/bsw@0 88 end
jbe/bsw@0 89 end
jbe/bsw@0 90 end
jbe/bsw@0 91
jbe@549 92 input_converters[atom.date] = function(conn, value, rawtext_mode)
jbe@549 93 if rawtext_mode then
jbe@549 94 return tostring(value)
jbe@549 95 else
jbe@549 96 return conn:quote_string(tostring(value)) .. "::date"
jbe@549 97 end
jbe/bsw@0 98 end
jbe/bsw@0 99
jbe@549 100 input_converters[atom.timestamp] = function(conn, value, rawtext_mode)
jbe@549 101 if rawtext_mode then
jbe@549 102 return tostring(value)
jbe@549 103 else
jbe@549 104 return conn:quote_string(tostring(value)) -- don't define type
jbe@549 105 end
jbe/bsw@0 106 end
jbe/bsw@0 107
jbe@549 108 input_converters[atom.time] = function(conn, value, rawtext_mode)
jbe@549 109 if rawtext_mode then
jbe@549 110 return tostring(value)
jbe@549 111 else
jbe@549 112 return conn:quote_string(tostring(value)) .. "::time"
jbe@549 113 end
jbe/bsw@0 114 end
jbe/bsw@0 115
jbe@549 116 input_converters["table"] = function(conn, value, rawtext_mode)
jbe@549 117 -- treat tables as arrays
jbe@548 118 local parts = { "{" }
jbe@548 119 for i, v in ipairs(value) do
jbe@548 120 if i > 1 then parts[#parts+1] = "," end
jbe@549 121 local converter =
jbe@549 122 input_converters[getmetatable(v)] or
jbe@549 123 input_converters[type(v)]
jbe@549 124 if converter then
jbe@549 125 v = converter(conn, v, true)
jbe@549 126 else
jbe@549 127 v = tostring(v)
jbe@549 128 end
jbe@548 129 parts[#parts+1] = '"'
jbe@549 130 parts[#parts+1] = string.gsub(v, '[\\"]', '\\%0')
jbe@548 131 parts[#parts+1] = '"'
jbe@548 132 end
jbe@548 133 parts[#parts+1] = "}"
jbe@548 134 return conn:quote_string(table.concat(parts))
jbe@548 135 end
jbe@548 136
jbe/bsw@0 137
jbe/bsw@0 138 output_converters = setmetatable({}, { __mode = "k" })
jbe/bsw@0 139
jbe/bsw@0 140 output_converters.int8 = function(str) return atom.integer:load(str) end
jbe/bsw@0 141 output_converters.int4 = function(str) return atom.integer:load(str) end
jbe/bsw@0 142 output_converters.int2 = function(str) return atom.integer:load(str) end
jbe/bsw@0 143
jbe/bsw@0 144 output_converters.numeric = function(str) return atom.number:load(str) end
jbe/bsw@0 145 output_converters.float4 = function(str) return atom.number:load(str) end
jbe/bsw@0 146 output_converters.float8 = function(str) return atom.number:load(str) end
jbe/bsw@0 147
jbe/bsw@0 148 output_converters.bool = function(str) return atom.boolean:load(str) end
jbe/bsw@0 149
jbe/bsw@0 150 output_converters.date = function(str) return atom.date:load(str) end
jbe/bsw@0 151
jbe@548 152 local function timestamp_loader_func(str)
jbe@1 153 local year, month, day, hour, minute, second = string.match(
jbe/bsw@0 154 str,
jbe@1 155 "^([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 156 )
jbe@1 157 if year then
jbe/bsw@0 158 return atom.timestamp{
jbe@1 159 year = tonumber(year),
jbe@1 160 month = tonumber(month),
jbe@1 161 day = tonumber(day),
jbe/bsw@0 162 hour = tonumber(hour),
jbe/bsw@0 163 minute = tonumber(minute),
jbe/bsw@0 164 second = tonumber(second)
jbe/bsw@0 165 }
jbe/bsw@0 166 else
jbe/bsw@0 167 return atom.timestamp.invalid
jbe/bsw@0 168 end
jbe/bsw@0 169 end
jbe/bsw@0 170 output_converters.timestamp = timestamp_loader_func
jbe/bsw@0 171 output_converters.timestamptz = timestamp_loader_func
jbe/bsw@0 172
jbe@548 173 local function time_loader_func(str)
jbe@1 174 local hour, minute, second = string.match(
jbe/bsw@0 175 str,
jbe@1 176 "^([0-9]?[0-9]):([0-9][0-9]):([0-9][0-9])"
jbe/bsw@0 177 )
jbe@1 178 if hour then
jbe/bsw@0 179 return atom.time{
jbe/bsw@0 180 hour = tonumber(hour),
jbe/bsw@0 181 minute = tonumber(minute),
jbe/bsw@0 182 second = tonumber(second)
jbe/bsw@0 183 }
jbe/bsw@0 184 else
jbe/bsw@0 185 return atom.time.invalid
jbe/bsw@0 186 end
jbe/bsw@0 187 end
jbe/bsw@0 188 output_converters.time = time_loader_func
jbe/bsw@0 189 output_converters.timetz = time_loader_func
jbe/bsw@0 190
jbe@548 191 local function json_loader_func(str)
jbe@178 192 return assert(json.import(str))
jbe@178 193 end
jbe@178 194 output_converters.json = json_loader_func
jbe@178 195 output_converters.jsonb = json_loader_func
jbe@176 196
jbe/bsw@0 197 mondelefant.postgresql_connection_prototype.type_mappings = {
jbe/bsw@0 198 int8 = atom.integer,
jbe/bsw@0 199 int4 = atom.integer,
jbe/bsw@0 200 int2 = atom.integer,
jbe/bsw@0 201 bool = atom.boolean,
jbe/bsw@0 202 date = atom.date,
jbe/bsw@0 203 timestamp = atom.timestamp,
jbe/bsw@0 204 time = atom.time,
jbe/bsw@0 205 text = atom.string,
jbe/bsw@0 206 varchar = atom.string,
jbe@176 207 json = json,
jbe@176 208 jsonb = json,
jbe/bsw@0 209 }
jbe/bsw@0 210
jbe/bsw@0 211
jbe/bsw@0 212 function mondelefant.postgresql_connection_prototype.input_converter(conn, value, info)
jbe/bsw@0 213 if value == nil then
jbe/bsw@0 214 return "NULL"
jbe/bsw@0 215 else
jbe/bsw@0 216 local converter =
jbe/bsw@0 217 input_converters[getmetatable(value)] or
jbe/bsw@0 218 input_converters[type(value)]
jbe/bsw@0 219 if converter then
jbe/bsw@0 220 return converter(conn, value)
jbe/bsw@0 221 else
jbe/bsw@0 222 return conn:quote_string(tostring(value))
jbe/bsw@0 223 end
jbe/bsw@0 224 end
jbe/bsw@0 225 end
jbe/bsw@0 226
jbe/bsw@0 227 function mondelefant.postgresql_connection_prototype.output_converter(conn, value, info)
jbe/bsw@0 228 if value == nil then
jbe/bsw@0 229 return nil
jbe/bsw@0 230 else
jbe@552 231 local array_type = nil
jbe@552 232 if info.type then
jbe@552 233 array_type = string.match(info.type, "^(.*)_array$")
jbe@552 234 end
jbe@548 235 if array_type then
jbe@548 236 local result = {}
jbe@548 237 local count = 0
jbe@548 238 local inner_converter = output_converters[array_type]
jbe@548 239 if not inner_converter then
jbe@548 240 inner_converter = function(x) return x end
jbe@548 241 end
jbe@548 242 value = string.match(value, "^{(.*)}$")
jbe@548 243 if not value then
jbe@548 244 error("Could not parse database array")
jbe@548 245 end
jbe@548 246 local pos = 1
jbe@548 247 while pos <= #value do
jbe@548 248 count = count + 1
jbe@548 249 if string.find(value, '^""$', pos) then
jbe@548 250 result[count] = inner_converter("")
jbe@548 251 pos = pos + 2
jbe@548 252 elseif string.find(value, '^"",', pos) then
jbe@548 253 result[count] = inner_converter("")
jbe@548 254 pos = pos + 3
jbe@548 255 elseif string.find(value, '^"', pos) then
jbe@548 256 local p1, p2, entry = string.find(value, '^"(.-[^\\])",', pos)
jbe@548 257 if not p1 then
jbe@548 258 p1, p2, entry = string.find(value, '^"(.*[^\\])"$', pos)
jbe@548 259 end
jbe@548 260 if not entry then error("Could not parse database array") end
jbe@548 261 entry = string.gsub(entry, "\\(.)", "%1")
jbe@548 262 result[count] = inner_converter(entry)
jbe@548 263 pos = p2 + 1
jbe@548 264 else
jbe@548 265 local p1, p2, entry = string.find(value, '^(.-),', pos)
jbe@548 266 if not p1 then p1, p2, entry = string.find(value, '^(.*)$', pos) end
jbe@548 267 result[count] = inner_converter(entry)
jbe@548 268 pos = p2 + 1
jbe@548 269 end
jbe@548 270 end
jbe@548 271 return result
jbe/bsw@0 272 else
jbe@548 273 local converter = output_converters[info.type]
jbe@548 274 if converter then
jbe@548 275 return converter(value)
jbe@548 276 else
jbe@548 277 return value
jbe@548 278 end
jbe/bsw@0 279 end
jbe/bsw@0 280 end
jbe/bsw@0 281 end
jbe/bsw@0 282
jbe@374 283
jbe@374 284 function mondelefant.save_mutability_state(value)
jbe@374 285 local jsontype = json.type(value)
jbe@374 286 if jsontype == "object" or jsontype == "array" then
jbe@374 287 return tostring(value)
jbe@374 288 end
jbe@374 289 end
jbe@374 290
jbe@374 291 function mondelefant.verify_mutability_state(value, state)
jbe@375 292 return tostring(value) ~= state
jbe@374 293 end
jbe@374 294
jbe@375 295
jbe@64 296 return _M
jbe@64 297
jbe/bsw@0 298
jbe/bsw@0 299 --[[
jbe/bsw@0 300
jbe/bsw@0 301 db = assert(mondelefant.connect{engine='postgresql', dbname='test'})
jbe/bsw@0 302 result = db:query{'SELECT ? + 1', atom.date{ year=1999, month=12, day=31}}
jbe/bsw@0 303 print(result[1][1].year)
jbe/bsw@0 304
jbe/bsw@0 305 --]]

Impressum / About Us