Paralife
Paralife

Reputation: 6236

In common-lisp is there a way to code an 'apply' equivalent for macros?

I have a macro: mac1(&rest args) that accepts any number of arguments

Since I cannot use apply with a macro, and since I have no control on the macro's implementation, how can I make a function fun1 (lst) that given a list, calls the macro with the expanded list?

UPDATE: I though the question was adequate but it seems better to state my real case:

Postmodern provides the query macro:

macro: query (query &rest args/format) 

For example you call it like this:

(query "select * from example where col1 = $1::integer and col2 = $2::date" 123 "2017-01-01" :str-alists)

Ok, now imagine that at runtime I am passed this list:

'((:query "select * from table1 where c1 = $1 and c2 = $2 and c3 = $3"
   :params (1 "2017-01-01" 3) :return :str-alists

  (:query "select * from tab2 where c3 = $1"
   :params ("somevalue") :return :lists)
   .
   .
   .
  ))

And I have to do the queries. I need to define a function that will take such a list as argument and do the queries. But since query is a macro I have the following problem: I need somewhere to evaluate the lists in order to pass them to the macro. Currently I am doing this (idea taken from @melpomene comments) :

(defun macro-apply(q p ret)
  (eval (macroexpand `(query ,q ,@p ,ret))))

So the function becomes:

(defun exec-queries (lst)
    (mapc 
       (lambda(x) (macro-apply 
                      (getf x :query) 
                      (getf x :params) 
                      (getf x :return))
       lst)) 

Is there a better way, or something obvious that I am missing?

Upvotes: 3

Views: 1182

Answers (2)

coredump
coredump

Reputation: 38799

Regarding your actual problem, note that the macro uses a low-level package, cl-postgres, to implement the queries using functions (see http://quickdocs.org/postmodern/api#system-cl-postgres). The macro is only syntactic sugar for the underlying API. Let's see how the macro is expanded with a parameterized query:

CL-USER> (macroexpand
           '(postmodern:query 
               "select * from table where col1=$1:integer" 
               20))

(PROGN
 (CL-POSTGRES:PREPARE-QUERY POSTMODERN:*DATABASE* ""
                            "select * from table where col1=$1:integer")
 (CL-POSTGRES:EXEC-PREPARED POSTMODERN:*DATABASE* "" (LIST 20)
                            'CL-POSTGRES:LIST-ROW-READER))

As usual with SQL, you first defined prepared statements, to avoid injection attacks. If you reuse the same queries often, it is more efficient to prepare them only once (see defprepared). Then, you execute the queries.

When you also specify the result type (last keyword argument from the query), the macro calls the unexported reader-for-format function, to know which callback function to use for each row.

(macroexpand
 '(postmodern:query "select * from table where col1=$1:integer"
   20
   :str-alist))
(MULTIPLE-VALUE-CALL
    #'(LAMBDA
          (&OPTIONAL (POSTMODERN::ROWS) (POSTMODERN::AFFECTED) &REST #:G843)
        (DECLARE (IGNORE #:G843))
        (IF POSTMODERN::AFFECTED
            (VALUES (CAR POSTMODERN::ROWS) POSTMODERN::AFFECTED)
            (CAR POSTMODERN::ROWS)))
  (PROGN
   (CL-POSTGRES:PREPARE-QUERY POSTMODERN:*DATABASE* ""
                              "select * from table where col1=$1:integer")
   (CL-POSTGRES:EXEC-PREPARED POSTMODERN:*DATABASE* "" (LIST 20)
                              'CL-POSTGRES:ALIST-ROW-READER)))
T

Since the macro already does all the job for you, you can effectively wrap your queries in new forms and evaluate them, provided you don't evaluate arbitrary Lisp forms (prepared statements guard you against SQL injections, but EVAL opens up another attack vector; check your inputs).

Or, try to see how to use the building blocks of the query to produce your own query interpreter. At first glance, it seems like some sort of "inner platform effect" to get arbitrary queries and process them, but maybe you have a good use case for this.

Upvotes: 3

Rainer Joswig
Rainer Joswig

Reputation: 139251

One can also use COMPILE or even COMPILE-FILE + LOAD to deal with that.

For example in some situations it might be useful to output the query forms in some format and some embedding into a file. You can then compile the file and load the code - either to execute on load or later.

Primitive version:

  1. generate a file, put an IN-PACKAGE form into it and dump one or more query forms into it.
  2. compile the file with COMPILE-FILE
  3. handle for errors...
  4. loading the file then executes the queries

If you want to do something with the results, you have to put some more code for that into the file...

This example would require that the function compile-file and the necessary library/macro code is available at runtime.

Upvotes: 2

Related Questions