Hi again,
Thanks for the fix and the new release, Marijn. I think I found another problem.
When I try to query with a prepared statement with the QUERY macro, and one of the arguments to the statement is the result of another QUERY macro (whether directly or from the result of a function), strange things crop up. In one instance, the inner QUERY was returning nil where it would return the expected integer otherwise. Here's an isolated case:
(postmodern:query "select cast ($1 as int)" (let ((foo (postmodern:query "select 12" :single))) (format t "result: ~S~%" foo) foo))
This gives me the expected message "result: 12", but also signals a condition, "Database error: Incorrect number of parameters given for prepared statement ."
Below is the complete macroexpansion of the above form (without LET and FORMAT).
(PROGN (CL-POSTGRES:PREPARE-QUERY POSTMODERN:*DATABASE* "" "select cast ($1 as int)") (CL-POSTGRES:EXEC-PREPARED POSTMODERN:*DATABASE* "" (MAPCAR 'S-SQL:SQL-IZE (LIST (CAR (CL-POSTGRES:EXEC-QUERY POSTMODERN:*DATABASE* "select 12" 'POSTMODERN::COLUMN-ROW-READER)))) 'CL-POSTGRES:LIST-ROW-READER))
The sequence of evaluation is PREPARE-QUERY, then EXEC-QUERY (for the inner "select 12"), then EXEC-PREPARED. I guessed that doing things with the database in-between PREPARE-QUERY and EXEC-PREPARED might be the problem, so I modified QUERY to evaluate prepared-statement arguments before PREPARE-QUERY. This allows the inner query to complete before the outer one actually starts.
diff -urNx '*~' postmodern-1.02/postmodern/query.lisp postmodern-1.02-modified/postmodern/query.lisp --- postmodern-1.02/postmodern/query.lisp 2007-07-25 04:10:42.000000000 -0700 +++ postmodern-1.02-modified/postmodern/query.lisp 2007-07-27 23:03:16.000000000 -0700 @@ -55,9 +55,10 @@ :else :collect arg))) (destructuring-bind (reader single-row) (cdr (assoc format *result-styles*)) (let ((base (if args - `(progn - (prepare-query *database* "" ,(real-query query)) - (exec-prepared *database* "" (mapcar 'sql-ize (list ,@args)) ',reader)) + (let ((arg-values (gensym))) + `(let ((,arg-values (mapcar 'sql-ize (list ,@args)))) + (prepare-query *database* "" ,(real-query query)) + (exec-prepared *database* "" ,arg-values ',reader))) `(exec-query *database* ,(real-query query) ',reader)))) (if single-row `(multiple-value-call 'car-of-first-value ,base)
Now my first example works, as does the result of one prepared-statement QUERY's being indirectly nested in another prepared-statement QUERY.
Hi,
Good catch. I applied a fix, more or less your patch but without the (unneccesary) gensym.
Cheers, Marijn
On Sat, 2007-07-28 at 18:28 +0200, Marijn Haverbeke wrote:
Good catch. I applied a fix, more or less your patch but without the (unneccesary) gensym.
Right, cargo-cult habits at work. =)
I'm doing some stuff involving binary data (geometry information with PostGIS), and I've found encoding binary data with SQL-COMPILE is very slow. For instance, 91 seconds for a 50KiB (VECTOR (UNSIGNED-BYTE 8)). The slowness is in S-SQL:ESCAPE-BYTES and S-SQL:SQL-ESCAPE-STRING. It turns out SBCL's WITH-OUTPUT-TO-STRING is quadratic in time, unless (as Juho Snellman pointed out on #lisp) you bind *PRINT-PRETTY* to NIL.
These functions are linear on both CLISP and OpenMCL.
Before he mentioned that, I spent some time micro-optimizing these two functions. They're ugly--not entirely unlike hairier C code. You could say I ended up implementing a very specific, low-level, non-general form of the functionality of WITH-OUTPUT-TO-STRING.
SQL-COMPILE on the same expression now takes 0.3 seconds, though, and they are about four times faster than simply making that binding. This could be especially important for more heavy-weight users of binary data in PostgreSQL--say, back-ends for object databases. (My own requirement for large binary data is temporary.)
If you're interested, I've attached a file with these functions and some comparison and testing functions (even uglier). If nothing else, could you bind *PRINT-PRETTY* to NIL for the above two functions?
Thanks,
Hahah, 91 seconds is definitely bad -- postgres' binary-data escaping method is pretty stupid, and I implemented that encoder more or less as an afterthought. The performance improvement of your code is impressive... but it is indeed hideous. I'll look into this in the next few days, I'm still hoping I can find a middle ground -- something that performs decently but does not require four screenfuls of c code ;)
Cheers, Marijn
Okay, I have to take back my remarks about the code being ugly -- it is pretty cool actually, very correct and not as big as I originally thought, since most of it is benchmarking code. Still, using textual queries for sending big amounts of binary data just isn't a very good idea, so I'm reluctant to add complex optimizations for that. On the bright side, I was able to reach results almost as good as your code (on SBCL at least, didn't test other implementations) by just turning off *PRINT-PRETTY* and not using FORMAT to create the octal values. A patch for that has been pushed.
cl-postgres has very simple support for sending binary data unescaped -- see the 'blob' test in its test suite for that. But the postmodern wrapper doesn't make use of this yet -- it was added by one of Attila's patches, and not part of my original 'vision' (heh). You could look into changing things so that blobs are passed through unescaped when they are given as 'parameters' to (prepared) queries. I'm not sure how to do that in a nice way -- maybe some kind of generalized system where binary sender functions can be registered for specific types.
Cheers, Marijn
postmodern-devel@common-lisp.net