pacman82 / odbc-api

ODBC (Open Database Connectivity) bindings for Rust.
MIT License
129 stars 23 forks source link

SQLAnywhere gives odbc driver error #93

Closed swi2012 closed 3 years ago

swi2012 commented 3 years ago

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

Hello, world!
row set1
Error: Diagnostics(State: HY024, Native error: 0, Message: [SAP][ODBC Driver]Invalid attribute value)

odbc.ini:

[sybase]
Driver=/opt/sqlanywhere17/lib64/libdbodbc17_r.so
DatabaseName=demo
ServerName=local
CommLinks=tcpip(host=localhost;port=2638)
CharSet=UTF-8

code example:

use odbc_api::{buffers::TextRowSet, Cursor, Environment};
use std::error::Error;
const BATCH_SIZE: u32 = 100_000;

fn main() -> Result<(), Box<dyn Error>> {
    println!("Hello, world!");

    let environment = unsafe { Environment::new() }?;

    let conn = environment.connect("sybase", "***", "***")?;

    match conn.execute("select current date", ())? {
        Some(cursor) => {
            let mut buffers = TextRowSet::for_cursor(BATCH_SIZE, &cursor, Some(4096))?;
            println!("row set1");
            let mut row_set_cursor = cursor.bind_buffer(&mut buffers)?;
            println!("row set2");

            while let Some(batch) = row_set_cursor.fetch()? {
                for row_index in 0..batch.num_rows() {
                    let rec = (0..batch.num_cols())
                        .map(|col_index| batch.at(col_index, row_index).unwrap_or(&[]));
                    println!("{:#?}", rec);
                }
            }
        }
        None => {
            eprintln!("Query is empty.");
        }
    }
    Ok(())
}

crate odbc is working with this odbc driver, but it's seems to be abandoned.

pacman82 commented 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.

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

swi2012 commented 3 years ago

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.

pacman82 commented 3 years ago

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

swi2012 commented 3 years ago

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.

odbc-api-1

pacman82 commented 3 years ago

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:

swi2012 commented 3 years ago

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.

pacman82 commented 3 years ago

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

swi2012 commented 3 years ago

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" }

pacman82 commented 3 years ago

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

pacman82 commented 3 years ago

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

swi2012 commented 3 years ago

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.

pacman82 commented 3 years ago

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

swi2012 commented 3 years ago

Error: InvalidRowArraySize { record: State: HY024, Native error: 0, Message: [SAP][ODBC Driver]Invalid attribute value, size: 66000 } Yep. Thanks a lot for your work!