crystal-lang / crystal-sqlite3

SQLite3 bindings for Crystal
https://crystaldoc.info/github/crystal-lang/crystal-sqlite3
MIT License
139 stars 30 forks source link

Exception handling : Desired clarifications #52

Open hutou opened 4 years ago

hutou commented 4 years ago

Hi, I am trying to understand how exceptions are handled in Crystal-sqlite3. Given the following code :

require "sqlite3"
DBCONN = DB.connect "sqlite3://%3Amemory%3A"
begin
  DBCONN.exec "create table test ( id integer primary key, code text not null unique)"
  DBCONN.exec "insert into test(code) values('A')"
  DBCONN.exec "insert into test(code) values('A')"
rescue e
  if e.message =~ /UNIQUE constraint failed/i
    puts ("Already in database")
  else
    puts (e.message)
  end
ensure
  DBCONN.close
end

output is :

Already in database Unhandled exception: UNIQUE constraint failed: test.code (SQLite3::Exception) from lib/sqlite3/src/sqlite3/statement.cr:81:5 in 'check' from lib/sqlite3/src/sqlite3/statement.cr:37:5 in 'do_close' from lib/db/src/db/disposable.cr:11:7 in 'close' from lib/db/src/db/connection.cr:60:38 in 'do_close' from lib/sqlite3/src/sqlite3/connection.cr:34:5 in 'do_close' from lib/db/src/db/disposable.cr:11:7 in 'close' from test1a.cr:14:3 in '__crystal_main' from /usr/lib/crystal/crystal/main.cr:97:5 in 'main_user_code' from /usr/lib/crystal/crystal/main.cr:86:7 in 'main' from /usr/lib/crystal/crystal/main.cr:106:3 in 'main' from __libc_start_main from _start from ???

Why does Crystal tell me the exception is unhandled when the displayed message in the rescue clause "proves" it was rescued ?

I noticed that if I comment out the DBCONN.close statement, I only get the "Already in database" message, no more unhandled exception.

If however, I run the following program :

require "sqlite3"
DBCONN = DB.connect "sqlite3://%3Amemory%3A"
begin
  DBCONN.exec "create table test ( id integer primary key, code text not null unique)"
  DBCONN.exec "insert into test(code) values('A')"
  sql = "select 1 from test where code = 'Z'"
  code = DBCONN.query_one sql, as: {String}
rescue e
  puts (e.message)
ensure
  DBCONN.close
end

the output is a bare

no rows

So, no more unhandled exception and I'm somewhat puzzled : can you enlighten me on this? Are there different "levels" of exceptions ? I also noticed that putting the DBCONN.close in an at_exit statement produce the same "unhandled exception" for the first code snippet.

Thanks

bcardiff commented 4 years ago

The unhandled error is triggered by the DBCONN.close that is why it is unhandled: there is no rescue to catch it.

It seems that https://github.com/crystal-lang/crystal-sqlite3/blob/master/src/sqlite3/statement.cr#L37 should not be checked according to https://stackoverflow.com/a/8391872/30948 🤔

In the second example, there is effectively no row to return (insert A, filter Z), plus there is no print of code.

Workaround to ignore the exception in DBCONN.close: DBCONN.close rescue nil

hutou commented 4 years ago

Thanks for the explanations and the workaround.

bcardiff commented 4 years ago

It’s still something that should be fixed. I will leave this open until then.

Thanks for reporting it.