12

I'm the author of SQL Fiddle. That knowledge may help frame this question:

I am attempting to write some Groovy code which will execute any arbitrary query/DML code in my database. Based on my reading of the Groovy Sql API, all of these functions expect something very particular. For example, "eachRow" expects there to be a resultset returned; if it isn't returned (as in the case of an UPDATE statement, for example) then using it will throw an error. I am able to call "execute" with any type of statement, however I can't get back a resultset for my SELECT statements when using that (which is definitely a requirement).

At this point I'm thinking I might have to abandon Groovy's Sql library in favor of some lower-level JDBC implementation. This would be a shame, I think, but I'm willing to go there if necessary. I would greatly prefer to keep this as Groovy-esqe as possible, though. How might I go about doing that?

Jake Feasel
  • 16,785
  • 5
  • 53
  • 66

1 Answers1

3

Have a look at the varieties of execute() methods which returns a boolean. The return type as per doc:

true if the first result is a ResultSet object; false if it is an update count or there are no results

CREATE, DROP and INSERT has been mentioned in the example. I hope it works the same way for UPDATE.

For SELECT, the flag has to be checked if there is a ResultSet present. If yes, then a second query can be fired to get the rows. For example:

//If statement qualifies for select statements
//because .execute() will return true if there is a ResultSet
//present. It will be false for CREATE, UPDATE and DROP.
if( sql.execute("select name from Foo") ) {

    //Fire the second query to get the rows in case of SELECT query
    result = sql.rows "select name from Foo"
}

return result

UPDATE
If there is a concern about executing query twice for performance reasons, then the below approach can be tried:

def queryString = "update Foo set name = 'hello' where name = 'baz'"

try {
    sql.query queryString, { ResultSet rs ->
        //Result set returned for select query only
        //For all other cases exception is thrown
    }
} catch( java.sql.SQLException ) {
    //Throws exception for any other type of query
    //By now you should be smart enough exception is not 
    //thrown for any other cause.

    sql.execute queryString
}
dmahapatro
  • 49,365
  • 7
  • 88
  • 117
  • This is a good suggestion, however I forgot to mention in my question that I did try this already. The problem is, I don't want to execute select statements twice. They could be quite intensive queries, so having to run them twice would be a huge waste of resources. – Jake Feasel Mar 23 '14 at 16:20
  • Then you can parse the query string to see if it [`startsWith`](http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#startsWith(java.lang.String)) `select` then use `sql.rows` otherwise go ahead with `sql.execute`? – dmahapatro Mar 23 '14 at 16:22
  • 3
    @dmahapatro That is a rather naive solution: some other statement types are capable of returning rows as well; and not all select statements start with `SELECT`. – Mark Rotteveel Mar 23 '14 at 16:25
  • 1
    What is an example of a select statement that doesn't start with select? By definition doesn't that make it not a select statement? Genuinely curious. – grantmcconnaughey Mar 23 '14 at 16:52
  • @MarkRotteveel Yes that was naive. But I believe in moving baby steps and face the issue first before worrying about lot of stuffs. However, here goes another baby step as an update to my answer to face the situation. Hope that helps. ;) – dmahapatro Mar 23 '14 at 17:08
  • 4
    @grantmc For example common table expressions (`WITH alias (....) SELECT`) (supported by SQL Server, [Firebird](http://www.firebirdsql.org/file/documentation/reference_manuals/reference_material/html/langrefupd25-select.html#langrefupd25-select-cte), Oracle, PostgreSQL and others) – Mark Rotteveel Mar 23 '14 at 17:13
  • @MarkRotteveel Any other example? If I want to show more naivity about it . ;). `startsWith select || with` – dmahapatro Mar 23 '14 at 17:17
  • @dmahapatro yes, this updated approach is closer. Part of the problem is what you allude to in the comment - errors can be thrown for lots of reasons, so I would have to parse out the ones related to not having a result set. It's possible I'll go with this solution, but it still seems pretty clunky. – Jake Feasel Mar 23 '14 at 18:02
  • Can try if you can extend `groovy.sql.Sql` and make utilize its extension methods to create your own [AbstractQueryCommand](http://groovy.codehaus.org/api/groovy/sql/Sql.AbstractQueryCommand.html) and then execute the query. Eventually I feel you would end up differentiating the path. Right now with the latest answer and additional check for the exception message with the verbiage as - *Method is only allowed for a query. Use execute or executeUpdate instead of executeQuery* will do the job for you to differentiate what caused the exception,in this case the unfit method for the query string :) – dmahapatro Mar 23 '14 at 21:01
  • Other examples are insert, update, delete, merge etc with the returning clause and consider queries that are prefixed with a comment. It is not unsolvable, but the jdbc Api already has a solution that groovy unfortunately excludes. – Mark Rotteveel Mar 23 '14 at 21:24
  • Counter examples are cases where with and select are used with an update. – Mark Rotteveel Mar 23 '14 at 21:35
  • @JakeFeasel Another option can be to call a SP every time with the content of the SP based on the inputs of the user(DMl/DDL or mix), and then use [Sql.call()](http://beta.groovy-lang.org/docs/groovy-2.3.0/html/gapi/) which would return a result set in case there is one. One caveat here, is the SP has to be per database, so you would end up with separate formats of SPs for each of them. WRT this [answer](http://stackoverflow.com/a/10804730/2051952) SP can be used for both DML and DDL and can be made sure to return an implicit cursor. I can give it a try if you think this is a feasible option. – dmahapatro May 11 '14 at 21:31
  • @dmahapatro I think I would rather explore the route of using a lower-level JDBC API before I attempted to use stored procedures. Keep in mind that I would need to implement a stored-procedure wrapper that worked in every database I support (PG,MY,MS,Oracle) - would be a serious hassle. – Jake Feasel May 12 '14 at 16:08