-
Notifications
You must be signed in to change notification settings - Fork 4
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
Resetting Schema and Catalog, when connection returend to pool #104
Merged
rbygrave
merged 1 commit into
ebean-orm:master
from
FOCONIS:fix-changing-schema-catalog-on-connection-pool
Oct 23, 2024
+129
−10
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
ebean-datasource/src/test/java/io/ebean/datasource/pool/SchemaTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package io.ebean.datasource.pool; | ||
|
||
import io.ebean.datasource.DataSourceConfig; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.sql.Connection; | ||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import java.sql.Statement; | ||
|
||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | ||
|
||
class SchemaTest { | ||
|
||
private final ConnectionPool pool; | ||
|
||
SchemaTest() { | ||
pool = createPool(); | ||
} | ||
|
||
private ConnectionPool createPool() { | ||
DataSourceConfig config = new DataSourceConfig(); | ||
config.setUrl("jdbc:h2:mem:tests"); | ||
config.setUsername("sa"); | ||
config.setPassword(""); | ||
config.setMinConnections(2); | ||
config.setMaxConnections(4); | ||
|
||
return new ConnectionPool("test", config); | ||
} | ||
|
||
@BeforeEach | ||
void initTables() throws SQLException { | ||
try (Connection conn = pool.getConnection()) { | ||
Statement statement = conn.createStatement(); | ||
statement.execute("CREATE TABLE test (name VARCHAR(255))"); | ||
statement.execute("INSERT INTO test (name) VALUES ('default schema')"); | ||
statement.execute("CREATE SCHEMA SCHEMA1"); | ||
statement.execute("CREATE SCHEMA SCHEMA2"); | ||
conn.commit(); | ||
} | ||
try (Connection conn = pool.getConnection()) { | ||
String orig = conn.getSchema(); | ||
conn.setSchema("SCHEMA1"); | ||
Statement statement = conn.createStatement(); | ||
statement.execute("CREATE TABLE test (name VARCHAR(255))"); | ||
statement.execute("INSERT INTO test (name) VALUES ('schema1')"); | ||
conn.setSchema("SCHEMA2"); | ||
statement.execute("CREATE TABLE test (name VARCHAR(255))"); | ||
statement.execute("INSERT INTO test (name) VALUES ('schema2')"); | ||
conn.setSchema(orig); | ||
conn.commit(); | ||
} | ||
} | ||
|
||
@AfterEach | ||
void after() { | ||
pool.shutdown(); | ||
} | ||
|
||
@Test | ||
void getConnectionWithSchemaSwitch() throws SQLException { | ||
try (Connection conn = pool.getConnection()) { | ||
Statement statement = conn.createStatement(); | ||
ResultSet rs = statement.executeQuery("SELECT name FROM test"); | ||
assertThat(rs.next()).isTrue(); | ||
assertThat(rs.getString("name")).isEqualTo("default schema"); | ||
} | ||
try (Connection conn = pool.getConnection()) { | ||
conn.setSchema("SCHEMA1"); | ||
Statement statement = conn.createStatement(); | ||
ResultSet rs = statement.executeQuery("SELECT name FROM test"); | ||
assertThat(rs.next()).isTrue(); | ||
assertThat(rs.getString("name")).isEqualTo("schema1"); | ||
} | ||
try (Connection conn = pool.getConnection()) { | ||
conn.setSchema("SCHEMA2"); | ||
Statement statement = conn.createStatement(); | ||
ResultSet rs = statement.executeQuery("SELECT name FROM test"); | ||
assertThat(rs.next()).isTrue(); | ||
assertThat(rs.getString("name")).isEqualTo("schema2"); | ||
} | ||
try (Connection conn = pool.getConnection()) { | ||
Statement statement = conn.createStatement(); | ||
ResultSet rs = statement.executeQuery("SELECT name FROM test"); | ||
assertThat(rs.next()).isTrue(); | ||
assertThat(rs.getString("name")).isEqualTo("default schema"); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @rbygrave thanks for merging,
this PR solves our current problem.
But I want to share a few more thoughts that occurred to me about this PR:
getSchema
andgetCatalog
may communicate with the database. In other words, we send some TCP/IP packets over the network. Since this code is only called when the pool grows, which should rarely happen, it will not cost much runtime.But what is your philosophy here?
resetXXX
is set)The latter point in particular is giving me a headache: as long as you use the
setSchema
andsetCatalog
methods, everything works correctly. But as soon as someone would manually execute a statement likeset schema xyz
(oruse database xyz
), the pool doesn't recognize a schema change and the connection points to the wrong database.How I discovered this:
You may remember our problem that we currently have a separate connection pool for each tenant, which leads to a resource problem for many tenants. (Ebean runs with
TenantMode.DB_WITH_MASTER
)We have now experimented to see whether we can use a connection pool and simply switch the schema beforehand. (We still use
DB_WITH_MASTER
but ourTenantDataSourceProvider
only provides a simple delegate data source that takes care of the schema change)In concrete terms, there is a
new SchemaSwitchingDataSource(config.getDataSource(), tenantSchema)
for each tenant, which then points to the MASTER db and uses theconfig.getDataSource()
pool from ebean.This also worked well for the tenants, as long as the SchemaSwitchingDataSource is always used and a schema change is carried out beforehand.
The code that runs in our management tenant (=MASTER) uses plain
config.getDataSource()
and here I noticed that I was sometimes in the wrong schema (fixed by this PR)As mentioned above, as long as
setSchema
/setCatalog
is used, everything works now. But if some bad written code will invoke aset schema xyz
statement, the management tenant may point to the wrong schema when it picks that connection, because it does no schema switch here. (By the way, the same problem may exist also for autocommit and isolation level - but it is not as bad as if incorrect data is accessed, as would be the case here.)So I'm wondering,
DataSourcePoolListener.onAfterBorrowConnection
that checks the connection or use also aSchemaSwitchingDataSource
for the master DB (I think I will take this option)I don't think there is any need for action on your part for now, but I wanted to share this with you.
Roland