Mercurial > urweb
comparison src/sqlcache.sml @ 2213:365727ff68f4
Complete overhaul: cache queries based on immediate query result, not eventual HTML output.
author | Ziv Scully <ziv@mit.edu> |
---|---|
date | Tue, 14 Oct 2014 18:05:09 -0400 |
parents | 388ba4dc7c96 |
children | 639e62ca2530 |
comparison
equal
deleted
inserted
replaced
2212:388ba4dc7c96 | 2213:365727ff68f4 |
---|---|
1 structure Sqlcache :> SQLCACHE = struct | 1 structure Sqlcache (* :> SQLCACHE *) = struct |
2 | 2 |
3 open Sql | 3 open Sql |
4 open Mono | 4 open Mono |
5 | 5 |
6 structure IS = IntBinarySet | 6 structure IS = IntBinarySet |
7 structure IM = IntBinaryMap | 7 structure IM = IntBinaryMap |
8 structure StringKey = struct type ord_key = string val compare = String.compare end | 8 structure SK = struct type ord_key = string val compare = String.compare end |
9 structure SS = BinarySetFn (StringKey) | 9 structure SS = BinarySetFn(SK) |
10 structure SM = BinaryMapFn (StringKey) | 10 structure SM = BinaryMapFn(SK) |
11 structure SIMM = MultimapFn (structure KeyMap = SM structure ValSet = IS) | 11 structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS) |
12 | 12 |
13 val ffiIndices : int list ref = ref [] | 13 (* Filled in by cacheWrap during Sqlcache. *) |
14 | 14 val ffiInfo : {index : int, params : int} list ref = ref [] |
15 (* Expression construction utilities. *) | 15 |
16 fun getFfiInfo () = !ffiInfo | |
17 | |
18 (* Program analysis. *) | |
19 | |
20 val useInjIfPossible = | |
21 fn SqConst prim => Inj (EPrim (Prim.String (Prim.Normal, Prim.toString prim)), ErrorMsg.dummySpan) | |
22 | sqexp => sqexp | |
23 | |
24 fun equalities (canonicalTable : string -> string) : | |
25 sqexp -> ((string * string) * Mono.exp) list option = | |
26 let | |
27 val rec eqs = | |
28 fn Binop (Exps f, e1, e2) => | |
29 (* TODO: use a custom datatype in Exps instead of a function. *) | |
30 (case f (Var 1, Var 2) of | |
31 Reln (Eq, [Var 1, Var 2]) => | |
32 let | |
33 val (e1', e2') = (useInjIfPossible e1, useInjIfPossible e2) | |
34 in | |
35 case (e1', e2') of | |
36 (Field (t, f), Inj i) => SOME [((canonicalTable t, f), i)] | |
37 | (Inj i, Field (t, f)) => SOME [((canonicalTable t, f), i)] | |
38 | _ => NONE | |
39 end | |
40 | _ => NONE) | |
41 | Binop (Props f, e1, e2) => | |
42 (* TODO: use a custom datatype in Props instead of a function. *) | |
43 (case f (True, False) of | |
44 And (True, False) => | |
45 (case (eqs e1, eqs e2) of | |
46 (SOME eqs1, SOME eqs2) => SOME (eqs1 @ eqs2) | |
47 | _ => NONE) | |
48 | _ => NONE) | |
49 | _ => NONE | |
50 in | |
51 eqs | |
52 end | |
53 | |
54 val equalitiesQuery = | |
55 fn Query1 {From = tablePairs, Where = SOME exp, ...} => | |
56 equalities | |
57 (* If we have [SELECT ... FROM T AS T' ...], use T, not T'. *) | |
58 (fn t => | |
59 case List.find (fn (_, tAs) => t = tAs) tablePairs of | |
60 NONE => t | |
61 | SOME (tOrig, _) => tOrig) | |
62 exp | |
63 | Query1 {Where = NONE, ...} => SOME [] | |
64 | _ => NONE | |
65 | |
66 val equalitiesDml = | |
67 fn Insert (tab, eqs) => SOME (List.mapPartial | |
68 (fn (name, sqexp) => | |
69 case useInjIfPossible sqexp of | |
70 Inj e => SOME ((tab, name), e) | |
71 | _ => NONE) | |
72 eqs) | |
73 | Delete (tab, exp) => equalities (fn _ => tab) exp | |
74 (* TODO: examine the updated values and not just the way they're filtered. *) | |
75 (* For example, UPDATE foo SET Id = 9001 WHERE Id = 42 should update both the | |
76 Id = 42 and Id = 9001 cache entries. Could also think of it as doing a | |
77 Delete immediately followed by an Insert. *) | |
78 | Update (tab, _, exp) => equalities (fn _ => tab) exp | |
79 | |
80 val rec tablesQuery = | |
81 fn Query1 {From = tablePairs, ...} => SS.fromList (map #1 tablePairs) | |
82 | Union (q1, q2) => SS.union (tablesQuery q1, tablesQuery q2) | |
83 | |
84 val tableDml = | |
85 fn Insert (tab, _) => tab | |
86 | Delete (tab, _) => tab | |
87 | Update (tab, _, _) => tab | |
88 | |
89 | |
90 (* Program instrumentation. *) | |
91 | |
92 val stringTyp = (TFfi ("Basis", "string"), ErrorMsg.dummySpan) | |
93 | |
94 val sequence = | |
95 fn (exp :: exps) => | |
96 let | |
97 val loc = ErrorMsg.dummySpan | |
98 in | |
99 List.foldl (fn (e', seq) => ESeq ((seq, loc), (e', loc))) exp exps | |
100 end | |
101 | _ => raise Match | |
102 | |
103 fun ffiAppCache' (func, index, args) : Mono.exp' = | |
104 EFfiApp ("Sqlcache", func ^ Int.toString index, args) | |
105 | |
106 fun ffiAppCache (func, index, args) : Mono. exp = | |
107 (ffiAppCache' (func, index, args), ErrorMsg.dummySpan) | |
108 | |
109 val varPrefix = "queryResult" | |
110 | |
111 fun indexOfName varName = | |
112 if String.isPrefix varPrefix varName | |
113 then Int.fromString (String.extract (varName, String.size varPrefix, NONE)) | |
114 else NONE | |
115 | |
116 val incRels = MonoUtil.Exp.map {typ = fn x => x, exp = fn ERel n => ERel (n + 1) | x => x} | |
117 | |
118 (* Filled in by instrumentQuery during Monoize, used during Sqlcache. *) | |
119 val urlifiedRel0s : Mono.exp IM.map ref = ref IM.empty | |
120 | |
121 (* Used by Monoize. *) | |
122 val instrumentQuery = | |
123 let | |
124 val nextQuery = ref 0 | |
125 fun iq (query, urlifiedRel0) = | |
126 case query of | |
127 (EQuery {state = typ, ...}, loc) => | |
128 let | |
129 val i = !nextQuery before nextQuery := !nextQuery + 1 | |
130 in | |
131 urlifiedRel0s := IM.insert (!urlifiedRel0s, i, urlifiedRel0); | |
132 (* ASK: name variables properly? *) | |
133 (ELet (varPrefix ^ Int.toString i, typ, query, | |
134 (* Uses a dummy FFI call to keep the urlified expression around, which | |
135 in turn keeps the declarations required for urlification safe from | |
136 MonoShake. The dummy call is removed during Sqlcache. *) | |
137 (* ASK: is there a better way? *) | |
138 (ESeq ((EFfiApp ("Sqlcache", "dummy", [(urlifiedRel0, stringTyp)]), loc), | |
139 (ERel 0, loc)), | |
140 loc)), | |
141 loc) | |
142 end | |
143 | _ => raise Match | |
144 in | |
145 iq | |
146 end | |
147 | |
148 val gunk : ((string * string) * Mono.exp) list list ref = ref [[]] | |
149 | |
150 fun cacheWrap (query, i, urlifiedRel0, eqs) = | |
151 case query of | |
152 (EQuery {state = typ, ...}, _) => | |
153 let | |
154 val loc = ErrorMsg.dummySpan | |
155 (* TODO: deal with effectful injected expressions. *) | |
156 val args = (ffiInfo := {index = i, params = length eqs} :: !ffiInfo; | |
157 map (fn (_, e) => (e, stringTyp)) eqs) before gunk := eqs :: !gunk | |
158 val argsInc = map (fn (e, t) => (incRels e, t)) args | |
159 in | |
160 (ECase (ffiAppCache ("check", i, args), | |
161 [((PNone stringTyp, loc), | |
162 (ELet ("q", typ, query, | |
163 (ESeq (ffiAppCache ("store", i, (urlifiedRel0, stringTyp) :: argsInc), | |
164 (ERel 0, loc)), | |
165 loc)), | |
166 loc)), | |
167 ((PSome (stringTyp, (PVar ("hit", stringTyp), loc)), loc), | |
168 (* ASK: what does this bool do? *) | |
169 (EUnurlify ((ERel 0, loc), typ, false), loc))], | |
170 {disc = stringTyp, result = typ}), | |
171 loc) | |
172 end | |
173 | _ => raise Match | |
174 | |
175 fun fileMapfold doExp file start = | |
176 case MonoUtil.File.mapfold {typ = Search.return2, | |
177 exp = fn x => (fn s => Search.Continue (doExp x s)), | |
178 decl = Search.return2} file start of | |
179 Search.Continue x => x | |
180 | Search.Return _ => raise Match | |
181 | |
182 fun fileMap doExp file = #1 (fileMapfold (fn x => fn _ => (doExp x, ())) file ()) | |
183 | |
184 val addChecking = | |
185 let | |
186 fun doExp queryInfo = | |
187 fn e' as ELet (v, t, queryExp as (EQuery {query = queryText, ...}, _), body) => | |
188 let | |
189 fun bind x f = Option.mapPartial f x | |
190 val attempt = | |
191 (* Ziv misses Haskell's do notation.... *) | |
192 bind (parse query queryText) (fn queryParsed => | |
193 (Print.preface ("gunk> ", (MonoPrint.p_exp MonoEnv.empty queryExp)); | |
194 bind (indexOfName v) (fn i => | |
195 bind (equalitiesQuery queryParsed) (fn eqs => | |
196 bind (IM.find (!urlifiedRel0s, i)) (fn urlifiedRel0 => | |
197 SOME (ELet (v, t, cacheWrap (queryExp, i, urlifiedRel0, eqs), body), | |
198 SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, i)) | |
199 queryInfo | |
200 (tablesQuery queryParsed))))))) | |
201 in | |
202 case attempt of | |
203 SOME pair => pair | |
204 | NONE => (e', queryInfo) | |
205 end | |
206 | ESeq ((EFfiApp ("Sqlcache", "dummy", _), _), (e', _)) => (e', queryInfo) | |
207 | e' => (e', queryInfo) | |
208 in | |
209 fn file => fileMapfold (fn exp => fn state => doExp state exp) file SIMM.empty | |
210 end | |
211 | |
212 fun addFlushing (file, queryInfo) = | |
213 let | |
214 val allIndices : int list = SM.foldr (fn (x, acc) => IS.listItems x @ acc) [] queryInfo | |
215 fun flushes indices = map (fn i => ffiAppCache' ("flush", i, [])) indices | |
216 val doExp = | |
217 fn dmlExp as EDml (dmlText, _) => | |
218 let | |
219 val indices = | |
220 case parse dml dmlText of | |
221 SOME dmlParsed => SIMM.findList (queryInfo, tableDml dmlParsed) | |
222 | NONE => allIndices | |
223 in | |
224 sequence (flushes indices @ [dmlExp]) | |
225 end | |
226 | e' => e' | |
227 in | |
228 fileMap doExp file | |
229 end | |
230 | |
231 fun go file = | |
232 let | |
233 val () = Sql.sqlcacheMode := true | |
234 in | |
235 addFlushing (addChecking file) before Sql.sqlcacheMode := false | |
236 end | |
237 | |
238 | |
239 (* BEGIN OLD | |
16 | 240 |
17 fun intExp (n, loc) = (EPrim (Prim.Int (Int64.fromInt n)), loc) | 241 fun intExp (n, loc) = (EPrim (Prim.Int (Int64.fromInt n)), loc) |
18 fun intTyp loc = (TFfi ("Basis", "int"), loc) | 242 fun intTyp loc = (TFfi ("Basis", "int"), loc) |
243 fun stringExp (s, loc) = (EPrim (Prim.String (Prim.Normal, s)), loc) | |
244 | |
19 fun boolPat (b, loc) = (PCon (Enum, | 245 fun boolPat (b, loc) = (PCon (Enum, |
20 PConFfi {mod = "Basis", datatyp = "bool", arg = NONE, | 246 PConFfi {mod = "Basis", datatyp = "bool", arg = NONE, |
21 con = if b then "True" else "False"}, | 247 con = if b then "True" else "False"}, |
22 NONE), | 248 NONE), |
23 loc) | 249 loc) |
24 fun boolTyp loc = (TFfi ("Basis", "int"), loc) | 250 fun boolTyp loc = (TFfi ("Basis", "int"), loc) |
25 | 251 |
26 fun ffiAppExp (module, func, index, loc) = | 252 fun ffiAppExp (module, func, index, args, loc) = |
27 (EFfiApp (module, func ^ Int.toString index, []), loc) | 253 (EFfiApp (module, func ^ Int.toString index, args), loc) |
28 | 254 |
29 fun sequence ((exp :: exps), loc) = | 255 val sequence = |
256 fn ((exp :: exps), loc) => | |
30 List.foldl (fn (exp, seq) => (ESeq (seq, exp), loc)) exp exps | 257 List.foldl (fn (exp, seq) => (ESeq (seq, exp), loc)) exp exps |
258 | _ => raise Match | |
31 | 259 |
32 fun antiguardUnit (cond, exp, loc) = | 260 fun antiguardUnit (cond, exp, loc) = |
33 (ECase (cond, | 261 (ECase (cond, |
34 [(boolPat (false, loc), exp), | 262 [(boolPat (false, loc), exp), |
35 (boolPat (true, loc), (ERecord [], loc))], | 263 (boolPat (true, loc), (ERecord [], loc))], |
39 fun underAbs f (exp as (exp', loc)) = | 267 fun underAbs f (exp as (exp', loc)) = |
40 case exp' of | 268 case exp' of |
41 EAbs (x, y, z, body) => (EAbs (x, y, z, underAbs f body), loc) | 269 EAbs (x, y, z, body) => (EAbs (x, y, z, underAbs f body), loc) |
42 | _ => f exp | 270 | _ => f exp |
43 | 271 |
44 (* Program analysis and augmentation. *) | |
45 | 272 |
46 val rec tablesRead = | 273 val rec tablesRead = |
47 fn Query1 {From=tablePairs, ...} => SS.fromList (map #1 tablePairs) | 274 fn Query1 {From = tablePairs, ...} => SS.fromList (map #1 tablePairs) |
48 | Union (q1,q2) => SS.union (tablesRead q1, tablesRead q2) | 275 | Union (q1, q2) => SS.union (tablesRead q1, tablesRead q2) |
49 | 276 |
50 val tableWritten = | 277 val tableWritten = |
51 fn Insert (tab, _) => tab | 278 fn Insert (tab, _) => tab |
52 | Delete (tab, _) => tab | 279 | Delete (tab, _) => tab |
53 | Update (tab, _, _) => tab | 280 | Update (tab, _, _) => tab |
55 fun tablesInExp' exp' = | 282 fun tablesInExp' exp' = |
56 let | 283 let |
57 val nothing = {read = SS.empty, written = SS.empty} | 284 val nothing = {read = SS.empty, written = SS.empty} |
58 in | 285 in |
59 case exp' of | 286 case exp' of |
60 EQuery {query=e, ...} => | 287 EQuery {query = e, ...} => |
61 (case parse query e of | 288 (case parse query e of |
62 SOME q => {read = tablesRead q, written = SS.empty} | 289 SOME q => {read = tablesRead q, written = SS.empty} |
63 | NONE => nothing) | 290 | NONE => nothing) |
64 | EDml (e, _) => | 291 | EDml (e, _) => |
65 (case parse dml e of | 292 (case parse dml e of |
69 end | 296 end |
70 | 297 |
71 val tablesInExp = | 298 val tablesInExp = |
72 let | 299 let |
73 fun addTables (exp', {read, written}) = | 300 fun addTables (exp', {read, written}) = |
74 let val {read = r, written = w} = tablesInExp' exp' | 301 let |
75 in {read = SS.union (r, read), written = SS.union (w, written)} end | 302 val {read = r, written = w} = tablesInExp' exp' |
303 in | |
304 {read = SS.union (r, read), written = SS.union (w, written)} | |
305 end | |
76 in | 306 in |
77 MonoUtil.Exp.fold {typ = #2, exp = addTables} | 307 MonoUtil.Exp.fold {typ = #2, exp = addTables} |
78 {read = SS.empty, written = SS.empty} | 308 {read = SS.empty, written = SS.empty} |
79 end | 309 end |
80 | 310 |
148 end | 378 end |
149 fun nada x y = Search.Continue (x, y) | 379 fun nada x y = Search.Continue (x, y) |
150 in | 380 in |
151 case MonoUtil.File.mapfold {typ = nada, exp = nada, decl = doDecl} file init of | 381 case MonoUtil.File.mapfold {typ = nada, exp = nada, decl = doDecl} file init of |
152 Search.Continue x => x | 382 Search.Continue x => x |
153 | _ => (file, init) (* Should never happen. *) | 383 | _ => raise Match (* Should never happen. *) |
154 end | 384 end |
155 | 385 |
156 fun fileMapSelected f = #1 o fileFoldMapiSelected (fn (_, x, _) => (f x, ())) () | 386 fun fileMapSelected f = #1 o fileFoldMapiSelected (fn (_, x, _) => (f x, ())) () |
157 | 387 |
158 val addCacheChecking = | 388 val addCacheChecking = |
176 in | 406 in |
177 ffiIndices := IS.listItems readers; | 407 ffiIndices := IS.listItems readers; |
178 addCacheFlushing (fileWithChecks, tablesToIndices, writers) | 408 addCacheFlushing (fileWithChecks, tablesToIndices, writers) |
179 end | 409 end |
180 | 410 |
411 END OLD *) | |
412 | |
181 end | 413 end |