Skip to content

Commit 1a39a22

Browse files
committed
Ensure scoped table retrieval for MySQL/MariaDB connections
Updated table retrieval logic to use `connection.catalog` when no specific catalog is provided. Added tests to verify tables are limited to the active database in MySQL, MariaDB, and MSSQL scenarios. Fixed connection URL constants in test files.
1 parent 7271809 commit 1a39a22

5 files changed

Lines changed: 106 additions & 4 deletions

File tree

dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readDataFrameSchema.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,12 @@ public fun DataFrameSchema.Companion.readAllSqlTables(
432432

433433
// exclude system- and other tables without data
434434
val tableTypes = determinedDbType.tableTypes?.toTypedArray()
435-
val tables = metaData.getTables(null, null, null, tableTypes)
435+
// Same catalog-scoping fix as in readJdbc.kt: use connection.catalog to avoid reading
436+
// tables from all MySQL/MariaDB databases when no specific catalog is given.
437+
val effectiveCatalog = try {
438+
connection.catalog.takeUnless { it.isNullOrBlank() }
439+
} catch (_: Exception) { null }
440+
val tables = metaData.getTables(effectiveCatalog, null, null, tableTypes)
436441

437442
val dataFrameSchemas = mutableMapOf<String, DataFrameSchema>()
438443

dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readJdbc.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,13 @@ public fun DataFrame.Companion.readAllSqlTables(
765765
validateLimit(limit)
766766
val determinedDbType = dbType ?: extractDBTypeFromConnection(connection)
767767
val metaData = connection.metaData
768-
val tablesResultSet = retrieveTableMetadata(metaData, catalogue, determinedDbType)
768+
// Fall back to connection.catalog so that MySQL/MariaDB connections scoped to a specific database
769+
// (e.g. jdbc:mysql://host/mydb) don't leak tables from other databases. MySQL's getTables(null,...)
770+
// returns tables from ALL catalogs, unlike PostgreSQL/SQLite where the connection is already scoped.
771+
val effectiveCatalogue = catalogue ?: try {
772+
connection.catalog.takeUnless { it.isNullOrBlank() }
773+
} catch (_: Exception) { null }
774+
val tablesResultSet = retrieveTableMetadata(metaData, effectiveCatalogue, determinedDbType)
769775

770776
return buildMap {
771777
while (tablesResultSet.next()) {

dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/local/mariadbTest.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import java.util.Date
2727
import kotlin.reflect.typeOf
2828
import kotlin.time.Instant
2929

30-
private const val URL = "jdbc:mariadb://localhost:3307"
30+
private const val URL = "jdbc:mariadb://localhost:3306"
3131
private const val USER_NAME = "root"
3232
private const val PASSWORD = "pass"
3333
private const val TEST_DATABASE_NAME = "testKDFdatabase"
@@ -488,4 +488,33 @@ class MariadbTest {
488488
fun `infer nullability`() {
489489
inferNullability(connection)
490490
}
491+
492+
// https://github.com/Kotlin/dataframe/issues/1746
493+
@Test
494+
fun `readAllSqlTables without catalogue should only return tables from URL database`() {
495+
val secondDb = "testKDFdatabase2"
496+
val testRootConn = DriverManager.getConnection(URL, USER_NAME, PASSWORD)
497+
try {
498+
testRootConn.createStatement().use { stmt ->
499+
stmt.executeUpdate("DROP DATABASE IF EXISTS $secondDb")
500+
stmt.executeUpdate("CREATE DATABASE $secondDb")
501+
}
502+
DriverManager.getConnection("$URL/$secondDb", USER_NAME, PASSWORD).use { conn2 ->
503+
conn2.createStatement().use { stmt ->
504+
stmt.executeUpdate("CREATE TABLE onlyInDb2 (id INT PRIMARY KEY, val VARCHAR(50))")
505+
}
506+
}
507+
508+
DriverManager.getConnection("$URL/$TEST_DATABASE_NAME", USER_NAME, PASSWORD).use { scopedConn ->
509+
val tableNames = DataFrame.readAllSqlTables(scopedConn).keys
510+
511+
tableNames.none { "onlyInDb2" in it } shouldBe true
512+
tableNames.any { "table1" in it } shouldBe true
513+
}
514+
} finally {
515+
testRootConn.use { conn ->
516+
conn.createStatement().execute("DROP DATABASE IF EXISTS $secondDb")
517+
}
518+
}
519+
}
491520
}

dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/local/mssqlTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,37 @@ class MSSQLTest {
296296
fun `infer nullability`() {
297297
inferNullability(connection)
298298
}
299+
300+
// https://github.com/Kotlin/dataframe/issues/1746
301+
@Test
302+
fun `readAllSqlTables without catalogue should only return tables from URL database`() {
303+
val secondDb = "testKDFdatabase2"
304+
val testRootConn = DriverManager.getConnection(URL, USER_NAME, PASSWORD)
305+
try {
306+
testRootConn.createStatement().use { stmt ->
307+
stmt.executeUpdate(
308+
"IF DB_ID('$secondDb') IS NOT NULL DROP DATABASE $secondDb"
309+
)
310+
stmt.executeUpdate("CREATE DATABASE $secondDb")
311+
}
312+
DriverManager.getConnection("$URL;databaseName=$secondDb", USER_NAME, PASSWORD).use { conn2 ->
313+
conn2.createStatement().use { stmt ->
314+
stmt.executeUpdate("CREATE TABLE onlyInDb2 (id INT PRIMARY KEY, val VARCHAR(50))")
315+
}
316+
}
317+
318+
DriverManager.getConnection("$URL;databaseName=$TEST_DATABASE_NAME", USER_NAME, PASSWORD).use { scopedConn ->
319+
val tableNames = DataFrame.readAllSqlTables(scopedConn).keys
320+
321+
tableNames.none { "onlyInDb2" in it } shouldBe true
322+
tableNames.any { "Table1" in it } shouldBe true
323+
}
324+
} finally {
325+
testRootConn.use { conn ->
326+
conn.createStatement().execute(
327+
"IF DB_ID('$secondDb') IS NOT NULL DROP DATABASE $secondDb"
328+
)
329+
}
330+
}
331+
}
299332
}

dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/local/mysqlTest.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import java.util.Date
2727
import kotlin.reflect.typeOf
2828
import kotlin.time.Instant
2929

30-
private const val URL = "jdbc:mysql://localhost:3306"
30+
private const val URL = "jdbc:mysql://localhost:3307"
3131
private const val USER_NAME = "root"
3232
private const val PASSWORD = "pass"
3333
private const val TEST_DATABASE_NAME = "testKDFdatabase"
@@ -491,4 +491,33 @@ class MySqlTest {
491491
fun `infer nullability`() {
492492
inferNullability(connection)
493493
}
494+
495+
// https://github.com/Kotlin/dataframe/issues/1746
496+
@Test
497+
fun `readAllSqlTables without catalogue should only return tables from URL database`() {
498+
val secondDb = "testKDFdatabase2"
499+
val testRootConn = DriverManager.getConnection(URL, USER_NAME, PASSWORD)
500+
try {
501+
testRootConn.createStatement().use { stmt ->
502+
stmt.executeUpdate("DROP DATABASE IF EXISTS $secondDb")
503+
stmt.executeUpdate("CREATE DATABASE $secondDb")
504+
}
505+
DriverManager.getConnection("$URL/$secondDb", USER_NAME, PASSWORD).use { conn2 ->
506+
conn2.createStatement().use { stmt ->
507+
stmt.executeUpdate("CREATE TABLE onlyInDb2 (id INT PRIMARY KEY, val VARCHAR(50))")
508+
}
509+
}
510+
511+
DriverManager.getConnection("$URL/$TEST_DATABASE_NAME", USER_NAME, PASSWORD).use { scopedConn ->
512+
val tableNames = DataFrame.readAllSqlTables(scopedConn).keys
513+
514+
tableNames.none { "onlyInDb2" in it } shouldBe true
515+
tableNames.any { "table1" in it } shouldBe true
516+
}
517+
} finally {
518+
testRootConn.use { conn ->
519+
conn.createStatement().execute("DROP DATABASE IF EXISTS $secondDb")
520+
}
521+
}
522+
}
494523
}

0 commit comments

Comments
 (0)