Mercurial > urweb
comparison src/sqlcache.sml @ 2216:70ec9bb337be
Progress towards invalidation based on equalities of fields.
author | Ziv Scully <ziv@mit.edu> |
---|---|
date | Mon, 10 Nov 2014 22:04:40 -0500 |
parents | 639e62ca2530 |
children | f7113855f3b7 |
comparison
equal
deleted
inserted
replaced
2215:639e62ca2530 | 2216:70ec9bb337be |
---|---|
1 structure Sqlcache (* :> SQLCACHE *) = struct | 1 structure Sqlcache (* :> SQLCACHE *) = struct |
2 | 2 |
3 open Sql | |
4 open Mono | 3 open Mono |
5 | 4 |
6 structure IS = IntBinarySet | 5 structure IS = IntBinarySet |
7 structure IM = IntBinaryMap | 6 structure IM = IntBinaryMap |
8 structure SK = struct type ord_key = string val compare = String.compare end | 7 structure SK = struct type ord_key = string val compare = String.compare end |
9 structure SS = BinarySetFn(SK) | 8 structure SS = BinarySetFn(SK) |
10 structure SM = BinaryMapFn(SK) | 9 structure SM = BinaryMapFn(SK) |
11 structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS) | 10 structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS) |
12 | 11 |
13 (* Filled in by cacheWrap during Sqlcache. *) | 12 (* Filled in by [cacheWrap] during [Sqlcache]. *) |
14 val ffiInfo : {index : int, params : int} list ref = ref [] | 13 val ffiInfo : {index : int, params : int} list ref = ref [] |
15 | 14 |
16 fun getFfiInfo () = !ffiInfo | 15 fun getFfiInfo () = !ffiInfo |
17 | 16 |
18 (* Some FFIs have writing as their only effect, which the caching records. *) | 17 (* Some FFIs have writing as their only effect, which the caching records. *) |
19 val ffiEffectful = | 18 val ffiEffectful = |
19 (* TODO: have this less hard-coded. *) | |
20 let | 20 let |
21 val fs = SS.fromList ["htmlifyInt_w", | 21 val fs = SS.fromList ["htmlifyInt_w", |
22 "htmlifyFloat_w", | 22 "htmlifyFloat_w", |
23 "htmlifyString_w", | 23 "htmlifyString_w", |
24 "htmlifyBool_w", | 24 "htmlifyBool_w", |
38 end | 38 end |
39 | 39 |
40 | 40 |
41 (* Effect analysis. *) | 41 (* Effect analysis. *) |
42 | 42 |
43 (* Makes an exception for EWrite (which is recorded when caching). *) | 43 (* Makes an exception for [EWrite] (which is recorded when caching). *) |
44 fun effectful doPrint (effs : IS.set) (inFunction : bool) (bound : int) : Mono.exp -> bool = | 44 fun effectful doPrint (effs : IS.set) (inFunction : bool) (bound : int) : Mono.exp -> bool = |
45 (* If result is true, expression is definitely effectful. If result is | 45 (* If result is true, expression is definitely effectful. If result is |
46 false, then expression is definitely not effectful if effs is fully | 46 false, then expression is definitely not effectful if effs is fully |
47 populated. The intended pattern is to use this a number of times equal | 47 populated. The intended pattern is to use this a number of times equal |
48 to the number of declarations in a file, Bellman-Ford style. *) | 48 to the number of declarations in a file, Bellman-Ford style. *) |
60 | ENamed name => if IS.member (effs, name) then tru "named" else false | 60 | ENamed name => if IS.member (effs, name) then tru "named" else false |
61 | ECon (_, _, NONE) => false | 61 | ECon (_, _, NONE) => false |
62 | ECon (_, _, SOME e) => eff e | 62 | ECon (_, _, SOME e) => eff e |
63 | ENone _ => false | 63 | ENone _ => false |
64 | ESome (_, e) => eff e | 64 | ESome (_, e) => eff e |
65 (* TODO: use FFI whitelist. *) | |
66 | EFfi (m, f) => if ffiEffectful (m, f) then tru "ffi" else false | 65 | EFfi (m, f) => if ffiEffectful (m, f) then tru "ffi" else false |
67 | EFfiApp (m, f, _) => if ffiEffectful (m, f) then tru "ffiapp" else false | 66 | EFfiApp (m, f, _) => if ffiEffectful (m, f) then tru "ffiapp" else false |
68 (* ASK: we're calling functions effectful if they have effects when | 67 (* ASK: we're calling functions effectful if they have effects when |
69 applied or if the function expressions themselves have effects. | 68 applied or if the function expressions themselves have effects. |
70 Is that okay? *) | 69 Is that okay? *) |
129 in | 128 in |
130 MonoUtil.File.fold {typ = #2, exp = #2, decl = doDecl} IS.empty | 129 MonoUtil.File.fold {typ = #2, exp = #2, decl = doDecl} IS.empty |
131 end | 130 end |
132 | 131 |
133 | 132 |
133 (* Boolean formula normalization. *) | |
134 | |
135 datatype normalForm = Cnf | Dnf | |
136 | |
137 datatype 'atom formula = | |
138 Atom of 'atom | |
139 | Negate of 'atom formula | |
140 | Combo of normalForm * 'atom formula list | |
141 | |
142 val flipNf = fn Cnf => Dnf | Dnf => Cnf | |
143 | |
144 fun bind xs f = List.concat (map f xs) | |
145 | |
146 val rec cartesianProduct : 'a list list -> 'a list list = | |
147 fn [] => [[]] | |
148 | (xs :: xss) => bind (cartesianProduct xss) | |
149 (fn ys => bind xs (fn x => [x :: ys])) | |
150 | |
151 fun normalize (negate : 'atom -> 'atom) (norm : normalForm) = | |
152 fn Atom x => [[x]] | |
153 | Negate f => map (map negate) (normalize negate (flipNf norm) f) | |
154 | Combo (n, fs) => | |
155 let | |
156 val fss = bind fs (normalize negate n) | |
157 in | |
158 if n = norm then fss else cartesianProduct fss | |
159 end | |
160 | |
161 fun mapFormula mf = | |
162 fn Atom x => Atom (mf x) | |
163 | Negate f => Negate (mapFormula mf f) | |
164 | Combo (n, fs) => Combo (n, map (mapFormula mf) fs) | |
165 | |
166 | |
134 (* SQL analysis. *) | 167 (* SQL analysis. *) |
135 | 168 |
136 val useInjIfPossible = | 169 val rec chooseTwos : 'a list -> ('a * 'a) list = |
137 fn SqConst prim => Inj (EPrim (Prim.String (Prim.Normal, Prim.toString prim)), | 170 fn [] => [] |
138 ErrorMsg.dummySpan) | 171 | x :: ys => map (fn y => (x, y)) ys @ chooseTwos ys |
139 | sqexp => sqexp | 172 |
140 | 173 datatype atomExp = |
141 fun equalities (canonicalTable : string -> string) : | 174 QueryArg of int |
142 sqexp -> ((string * string) * Mono.exp) list option = | 175 | DmlRel of int |
143 let | 176 | Prim of Prim.t |
144 val rec eqs = | 177 | Field of string * string |
145 fn Binop (Exps f, e1, e2) => | 178 |
146 (* TODO: use a custom datatype in Exps instead of a function. *) | 179 structure AtomExpKey : ORD_KEY = struct |
147 (case f (Var 1, Var 2) of | 180 |
148 Reln (Eq, [Var 1, Var 2]) => | 181 type ord_key = atomExp |
149 let | 182 |
150 val (e1', e2') = (useInjIfPossible e1, useInjIfPossible e2) | 183 val compare = |
151 in | 184 fn (QueryArg n1, QueryArg n2) => Int.compare (n1, n2) |
152 case (e1', e2') of | 185 | (QueryArg _, _) => LESS |
153 (Field (t, f), Inj i) => SOME [((canonicalTable t, f), i)] | 186 | (_, QueryArg _) => GREATER |
154 | (Inj i, Field (t, f)) => SOME [((canonicalTable t, f), i)] | 187 | (DmlRel n1, DmlRel n2) => Int.compare (n1, n2) |
155 | _ => NONE | 188 | (DmlRel _, _) => LESS |
156 end | 189 | (_, DmlRel _) => GREATER |
190 | (Prim p1, Prim p2) => Prim.compare (p1, p2) | |
191 | (Prim _, _) => LESS | |
192 | (_, Prim _) => GREATER | |
193 | (Field (t1, f1), Field (t2, f2)) => String.compare (t1 ^ "." ^ f1, t2 ^ "." ^ f2) | |
194 | |
195 end | |
196 | |
197 structure UF = UnionFindFn(AtomExpKey) | |
198 | |
199 fun conflictMaps (fQuery : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula, | |
200 fDml : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula) = | |
201 let | |
202 val toKnownEquality = | |
203 (* [NONE] here means unkown. Anything that isn't a comparison between | |
204 two knowns shouldn't be used, and simply dropping unused terms is | |
205 okay in disjunctive normal form. *) | |
206 fn (Sql.Eq, SOME e1, SOME e2) => SOME (e1, e2) | |
207 | _ => NONE | |
208 val equivClasses : (Sql.cmp * atomExp option * atomExp option) list -> atomExp list list = | |
209 UF.classes | |
210 o List.foldl UF.union' UF.empty | |
211 o List.mapPartial toKnownEquality | |
212 fun addToEqs (eqs, n, e) = | |
213 case IM.find (eqs, n) of | |
214 (* Comparing to a constant seems better? *) | |
215 SOME (EPrim _) => eqs | |
216 | _ => IM.insert (eqs, n, e) | |
217 val accumulateEqs = | |
218 (* [NONE] means we have a contradiction. *) | |
219 fn (_, NONE) => NONE | |
220 | ((Prim p1, Prim p2), eqso) => | |
221 (case Prim.compare (p1, p2) of | |
222 EQUAL => eqso | |
157 | _ => NONE) | 223 | _ => NONE) |
158 | Binop (Props f, e1, e2) => | 224 | ((QueryArg n, Prim p), SOME eqs) => SOME (addToEqs (eqs, n, EPrim p)) |
159 (* TODO: use a custom datatype in Props instead of a function. *) | 225 | ((QueryArg n, DmlRel r), SOME eqs) => SOME (addToEqs (eqs, n, ERel r)) |
160 (case f (True, False) of | 226 | ((Prim p, QueryArg n), SOME eqs) => SOME (addToEqs (eqs, n, EPrim p)) |
161 And (True, False) => | 227 | ((DmlRel r, QueryArg n), SOME eqs) => SOME (addToEqs (eqs, n, ERel r)) |
162 (case (eqs e1, eqs e2) of | 228 (* TODO: deal with equalities involving just [DmlRel]s and [Prim]s. *) |
163 (SOME eqs1, SOME eqs2) => SOME (eqs1 @ eqs2) | 229 | (_, eqso) => eqso |
164 | _ => NONE) | 230 val eqsOfClass : atomExp list -> Mono.exp' IM.map option = |
165 | _ => NONE) | 231 List.foldl accumulateEqs (SOME IM.empty) |
166 | _ => NONE | 232 o chooseTwos |
167 in | 233 fun toAtomExps rel (cmp, e1, e2) = |
168 eqs | 234 let |
169 end | 235 val qa = |
170 | 236 (* Here [NONE] means unkown. *) |
171 val equalitiesQuery = | 237 fn Sql.SqConst p => SOME (Prim p) |
172 fn Query1 {From = tablePairs, Where = SOME exp, ...} => | 238 | Sql.Field tf => SOME (Field tf) |
173 equalities | 239 | Sql.Inj (EPrim p, _) => SOME (Prim p) |
174 (* If we have [SELECT ... FROM T AS T' ...], use T, not T'. *) | 240 | Sql.Inj (ERel n, _) => SOME (rel n) |
175 (fn t => | 241 (* We can't deal with anything else. *) |
176 case List.find (fn (_, tAs) => t = tAs) tablePairs of | 242 | _ => NONE |
177 NONE => t | 243 in |
178 | SOME (tOrig, _) => tOrig) | 244 (cmp, qa e1, qa e2) |
179 exp | 245 end |
180 | Query1 {Where = NONE, ...} => SOME [] | 246 fun negateCmp (cmp, e1, e2) = |
181 | _ => NONE | 247 (case cmp of |
182 | 248 Sql.Eq => Sql.Ne |
183 val equalitiesDml = | 249 | Sql.Ne => Sql.Eq |
184 fn Insert (tab, eqs) => SOME (List.mapPartial | 250 | Sql.Lt => Sql.Ge |
185 (fn (name, sqexp) => | 251 | Sql.Le => Sql.Gt |
186 case useInjIfPossible sqexp of | 252 | Sql.Gt => Sql.Le |
187 Inj e => SOME ((tab, name), e) | 253 | Sql.Ge => Sql.Lt, |
188 | _ => NONE) | 254 e1, e2) |
189 eqs) | 255 val markQuery = mapFormula (toAtomExps QueryArg) |
190 | Delete (tab, exp) => equalities (fn _ => tab) exp | 256 val markDml = mapFormula (toAtomExps DmlRel) |
191 (* TODO: examine the updated values and not just the way they're filtered. *) | 257 val dnf = normalize negateCmp Dnf (Combo (Cnf, [markQuery fQuery, markDml fDml])) |
192 (* For example, UPDATE foo SET Id = 9001 WHERE Id = 42 should update both the | 258 (* If one of the terms in a conjunction leads to a contradiction, which |
193 Id = 42 and Id = 9001 cache entries. Could also think of it as doing a | 259 is represented by [NONE], drop the entire conjunction. *) |
194 Delete immediately followed by an Insert. *) | 260 val sequenceOption = List.foldr (fn (SOME x, SOME xs) => SOME (x :: xs) | _ => NONE) |
195 | Update (tab, _, exp) => equalities (fn _ => tab) exp | 261 (SOME []) |
262 in | |
263 List.mapPartial (sequenceOption o map eqsOfClass o equivClasses) dnf | |
264 end | |
265 | |
266 val rec sqexpToFormula = | |
267 fn Sql.SqTrue => Combo (Cnf, []) | |
268 | Sql.SqFalse => Combo (Dnf, []) | |
269 | Sql.SqNot e => Negate (sqexpToFormula e) | |
270 | Sql.Binop (Sql.RCmp c, e1, e2) => Atom (c, e1, e2) | |
271 | Sql.Binop (Sql.RLop l, p1, p2) => Combo (case l of Sql.And => Cnf | Sql.Or => Dnf, | |
272 [sqexpToFormula p1, sqexpToFormula p2]) | |
273 (* ASK: any other sqexps that can be props? *) | |
274 | _ => raise Match | |
275 | |
276 val rec queryToFormula = | |
277 fn Sql.Query1 {From = tablePairs, Where = NONE, ...} => Combo (Cnf, []) | |
278 | Sql.Query1 {From = tablePairs, Where = SOME e, ...} => | |
279 let | |
280 fun renameString table = | |
281 case List.find (fn (_, t) => table = t) tablePairs of | |
282 NONE => table | |
283 | SOME (realTable, _) => realTable | |
284 val renameSqexp = | |
285 fn Sql.Field (table, field) => Sql.Field (renameString table, field) | |
286 | e => e | |
287 fun renameAtom (cmp, e1, e2) = (cmp, renameSqexp e1, renameSqexp e2) | |
288 in | |
289 mapFormula renameAtom (sqexpToFormula e) | |
290 end | |
291 | Sql.Union (q1, q2) => Combo (Dnf, [queryToFormula q1, queryToFormula q2]) | |
292 | |
293 val rec dmlToFormula = | |
294 fn Sql.Insert (table, vals) => | |
295 Combo (Cnf, map (fn (field, v) => Atom (Sql.Eq, Sql.Field (table, field), v)) vals) | |
296 | Sql.Delete (_, wher) => sqexpToFormula wher | |
297 (* TODO: refine formula for the vals part, which could take into account the wher part. *) | |
298 | Sql.Update (table, vals, wher) => Combo (Dnf, [dmlToFormula (Sql.Insert (table, vals)), | |
299 dmlToFormula (Sql.Delete (table, wher))]) | |
196 | 300 |
197 val rec tablesQuery = | 301 val rec tablesQuery = |
198 fn Query1 {From = tablePairs, ...} => SS.fromList (map #1 tablePairs) | 302 fn Sql.Query1 {From = tablePairs, ...} => SS.fromList (map #1 tablePairs) |
199 | Union (q1, q2) => SS.union (tablesQuery q1, tablesQuery q2) | 303 | Sql.Union (q1, q2) => SS.union (tablesQuery q1, tablesQuery q2) |
200 | 304 |
201 val tableDml = | 305 val tableDml = |
202 fn Insert (tab, _) => tab | 306 fn Sql.Insert (tab, _) => tab |
203 | Delete (tab, _) => tab | 307 | Sql.Delete (tab, _) => tab |
204 | Update (tab, _, _) => tab | 308 | Sql.Update (tab, _, _) => tab |
205 | 309 |
206 | 310 |
207 (* Program instrumentation. *) | 311 (* Program instrumentation. *) |
208 | 312 |
209 fun stringExp s = (EPrim (Prim.String (Prim.Normal, s)), ErrorMsg.dummySpan) | 313 fun stringExp s = (EPrim (Prim.String (Prim.Normal, s)), ErrorMsg.dummySpan) |
314 | |
210 val stringTyp = (TFfi ("Basis", "string"), ErrorMsg.dummySpan) | 315 val stringTyp = (TFfi ("Basis", "string"), ErrorMsg.dummySpan) |
211 | 316 |
212 val sequence = | 317 val sequence = |
213 fn (exp :: exps) => | 318 fn (exp :: exps) => |
214 let | 319 let |
241 bind = fn (level, MonoUtil.Exp.RelE _) => level + 1 | (level, _) => level} | 346 bind = fn (level, MonoUtil.Exp.RelE _) => level + 1 | (level, _) => level} |
242 bound | 347 bound |
243 | 348 |
244 val incRels = incRelsBound 0 | 349 val incRels = incRelsBound 0 |
245 | 350 |
246 (* Filled in by instrumentQuery during Monoize, used during Sqlcache. *) | 351 (* Filled in by instrumentQuery during [Monoize], used during [Sqlcache]. *) |
247 val urlifiedRel0s : Mono.exp IM.map ref = ref IM.empty | 352 val urlifiedRel0s : Mono.exp IM.map ref = ref IM.empty |
248 | 353 |
249 (* Used by Monoize. *) | 354 (* Used by [Monoize]. *) |
250 val instrumentQuery = | 355 val instrumentQuery = |
251 let | 356 let |
252 val nextQuery = ref 0 | 357 val nextQuery = ref 0 |
253 fun iq (query, urlifiedRel0) = | 358 fun iq (query, urlifiedRel0) = |
254 case query of | 359 case query of |
258 in | 363 in |
259 urlifiedRel0s := IM.insert (!urlifiedRel0s, i, urlifiedRel0); | 364 urlifiedRel0s := IM.insert (!urlifiedRel0s, i, urlifiedRel0); |
260 (ELet (varPrefix ^ Int.toString i, typ, query, | 365 (ELet (varPrefix ^ Int.toString i, typ, query, |
261 (* Uses a dummy FFI call to keep the urlified expression around, which | 366 (* Uses a dummy FFI call to keep the urlified expression around, which |
262 in turn keeps the declarations required for urlification safe from | 367 in turn keeps the declarations required for urlification safe from |
263 MonoShake. The dummy call is removed during Sqlcache. *) | 368 [MonoShake]. The dummy call is removed during [Sqlcache]. *) |
264 (* TODO: thread a Monoize.Fm.t through this module. *) | 369 (* TODO: thread a [Monoize.Fm.t] through this module. *) |
265 (ESeq ((EFfiApp ("Sqlcache", "dummy", [(urlifiedRel0, stringTyp)]), loc), | 370 (ESeq ((EFfiApp ("Sqlcache", |
371 "dummy", | |
372 [(urlifiedRel0, stringTyp)]), | |
373 loc), | |
266 (ERel 0, loc)), | 374 (ERel 0, loc)), |
267 loc)), | 375 loc)), |
268 loc) | 376 loc) |
269 end | 377 end |
270 | _ => raise Match | 378 | _ => raise Match |
271 in | 379 in |
272 iq | 380 iq |
273 end | 381 end |
274 | 382 |
275 fun cacheWrap (query, i, urlifiedRel0, eqs) = | 383 fun cacheWrap (query, i, urlifiedRel0, args) = |
276 case query of | 384 case query of |
277 (EQuery {state = typ, ...}, _) => | 385 (EQuery {state = typ, ...}, _) => |
278 let | 386 let |
279 val () = ffiInfo := {index = i, params = length eqs} :: !ffiInfo | 387 val () = ffiInfo := {index = i, params = length args} :: !ffiInfo |
280 val loc = ErrorMsg.dummySpan | 388 val loc = ErrorMsg.dummySpan |
281 (* We ensure before this step that all arguments aren't effectful. | 389 (* We ensure before this step that all arguments aren't effectful. |
282 by turning them into local variables as needed. *) | 390 by turning them into local variables as needed. *) |
283 val args = map (fn (_, e) => (e, stringTyp)) eqs | 391 val argTyps = map (fn e => (e, stringTyp)) args |
284 val argsInc = map (fn (e, typ) => (incRels 1 e, typ)) args | 392 val argTypsInc = map (fn (e, typ) => (incRels 1 e, typ)) argTyps |
285 val check = ffiAppCache ("check", i, args) | 393 val check = ffiAppCache ("check", i, argTyps) |
286 val store = ffiAppCache ("store", i, (urlifiedRel0, stringTyp) :: argsInc) | 394 val store = ffiAppCache ("store", i, (urlifiedRel0, stringTyp) :: argTypsInc) |
287 val rel0 = (ERel 0, loc) | 395 val rel0 = (ERel 0, loc) |
288 in | 396 in |
289 (ECase (check, | 397 (ECase (check, |
290 [((PNone stringTyp, loc), | 398 [((PNone stringTyp, loc), |
291 (ELet ("q", typ, query, (ESeq (store, rel0), loc)), loc)), | 399 (ELet ("q", typ, query, (ESeq (store, rel0), loc)), loc)), |
313 queryExp' as (EQuery {query = origQueryText, | 421 queryExp' as (EQuery {query = origQueryText, |
314 initial, body, state, tables, exps}, queryLoc), | 422 initial, body, state, tables, exps}, queryLoc), |
315 letBody) => | 423 letBody) => |
316 let | 424 let |
317 val loc = ErrorMsg.dummySpan | 425 val loc = ErrorMsg.dummySpan |
318 val chunks = chunkify origQueryText | 426 val chunks = Sql.chunkify origQueryText |
319 fun strcat (e1, e2) = (EStrcat (e1, e2), loc) | 427 fun strcat (e1, e2) = (EStrcat (e1, e2), loc) |
320 val (newQueryText, newVariables) = | 428 val (newQueryText, newVariables) = |
321 (* Important that this is foldr (to oppose foldl below). *) | 429 (* Important that this is foldr (to oppose foldl below). *) |
322 List.foldr | 430 List.foldr |
323 (fn (chunk, (qText, newVars)) => | 431 (fn (chunk, (qText, newVars)) => |
432 (* Variable bound to the head of newBs will have the lowest index. *) | |
324 case chunk of | 433 case chunk of |
325 Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) | 434 Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) |
326 | Exp (e as (ERel _, _)) => (strcat (e, qText), newVars) | 435 | Sql.Exp e => |
327 | Exp (e as (ENamed _, _)) => (strcat (e, qText), newVars) | |
328 (* Head of newVars has lowest index. *) | |
329 | Exp e => | |
330 let | 436 let |
331 val n = length newVars | 437 val n = length newVars |
332 in | 438 in |
333 (* This is the (n + 1)th new variable, so | 439 (* This is the (n + 1)th new variable, so |
334 there are already n new variables bound, | 440 there are already n new variables bound, |
335 so we increment indices by n. *) | 441 so we increment indices by n. *) |
336 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) | 442 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) |
337 end | 443 end |
338 | String s => (strcat (stringExp s, qText), newVars)) | 444 | Sql.String s => (strcat (stringExp s, qText), newVars)) |
339 (stringExp "", []) | 445 (stringExp "", []) |
340 chunks | 446 chunks |
341 fun wrapLets e' = | 447 fun wrapLets e' = |
342 (* Important that this is foldl (to oppose foldr above). *) | 448 (* Important that this is foldl (to oppose foldr above). *) |
343 List.foldl (fn (v, e') => ELet ("sqlArgz", stringTyp, v, (e', loc))) e' newVariables | 449 List.foldl (fn (v, e') => ELet ("sqlArgz", stringTyp, v, (e', loc))) |
450 e' | |
451 newVariables | |
452 val numArgs = length newVariables | |
344 (* Increment once for each new variable just made. *) | 453 (* Increment once for each new variable just made. *) |
345 val queryExp = incRels (length newVariables) | 454 val queryExp = incRels (length newVariables) |
346 (EQuery {query = newQueryText, | 455 (EQuery {query = newQueryText, |
347 initial = initial, | 456 initial = initial, |
348 body = body, | 457 body = body, |
350 tables = tables, | 459 tables = tables, |
351 exps = exps}, | 460 exps = exps}, |
352 queryLoc) | 461 queryLoc) |
353 val (EQuery {query = queryText, ...}, _) = queryExp | 462 val (EQuery {query = queryText, ...}, _) = queryExp |
354 (* val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty queryText)); *) | 463 (* val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty queryText)); *) |
464 val args = List.tabulate (numArgs, fn n => (ERel n, loc)) | |
355 fun bind x f = Option.mapPartial f x | 465 fun bind x f = Option.mapPartial f x |
356 fun guard b x = if b then x else NONE | 466 fun guard b x = if b then x else NONE |
357 (* DEBUG: set first boolean argument to true to turn on printing. *) | 467 (* DEBUG: set first boolean argument to true to turn on printing. *) |
358 fun safe bound = not o effectful true (effectfulMap file) false bound | 468 fun safe bound = not o effectful true (effectfulMap file) false bound |
359 val attempt = | 469 val attempt = |
360 (* Ziv misses Haskell's do notation.... *) | 470 (* Ziv misses Haskell's do notation.... *) |
361 guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) ( | 471 guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) ( |
362 bind (parse query queryText) (fn queryParsed => | 472 bind (Sql.parse Sql.query queryText) (fn queryParsed => |
363 bind (indexOfName v) (fn i => | 473 bind (indexOfName v) (fn i => |
364 bind (equalitiesQuery queryParsed) (fn eqs => | |
365 bind (IM.find (!urlifiedRel0s, i)) (fn urlifiedRel0 => | 474 bind (IM.find (!urlifiedRel0s, i)) (fn urlifiedRel0 => |
366 SOME (wrapLets (ELet (v, t, | 475 SOME (wrapLets (ELet (v, t, |
367 cacheWrap (queryExp, i, urlifiedRel0, eqs), | 476 cacheWrap (queryExp, i, urlifiedRel0, args), |
368 incRelsBound 1 (length newVariables) letBody)), | 477 incRelsBound 1 (length newVariables) letBody)), |
369 SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, i)) | 478 SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, i)) |
370 queryInfo | 479 queryInfo |
371 (tablesQuery queryParsed))))))) | 480 (tablesQuery queryParsed)))))) |
372 in | 481 in |
373 case attempt of | 482 case attempt of |
374 SOME pair => pair | 483 SOME pair => pair |
375 | NONE => (e', queryInfo) | 484 | NONE => (e', queryInfo) |
376 end | 485 end |
378 | e' => (e', queryInfo) | 487 | e' => (e', queryInfo) |
379 in | 488 in |
380 fileMapfold (fn exp => fn state => doExp state exp) file SIMM.empty | 489 fileMapfold (fn exp => fn state => doExp state exp) file SIMM.empty |
381 end | 490 end |
382 | 491 |
492 fun invalidations (nQueryArgs, query, dml) = | |
493 let | |
494 val loc = ErrorMsg.dummySpan | |
495 val optionToExp = | |
496 fn NONE => (ENone stringTyp, loc) | |
497 | SOME e => (ESome (stringTyp, (e, loc)), loc) | |
498 fun eqsToInvalidation eqs = | |
499 let | |
500 fun inv n = if n < 0 then [] else optionToExp (IM.find (eqs, n)) :: inv (n - 1) | |
501 in | |
502 inv (nQueryArgs - 1) | |
503 end | |
504 in | |
505 map (map eqsToInvalidation) (conflictMaps (queryToFormula query, dmlToFormula dml)) | |
506 end | |
507 | |
383 fun addFlushing (file, queryInfo) = | 508 fun addFlushing (file, queryInfo) = |
384 let | 509 let |
385 val allIndices : int list = SM.foldr (fn (x, acc) => IS.listItems x @ acc) [] queryInfo | 510 val allIndices : int list = SM.foldr (fn (x, acc) => IS.listItems x @ acc) [] queryInfo |
386 fun flushes indices = map (fn i => ffiAppCache' ("flush", i, [])) indices | 511 fun flushes indices = map (fn i => ffiAppCache' ("flush", i, [])) indices |
387 val doExp = | 512 val doExp = |
388 fn dmlExp as EDml (dmlText, _) => | 513 fn dmlExp as EDml (dmlText, _) => |
389 let | 514 let |
390 val indices = | 515 val indices = |
391 case parse dml dmlText of | 516 case Sql.parse Sql.dml dmlText of |
392 SOME dmlParsed => SIMM.findList (queryInfo, tableDml dmlParsed) | 517 SOME dmlParsed => SIMM.findList (queryInfo, tableDml dmlParsed) |
393 | NONE => allIndices | 518 | NONE => allIndices |
394 in | 519 in |
395 sequence (flushes indices @ [dmlExp]) | 520 sequence (flushes indices @ [dmlExp]) |
396 end | 521 end |
406 val () = Sql.sqlcacheMode := false | 531 val () = Sql.sqlcacheMode := false |
407 in | 532 in |
408 file' | 533 file' |
409 end | 534 end |
410 | 535 |
411 | |
412 (* BEGIN OLD | |
413 | |
414 fun intExp (n, loc) = (EPrim (Prim.Int (Int64.fromInt n)), loc) | |
415 fun intTyp loc = (TFfi ("Basis", "int"), loc) | |
416 fun stringExp (s, loc) = (EPrim (Prim.String (Prim.Normal, s)), loc) | |
417 | |
418 fun boolPat (b, loc) = (PCon (Enum, | |
419 PConFfi {mod = "Basis", datatyp = "bool", arg = NONE, | |
420 con = if b then "True" else "False"}, | |
421 NONE), | |
422 loc) | |
423 fun boolTyp loc = (TFfi ("Basis", "int"), loc) | |
424 | |
425 fun ffiAppExp (module, func, index, args, loc) = | |
426 (EFfiApp (module, func ^ Int.toString index, args), loc) | |
427 | |
428 val sequence = | |
429 fn ((exp :: exps), loc) => | |
430 List.foldl (fn (exp, seq) => (ESeq (seq, exp), loc)) exp exps | |
431 | _ => raise Match | |
432 | |
433 fun antiguardUnit (cond, exp, loc) = | |
434 (ECase (cond, | |
435 [(boolPat (false, loc), exp), | |
436 (boolPat (true, loc), (ERecord [], loc))], | |
437 {disc = boolTyp loc, result = (TRecord [], loc)}), | |
438 loc) | |
439 | |
440 fun underAbs f (exp as (exp', loc)) = | |
441 case exp' of | |
442 EAbs (x, y, z, body) => (EAbs (x, y, z, underAbs f body), loc) | |
443 | _ => f exp | |
444 | |
445 | |
446 val rec tablesRead = | |
447 fn Query1 {From = tablePairs, ...} => SS.fromList (map #1 tablePairs) | |
448 | Union (q1, q2) => SS.union (tablesRead q1, tablesRead q2) | |
449 | |
450 val tableWritten = | |
451 fn Insert (tab, _) => tab | |
452 | Delete (tab, _) => tab | |
453 | Update (tab, _, _) => tab | |
454 | |
455 fun tablesInExp' exp' = | |
456 let | |
457 val nothing = {read = SS.empty, written = SS.empty} | |
458 in | |
459 case exp' of | |
460 EQuery {query = e, ...} => | |
461 (case parse query e of | |
462 SOME q => {read = tablesRead q, written = SS.empty} | |
463 | NONE => nothing) | |
464 | EDml (e, _) => | |
465 (case parse dml e of | |
466 SOME q => {read = SS.empty, written = SS.singleton (tableWritten q)} | |
467 | NONE => nothing) | |
468 | _ => nothing | |
469 end | |
470 | |
471 val tablesInExp = | |
472 let | |
473 fun addTables (exp', {read, written}) = | |
474 let | |
475 val {read = r, written = w} = tablesInExp' exp' | |
476 in | |
477 {read = SS.union (r, read), written = SS.union (w, written)} | |
478 end | |
479 in | |
480 MonoUtil.Exp.fold {typ = #2, exp = addTables} | |
481 {read = SS.empty, written = SS.empty} | |
482 end | |
483 | |
484 fun addCacheCheck (index, exp) = | |
485 let | |
486 fun f (body as (_, loc)) = | |
487 let | |
488 val check = ffiAppExp ("Cache", "check", index, loc) | |
489 val store = ffiAppExp ("Cache", "store", index, loc) | |
490 in | |
491 antiguardUnit (check, sequence ([body, store], loc), loc) | |
492 end | |
493 in | |
494 underAbs f exp | |
495 end | |
496 | |
497 fun addCacheFlush (exp, tablesToIndices) = | |
498 let | |
499 fun addIndices (table, indices) = IS.union (indices, SIMM.find (tablesToIndices, table)) | |
500 fun f (body as (_, loc)) = | |
501 let | |
502 fun mapFfi func = List.map (fn i => ffiAppExp ("Cache", func, i, loc)) | |
503 val flushes = | |
504 IS.listItems (SS.foldr addIndices IS.empty (#written (tablesInExp body))) | |
505 in | |
506 sequence (mapFfi "flush" flushes @ [body] @ mapFfi "ready" flushes, loc) | |
507 end | |
508 in | |
509 underAbs f exp | |
510 end | |
511 | |
512 val handlerIndices = | |
513 let | |
514 val isUnit = | |
515 fn (TRecord [], _) => true | |
516 | _ => false | |
517 fun maybeAdd (d, soFar as {readers, writers}) = | |
518 case d of | |
519 DExport (Link ReadOnly, _, name, typs, typ, _) => | |
520 if List.all isUnit (typ::typs) | |
521 then {readers = IS.add (readers, name), writers = writers} | |
522 else soFar | |
523 | DExport (_, _, name, _, _, _) => (* Not read only. *) | |
524 {readers = readers, writers = IS.add (writers, name)} | |
525 | _ => soFar | |
526 in | |
527 MonoUtil.File.fold {typ = #2, exp = #2, decl = maybeAdd} | |
528 {readers = IS.empty, writers = IS.empty} | |
529 end | |
530 | |
531 fun fileFoldMapiSelected f init (file, indices) = | |
532 let | |
533 fun doExp (original as ((a, index, b, exp, c), state)) = | |
534 if IS.member (indices, index) | |
535 then let val (newExp, newState) = f (index, exp, state) | |
536 in ((a, index, b, newExp, c), newState) end | |
537 else original | |
538 fun doDecl decl state = | |
539 let | |
540 val result = | |
541 case decl of | |
542 DVal x => | |
543 let val (y, newState) = doExp (x, state) | |
544 in (DVal y, newState) end | |
545 | DValRec xs => | |
546 let val (ys, newState) = ListUtil.foldlMap doExp state xs | |
547 in (DValRec ys, newState) end | |
548 | _ => (decl, state) | |
549 in | |
550 Search.Continue result | |
551 end | |
552 fun nada x y = Search.Continue (x, y) | |
553 in | |
554 case MonoUtil.File.mapfold {typ = nada, exp = nada, decl = doDecl} file init of | |
555 Search.Continue x => x | |
556 | _ => raise Match (* Should never happen. *) | |
557 end | |
558 | |
559 fun fileMapSelected f = #1 o fileFoldMapiSelected (fn (_, x, _) => (f x, ())) () | |
560 | |
561 val addCacheChecking = | |
562 let | |
563 fun f (index, exp, tablesToIndices) = | |
564 (addCacheCheck (index, exp), | |
565 SS.foldr (fn (table, tsToIs) => SIMM.insert (tsToIs, table, index)) | |
566 tablesToIndices | |
567 (#read (tablesInExp exp))) | |
568 in | |
569 fileFoldMapiSelected f (SM.empty) | |
570 end | |
571 | |
572 fun addCacheFlushing (file, tablesToIndices, writers) = | |
573 fileMapSelected (fn exp => addCacheFlush (exp, tablesToIndices)) (file, writers) | |
574 | |
575 fun go file = | |
576 let | |
577 val {readers, writers} = handlerIndices file | |
578 val (fileWithChecks, tablesToIndices) = addCacheChecking (file, readers) | |
579 in | |
580 ffiIndices := IS.listItems readers; | |
581 addCacheFlushing (fileWithChecks, tablesToIndices, writers) | |
582 end | |
583 | |
584 END OLD *) | |
585 | |
586 end | 536 end |