Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite call procedure #376

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

Rewrite call procedure #376

wants to merge 2 commits into from

Conversation

markdirish
Copy link
Contributor

This PR changes the procedure workflow from:

SQLProcedures -> SQLProcedureColumns -> SQLExecDirect

to

SQLPrepare -> SQLDescribeParam -> SQLBind -> SQLExecDirect

This will allow users to call a procedure with just the procedure name, even if that procedure exists in multiple schemas/libraries.

@markdirish markdirish requested a review from kadler April 3, 2024 02:34
@brandonp42
Copy link

Will this allow CLOB parameters on procedure calls? See #355 for context

@brandonp42
Copy link

Hi Mark,

Just curious how close you are to releasing this change?

Thanks!
Brandon

Copy link
Member

@kadler kadler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it should be ok, at least for the IBM i Access driver, but we may need to do some extra work to enable automatic IPD support on other drivers and for drivers that don't support it, we still don't have a solution.

Comment on lines +1278 to +1286
parameterString[0] = '\0';

for (int i = 0; i < data->parameterCount; i++) {
if (i == (data->parameterCount - 1)) {
strcat(parameterString, "?"); // for last parameter, don't add ','
} else {
strcat(parameterString, "?,");
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the above does work, it's inefficient. Though it likely doesn't matter in the grand scheme of things.

char* buffer = parameterString;

for (int i = 0; i < data->parameterCount; i++) {
  buffer[0] = '?';
  buffer[1] = ',';
  buffer += 2;
}

if (data->parameterCount) {
  buffer[-1] = '\0';
}
else {
  buffer[0] = '\0';
}

Or using a C++ string would be even easier, since you could get rid of all the memory management:


string parameterString;
parameterString.reserve(parameterStringSize);

for (int i = 0; i < data->parameterCount; i++) {
  parameterString += "?,";
}

if (data->parameterCount) {
  // remove trailing comma
  parameterString.resize(parameterStringSize-1);
}

Comment on lines +1288 to +1301
SQLUINTEGER procedure_name_length = 0;
if (data->catalog)
{
procedure_name_length += strlen((const char *)data->catalog) + 1;
}
if (data->schema)
{
procedure_name_length += strlen((const char *)data->schema) + 1;
}
// "procedure" is always passed to this API
procedure_name_length += strlen((const char *)data->procedure);

#ifndef UNICODE
char *combinedProcedureName = new char[1024]();
char *combinedProcedureName = new char[procedure_name_length + 1]();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this code could be simplified by using a std::string as well. Could just reserve say 256 or 512 bytes which should be enough in the majority of cases for the whole CALL statement and then just use std::string appends to put it all together (or std::wstring with UNICODE). In the event that not enough space was reserved, the string will automatically resize itself and continue on and then it will automatically free it's memory when it destructs too.

So code is simpler and no manual memory management.

Comment on lines +1391 to +1399
SQLGetDescField
(
hdesc,
i + 1,
SQL_DESC_PARAMETER_TYPE,
&buffer,
0,
0
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an IPD, the field is set to SQL_PARAM_INPUT by default if the IPD is not automatically populated by the driver (the SQL_ATTR_ENABLE_AUTO_IPD statement attribute is SQL_FALSE). An application should set this field in the IPD for parameters that are not input parameters.

I don't know that we can rely on this for all drivers, unfortunately. Looks like it's supposed to be off by default, so it seems our driver is behaving contrary to spec: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/automatic-population-of-the-ipd

@brandonp42
Copy link

Would it make sense to use the old code path for drivers that don't support IPD?

@kadler
Copy link
Member

kadler commented Jun 12, 2024

Given the problems we've had with it, I think it'd be a better idea to design a way to have the user specify the direction as-needed.

Maybe something like:

// IN, OUT, INOUT exported by the node-odbc package
params = [
    { data: 1, direction: IN },
    { data: undefined, direction: OUT },
    { data: 'hello', direction: INOUT },
}
const result = await connection.callProcedure(null, null, 'MY_PROC', params);

Not sure if it actually matters on OUT vs INOUT. If not, it could be simplified by just binding them both as INOUT and then the direction field could be changed to a boolean is_output. Although, now that I'm thinking about it could just bind everything as INOUT in that case and not even worry about it. But that all hinges on whether database drivers are ok with binding that way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants