ziv@2213: structure Sqlcache (* :> SQLCACHE *) = struct ziv@2209: ziv@2209: open Mono ziv@2209: ziv@2209: structure IS = IntBinarySet ziv@2209: structure IM = IntBinaryMap ziv@2213: structure SK = struct type ord_key = string val compare = String.compare end ziv@2213: structure SS = BinarySetFn(SK) ziv@2213: structure SM = BinaryMapFn(SK) ziv@2213: structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS) ziv@2209: ziv@2216: (* Filled in by [cacheWrap] during [Sqlcache]. *) ziv@2213: val ffiInfo : {index : int, params : int} list ref = ref [] ziv@2209: ziv@2213: fun getFfiInfo () = !ffiInfo ziv@2213: ziv@2215: (* Some FFIs have writing as their only effect, which the caching records. *) ziv@2215: val ffiEffectful = ziv@2216: (* TODO: have this less hard-coded. *) ziv@2215: let ziv@2215: val fs = SS.fromList ["htmlifyInt_w", ziv@2215: "htmlifyFloat_w", ziv@2215: "htmlifyString_w", ziv@2215: "htmlifyBool_w", ziv@2215: "htmlifyTime_w", ziv@2215: "attrifyInt_w", ziv@2215: "attrifyFloat_w", ziv@2215: "attrifyString_w", ziv@2215: "attrifyChar_w", ziv@2215: "urlifyInt_w", ziv@2215: "urlifyFloat_w", ziv@2215: "urlifyString_w", ziv@2215: "urlifyBool_w", ziv@2215: "urlifyChannel_w"] ziv@2215: in ziv@2215: fn (m, f) => Settings.isEffectful (m, f) ziv@2215: andalso not (m = "Basis" andalso SS.member (fs, f)) ziv@2215: end ziv@2215: ziv@2215: ziv@2215: (* Effect analysis. *) ziv@2215: ziv@2216: (* Makes an exception for [EWrite] (which is recorded when caching). *) ziv@2215: fun effectful doPrint (effs : IS.set) (inFunction : bool) (bound : int) : Mono.exp -> bool = ziv@2215: (* If result is true, expression is definitely effectful. If result is ziv@2215: false, then expression is definitely not effectful if effs is fully ziv@2215: populated. The intended pattern is to use this a number of times equal ziv@2215: to the number of declarations in a file, Bellman-Ford style. *) ziv@2215: (* TODO: make incrementing of bound less janky, probably by using MonoUtil ziv@2215: instead of all this. *) ziv@2215: let ziv@2215: (* DEBUG: remove printing when done. *) ziv@2215: fun tru msg = if doPrint then (print (msg ^ "\n"); true) else true ziv@2215: val rec eff' = ziv@2215: (* ASK: is there a better way? *) ziv@2215: fn EPrim _ => false ziv@2215: (* We don't know if local functions have effects when applied. *) ziv@2215: | ERel idx => if inFunction andalso idx >= bound ziv@2215: then tru ("rel" ^ Int.toString idx) else false ziv@2215: | ENamed name => if IS.member (effs, name) then tru "named" else false ziv@2215: | ECon (_, _, NONE) => false ziv@2215: | ECon (_, _, SOME e) => eff e ziv@2215: | ENone _ => false ziv@2215: | ESome (_, e) => eff e ziv@2215: | EFfi (m, f) => if ffiEffectful (m, f) then tru "ffi" else false ziv@2215: | EFfiApp (m, f, _) => if ffiEffectful (m, f) then tru "ffiapp" else false ziv@2215: (* ASK: we're calling functions effectful if they have effects when ziv@2215: applied or if the function expressions themselves have effects. ziv@2215: Is that okay? *) ziv@2215: (* This is okay because the values we ultimately care about aren't ziv@2215: functions, and this is a conservative approximation, anyway. *) ziv@2215: | EApp (eFun, eArg) => effectful doPrint effs true bound eFun orelse eff eArg ziv@2215: | EAbs (_, _, _, e) => effectful doPrint effs inFunction (bound+1) e ziv@2215: | EUnop (_, e) => eff e ziv@2215: | EBinop (_, _, e1, e2) => eff e1 orelse eff e2 ziv@2215: | ERecord xs => List.exists (fn (_, e, _) => eff e) xs ziv@2215: | EField (e, _) => eff e ziv@2215: (* If any case could be effectful, consider it effectful. *) ziv@2215: | ECase (e, xs, _) => eff e orelse List.exists (fn (_, e) => eff e) xs ziv@2215: | EStrcat (e1, e2) => eff e1 orelse eff e2 ziv@2215: (* ASK: how should we treat these three? *) ziv@2215: | EError _ => tru "error" ziv@2215: | EReturnBlob _ => tru "blob" ziv@2215: | ERedirect _ => tru "redirect" ziv@2215: (* EWrite is a special exception because we record writes when caching. *) ziv@2215: | EWrite _ => false ziv@2215: | ESeq (e1, e2) => eff e1 orelse eff e2 ziv@2215: (* TODO: keep context of which local variables aren't effectful? Only ziv@2215: makes a difference for function expressions, though. *) ziv@2215: | ELet (_, _, eBind, eBody) => eff eBind orelse ziv@2215: effectful doPrint effs inFunction (bound+1) eBody ziv@2215: | EClosure (_, es) => List.exists eff es ziv@2215: (* TODO: deal with EQuery. *) ziv@2215: | EQuery _ => tru "query" ziv@2215: | EDml _ => tru "dml" ziv@2215: | ENextval _ => tru "nextval" ziv@2215: | ESetval _ => tru "setval" ziv@2215: | EUnurlify (e, _, _) => eff e ziv@2215: (* ASK: how should we treat this? *) ziv@2215: | EJavaScript _ => tru "javascript" ziv@2215: (* ASK: these are all effectful, right? *) ziv@2215: | ESignalReturn _ => tru "signalreturn" ziv@2215: | ESignalBind _ => tru "signalbind" ziv@2215: | ESignalSource _ => tru "signalsource" ziv@2215: | EServerCall _ => tru "servercall" ziv@2215: | ERecv _ => tru "recv" ziv@2215: | ESleep _ => tru "sleep" ziv@2215: | ESpawn _ => tru "spawn" ziv@2215: and eff = fn (e', _) => eff' e' ziv@2215: in ziv@2215: eff ziv@2215: end ziv@2215: ziv@2215: (* TODO: test this. *) ziv@2215: val effectfulMap = ziv@2215: let ziv@2215: fun doVal ((_, name, _, e, _), effMap) = ziv@2215: if effectful false effMap false 0 e ziv@2215: then IS.add (effMap, name) ziv@2215: else effMap ziv@2215: val doDecl = ziv@2215: fn (DVal v, effMap) => doVal (v, effMap) ziv@2215: (* Repeat the list of declarations a number of times equal to its size. *) ziv@2215: | (DValRec vs, effMap) => ziv@2215: List.foldl doVal effMap (List.concat (List.map (fn _ => vs) vs)) ziv@2215: (* ASK: any other cases? *) ziv@2215: | (_, effMap) => effMap ziv@2215: in ziv@2215: MonoUtil.File.fold {typ = #2, exp = #2, decl = doDecl} IS.empty ziv@2215: end ziv@2215: ziv@2215: ziv@2216: (* Boolean formula normalization. *) ziv@2216: ziv@2216: datatype normalForm = Cnf | Dnf ziv@2216: ziv@2216: datatype 'atom formula = ziv@2216: Atom of 'atom ziv@2216: | Negate of 'atom formula ziv@2216: | Combo of normalForm * 'atom formula list ziv@2216: ziv@2216: val flipNf = fn Cnf => Dnf | Dnf => Cnf ziv@2216: ziv@2216: fun bind xs f = List.concat (map f xs) ziv@2216: ziv@2216: val rec cartesianProduct : 'a list list -> 'a list list = ziv@2216: fn [] => [[]] ziv@2216: | (xs :: xss) => bind (cartesianProduct xss) ziv@2216: (fn ys => bind xs (fn x => [x :: ys])) ziv@2216: ziv@2216: fun normalize (negate : 'atom -> 'atom) (norm : normalForm) = ziv@2216: fn Atom x => [[x]] ziv@2216: | Negate f => map (map negate) (normalize negate (flipNf norm) f) ziv@2216: | Combo (n, fs) => ziv@2216: let ziv@2216: val fss = bind fs (normalize negate n) ziv@2216: in ziv@2216: if n = norm then fss else cartesianProduct fss ziv@2216: end ziv@2216: ziv@2216: fun mapFormula mf = ziv@2216: fn Atom x => Atom (mf x) ziv@2216: | Negate f => Negate (mapFormula mf f) ziv@2216: | Combo (n, fs) => Combo (n, map (mapFormula mf) fs) ziv@2216: ziv@2216: ziv@2215: (* SQL analysis. *) ziv@2213: ziv@2216: val rec chooseTwos : 'a list -> ('a * 'a) list = ziv@2216: fn [] => [] ziv@2216: | x :: ys => map (fn y => (x, y)) ys @ chooseTwos ys ziv@2213: ziv@2216: datatype atomExp = ziv@2216: QueryArg of int ziv@2216: | DmlRel of int ziv@2216: | Prim of Prim.t ziv@2216: | Field of string * string ziv@2216: ziv@2216: structure AtomExpKey : ORD_KEY = struct ziv@2216: ziv@2216: type ord_key = atomExp ziv@2216: ziv@2216: val compare = ziv@2216: fn (QueryArg n1, QueryArg n2) => Int.compare (n1, n2) ziv@2216: | (QueryArg _, _) => LESS ziv@2216: | (_, QueryArg _) => GREATER ziv@2216: | (DmlRel n1, DmlRel n2) => Int.compare (n1, n2) ziv@2216: | (DmlRel _, _) => LESS ziv@2216: | (_, DmlRel _) => GREATER ziv@2216: | (Prim p1, Prim p2) => Prim.compare (p1, p2) ziv@2216: | (Prim _, _) => LESS ziv@2216: | (_, Prim _) => GREATER ziv@2216: | (Field (t1, f1), Field (t2, f2)) => String.compare (t1 ^ "." ^ f1, t2 ^ "." ^ f2) ziv@2216: ziv@2216: end ziv@2216: ziv@2216: structure UF = UnionFindFn(AtomExpKey) ziv@2216: ziv@2216: fun conflictMaps (fQuery : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula, ziv@2216: fDml : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula) = ziv@2213: let ziv@2216: val toKnownEquality = ziv@2216: (* [NONE] here means unkown. Anything that isn't a comparison between ziv@2216: two knowns shouldn't be used, and simply dropping unused terms is ziv@2216: okay in disjunctive normal form. *) ziv@2216: fn (Sql.Eq, SOME e1, SOME e2) => SOME (e1, e2) ziv@2216: | _ => NONE ziv@2216: val equivClasses : (Sql.cmp * atomExp option * atomExp option) list -> atomExp list list = ziv@2216: UF.classes ziv@2216: o List.foldl UF.union' UF.empty ziv@2216: o List.mapPartial toKnownEquality ziv@2216: fun addToEqs (eqs, n, e) = ziv@2216: case IM.find (eqs, n) of ziv@2216: (* Comparing to a constant seems better? *) ziv@2216: SOME (EPrim _) => eqs ziv@2216: | _ => IM.insert (eqs, n, e) ziv@2216: val accumulateEqs = ziv@2216: (* [NONE] means we have a contradiction. *) ziv@2216: fn (_, NONE) => NONE ziv@2216: | ((Prim p1, Prim p2), eqso) => ziv@2216: (case Prim.compare (p1, p2) of ziv@2216: EQUAL => eqso ziv@2213: | _ => NONE) ziv@2216: | ((QueryArg n, Prim p), SOME eqs) => SOME (addToEqs (eqs, n, EPrim p)) ziv@2216: | ((QueryArg n, DmlRel r), SOME eqs) => SOME (addToEqs (eqs, n, ERel r)) ziv@2216: | ((Prim p, QueryArg n), SOME eqs) => SOME (addToEqs (eqs, n, EPrim p)) ziv@2216: | ((DmlRel r, QueryArg n), SOME eqs) => SOME (addToEqs (eqs, n, ERel r)) ziv@2216: (* TODO: deal with equalities involving just [DmlRel]s and [Prim]s. *) ziv@2216: | (_, eqso) => eqso ziv@2216: val eqsOfClass : atomExp list -> Mono.exp' IM.map option = ziv@2216: List.foldl accumulateEqs (SOME IM.empty) ziv@2216: o chooseTwos ziv@2216: fun toAtomExps rel (cmp, e1, e2) = ziv@2216: let ziv@2216: val qa = ziv@2216: (* Here [NONE] means unkown. *) ziv@2216: fn Sql.SqConst p => SOME (Prim p) ziv@2216: | Sql.Field tf => SOME (Field tf) ziv@2216: | Sql.Inj (EPrim p, _) => SOME (Prim p) ziv@2216: | Sql.Inj (ERel n, _) => SOME (rel n) ziv@2216: (* We can't deal with anything else. *) ziv@2216: | _ => NONE ziv@2216: in ziv@2216: (cmp, qa e1, qa e2) ziv@2216: end ziv@2216: fun negateCmp (cmp, e1, e2) = ziv@2216: (case cmp of ziv@2216: Sql.Eq => Sql.Ne ziv@2216: | Sql.Ne => Sql.Eq ziv@2216: | Sql.Lt => Sql.Ge ziv@2216: | Sql.Le => Sql.Gt ziv@2216: | Sql.Gt => Sql.Le ziv@2216: | Sql.Ge => Sql.Lt, ziv@2216: e1, e2) ziv@2216: val markQuery = mapFormula (toAtomExps QueryArg) ziv@2216: val markDml = mapFormula (toAtomExps DmlRel) ziv@2216: val dnf = normalize negateCmp Dnf (Combo (Cnf, [markQuery fQuery, markDml fDml])) ziv@2216: (* If one of the terms in a conjunction leads to a contradiction, which ziv@2216: is represented by [NONE], drop the entire conjunction. *) ziv@2216: val sequenceOption = List.foldr (fn (SOME x, SOME xs) => SOME (x :: xs) | _ => NONE) ziv@2216: (SOME []) ziv@2213: in ziv@2216: List.mapPartial (sequenceOption o map eqsOfClass o equivClasses) dnf ziv@2213: end ziv@2213: ziv@2216: val rec sqexpToFormula = ziv@2216: fn Sql.SqTrue => Combo (Cnf, []) ziv@2216: | Sql.SqFalse => Combo (Dnf, []) ziv@2216: | Sql.SqNot e => Negate (sqexpToFormula e) ziv@2216: | Sql.Binop (Sql.RCmp c, e1, e2) => Atom (c, e1, e2) ziv@2216: | Sql.Binop (Sql.RLop l, p1, p2) => Combo (case l of Sql.And => Cnf | Sql.Or => Dnf, ziv@2216: [sqexpToFormula p1, sqexpToFormula p2]) ziv@2216: (* ASK: any other sqexps that can be props? *) ziv@2216: | _ => raise Match ziv@2213: ziv@2216: val rec queryToFormula = ziv@2216: fn Sql.Query1 {From = tablePairs, Where = NONE, ...} => Combo (Cnf, []) ziv@2216: | Sql.Query1 {From = tablePairs, Where = SOME e, ...} => ziv@2216: let ziv@2216: fun renameString table = ziv@2216: case List.find (fn (_, t) => table = t) tablePairs of ziv@2216: NONE => table ziv@2216: | SOME (realTable, _) => realTable ziv@2216: val renameSqexp = ziv@2216: fn Sql.Field (table, field) => Sql.Field (renameString table, field) ziv@2216: | e => e ziv@2216: fun renameAtom (cmp, e1, e2) = (cmp, renameSqexp e1, renameSqexp e2) ziv@2216: in ziv@2216: mapFormula renameAtom (sqexpToFormula e) ziv@2216: end ziv@2216: | Sql.Union (q1, q2) => Combo (Dnf, [queryToFormula q1, queryToFormula q2]) ziv@2216: ziv@2216: val rec dmlToFormula = ziv@2216: fn Sql.Insert (table, vals) => ziv@2216: Combo (Cnf, map (fn (field, v) => Atom (Sql.Eq, Sql.Field (table, field), v)) vals) ziv@2216: | Sql.Delete (_, wher) => sqexpToFormula wher ziv@2216: (* TODO: refine formula for the vals part, which could take into account the wher part. *) ziv@2216: | Sql.Update (table, vals, wher) => Combo (Dnf, [dmlToFormula (Sql.Insert (table, vals)), ziv@2216: dmlToFormula (Sql.Delete (table, wher))]) ziv@2213: ziv@2213: val rec tablesQuery = ziv@2216: fn Sql.Query1 {From = tablePairs, ...} => SS.fromList (map #1 tablePairs) ziv@2216: | Sql.Union (q1, q2) => SS.union (tablesQuery q1, tablesQuery q2) ziv@2213: ziv@2213: val tableDml = ziv@2216: fn Sql.Insert (tab, _) => tab ziv@2216: | Sql.Delete (tab, _) => tab ziv@2216: | Sql.Update (tab, _, _) => tab ziv@2213: ziv@2213: ziv@2213: (* Program instrumentation. *) ziv@2213: ziv@2215: fun stringExp s = (EPrim (Prim.String (Prim.Normal, s)), ErrorMsg.dummySpan) ziv@2216: ziv@2213: val stringTyp = (TFfi ("Basis", "string"), ErrorMsg.dummySpan) ziv@2213: ziv@2213: val sequence = ziv@2213: fn (exp :: exps) => ziv@2213: let ziv@2213: val loc = ErrorMsg.dummySpan ziv@2213: in ziv@2213: List.foldl (fn (e', seq) => ESeq ((seq, loc), (e', loc))) exp exps ziv@2213: end ziv@2213: | _ => raise Match ziv@2213: ziv@2213: fun ffiAppCache' (func, index, args) : Mono.exp' = ziv@2213: EFfiApp ("Sqlcache", func ^ Int.toString index, args) ziv@2213: ziv@2215: fun ffiAppCache (func, index, args) : Mono.exp = ziv@2213: (ffiAppCache' (func, index, args), ErrorMsg.dummySpan) ziv@2213: ziv@2213: val varPrefix = "queryResult" ziv@2213: ziv@2213: fun indexOfName varName = ziv@2213: if String.isPrefix varPrefix varName ziv@2213: then Int.fromString (String.extract (varName, String.size varPrefix, NONE)) ziv@2213: else NONE ziv@2213: ziv@2215: (* Always increments negative indices because that's what we need later. *) ziv@2215: fun incRelsBound bound inc = ziv@2215: MonoUtil.Exp.mapB ziv@2215: {typ = fn x => x, ziv@2215: exp = fn level => ziv@2215: (fn ERel n => ERel (if n >= level orelse n < 0 then n + inc else n) ziv@2215: | x => x), ziv@2215: bind = fn (level, MonoUtil.Exp.RelE _) => level + 1 | (level, _) => level} ziv@2215: bound ziv@2215: ziv@2215: val incRels = incRelsBound 0 ziv@2213: ziv@2216: (* Filled in by instrumentQuery during [Monoize], used during [Sqlcache]. *) ziv@2213: val urlifiedRel0s : Mono.exp IM.map ref = ref IM.empty ziv@2213: ziv@2216: (* Used by [Monoize]. *) ziv@2213: val instrumentQuery = ziv@2213: let ziv@2213: val nextQuery = ref 0 ziv@2213: fun iq (query, urlifiedRel0) = ziv@2213: case query of ziv@2213: (EQuery {state = typ, ...}, loc) => ziv@2213: let ziv@2213: val i = !nextQuery before nextQuery := !nextQuery + 1 ziv@2213: in ziv@2213: urlifiedRel0s := IM.insert (!urlifiedRel0s, i, urlifiedRel0); ziv@2213: (ELet (varPrefix ^ Int.toString i, typ, query, ziv@2213: (* Uses a dummy FFI call to keep the urlified expression around, which ziv@2213: in turn keeps the declarations required for urlification safe from ziv@2216: [MonoShake]. The dummy call is removed during [Sqlcache]. *) ziv@2216: (* TODO: thread a [Monoize.Fm.t] through this module. *) ziv@2216: (ESeq ((EFfiApp ("Sqlcache", ziv@2216: "dummy", ziv@2216: [(urlifiedRel0, stringTyp)]), ziv@2216: loc), ziv@2213: (ERel 0, loc)), ziv@2213: loc)), ziv@2213: loc) ziv@2213: end ziv@2213: | _ => raise Match ziv@2213: in ziv@2213: iq ziv@2213: end ziv@2213: ziv@2216: fun cacheWrap (query, i, urlifiedRel0, args) = ziv@2213: case query of ziv@2213: (EQuery {state = typ, ...}, _) => ziv@2213: let ziv@2216: val () = ffiInfo := {index = i, params = length args} :: !ffiInfo ziv@2213: val loc = ErrorMsg.dummySpan ziv@2215: (* We ensure before this step that all arguments aren't effectful. ziv@2215: by turning them into local variables as needed. *) ziv@2216: val argTyps = map (fn e => (e, stringTyp)) args ziv@2216: val argTypsInc = map (fn (e, typ) => (incRels 1 e, typ)) argTyps ziv@2216: val check = ffiAppCache ("check", i, argTyps) ziv@2216: val store = ffiAppCache ("store", i, (urlifiedRel0, stringTyp) :: argTypsInc) ziv@2215: val rel0 = (ERel 0, loc) ziv@2213: in ziv@2215: (ECase (check, ziv@2213: [((PNone stringTyp, loc), ziv@2215: (ELet ("q", typ, query, (ESeq (store, rel0), loc)), loc)), ziv@2213: ((PSome (stringTyp, (PVar ("hit", stringTyp), loc)), loc), ziv@2215: (* Boolean is false because we're not unurlifying from a cookie. *) ziv@2215: (EUnurlify (rel0, typ, false), loc))], ziv@2213: {disc = stringTyp, result = typ}), ziv@2213: loc) ziv@2213: end ziv@2213: | _ => raise Match ziv@2213: ziv@2213: fun fileMapfold doExp file start = ziv@2213: case MonoUtil.File.mapfold {typ = Search.return2, ziv@2213: exp = fn x => (fn s => Search.Continue (doExp x s)), ziv@2213: decl = Search.return2} file start of ziv@2213: Search.Continue x => x ziv@2213: | Search.Return _ => raise Match ziv@2213: ziv@2213: fun fileMap doExp file = #1 (fileMapfold (fn x => fn _ => (doExp x, ())) file ()) ziv@2213: ziv@2215: fun addChecking file = ziv@2213: let ziv@2213: fun doExp queryInfo = ziv@2215: fn e' as ELet (v, t, ziv@2215: queryExp' as (EQuery {query = origQueryText, ziv@2215: initial, body, state, tables, exps}, queryLoc), ziv@2215: letBody) => ziv@2213: let ziv@2215: val loc = ErrorMsg.dummySpan ziv@2216: val chunks = Sql.chunkify origQueryText ziv@2215: fun strcat (e1, e2) = (EStrcat (e1, e2), loc) ziv@2215: val (newQueryText, newVariables) = ziv@2215: (* Important that this is foldr (to oppose foldl below). *) ziv@2215: List.foldr ziv@2215: (fn (chunk, (qText, newVars)) => ziv@2216: (* Variable bound to the head of newBs will have the lowest index. *) ziv@2215: case chunk of ziv@2216: Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) ziv@2216: | Sql.Exp e => ziv@2215: let ziv@2215: val n = length newVars ziv@2215: in ziv@2215: (* This is the (n + 1)th new variable, so ziv@2215: there are already n new variables bound, ziv@2215: so we increment indices by n. *) ziv@2215: (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) ziv@2215: end ziv@2216: | Sql.String s => (strcat (stringExp s, qText), newVars)) ziv@2215: (stringExp "", []) ziv@2215: chunks ziv@2215: fun wrapLets e' = ziv@2215: (* Important that this is foldl (to oppose foldr above). *) ziv@2216: List.foldl (fn (v, e') => ELet ("sqlArgz", stringTyp, v, (e', loc))) ziv@2216: e' ziv@2216: newVariables ziv@2216: val numArgs = length newVariables ziv@2215: (* Increment once for each new variable just made. *) ziv@2215: val queryExp = incRels (length newVariables) ziv@2215: (EQuery {query = newQueryText, ziv@2215: initial = initial, ziv@2215: body = body, ziv@2215: state = state, ziv@2215: tables = tables, ziv@2215: exps = exps}, ziv@2215: queryLoc) ziv@2215: val (EQuery {query = queryText, ...}, _) = queryExp ziv@2215: (* val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty queryText)); *) ziv@2216: val args = List.tabulate (numArgs, fn n => (ERel n, loc)) ziv@2213: fun bind x f = Option.mapPartial f x ziv@2215: fun guard b x = if b then x else NONE ziv@2215: (* DEBUG: set first boolean argument to true to turn on printing. *) ziv@2215: fun safe bound = not o effectful true (effectfulMap file) false bound ziv@2213: val attempt = ziv@2213: (* Ziv misses Haskell's do notation.... *) ziv@2215: guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) ( ziv@2216: bind (Sql.parse Sql.query queryText) (fn queryParsed => ziv@2213: bind (indexOfName v) (fn i => ziv@2213: bind (IM.find (!urlifiedRel0s, i)) (fn urlifiedRel0 => ziv@2215: SOME (wrapLets (ELet (v, t, ziv@2216: cacheWrap (queryExp, i, urlifiedRel0, args), ziv@2215: incRelsBound 1 (length newVariables) letBody)), ziv@2213: SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, i)) ziv@2213: queryInfo ziv@2216: (tablesQuery queryParsed)))))) ziv@2213: in ziv@2213: case attempt of ziv@2213: SOME pair => pair ziv@2213: | NONE => (e', queryInfo) ziv@2213: end ziv@2213: | ESeq ((EFfiApp ("Sqlcache", "dummy", _), _), (e', _)) => (e', queryInfo) ziv@2213: | e' => (e', queryInfo) ziv@2213: in ziv@2215: fileMapfold (fn exp => fn state => doExp state exp) file SIMM.empty ziv@2213: end ziv@2213: ziv@2216: fun invalidations (nQueryArgs, query, dml) = ziv@2216: let ziv@2216: val loc = ErrorMsg.dummySpan ziv@2216: val optionToExp = ziv@2216: fn NONE => (ENone stringTyp, loc) ziv@2216: | SOME e => (ESome (stringTyp, (e, loc)), loc) ziv@2216: fun eqsToInvalidation eqs = ziv@2216: let ziv@2216: fun inv n = if n < 0 then [] else optionToExp (IM.find (eqs, n)) :: inv (n - 1) ziv@2216: in ziv@2216: inv (nQueryArgs - 1) ziv@2216: end ziv@2216: in ziv@2216: map (map eqsToInvalidation) (conflictMaps (queryToFormula query, dmlToFormula dml)) ziv@2216: end ziv@2216: ziv@2213: fun addFlushing (file, queryInfo) = ziv@2213: let ziv@2213: val allIndices : int list = SM.foldr (fn (x, acc) => IS.listItems x @ acc) [] queryInfo ziv@2213: fun flushes indices = map (fn i => ffiAppCache' ("flush", i, [])) indices ziv@2213: val doExp = ziv@2213: fn dmlExp as EDml (dmlText, _) => ziv@2213: let ziv@2213: val indices = ziv@2216: case Sql.parse Sql.dml dmlText of ziv@2213: SOME dmlParsed => SIMM.findList (queryInfo, tableDml dmlParsed) ziv@2213: | NONE => allIndices ziv@2213: in ziv@2213: sequence (flushes indices @ [dmlExp]) ziv@2213: end ziv@2213: | e' => e' ziv@2213: in ziv@2213: fileMap doExp file ziv@2213: end ziv@2213: ziv@2213: fun go file = ziv@2213: let ziv@2213: val () = Sql.sqlcacheMode := true ziv@2215: val file' = addFlushing (addChecking file) ziv@2215: val () = Sql.sqlcacheMode := false ziv@2213: in ziv@2215: file' ziv@2213: end ziv@2213: ziv@2209: end