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

Fix NPE issue when batch execution is called without any queries #207

Merged
merged 3 commits into from
Jan 22, 2024

Conversation

T45K
Copy link
Contributor

@T45K T45K commented Jan 19, 2024

Motivation:
(Please describe the problem you are trying to solve, or the new feature you are trying to add.)

we're using this R2DBC client with jOOQ.
NPE occurs when we give empty list to DSLContext#batch

val queries = emptyList()
dslContext.batch(queries).awaitLast()

this is because StringBuilder in MySqlBatchingBatch is not initialized until add method is called.
so, execute method will refer to not-initialized builder when add method is not called.

Modification:
(Please describe the changes you have made to the codebase, including any new files, modified files, or deleted files.)

modify execute to return Flux#empty when builder is not initialized (i.e., add method is not called).
send empty string to client without NPE

Result:
(Please describe the expected outcome of your changes, and any potential side effects or drawbacks.
If possible, please also include any relevant testing results.)

it now does not throw NPE but returns empty flux instead.

@T45K T45K changed the title add guard clause Fix NPE issue when batch execution is called without any queries Jan 19, 2024
@mirromutth
Copy link
Contributor

mirromutth commented Jan 19, 2024

The io.r2dbc.spi.Batch spec shows a specification like following:

Batch objects are run by calling the execute() method after adding one or more SQL statements to a Batch.

So we cannot return an empty Flux. Here are some suggestions:

  • Execute an empty query. Then server may return an error (e.g. MySQL and MariaDB), or do nothing (e.g. TiDB)
  • Emit an exception that should not be a NPE, and it cannot be thrown directly.
    • In r2dbc-mariadb MariadbBatch.java#L92, it will emit a NoSuchElementException due to iterator.next() without checking
    • Note: the empty checking should be deferred.

However, it seems like a code smell coming from MySqlBatchingBatch.builder, could you please add a @Nullable to the builder property? Then, we can see a null-check warning at MySqlBatchingBatch.getSql(), so we can add a null-check to the getSql() instead of execute(), which should make sense.

@T45K
Copy link
Contributor Author

T45K commented Jan 19, 2024

thank you for confirming my PR!

The io.r2dbc.spi.Batch spec shows a specification like following:

Batch objects are run by calling the execute() method after adding one or more SQL statements to a Batch.

sorry, i didn't know the spi spec 🙏

Execute an empty query. Then server may return an error (e.g. MySQL and MariaDB), or do nothing (e.g. TiDB)

i think this is more reasonable. i'd like to rewrite in this way.


(question)

i think we can make builder not-null by initializing it when MySqlBatchingBatch is constructed.

-     private StringBuilder builder;
+     private final StringBuilder builder = new StringBuilder();

and then, requireBuilder can be rewritten like

    private StringBuilder requireBuilder() {
-      if (builder == null) {
-          return (builder = new StringBuilder());
-      }

+        if (builder.length() == 0) {
+            return builder;
+        }

        return builder.append(';');
    }

this looks better for me. what do you think?

@mirromutth
Copy link
Contributor

mirromutth commented Jan 19, 2024

i think we can make builder not-null by initializing it when MySqlBatchingBatch is constructed.

Hmmm, if we check only it is empty or not, consider following code:

batch1.add(";").add("SELECT 1").execute()
batch2.add("").add("SELECT 1").execute()
batch3.add("SELECT 1").execute()

There Batchs will execute same query SELECT 1, but first and second Batchs should be ;SELECT 1, third should be SELECT 1.

Maybe it would be simpler to refactor the entire builder into a List. Note that statement joins should be deferred.

@T45K
Copy link
Contributor Author

T45K commented Jan 19, 2024

i see. i'll update this PR later

Copy link
Contributor Author

@T45K T45K left a comment

Choose a reason for hiding this comment

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

@mirromutth
i updated this PR. could you check this again?

Copy link
Contributor

@mirromutth mirromutth left a comment

Choose a reason for hiding this comment

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

LGTM

@jchrys jchrys merged commit fa2ed46 into asyncer-io:trunk Jan 22, 2024
12 checks passed
@jchrys jchrys added the enhancement New feature or request label Jan 24, 2024
@jchrys jchrys added this to the 1.1.0/0.10.0 milestone Jan 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants