Skip to content

Commit 5e54fe2

Browse files
committed
Move sql error message formatting into Ruby to handle newlines
and handle versions of sqlite3 without sqlite3_error_offset()
1 parent 6374581 commit 5e54fe2

File tree

4 files changed

+78
-32
lines changed

4 files changed

+78
-32
lines changed

ext/sqlite3/exception.c

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,18 @@ rb_sqlite3_raise_with_sql(sqlite3 *db, int status, const char *sql)
105105
return;
106106
}
107107

108-
VALUE error_message = Qnil;
109-
const char *sqlite_error_msg = sqlite3_errmsg(db);
110-
int error_offset = sqlite3_error_offset(db);
108+
const char *error_msg = sqlite3_errmsg(db);
109+
int error_offset = -1;
110+
#ifdef HAVE_SQLITE3_ERROR_OFFSET
111+
error_offset = sqlite3_error_offset(db);
112+
#endif
111113

112-
// Create a more detailed error message
113-
if (error_offset >= 0 && sql) {
114-
error_message = rb_sprintf(
115-
"%s:\n%s\n%*s^\n",
116-
sqlite_error_msg, sql, error_offset, ""
117-
);
118-
} else {
119-
error_message = rb_str_new2(sqlite_error_msg);
120-
}
121-
122-
VALUE exception = rb_exc_new3(klass, error_message);
114+
VALUE exception = rb_exc_new2(klass, error_msg);
123115
rb_iv_set(exception, "@code", INT2FIX(status));
124-
rb_iv_set(exception, "@error_offset", INT2FIX(error_offset));
116+
if (sql) {
117+
rb_iv_set(exception, "@sql", rb_str_new2(sql));
118+
rb_iv_set(exception, "@sql_offset", INT2FIX(error_offset));
119+
}
125120

126121
rb_exc_raise(exception);
127122
}

ext/sqlite3/extconf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def configure_extension
132132

133133
have_func("sqlite3_prepare_v2")
134134
have_func("sqlite3_db_name", "sqlite3.h") # v3.39.0
135+
have_func("sqlite3_error_offset", "sqlite3.h") # v3.38.0
135136

136137
have_type("sqlite3_int64", "sqlite3.h")
137138
have_type("sqlite3_uint64", "sqlite3.h")

lib/sqlite3/errors.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@ module SQLite3
44
class Exception < ::StandardError
55
# A convenience for accessing the error code for this exception.
66
attr_reader :code
7+
8+
# If the error is associated with a SQL query, this is the query
9+
attr_reader :sql
10+
11+
# If the error is associated with a particular offset in a SQL query, this is the integer offset.
12+
attr_reader :sql_offset
13+
14+
def message
15+
[super, sql_error].compact.join(":\n")
16+
end
17+
18+
private def sql_error
19+
return nil unless @sql
20+
return @sql unless @sql_offset.is_a?(Integer)
21+
22+
offset = @sql_offset
23+
sql.lines.flat_map do |line|
24+
if offset >= 0 && line.length > offset
25+
blanks = " " * offset
26+
offset = -1
27+
[line.chomp, blanks + "^"]
28+
else
29+
offset -= line.length if offset
30+
line.chomp
31+
end
32+
end.join("\n")
33+
end
734
end
835

936
class SQLException < Exception; end

test/test_integration.rb

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,18 @@ def test_prepare_exception_shows_error_position
111111
exception = assert_raise(SQLite3::SQLException) do
112112
@db.prepare "select from foo"
113113
end
114-
assert_equal(<<~MSG, exception.message)
115-
near "from": syntax error:
116-
select from foo
117-
^
118-
MSG
114+
if exception.sql_offset >= 0 # HAVE_SQLITE_ERROR_OFFSET
115+
assert_equal(<<~MSG.chomp, exception.message)
116+
near "from": syntax error:
117+
select from foo
118+
^
119+
MSG
120+
else
121+
assert_equal(<<~MSG.chomp, exception.message)
122+
near "from": syntax error:
123+
select from foo
124+
MSG
125+
end
119126
end
120127

121128
def test_prepare_exception_shows_error_position_newline1
@@ -125,12 +132,20 @@ def test_prepare_exception_shows_error_position_newline1
125132
from foo
126133
SQL
127134
end
128-
assert_equal(<<~MSG, exception.message)
129-
near "from": syntax error:
130-
select
131-
from foo
132-
^
133-
MSG
135+
if exception.sql_offset >= 0 # HAVE_SQLITE_ERROR_OFFSET
136+
assert_equal(<<~MSG.chomp, exception.message)
137+
near "from": syntax error:
138+
select
139+
from foo
140+
^
141+
MSG
142+
else
143+
assert_equal(<<~MSG.chomp, exception.message)
144+
near "from": syntax error:
145+
select
146+
from foo
147+
MSG
148+
end
134149
end
135150

136151
def test_prepare_exception_shows_error_position_newline2
@@ -140,12 +155,20 @@ def test_prepare_exception_shows_error_position_newline2
140155
from foo
141156
SQL
142157
end
143-
assert_equal(<<~MSG, exception.message)
144-
no such column: asdf:
145-
select asdf
146-
^
147-
from foo
148-
MSG
158+
if exception.sql_offset >= 0 # HAVE_SQLITE_ERROR_OFFSET
159+
assert_equal(<<~MSG.chomp, exception.message)
160+
no such column: asdf:
161+
select asdf
162+
^
163+
from foo
164+
MSG
165+
else
166+
assert_equal(<<~MSG.chomp, exception.message)
167+
no such column: asdf:
168+
select asdf
169+
from foo
170+
MSG
171+
end
149172
end
150173

151174
def test_prepare_invalid_column

0 commit comments

Comments
 (0)