Closed swi2012 closed 3 years ago
Hello @swi2012 ,
thanks for filing a bug report and taking a time to make a reproducing example. Sadly a crucial component is still missing for me to learn what exactly goes wrong, namely I do not have access to an SAP system to test against. So your help going further would be greatly appreciated.
Thanks to your example and the error message I can tell you two things so far.
Invalid attribute value
roughly means I requested a functionality from the ODBC driver, it does not know about. Sadly I do not know much more than that, due to the lacking context of which ODBC function with what arguments caused this exception. It would be a huge help to me if you could step into bind_buffer
with a debugger and tell me about the call stack, then the error occurrs.odbc
crate is abandoned is correct. I abandoned odbc
and odbc-safe
because my initial assumption that I could build higher level abstractions on top of a safe abstractions of ODBC turned out to be wrong. I am aiming for a zero cost abstraction, but that implies that I can not abstract the low level stuff safely. I do not think that these crates can be salvaged without breaking the interface so much that it is in essence a new crate. This is why odbc-api
exists.odbc
crate, it is likely due to the fact that the odbc
crate does not bind a buffer to the statement before it is executed. I do not use this technique in the examples and discourage it in the documentation, as it usually is a horrible inefficient way to extract data (I've seen real world use cases being 500x times slower and they are not the exception). Yet you can do the same thing with this crate by calling Cursor::next_row
directly on the cursor without binding a buffer first. You may then call either get_data
, get_text
or get_binary
to retrieve data if the option contains a row.That is all I can say for now without either improving diagnostics, or having access to a test system to reproduce the error. To reiterate: You helping me to figure out which ODBC function call within bind_buffer
would help me a lot in figuring out the root cause of this.
Cheers, Markus
Thanks for answer.
I'm new to Rust so if you can explain to me what i need to do to give your more info on this issue i'm gladly give you more info. I.e. how can i view what function called (gdb?) within bind_buffer
.
Hello @swi2012 ,
thanks for helping out. Yeah, I think setting up a debugger is the right way to go. I know little about your background or platform, so I won't write a complete tutorial on how to setup a debugger for Rust. Yet I gladly give you some pointers. Personally I use vscode. You should install the rust-analyzer
extension. For debugging I go with the C/C++ extension on windows, on Linux or OS-X I use the CodeLLDB extension.
With these extensions setup you should be able to debug the application and step into function calls. You may want to set a breakpoint here: https://github.com/pacman82/odbc-api/blob/8fbde14920c3a2e0f42aa3321c430f5155a06792/odbc-api/src/handles/error.rs#L48.
This is where ODBC return values are translated into errors. I am interessted at the callstack at the moment you example reaches this line.
Hope this helps you moving forward. Otherwise please get back to me.
Cheers, Markus
I'm on linux. Emacs+Rustic+LSP (rust-analyzer). I've tried to debug in console with rust-gdb but can't figure out how to set breakpoint in odbc_api::handlers:error:48 (gdb mark this bp as pdending because of it located in shared lib). I've set dap-mode for emacs, run it first time and get to the bp you specified. I dont realy understand is this picture is right or something not working right in my debug environment but at least i see self and handle there.
I use neither Emacs, Rustor or LSP. So I do not understand your setup. Yet the screenshot looks like a debug session and the little green dot looks suspiciously like a breakpoint. So you did a lot of things right. Sadly I can not see the callstack anywhere in the screenshot. You can try this:
bt
in gdb console session.odbc-api
codebase to panic!
at that point and set RUST_BACKTRACE=1.I've tried to use your hint to get more info from gdb.
Thread 1 "oapitest" hit Breakpoint 1, odbc_api::handles::error::{impl#0}::into_result (self=..., handle=...) at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/error.rs:48
warning: Source file is more recent than executable.
48 let mut rec = DiagnosticRecord::default();
(gdb) bt
#0 odbc_api::handles::error::{impl#0}::into_result (self=..., handle=...) at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/error.rs:48
#1 0x000055555556d9bb in odbc_api::handles::statement::{impl#3}::set_row_array_size (self=0x7fffffffd8a0, size=100000) at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/statement.rs:530
#2 0x00005555555620bb in odbc_api::cursor::{impl#6}::bind_buffer<odbc_api::handles::statement::StatementImpl, &mut odbc_api::buffers::text_row_set::TextRowSet> (self=..., row_set_buffer=0x7fffffffdfc0)
at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/cursor.rs:389
#3 0x00005555555665e6 in oapitest::main () at src/main.rs:16
(gdb) i frame
Stack level 0, frame at 0x7fffffffd820:
rip = 0x55555557013f in odbc_api::handles::error::{impl#0}::into_result (/home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/error.rs:48); saved rip = 0x55555556d9bb
called by frame at 0x7fffffffd860
source language rust.
Arglist at 0x7fffffffd698, args: self=..., handle=...
Locals at 0x7fffffffd698, Previous frame's sp is 0x7fffffffd820
Saved registers:
rip at 0x7fffffffd818
(gdb) n
49 if rec.fill_from(handle, 1) {
(gdb) bt
#0 odbc_api::handles::error::{impl#0}::into_result (self=..., handle=...) at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/error.rs:49
#1 0x000055555556d9bb in odbc_api::handles::statement::{impl#3}::set_row_array_size (self=0x7fffffffd8a0, size=100000) at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/statement.rs:530
#2 0x00005555555620bb in odbc_api::cursor::{impl#6}::bind_buffer<odbc_api::handles::statement::StatementImpl, &mut odbc_api::buffers::text_row_set::TextRowSet> (self=..., row_set_buffer=0x7fffffffdfc0)
at /home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/cursor.rs:389
#3 0x00005555555665e6 in oapitest::main () at src/main.rs:16
(gdb) i frame
Stack level 0, frame at 0x7fffffffd820:
rip = 0x5555555701b6 in odbc_api::handles::error::{impl#0}::into_result (/home/morsov/.cargo/registry/src/github.com-1ecc6299db9ec823/odbc-api-0.24.1/src/handles/error.rs:49); saved rip = 0x55555556d9bb
called by frame at 0x7fffffffd860
source language rust.
Arglist at 0x7fffffffd698, args: self=..., handle=...
Locals at 0x7fffffffd698, Previous frame's sp is 0x7fffffffd820
Saved registers:
rip at 0x7fffffffd818
(gdb)
As i understand function that hit error was set_row_array_size
, maybe i'm wrong.
Hello @swi2012 ,
sorry for the delayed response, but I do not have internet these next few days. The information you provided is exactly what I needed thanks. I discovered that u32
instead usize
had been used to transport the row_array_size
. This has been fixed in version 0.25.0
. Please tell me if this fixes your error. If it does not please also try it with BATCH_SIZE
1
and 5000
. Tell me how it goes.
I'll write a more thourough response once I have a bit more time (and internet).
Cheers, Markus
Thanks for the answer. On 0.25 version with BATCH_SIZE up to 65000 no error appear. At 66000 it throw Error: Diagnostics { record: State: HY024, Native error: 0, Message: [SAP][ODBC Driver]Invalid attribute value, function: "SQLSetStmtAttrW" }
Hello @swi2012 ,
Thank you for conducting the experiments. From what you tell me I would guess the SAP ODBC driver uses a 16 Bit integer to store the batch size. This would make the upper bound 65536. It is fine for ODBC drivers to have an upper bound. Here is what the reference has to say in the matter:
If the specified rowset size exceeds the maximum rowset size supported by the data source, the driver substitutes that value and returns SQLSTATE 01S02 (Option value changed).
Found here: https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetstmtattr-function?view=sql-server-ver15
I read this as the driver should emit a warning and just emit smaller batches. You may want to file a bug with SAP. On a practical matter 65536 should be enough for all applications, and even too large for most. After all your App has to preallocate all that memory.
Cheer, Markus
From the viewpoint of this crate, I will improve the error on invalid attributes then specifing array size. But I must wait until I have internet again.
Cheers Markus
Thank for your answer. I think just constanting MAX buf value not bigger than 64k for my app will be fine enough. I think write to SAP about that would be useless cause they point out that they have native C/C++ C# drivers for that and have no support for rust at all. Well, their own sql server manager app on windows works through odbc :) Thanks a lot for you help. Now i can play with my DB with odbc-api crate futher.
Hello @swi2012 ,
I added tests for binding large buffers with the three db systems I currently test with. Non of them reproduce the error though. In any case the crate is now emmiting a way more meaninful error in version 0.26.0
. Feel free to tell me if you can actually observe it, and would find it helpful.
Cheers, Markus
Error: InvalidRowArraySize { record: State: HY024, Native error: 0, Message: [SAP][ODBC Driver]Invalid attribute value, size: 66000 }
Yep. Thanks a lot for your work!
Hello. I've tried example from guide to make simple query for SAP sqlanywhere db with odbc source. but get error message from odbc driver
odbc.ini:
code example:
crate
odbc
is working with this odbc driver, but it's seems to be abandoned.