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)