1 - connect to DB (with potential error)
2 - run query (with potential error)
3 - return query result
4 - make sure the connection is closed
The most concise way to write this would be:
try # catch all errors here
conn = db_connect(...)
result = db_query(conn, ...)
finally # make sure you disconnect
disconnect(conn)
end
do_stuff_with_query_result(result)
But due to the scoping rules, you end up with something like:
conn = db_connect(...) # this will require another try - catch if you want to handle the exception
result = try
db_query(conn, ...)
finally
disconnect(conn)
end
do_stuff_with_query_result(result)
It seems unnecessarily complicated. Is there any other way to handle this?
Also, why isn’t a variable defined inside try available inside finally ? I expected that the whole try ... end is just one scope block.
julia> try
a = 2+2
finally
@show a
end
------ UndefVarError ------------------- Stacktrace (most recent call last)
[1] — anonymous at <missing>:?
[2] — macro expansion; at REPL[1]:4 [inlined]
[3] — macro expansion; at show.jl:218 [inlined]
UndefVarError: a not defined
If variables that are created inside the try block are available inside the finally block, then that implicitly assumes that part of the code inside the try block will run successfully. But the point of try is that code inside it might fail…
It’s similar to result in my first example. It is initialised but if the try fails, it will reference something else (whatever the catch will return).
And that needs to be checked after the try block. So it just moves the check somewhere else.
I often handle this problem by making an zero or empty value for result before doing the try catch finally dance.
result = QueryResult()
conn = DBConnection()
try
conn = connect(params)
res = query(conn, "select fields from tables")
finally
close(conn)
end
return res
Then when you call it you check that res != QueryResult() to make sure that the query ran successfully.
This only works for types that have a valid “uninitialized” state. If there is no such “invalid” value, then you can use something like https://github.com/iamed2/ResultTypes.jl. Where you return the value that contains the data and another value of a Error type to indicate failure.
As a final option, you can use local conn, result to introduce the variables into the outer scope (even without a definition). They’re then inherited into the try/catch/finally scopes. This has the downside of an undefined variable error if db_connect fails before it returns a value for conn.
Yeah, once you start going down the rabbit hole it gets crazy fast.
Luckily, with the notable exception of GUI apps, DB connection errors are usually allowed to bubble all the way up and crash the app since you can’t do anything without the DB handle.