webmcp

view libraries/mondelefant/mondelefant_atom_connector.lua @ 548:a006593b747c

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

Impressum / About Us