Mercurial > urweb
comparison src/sqlcache.sml @ 2267:e5b7b066bf1b
Factor out SQL simplification.
author | Ziv Scully <ziv@mit.edu> |
---|---|
date | Wed, 14 Oct 2015 20:40:57 -0400 |
parents | afd12c75e0d6 |
children | bc1ef958d801 |
comparison
equal
deleted
inserted
replaced
2266:afd12c75e0d6 | 2267:e5b7b066bf1b |
---|---|
553 Search.Continue x => x | 553 Search.Continue x => x |
554 | Search.Return _ => raise Match | 554 | Search.Return _ => raise Match |
555 | 555 |
556 fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) | 556 fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) |
557 | 557 |
558 (* Takes a text expression and returns | 558 (* TODO: make this a bit prettier.... *) |
559 newText: a new expression with any subexpressions that do computation | 559 val simplifySql = |
560 replaced with variables, | 560 let |
561 wrapLets: a function that wraps its argument expression with lets binding | 561 fun factorOutNontrivial text = |
562 those variables to their corresponding computations, and | 562 let |
563 numArgs: the number of such bindings. | 563 val loc = dummyLoc |
564 The De Bruijn indices work out for [wrapLets (incRels numArgs newText)], but | 564 fun strcat (e1, e2) = (EStrcat (e1, e2), loc) |
565 the intention is that newText might be augmented. *) | 565 val chunks = Sql.chunkify text |
566 fun factorOutNontrivial text = | 566 val (newText, newVariables) = |
567 let | 567 (* Important that this is foldr (to oppose foldl below). *) |
568 val loc = dummyLoc | 568 List.foldr |
569 fun strcat (e1, e2) = (EStrcat (e1, e2), loc) | 569 (fn (chunk, (qText, newVars)) => |
570 val chunks = Sql.chunkify text | 570 (* Variable bound to the head of newVars will have the lowest index. *) |
571 val (newText, newVariables) = | 571 case chunk of |
572 (* Important that this is foldr (to oppose foldl below). *) | 572 (* EPrim should always be a string in this case. *) |
573 List.foldr | 573 Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) |
574 (fn (chunk, (qText, newVars)) => | 574 | Sql.Exp e => |
575 (* Variable bound to the head of newVars will have the lowest index. *) | 575 let |
576 case chunk of | 576 val n = length newVars |
577 (* EPrim should always be a string in this case. *) | 577 in |
578 Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) | 578 (* This is the (n+1)th new variable, so there are |
579 | Sql.Exp e => | 579 already n new variables bound, so we increment |
580 let | 580 indices by n. *) |
581 val n = length newVars | 581 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) |
582 in | 582 end |
583 (* This is the (n+1)th new variable, so there are | 583 | Sql.String s => (strcat (stringExp s, qText), newVars)) |
584 already n new variables bound, so we increment | 584 (stringExp "", []) |
585 indices by n. *) | 585 chunks |
586 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) | 586 fun wrapLets e' = |
587 end | 587 (* Important that this is foldl (to oppose foldr above). *) |
588 | Sql.String s => (strcat (stringExp s, qText), newVars)) | 588 List.foldl (fn (v, e') => ELet (varName "sqlArg", stringTyp, v, (e', loc))) |
589 (stringExp "", []) | 589 e' |
590 chunks | 590 newVariables |
591 fun wrapLets e' = | 591 val numArgs = length newVariables |
592 (* Important that this is foldl (to oppose foldr above). *) | 592 in |
593 List.foldl (fn (v, e') => ELet (varName "sqlArg", stringTyp, v, (e', loc))) | 593 (newText, wrapLets, numArgs) |
594 e' | 594 end |
595 newVariables | 595 fun doExp exp' = |
596 val numArgs = length newVariables | 596 let |
597 in | 597 val text = case exp' of |
598 (newText, wrapLets, numArgs) | 598 EQuery {query = text, ...} => text |
599 | EDml (text, _) => text | |
600 | _ => raise Match | |
601 val (newText, wrapLets, numArgs) = factorOutNontrivial text | |
602 val newExp' = case exp' of | |
603 EQuery q => EQuery {query = newText, | |
604 exps = #exps q, | |
605 tables = #tables q, | |
606 state = #state q, | |
607 body = #body q, | |
608 initial = #initial q} | |
609 | EDml (_, failureMode) => EDml (newText, failureMode) | |
610 | _ => raise Match | |
611 in | |
612 (* Increment once for each new variable just made. This is | |
613 where we use the negative De Bruijn indices hack. *) | |
614 (* TODO: please don't use that hack. As anyone could have | |
615 predicted, it was incomprehensible a year later.... *) | |
616 wrapLets (#1 (incRels numArgs (newExp', dummyLoc))) | |
617 end | |
618 in | |
619 fileMap (fn exp' => case exp' of | |
620 EQuery _ => doExp exp' | |
621 | EDml _ => doExp exp' | |
622 | _ => exp') | |
599 end | 623 end |
600 | 624 |
601 | 625 |
602 (**********************) | 626 (**********************) |
603 (* Mono Type Checking *) | 627 (* Mono Type Checking *) |
657 | 681 |
658 (***********) | 682 (***********) |
659 (* Caching *) | 683 (* Caching *) |
660 (***********) | 684 (***********) |
661 | 685 |
686 (* | |
687 | |
688 To get the invalidations for a dml, we need (each <- is list-monad-y): | |
689 * table <- dml | |
690 * cache <- table | |
691 * query <- cache | |
692 * inval <- (query, dml), | |
693 where inval is a list of query argument indices, so | |
694 * way to change query args in inval to cache args. | |
695 For now, the last one is just | |
696 * a map from query arg number to the corresponding free variable (per query) | |
697 * a map from free variable to cache arg number (per cache). | |
698 Both queries and caches should have IDs. | |
699 | |
700 *) | |
701 | |
662 fun cacheWrap (env, exp, resultTyp, args, i) = | 702 fun cacheWrap (env, exp, resultTyp, args, i) = |
663 let | 703 let |
664 val loc = dummyLoc | 704 val loc = dummyLoc |
665 val rel0 = (ERel 0, loc) | 705 val rel0 = (ERel 0, loc) |
666 in | 706 in |
684 (EUnurlify (rel0, resultTyp, false), loc))], | 724 (EUnurlify (rel0, resultTyp, false), loc))], |
685 {disc = (TOption stringTyp, loc), result = resultTyp})) | 725 {disc = (TOption stringTyp, loc), result = resultTyp})) |
686 end | 726 end |
687 end | 727 end |
688 | 728 |
729 val maxFreeVar = | |
730 MonoUtil.Exp.foldB | |
731 {typ = #2, | |
732 exp = fn (bound, ERel n, v) => Int.max (v, n - bound) | (_, _, v) => v, | |
733 bind = fn (bound, MonoUtil.Exp.RelE _) => bound + 1 | (bound, _) => bound} | |
734 0 | |
735 ~1 | |
736 | |
689 val freeVars = | 737 val freeVars = |
690 IS.listItems | 738 IS.listItems |
691 o MonoUtil.Exp.foldB | 739 o MonoUtil.Exp.foldB |
692 {typ = #2, | 740 {typ = #2, |
693 exp = fn (bound, ERel n, vars) => if n < bound | 741 exp = fn (bound, ERel n, vars) => if n < bound |
698 0 | 746 0 |
699 IS.empty | 747 IS.empty |
700 | 748 |
701 val expSize = MonoUtil.Exp.fold {typ = #2, exp = fn (_, n) => n+1} 0 | 749 val expSize = MonoUtil.Exp.fold {typ = #2, exp = fn (_, n) => n+1} 0 |
702 | 750 |
703 datatype subexp = Pure of unit -> exp | Impure of exp | 751 datatype subexp = Cachable of unit -> exp | Impure of exp |
704 | 752 |
705 val isImpure = | 753 val isImpure = |
706 fn Pure _ => false | 754 fn Cachable _ => false |
707 | Impure _ => true | 755 | Impure _ => true |
708 | 756 |
709 val expOfSubexp = | 757 val expOfSubexp = |
710 fn Pure f => f () | 758 fn Cachable f => f () |
711 | Impure e => e | 759 | Impure e => e |
712 | 760 |
713 (* TODO: pick a number. *) | 761 (* TODO: pick a number. *) |
714 val sizeWorthCaching = 5 | 762 val sizeWorthCaching = 5 |
715 | 763 |
716 type state = (SIMM.multimap * (Sql.query * int) IntBinaryMap.map * int) | 764 type state = (SIMM.multimap * (Sql.query * int) IntBinaryMap.map * int) |
717 | 765 |
718 fun incIndex (x, y, index) = (x, y, index+1) | 766 fun incIndex (x, y, index) = (x, y, index+1) |
719 | 767 |
720 fun cacheQuery effs env (state as (tableToIndices, indexToQueryNumArgs, index)) = | 768 fun cacheQuery effs env (state as (tableToIndices, indexToQueryNumArgs, index)) = |
721 fn q as {query = origQueryText, | 769 fn q as {query = queryText, |
722 state = resultTyp, | 770 state = resultTyp, |
723 initial, body, tables, exps} => | 771 initial, body, tables, exps} => |
724 let | 772 let |
725 val (newQueryText, wrapLets, numArgs) = factorOutNontrivial origQueryText | 773 val numArgs = maxFreeVar queryText + 1 |
726 (* Increment once for each new variable just made. This is where we | 774 val queryExp = (EQuery q, dummyLoc) |
727 use the negative De Bruijn indices hack. *) | |
728 (* TODO: please don't use that hack. As anyone could have predicted, it | |
729 was incomprehensible a year later.... *) | |
730 val queryExp = incRels numArgs | |
731 (EQuery {query = newQueryText, | |
732 state = resultTyp, | |
733 initial = initial, | |
734 body = body, | |
735 tables = tables, | |
736 exps = exps}, | |
737 dummyLoc) | |
738 (* DEBUG *) | 775 (* DEBUG *) |
739 (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *) | 776 (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *) |
740 val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc)) | 777 val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc)) |
741 (* We use dummyTyp here. I think this is okay because databases don't | 778 (* We use dummyTyp here. I think this is okay because databases don't |
742 store (effectful) functions, but perhaps there's some pathalogical | 779 store (effectful) functions, but perhaps there's some pathalogical |
745 not | 782 not |
746 o effectful effs | 783 o effectful effs |
747 (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE) | 784 (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE) |
748 bound | 785 bound |
749 env) | 786 env) |
750 val textOfQuery = fn (EQuery {query, ...}, _) => SOME query | _ => NONE | |
751 val attempt = | 787 val attempt = |
752 (* Ziv misses Haskell's do notation.... *) | 788 (* Ziv misses Haskell's do notation.... *) |
753 textOfQuery queryExp | 789 (safe 0 queryText andalso safe 0 initial andalso safe 2 body) |
790 <\oguard\> | |
791 Sql.parse Sql.query queryText | |
754 <\obind\> | 792 <\obind\> |
755 (fn queryText => | 793 (fn queryParsed => |
756 (safe 0 queryText andalso safe 0 initial andalso safe 2 body) | 794 (cacheWrap (env, queryExp, resultTyp, args, index)) |
757 <\oguard\> | |
758 Sql.parse Sql.query queryText | |
759 <\obind\> | 795 <\obind\> |
760 (fn queryParsed => | 796 (fn cachedExp => |
761 (cacheWrap (env, queryExp, resultTyp, args, index)) | 797 SOME (cachedExp, |
762 <\obind\> | 798 (SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, index)) |
763 (fn cachedExp => | 799 tableToIndices |
764 SOME (wrapLets cachedExp, | 800 (tablesQuery queryParsed), |
765 (SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, index)) | 801 IM.insert (indexToQueryNumArgs, index, (queryParsed, numArgs)), |
766 tableToIndices | 802 index + 1)))) |
767 (tablesQuery queryParsed), | |
768 IM.insert (indexToQueryNumArgs, index, (queryParsed, numArgs)), | |
769 index + 1))))) | |
770 in | 803 in |
771 case attempt of | 804 case attempt of |
772 SOME pair => pair | 805 SOME pair => pair |
773 (* Even in this case, we have to increment index to avoid some bug, | 806 (* Even in this case, we have to increment index to avoid some bug, |
774 but I forget exactly what it is or why this helps. *) | 807 but I forget exactly what it is or why this helps. *) |
775 (* TODO: just use a reference for current index.... *) | 808 (* TODO: just use a reference for current index.... *) |
776 | NONE => (EQuery q, incIndex state) | 809 | NONE => (EQuery q, incIndex state) |
777 end | 810 end |
778 | 811 |
779 fun cachePure (env, exp', (_, _, index)) = | 812 fun cachePure (env, exp', (_, _, index)) = |
780 case typOfExp' env exp' of | 813 case (expSize (exp', dummyLoc) > sizeWorthCaching) |
814 </oguard/> | |
815 typOfExp' env exp' of | |
781 NONE => NONE | 816 NONE => NONE |
782 | SOME (TFun _, _) => NONE | 817 | SOME (TFun _, _) => NONE |
783 | SOME typ => | 818 | SOME typ => |
784 (expSize (exp', dummyLoc) < sizeWorthCaching) | 819 (List.foldr (fn (_, NONE) => NONE |
785 </oguard/> | 820 | ((n, typ), SOME args) => |
786 (List.foldr (fn (_, NONE) => NONE | 821 (MonoFooify.urlify env ((ERel n, dummyLoc), typ)) |
787 | ((n, typ), SOME args) => | 822 </obind/> |
788 (MonoFooify.urlify env ((ERel n, dummyLoc), typ)) | 823 (fn arg => SOME (arg :: args))) |
789 </obind/> | 824 (SOME []) |
790 (fn arg => SOME (arg :: args))) | 825 (map (fn n => (n, #2 (MonoEnv.lookupERel env n))) |
791 (SOME []) | 826 (freeVars (exp', dummyLoc)))) |
792 (map (fn n => (n, #2 (MonoEnv.lookupERel env n))) | |
793 (freeVars (exp', dummyLoc)))) | |
794 </obind/> | 827 </obind/> |
795 (fn args => cacheWrap (env, (exp', dummyLoc), typ, args, index)) | 828 (fn args => cacheWrap (env, (exp', dummyLoc), typ, args, index)) |
796 | 829 |
797 fun cache (effs : IS.set) ((env, exp as (exp', loc)), state) : subexp * state = | 830 fun cache (effs : IS.set) ((env, exp as (exp', loc)), state) : subexp * state = |
798 let | 831 let |
801 val (subexps, state) = ListUtil.foldlMap (cache effs) state args | 834 val (subexps, state) = ListUtil.foldlMap (cache effs) state args |
802 fun mkExp () = (f (map expOfSubexp subexps), loc) | 835 fun mkExp () = (f (map expOfSubexp subexps), loc) |
803 in | 836 in |
804 if List.exists isImpure subexps | 837 if List.exists isImpure subexps |
805 then (Impure (mkExp ()), state) | 838 then (Impure (mkExp ()), state) |
806 else (Pure (fn () => case cachePure (env, f (map #2 args), state) of | 839 else (Cachable (fn () => case cachePure (env, f (map #2 args), state) of |
807 NONE => mkExp () | 840 NONE => mkExp () |
808 | SOME e' => (e', loc)), | 841 | SOME e' => (e', loc)), |
809 (* Conservatively increment index. *) | 842 (* Conservatively increment index. *) |
810 incIndex state) | 843 incIndex state) |
811 end | 844 end |
812 fun wrapBind1 f arg = | 845 fun wrapBind1 f arg = |
813 wrapBindN (fn [arg] => f arg | _ => raise Match) [arg] | 846 wrapBindN (fn [arg] => f arg | _ => raise Match) [arg] |
858 in | 891 in |
859 (Impure (exp', loc), state) | 892 (Impure (exp', loc), state) |
860 end | 893 end |
861 | _ => if effectful effs env exp | 894 | _ => if effectful effs env exp |
862 then (Impure exp, state) | 895 then (Impure exp, state) |
863 else (Pure (fn () => (case cachePure (env, exp', state) of | 896 else (Cachable (fn () => (case cachePure (env, exp', state) of |
864 NONE => exp' | 897 NONE => exp' |
865 | SOME e' => e', | 898 | SOME e' => e', |
866 loc)), | 899 loc)), |
867 incIndex state) | 900 incIndex state) |
868 end | 901 end |
869 | 902 |
870 fun addCaching file = | 903 fun addCaching file = |
871 let | 904 let |
937 fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = | 970 fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = |
938 let | 971 let |
939 val flushes = List.concat | 972 val flushes = List.concat |
940 o map (fn (i, argss) => map (fn args => flush (i, args)) argss) | 973 o map (fn (i, argss) => map (fn args => flush (i, args)) argss) |
941 val doExp = | 974 val doExp = |
942 fn EDml (origDmlText, failureMode) => | 975 fn dmlExp as EDml (dmlText, failureMode) => |
943 let | 976 let |
944 (* DEBUG *) | 977 (* DEBUG *) |
945 (* val () = gunk' := origDmlText :: !gunk' *) | 978 (* val () = gunk' := origDmlText :: !gunk' *) |
946 val (newDmlText, wrapLets, numArgs) = factorOutNontrivial origDmlText | |
947 val dmlText = incRels numArgs newDmlText | |
948 val dmlExp = EDml (dmlText, failureMode) | |
949 (* DEBUG *) | |
950 (* val () = Print.preface ("SQLCACHE: ", (MonoPrint.p_exp MonoEnv.empty origDmlText)) *) | 979 (* val () = Print.preface ("SQLCACHE: ", (MonoPrint.p_exp MonoEnv.empty origDmlText)) *) |
951 val inval = | 980 val inval = |
952 case Sql.parse Sql.dml dmlText of | 981 case Sql.parse Sql.dml dmlText of |
953 SOME dmlParsed => | 982 SOME dmlParsed => |
954 SOME (map (fn i => (case IM.find (indexToQueryNumArgs, i) of | 983 SOME (map (fn i => (case IM.find (indexToQueryNumArgs, i) of |
962 | NONE => NONE | 991 | NONE => NONE |
963 in | 992 in |
964 case inval of | 993 case inval of |
965 (* TODO: fail more gracefully. *) | 994 (* TODO: fail more gracefully. *) |
966 NONE => raise Match | 995 NONE => raise Match |
967 | SOME invs => wrapLets (sequence (flushes invs @ [dmlExp])) | 996 | SOME invs => sequence (flushes invs @ [dmlExp]) |
968 end | 997 end |
969 | e' => e' | 998 | e' => e' |
970 in | 999 in |
971 (* DEBUG *) | 1000 (* DEBUG *) |
972 (* gunk := []; *) | 1001 (* gunk := []; *) |
999 val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls | 1028 val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls |
1000 in | 1029 in |
1001 (datatypes @ newDecls @ others, sideInfo) | 1030 (datatypes @ newDecls @ others, sideInfo) |
1002 end | 1031 end |
1003 | 1032 |
1004 val go' = addFlushing o addCaching o inlineSql | 1033 val go' = addFlushing o addCaching o simplifySql o inlineSql |
1005 | 1034 |
1006 fun go file = | 1035 fun go file = |
1007 let | 1036 let |
1008 (* TODO: do something nicer than [Sql] being in one of two modes. *) | 1037 (* TODO: do something nicer than [Sql] being in one of two modes. *) |
1009 val () = (resetFfiInfo (); Sql.sqlcacheMode := true) | 1038 val () = (resetFfiInfo (); Sql.sqlcacheMode := true) |