# HG changeset patch # User Adam Chlipala # Date 1220794093 14400 # Node ID 0236d9412ad259aa1ff12558a8e0a01e79fa933e # Parent 7d5860add50fc9b2703fb7a7bd1087b2fed87371 Ran a prepared statement with one string parameter diff -r 7d5860add50f -r 0236d9412ad2 include/urweb.h --- a/include/urweb.h Thu Sep 04 10:27:21 2008 -0400 +++ b/include/urweb.h Sun Sep 07 09:28:13 2008 -0400 @@ -64,3 +64,5 @@ lw_Basis_float lw_Basis_sqlifyFloat(lw_context, lw_Basis_float); lw_Basis_string lw_Basis_sqlifyString(lw_context, lw_Basis_string); lw_Basis_bool lw_Basis_sqlifyBool(lw_context, lw_Basis_bool); + +char *lw_Basis_ensqlBool(lw_Basis_bool); diff -r 7d5860add50f -r 0236d9412ad2 src/c/urweb.c --- a/src/c/urweb.c Thu Sep 04 10:27:21 2008 -0400 +++ b/src/c/urweb.c Sun Sep 07 09:28:13 2008 -0400 @@ -122,7 +122,7 @@ ctx->inputs[n] = value; - printf("[%d] %s = %s\n", n, name, value); + //printf("[%d] %s = %s\n", n, name, value); } char *lw_get_input(lw_context ctx, int n) { @@ -130,7 +130,7 @@ lw_error(ctx, FATAL, "Negative input index %d", n); if (n >= lw_inputs_len) lw_error(ctx, FATAL, "Out-of-bounds input index %d", n); - printf("[%d] = %s\n", n, ctx->inputs[n]); + //printf("[%d] = %s\n", n, ctx->inputs[n]); return ctx->inputs[n]; } @@ -656,3 +656,13 @@ else return "TRUE"; } + +char *lw_Basis_ensqlBool(lw_Basis_bool b) { + static lw_Basis_int true = 1; + static lw_Basis_int false = 0; + + if (b == lw_Basis_False) + return (char *)&false; + else + return (char *)&true; +} diff -r 7d5860add50f -r 0236d9412ad2 src/cjr.sml --- a/src/cjr.sml Thu Sep 04 10:27:21 2008 -0400 +++ b/src/cjr.sml Sun Sep 07 09:28:13 2008 -0400 @@ -76,7 +76,8 @@ state : typ, query : exp, body : exp, - initial : exp } + initial : exp, + prepared : int option } withtype exp = exp' located @@ -90,6 +91,7 @@ | DTable of string * (string * typ) list | DDatabase of string + | DPreparedStatements of (string * int) list withtype decl = decl' located diff -r 7d5860add50f -r 0236d9412ad2 src/cjr_env.sml --- a/src/cjr_env.sml Thu Sep 04 10:27:21 2008 -0400 +++ b/src/cjr_env.sml Sun Sep 07 09:28:13 2008 -0400 @@ -164,6 +164,7 @@ end) env vis | DTable _ => env | DDatabase _ => env + | DPreparedStatements _ => env end diff -r 7d5860add50f -r 0236d9412ad2 src/cjr_print.sml --- a/src/cjr_print.sml Thu Sep 04 10:27:21 2008 -0400 +++ b/src/cjr_print.sml Sun Sep 07 09:28:13 2008 -0400 @@ -333,6 +333,45 @@ Print.eprefaces' [("Type", p_typ env tAll)]; string "ERROR") +datatype sql_type = + Int + | Float + | String + | Bool + +fun p_sql_type t = + string (case t of + Int => "lw_Basis_int" + | Float => "lw_Basis_float" + | String => "lw_Basis_string" + | Bool => "lw_Basis_bool") + +fun getPargs (e, _) = + case e of + EPrim (Prim.String _) => [] + | EFfiApp ("Basis", "strcat", [e1, e2]) => getPargs e1 @ getPargs e2 + + | EFfiApp ("Basis", "sqlifyInt", [e]) => [(e, Int)] + | EFfiApp ("Basis", "sqlifyFloat", [e]) => [(e, Float)] + | EFfiApp ("Basis", "sqlifyString", [e]) => [(e, String)] + | EFfiApp ("Basis", "sqlifyBool", [e]) => [(e, Bool)] + + | _ => raise Fail "CjrPrint: getPargs" + +fun p_ensql t e = + case t of + Int => box [string "(char *)&", e] + | Float => box [string "(char *)&", e] + | String => e + | Bool => box [string "lw_Basis_ensqlBool(", e, string ")"] + +fun p_ensql_len t e = + case t of + Int => string "sizeof(lw_Basis_int)" + | Float => string "sizeof(lw_Basis_float)" + | String => box [string "strlen(", e, string ")"] + | Bool => string "sizeof(lw_Basis_bool)" + fun p_exp' par env (e, loc) = case e of EPrim p => Prim.p_t_GCC p @@ -560,7 +599,7 @@ newline, string "})"] - | EQuery {exps, tables, rnum, state, query, body, initial} => + | EQuery {exps, tables, rnum, state, query, body, initial, prepared} => let val exps = map (fn (x, t) => ("__lwf_" ^ x, t)) exps val tables = ListUtil.mapConcat (fn (x, xts) => @@ -573,10 +612,54 @@ newline, string "PGconn *conn = lw_get_db(ctx);", newline, - string "char *query = ", - p_exp env query, - string ";", - newline, + case prepared of + NONE => box [string "char *query = ", + p_exp env query, + string ";", + newline] + | SOME _ => + let + val ets = getPargs query + in + box [p_list_sepi newline + (fn i => fn (e, t) => + box [p_sql_type t, + space, + string "arg", + string (Int.toString (i + 1)), + space, + string "=", + space, + p_exp env e, + string ";"]) + ets, + newline, + newline, + + string "const char *paramValues[] = { ", + p_list_sepi (box [string ",", space]) + (fn i => fn (_, t) => p_ensql t (box [string "arg", + string (Int.toString (i + 1))])) + ets, + string " };", + newline, + newline, + + string "const int paramLengths[] = { ", + p_list_sepi (box [string ",", space]) + (fn i => fn (_, t) => p_ensql_len t (box [string "arg", + string (Int.toString (i + 1))])) + ets, + string " };", + newline, + newline, + + string "const static int paramFormats[] = { ", + p_list_sep (box [string ",", space]) (fn _ => string "1") ets, + string " };", + newline, + newline] + end, string "int n, i;", newline, p_typ env state, @@ -588,7 +671,14 @@ p_exp env initial, string ";", newline, - string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 1);", + string "PGresult *res = ", + case prepared of + NONE => string "PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 1);" + | SOME n => box [string "PQexecPrepared(conn, \"lw", + string (Int.toString n), + string "\", ", + string (Int.toString (length (getPargs query))), + string ", paramValues, paramLengths, paramFormats, 1);"], newline, newline, @@ -602,7 +692,11 @@ newline, string "lw_error(ctx, FATAL, \"", string (ErrorMsg.spanToString loc), - string ": Query failed:\\n%s\\n%s\", query, PQerrorMessage(conn));", + string ": Query failed:\\n%s\\n%s\", ", + case prepared of + NONE => string "query" + | SOME _ => p_exp env query, + string ", PQerrorMessage(conn));", newline], string "}", newline, @@ -814,6 +908,8 @@ newline] | DDatabase s => box [string "static void lw_db_validate(lw_context);", newline, + string "static void lw_db_prepare(lw_context);", + newline, newline, string "void lw_db_init(lw_context ctx) {", newline, @@ -843,6 +939,8 @@ newline, string "lw_db_validate(ctx);", newline, + string "lw_db_prepare(ctx);", + newline, string "}", newline, newline, @@ -853,6 +951,48 @@ string "}", newline] + | DPreparedStatements ss => + box [string "static void lw_db_prepare(lw_context ctx) {", + newline, + string "PGconn *conn = lw_get_db(ctx);", + newline, + string "PGresult *res;", + newline, + newline, + + p_list_sepi newline (fn i => fn (s, n) => + box [string "res = PQprepare(conn, \"lw", + string (Int.toString i), + string "\", \"", + string (String.toString s), + string "\", ", + string (Int.toString n), + string ", NULL);", + newline, + string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {", + newline, + box [string "char msg[1024];", + newline, + string "strncpy(msg, PQerrorMessage(conn), 1024);", + newline, + string "msg[1023] = 0;", + newline, + string "PQclear(res);", + newline, + string "PQfinish(conn);", + newline, + string "lw_error(ctx, FATAL, \"Unable to create prepared statement:\\n", + string (String.toString s), + string "\\n%s\", msg);", + newline], + string "}", + newline, + string "PQclear(res);", + newline]) + ss, + + string "}"] + datatype 'a search = Found of 'a | NotFound diff -r 7d5860add50f -r 0236d9412ad2 src/cjrize.sml --- a/src/cjrize.sml Thu Sep 04 10:27:21 2008 -0400 +++ b/src/cjrize.sml Sun Sep 07 09:28:13 2008 -0400 @@ -330,7 +330,7 @@ val (initial, sm) = cifyExp (initial, sm) in ((L'.EQuery {exps = exps', tables = tables', rnum = rnum, state = state, - query = query, body = body, initial = initial}, loc), sm) + query = query, body = body, initial = initial, prepared = NONE}, loc), sm) end diff -r 7d5860add50f -r 0236d9412ad2 src/compiler.sig --- a/src/compiler.sig Thu Sep 04 10:27:21 2008 -0400 +++ b/src/compiler.sig Sun Sep 07 09:28:13 2008 -0400 @@ -68,6 +68,7 @@ val mono_reduce : (Mono.file, Mono.file) phase val mono_shake : (Mono.file, Mono.file) phase val cjrize : (Mono.file, Cjr.file) phase + val prepare : (Cjr.file, Cjr.file) phase val sqlify : (Mono.file, Cjr.file) phase val toParseJob : (string, job) transform @@ -87,6 +88,7 @@ val toMono_shake : (string, Mono.file) transform val toMono_opt2 : (string, Mono.file) transform val toCjrize : (string, Cjr.file) transform + val toPrepare : (string, Cjr.file) transform val toSqlify : (string, Cjr.file) transform end diff -r 7d5860add50f -r 0236d9412ad2 src/compiler.sml --- a/src/compiler.sml Thu Sep 04 10:27:21 2008 -0400 +++ b/src/compiler.sml Sun Sep 07 09:28:13 2008 -0400 @@ -451,6 +451,13 @@ val toCjrize = transform cjrize "cjrize" o toMono_opt2 +val prepare = { + func = Prepare.prepare, + print = CjrPrint.p_file CjrEnv.empty +} + +val toPrepare = transform prepare "prepare" o toCjrize + val sqlify = { func = Cjrize.cjrize, print = CjrPrint.p_sql CjrEnv.empty @@ -472,7 +479,7 @@ end fun compile job = - case run toCjrize job of + case run toPrepare job of NONE => print "Ur compilation failed\n" | SOME file => let diff -r 7d5860add50f -r 0236d9412ad2 src/prepare.sig --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/prepare.sig Sun Sep 07 09:28:13 2008 -0400 @@ -0,0 +1,32 @@ +(* Copyright (c) 2008, Adam Chlipala + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - The names of contributors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *) + +signature PREPARE = sig + + val prepare : Cjr.file -> Cjr.file + +end diff -r 7d5860add50f -r 0236d9412ad2 src/prepare.sml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/prepare.sml Sun Sep 07 09:28:13 2008 -0400 @@ -0,0 +1,180 @@ +(* Copyright (c) 2008, Adam Chlipala + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - The names of contributors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *) + +structure Prepare :> PREPARE = struct + +open Cjr + +fun prepString (e, ss, n) = + case #1 e of + EPrim (Prim.String s) => + SOME (s :: ss, n) + | EFfiApp ("Basis", "strcat", [e1, e2]) => + (case prepString (e1, ss, n) of + NONE => NONE + | SOME (ss, n) => prepString (e2, ss, n)) + | EFfiApp ("Basis", "sqlifyInt", [e]) => + SOME ("$" ^ Int.toString (n + 1) ^ "::int8" :: ss, n + 1) + | EFfiApp ("Basis", "sqlifyFloat", [e]) => + SOME ("$" ^ Int.toString (n + 1) ^ "::float8" :: ss, n + 1) + | EFfiApp ("Basis", "sqlifyString", [e]) => + SOME ("$" ^ Int.toString (n + 1) ^ "::text" :: ss, n + 1) + | EFfiApp ("Basis", "sqlifyBool", [e]) => + SOME ("$" ^ Int.toString (n + 1) ^ "::bool" :: ss, n + 1) + + | _ => NONE + +fun prepExp (e as (_, loc), sns) = + case #1 e of + EPrim _ => (e, sns) + | ERel _ => (e, sns) + | ENamed _ => (e, sns) + | ECon (_, _, NONE) => (e, sns) + | ECon (dk, pc, SOME e) => + let + val (e, sns) = prepExp (e, sns) + in + ((ECon (dk, pc, SOME e), loc), sns) + end + | EFfi _ => (e, sns) + | EFfiApp (m, x, es) => + let + val (es, sns) = ListUtil.foldlMap prepExp sns es + in + ((EFfiApp (m, x, es), loc), sns) + end + | EApp (e1, e2) => + let + val (e1, sns) = prepExp (e1, sns) + val (e2, sns) = prepExp (e2, sns) + in + ((EApp (e1, e2), loc), sns) + end + + | ERecord (rn, xes) => + let + val (xes, sns) = ListUtil.foldlMap (fn ((x, e), sns) => + let + val (e, sns) = prepExp (e, sns) + in + ((x, e), sns) + end) sns xes + in + ((ERecord (rn, xes), loc), sns) + end + | EField (e, s) => + let + val (e, sns) = prepExp (e, sns) + in + ((EField (e, s), loc), sns) + end + + | ECase (e, pes, ts) => + let + val (e, sns) = prepExp (e, sns) + val (pes, sns) = ListUtil.foldlMap (fn ((p, e), sns) => + let + val (e, sns) = prepExp (e, sns) + in + ((p, e), sns) + end) sns pes + in + ((ECase (e, pes, ts), loc), sns) + end + + | EWrite e => + let + val (e, sns) = prepExp (e, sns) + in + ((EWrite e, loc), sns) + end + | ESeq (e1, e2) => + let + val (e1, sns) = prepExp (e1, sns) + val (e2, sns) = prepExp (e2, sns) + in + ((ESeq (e1, e2), loc), sns) + end + | ELet (x, t, e1, e2) => + let + val (e1, sns) = prepExp (e1, sns) + val (e2, sns) = prepExp (e2, sns) + in + ((ELet (x, t, e1, e2), loc), sns) + end + + | EQuery {exps, tables, rnum, state, query, body, initial, ...} => + (case prepString (query, [], 0) of + NONE => (e, sns) + | SOME (ss, n) => + ((EQuery {exps = exps, tables = tables, rnum = rnum, + state = state, query = query, body = body, + initial = initial, prepared = SOME (#2 sns)}, loc), + ((String.concat (rev ss), n) :: #1 sns, #2 sns + 1))) + +fun prepDecl (d as (_, loc), sns) = + case #1 d of + DStruct _ => (d, sns) + | DDatatype _ => (d, sns) + | DDatatypeForward _ => (d, sns) + | DVal (x, n, t, e) => + let + val (e, sns) = prepExp (e, sns) + in + ((DVal (x, n, t, e), loc), sns) + end + | DFun (x, n, xts, t, e) => + let + val (e, sns) = prepExp (e, sns) + in + ((DFun (x, n, xts, t, e), loc), sns) + end + | DFunRec fs => + let + val (fs, sns) = ListUtil.foldlMap (fn ((x, n, xts, t, e), sns) => + let + val (e, sns) = prepExp (e, sns) + in + ((x, n, xts, t, e), sns) + end) sns fs + in + ((DFunRec fs, loc), sns) + end + + | DTable _ => (d, sns) + | DDatabase _ => (d, sns) + | DPreparedStatements _ => (d, sns) + +fun prepare (ds, ps) = + let + val (ds, (sns, _)) = ListUtil.foldlMap prepDecl ([], 0) ds + in + ((DPreparedStatements (rev sns), ErrorMsg.dummySpan) :: ds, ps) + end + +end + diff -r 7d5860add50f -r 0236d9412ad2 src/sources --- a/src/sources Thu Sep 04 10:27:21 2008 -0400 +++ b/src/sources Sun Sep 07 09:28:13 2008 -0400 @@ -121,5 +121,8 @@ cjrize.sig cjrize.sml +prepare.sig +prepare.sml + compiler.sig compiler.sml