Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
* @author John Blum
* @author Mark Paluch
* @author Tomasz Lelek
* @author Ammar Khaku
* @since 3.0
*/
public class CqlSessionFactoryBean
Expand Down Expand Up @@ -450,11 +451,20 @@ public void afterPropertiesSet() {

this.session = buildSession(sessionBuilder);

executeCql(getStartupScripts().stream(), this.session);
performSchemaAction();
try {
SchemaRefreshUtils.withDisabledSchema(this.session, () -> {
executeCql(getStartupScripts().stream(), this.session);
performSchemaAction();
});
} catch (RuntimeException e) {
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason for disabling schema refresh here? And why is there the need to declare and catch exceptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The goal here is to disable schema refresh before applying user-provided schema actions. Datastax4 waits for schema refresh after applying changes and if you're applying multiple it's really slow.

Regarding catching and declaring exceptions: it's a little messy because while performSchemaAction here doesn't throw any exceptions, SessionFactoryFactoryBean#performSchemaAction throws Exception and I want to re-use the code to disable and re-enable schema metadata. That requires me to use a ThrowingRunnable (instead of a Runnable) which means that the entire SchemaRefreshUtils.withDisabledSchema method now needs to declare that it throws an Exception. We know that it will never throw a checked exception for CqlSessionFactoryBean but it may throw an unchecked one, so we re-throw any unchecked RuntimeException while wrapping impossible checked exceptions with IllegalStateException just to get the code to compile without propagating throws Exception up the call stack.

The alternative is to use a Runnable instead of ThrowingRunnable in SessionFactoryFactoryBean#performSchemaAction which would mean that #withDisabledSchema doesn't throw any exceptions, but then in SessionFactoryFactoryBean we need to wrap checked exceptions thrown in our performSchemaActions call with RuntimeException to match the Runnable signature.

So yeah a bit of a mess either way. I looked into removing the throws Exception from SessionFactoryFactoryBean#performSchemaAction for a bit but it comes from the AbstractFactoryBean#getObject signature (that's called inside performSchemaAction) so a little difficult.

Copy link
Member

Choose a reason for hiding this comment

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

I see. We can revisit exceptions in performSchemaAction to remove any unnecessary exceptions.

The goal here is to disable schema refresh before applying user-provided schema actions. Datastax4 waits for schema refresh after applying changes and if you're applying multiple it's really slow.

We should leave schema refresh up to the config. If schema refresh is disabled as per config, we don't need to disable it on our side.

Copy link
Contributor Author

@akhaku akhaku Apr 30, 2022

Choose a reason for hiding this comment

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

We should leave schema refresh up to the config

for anyone looking at this thread in the future: my impression based on what you said is that we should rely only on schema being disabled via config in performSchemaAction, but I see you added setSuspendLifecycleSchemaRefresh and I'm a big fan!

throw e;
} catch (Exception e) {
throw new IllegalStateException("Unexpected checked exception thrown", e);
}

this.systemSession.refreshSchema();
this.session.refreshSchema();
if (this.systemSession.isSchemaMetadataEnabled()) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is the refresh for the actual session removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It actually happens inside SchemaRefreshUtils.withDisabledSchema. It isn't obvious from the PR itself, but from the javadoc of session.setSchemaMetadataEnabled:

If calling this method re-enables the metadata (that is, #isSchemaMetadataEnabled() was false before, and becomes true as a result of the call), a refresh is also triggered.

In other words, if schema refresh becomes re-enabled as a result then a refresh will be triggered.

There is one difference though - previously we'd call refreshSchema which is sync and blocking while the schema refresh here is more equivalent to calling Session#refreshSchemaAsync and discarding the returned CompletionStage. I could update the new code here to wait on the returned future but I don't think it's necessary, let me know if you think otherwise!

this.systemSession.refreshSchema();
Copy link
Member

Choose a reason for hiding this comment

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

The schema refresh in SessionFactoryFactoryBean should be guarded as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe that's already tackled in this PR, let me know if I missed something!

}
}

protected CqlSessionBuilder buildBuilder() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.data.cassandra.config;

import com.datastax.oss.driver.api.core.session.Session;

/**
* Utility methods for executing schema actions with refresh disabled.
*
* @author Ammar Khaku
*/
class SchemaRefreshUtils {
@FunctionalInterface
interface ThrowingRunnable {
void run() throws Exception;
}

/**
* Programmatically disables schema refreshes on the session and runs the provided Runnable,
* taking care to restore the previous state of schema refresh config on the provided session.
* Note that the session could have had schema refreshes enabled/disabled either
* programmatically or via config.
*/
static void withDisabledSchema(Session session, ThrowingRunnable r) throws Exception {
boolean schemaEnabledPreviously = session.isSchemaMetadataEnabled();
session.setSchemaMetadataEnabled(false);
r.run();
session.setSchemaMetadataEnabled(null); // triggers schema refresh if results in true
if (schemaEnabledPreviously != session.isSchemaMetadataEnabled()) {
// user may have set it programmatically so set it back programmatically
session.setSchemaMetadataEnabled(schemaEnabledPreviously);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* keyspace before applying {@link SchemaAction schema actions} such as creating user-defined types and tables.
*
* @author Mark Paluch
* @author Ammar Khaku
* @since 3.0
* @see SessionFactoryInitializer
*/
Expand Down Expand Up @@ -128,9 +129,7 @@ public void afterPropertiesSet() throws Exception {
this.keyspacePopulator.populate(getObject().getSession());
}

performSchemaAction();

this.session.refreshSchema();
SchemaRefreshUtils.withDisabledSchema(session, this::performSchemaAction);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.data.cassandra.config;

import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.datastax.oss.driver.api.core.session.Session;

/**
* Test suite of unit tests testing the contract and functionality of the {@link SchemaRefreshUtils} class.
*/
@ExtendWith(MockitoExtension.class)
class SchemaRefreshUtilsUnitTests {
@Mock Session session;

@Test
void withDisabledSchemaRevert() throws Exception {
when(session.isSchemaMetadataEnabled()).thenReturn(true);
SchemaRefreshUtils.withDisabledSchema(session, () -> {});
verify(session).setSchemaMetadataEnabled(false);
verify(session).setSchemaMetadataEnabled(null);
}

@Test
void withDisabledSchemaDisabledPreviously() throws Exception {
when(session.isSchemaMetadataEnabled()).thenReturn(false);
SchemaRefreshUtils.withDisabledSchema(session, () -> {});
verify(session).setSchemaMetadataEnabled(false);
verify(session).setSchemaMetadataEnabled(null);
}

@Test
void withDisabledSchemaDisabledProgrammaticallyPreviously() throws Exception {
when(session.isSchemaMetadataEnabled()).thenReturn(false).thenReturn(true);
SchemaRefreshUtils.withDisabledSchema(session, () -> {});
verify(session, times(2)).setSchemaMetadataEnabled(false);
verify(session).setSchemaMetadataEnabled(null);
}

@Test
void withDisabledSchemaEnabledProgrammaticallyPreviously() throws Exception {
when(session.isSchemaMetadataEnabled()).thenReturn(true).thenReturn(false);
SchemaRefreshUtils.withDisabledSchema(session, () -> {});
verify(session).setSchemaMetadataEnabled(true);
verify(session).setSchemaMetadataEnabled(null);
}
}