Skip to content

Add table function to execute stored procedure in MySQL & SQLServer#15813

Closed
Praveen2112 wants to merge 1 commit intotrinodb:masterfrom
Praveen2112:praveen/stored_procedure_ptf
Closed

Add table function to execute stored procedure in MySQL & SQLServer#15813
Praveen2112 wants to merge 1 commit intotrinodb:masterfrom
Praveen2112:praveen/stored_procedure_ptf

Conversation

@Praveen2112
Copy link
Copy Markdown
Member

@Praveen2112 Praveen2112 commented Jan 23, 2023

Description

A dedicated table function which allows us to run stored procedures in MySQL and SQLServer. We can't use existing query table function - as it wraps with inside a query like SELECT * FROM (CALL my_procedure()) o - so we are handing them via a different function. All the pushdown are disabled for TableHandle resolved by this TableFunction.

Limitation

  • In case of stored procedures with multiple statement - we process the ResultSet only for the first statement. (Test to be added)
  • Stored procedures with output variables are not supported.
  • Input variables for the stored procedures are not parameterized(yet).

Example

SELECT * FROM TABLE(system.procedure(query => 'CALL my_new_procedure()')

Release notes

( ) This is not user-visible or docs only and no release notes are required.
( ) Release notes are required, please propose a release note for me.
( ) Release notes are required, with the following suggested text:

# Section
* Fix some things. ({issue}`issuenumber`)

@findepi
Copy link
Copy Markdown
Member

findepi commented Jan 23, 2023

Description

Additional context and related issues

Release notes

( ) This is not user-visible or docs only and no release notes are required. ( ) Release notes are required, please propose a release note for me. ( ) Release notes are required, with the following suggested text:

# Section
* Fix some things. ({issue}`issuenumber`)

If you're not filling the PR template, please remove it from the PR description so that it's obvious to the reader there there isn't any information to look for.

Thanks

@Praveen2112
Copy link
Copy Markdown
Member Author

@findepi Sorry for the confusion. Have updated the description.

@Praveen2112 Praveen2112 marked this pull request as ready for review January 23, 2023 16:37
Optional.empty());
}

private List<JdbcColumnHandle> getColumns(ConnectorSession session, Connection connection, ResultSetMetaData metadata)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will extract it to a new commit.

Copy link
Copy Markdown
Member

@skrzypo987 skrzypo987 left a comment

Choose a reason for hiding this comment

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

Skimmed thoroughly. Looks good

}
}

private static final class TableHandlesByProcedureCacheKey
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make it a record

public Optional<TableFunctionApplicationResult<ConnectorTableHandle>> applyTableFunction(ConnectorSession session, ConnectorTableFunctionHandle handle)
{
if (!(handle instanceof QueryFunctionHandle)) {
if (!(handle instanceof QueryFunctionHandle || handle instanceof ProcedureFunctionHandle)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit:
I think it would be clearer to do:

if (handle instanceof QueryFunctionHandle queryFunctionHandle) {
  ...
else if (handle instanceof ProcedureFunctionHandle procedureFunctionHandle) {
...
}
else{
    return Optional.empty();
}

.map(ColumnSchema::getName)
.map(columnHandlesByName::get)
.collect(toImmutableList());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rollback


import static java.util.Objects.requireNonNull;

public final class ProcedureQuery
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

record

super(
SCHEMA_NAME,
NAME,
List.of(ScalarArgumentSpecification.builder()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ImmutableList.of

}
}

public static class ProcedureFunctionHandle
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And another record

assertThat(e).hasMessageMatching("Column name must be shorter than or equal to '128' characters but got '129': '.*'");
}

@Test
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These tests look very similar to the ones in MySQL connector. Is it reasonable to put them in jdbc module as an abstract class?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Our initial implementation was to add in BaseJdbcConnectorTest - but not all JDBC based source supports it directly (Only MySQL and SqlServer returns a ResultSet - some of them works only with output parameter and some returns ResultSet with no fields. Plus both MySQL and SQLServer has a different way of creating and executing these stored procedures - and it might be some sort of overkill to merges these tests now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you believe this is too much of a hassle, I am fine with leaving that as it is.

@findepi
Copy link
Copy Markdown
Member

findepi commented Jan 24, 2023

Thank you for an informative PR description, @Praveen2112 !
It helps me understand what the change is about.

A dedicated table function which allows us to run stored procedures in MySQL and SQLServer. We can't use existing query table function - as it wraps with inside a query like SELECT * FROM (CALL my_procedure()) o - so we are handing them via a different function

We don't need a separate function for that.

Within io.trino.plugin.jdbc.BaseJdbcClient#getTableHandle(io.trino.spi.connector.ConnectorSession, io.trino.plugin.jdbc.PreparedQuery) we analyze the query passed by the user to retrieve the columns.
We can also analyze whether the query works when wrapped in SELECT¹. If it doesn't work when wrapped, we disable all pushdowns. This can be done by either by a flag in table handle, or by not accepting io.trino.plugin.jdbc.DefaultJdbcMetadata#applyTableFunction for such function, i.e. by not converting it to a table handle at all. I am in favor of this second approach.

¹) this can be done without overhead for the common case, where query can be wrapped. Simply, instead of analyzing the query results, we can first ask the connector to analyze results of SELECT * FROM (%s). If that doesn't work, we can fall back to analyzing the query without wrapping, and consider it unwrappable.

Copy link
Copy Markdown
Member

@findepi findepi left a comment

Choose a reason for hiding this comment

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

When writing my earlier #15813 (comment) i was overly focused on the query wrapping problem, which is only part of the problem.
As explained offline, the other part of the problem is the necessity to use the prepareCallable. For that reason, I agree that we cannot use the existing query pass-through table function and we indeed need a new function. Thank you @Praveen2112 for taking time to explain this to me.

To implement such a function, we should not use the JdbcTableHandle though. I think the best way would be not to implement io.trino.plugin.jdbc.DefaultJdbcMetadata#applyTableFunction and go with normal table function execution path. It's code complete (#15575) and already proven by @homar to work well.

throws SQLException
{
if (table.getRelationHandle() instanceof JdbcProcedureRelationHandle jdbcProcedureRelationHandle) {
return queryBuilder.callProcedure(this, session, connection, jdbcProcedureRelationHandle.getProcedureQuery());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this ignore all the attributes that may be set on table?

{
JdbcTableHandle handle = (JdbcTableHandle) table;
if (handle.isProcedure()) {
return Optional.empty();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That means handle is not really a table handle / a relation handle.

@Praveen2112
Copy link
Copy Markdown
Member Author

Closing in favor of #15982

@Praveen2112 Praveen2112 closed this Feb 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants