Hi,
I'm finally at a point with my program where I can send queries to EdgeDB, and I noticed that error messages are pretty unhelpful.
I'm using the eyre
crate (similar to anyhow
), and this is the current error I get:
Error: Error while storing the data in EdgeDB.
Caused by:
EdgeQLSyntaxError: Unexpected ')'
Location:
src/api/sync.rs:563:5
For some reason, the line and column number within the EdgeDB query source code isn't mentioned, which is pretty unhelpful (especially, because EdgeDB queries can be pretty big, because of the great compossibility of EdgeDB's query language).
If I change my main
function to return Result<(), edgedb_tokio::Error>
, the displayed error is as follows:
Error: Error(Inner { code: 67174656, messages: ["Unexpected ')'"], error: None, headers: {257: b"Traceback (most recent call last):\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/common/parsing.py\", line 420, in parse\n self.parser.token(token)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/parsing/lrparser.py\", line 57, in token\n self._act(token, tokenSpec) # type: ignore\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/parsing/lrparser.py\", line 81, in _act\n raise UnexpectedToken(\"Unexpected token: %r\" % sym)\nparsing.errors.UnexpectedToken: Unexpected token: <Token RPAREN \")\">\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler/compiler.py\", line 1738, in _compile\n return self._try_compile(ctx=ctx, source=source)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler/compiler.py\", line 1764, in _try_compile\n statements = edgeql.parse_block(source)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/edgeql/parser/__init__.py\", line 77, in parse_block\n return parser.parse(source)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/common/parsing.py\", line 433, in parse\n raise self.get_exception(\nedb.errors.EdgeQLSyntaxError: Unexpected ')'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/common/parsing.py\", line 420, in parse\n self.parser.token(token)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/parsing/lrparser.py\", line 57, in token\n self._act(token, tokenSpec) # type: ignore\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/parsing/lrparser.py\", line 81, in _act\n raise UnexpectedToken(\"Unexpected token: %r\" % sym)\nparsing.errors.UnexpectedToken: Unexpected token: <Token RPAREN \")\">\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler_pool/worker_proc.py\", line 55, in worker\n res = meth(*args)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler_pool/worker.py\", line 160, in compile\n units, cstate = COMPILER.compile(\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler/compiler.py\", line 2207, in compile\n unit_group = self._compile(ctx=ctx, source=source)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler/compiler.py\", line 1747, in _compile\n raise denormalized_err\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler/compiler.py\", line 1745, in _compile\n self._try_compile(ctx=ctx, source=original)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/server/compiler/compiler.py\", line 1764, in _try_compile\n statements = edgeql.parse_block(source)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/edgeql/parser/__init__.py\", line 77, in parse_block\n return parser.parse(source)\n File \"/home/user/.local/share/edgedb/portable/2.1/lib/python3.10/site-packages/edb/common/parsing.py\", line 433, in parse\n raise self.get_exception(\nedb.errors.EdgeQLSyntaxError: Unexpected ')'\n", 65530: b"1045", 65524: b"17", 65527: b"18", 65528: b"17", 65529: b"1044", 65525: b"16", 65522: b"1045", 65526: b"33", 65521: b"1044", 65523: b"33"} })
...which isn't great either.
If I unwrap
the Result
that contains the error, the displayed error is similar unhelpful.
Unfortunately, edgedb_errors::Error isn't documented (and seems more complicated than it should need to be), so I ended up adding the following code to figure out what is going on:
.map_err(|e| {
for (k, v) in e.headers() {
let v = std::str::from_utf8(v.as_ref()).unwrap();
println!("{k} -> {v}")
}
println!("kind_name => {}", e.kind_name());
println!("kind_debug => {}", e.kind_debug());
println!("initial_message => {:?}", e.initial_message());
println!("hint => {:?}", e.hint());
println!("details => {:?}", e.details());
println!("position_start => {:?}", e.position_start());
println!("position_end => {:?}", e.position_end());
println!("line => {:?}", e.line());
println!("column => {:?}", e.column());
println!("code => {}", e.code());
println!("server_traceback => {:?}", e.server_traceback());
println!(
"contexts => {:#?}",
e.contexts().map(|x| format!("{x:?}")).collect::<Vec<_>>()
);
println!(
"chain => {:#?}",
e.chain().map(|x| format!("{x:?}")).collect::<Vec<_>>()
);
e
})?;
Of that code, the following lines were useful:
println!("line => {:?}", e.line());
println!("column => {:?}", e.column());
It would be great, if the line and column number could be included with displayed errors.
In the meantime (in case someone else who needs this right now is stumbling upon this issue), I've created a small macro that adds the location of errors:
macro_rules! add_err_loc {
() => {
|err| {
let line = line!();
let column = column!();
let msg = if let (Some(line2), Some(column2)) = (err.line(), err.column()) {
format!("EdgeDB query error β at {line2}:{column2} of the query, and at {line}:{column} of the Rust src code.")
} else {
format!("EdgeDB query error β at {line}:{column} of the Rust src code.")
};
eyre::Report::new(err).wrap_err(msg)
}
};
}
...which can be used like this:
db.query::<String, _>("SELECT 'hello'", &())
.await
.map_err(add_err_loc!())?;
This also adds the Rust source code location, which seems to get lost when transactions are used, which is the reason why I've implemented this as a macro.
It's worrying me a bit that there is a Python traceback within the error (that it is there at all, and that the query parsing code fails with a traceback).
Are there any plans to replace that Python code with Rust?
Performance should be good enough with caching, but I'm pretty worried about correctness, which is why I think Rust would be a far better option for such code (code, which seems to handle potential user-supplied input, and therefore could lead to security vulnerabilities).
Rust also has fantastic libraries for displaying errors in source code, that would make it relatively easy to display errors similar to the Rust compiler:
Rust most likely also has better tools for type checking (for example RustTyC, which seems to make it easy to implement a Hindney-Milner-like type system)