diff --git a/dataframe-jdbc/api/dataframe-jdbc.api b/dataframe-jdbc/api/dataframe-jdbc.api index 43b8f1cf82..bd372d8c40 100644 --- a/dataframe-jdbc/api/dataframe-jdbc.api +++ b/dataframe-jdbc/api/dataframe-jdbc.api @@ -66,32 +66,40 @@ public final class org/jetbrains/kotlinx/dataframe/io/ReadJdbcKt { public static synthetic fun readAllSqlTables$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/Map; public static synthetic fun readAllSqlTables$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/Map; public static synthetic fun readAllSqlTables$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/Map; - public static final fun readDataFrame (Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readDataFrame (Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun readDataFrame (Ljava/sql/ResultSet;Ljava/sql/Connection;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun readDataFrame (Ljava/sql/ResultSet;Lorg/jetbrains/kotlinx/dataframe/io/db/DbType;Ljava/lang/Integer;Z)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readDataFrame (Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readDataFrame (Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readDataFrame$default (Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readDataFrame (Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readDataFrame (Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readDataFrame$default (Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static synthetic fun readDataFrame$default (Ljava/sql/ResultSet;Ljava/sql/Connection;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static synthetic fun readDataFrame$default (Ljava/sql/ResultSet;Lorg/jetbrains/kotlinx/dataframe/io/db/DbType;Ljava/lang/Integer;ZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readDataFrame$default (Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readDataFrame$default (Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readDataFrame$default (Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readDataFrame$default (Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun readResultSet (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/ResultSet;Ljava/sql/Connection;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static final fun readResultSet (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/ResultSet;Lorg/jetbrains/kotlinx/dataframe/io/db/DbType;Ljava/lang/Integer;Z)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static synthetic fun readResultSet$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/ResultSet;Ljava/sql/Connection;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; public static synthetic fun readResultSet$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/ResultSet;Lorg/jetbrains/kotlinx/dataframe/io/db/DbType;Ljava/lang/Integer;ZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readSqlQuery (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readSqlQuery (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readSqlQuery (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readSqlQuery$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readSqlQuery$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readSqlQuery$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readSqlTable (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readSqlTable (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static final fun readSqlTable (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readSqlTable$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readSqlTable$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; - public static synthetic fun readSqlTable$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readSqlQuery (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readSqlQuery (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readSqlQuery (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readSqlQuery$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readSqlQuery$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readSqlQuery$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readSqlTable (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readSqlTable (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static final fun readSqlTable (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readSqlTable$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljava/sql/Connection;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readSqlTable$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Ljavax/sql/DataSource;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; + public static synthetic fun readSqlTable$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame$Companion;Lorg/jetbrains/kotlinx/dataframe/io/DbConnectionConfig;Ljava/lang/String;Ljava/lang/Integer;ZLorg/jetbrains/kotlinx/dataframe/io/db/DbType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; +} + +public final class org/jetbrains/kotlinx/dataframe/io/SqlValidation : java/lang/Enum { + public static final field None Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation; + public static final field ReadOnly Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation; + public static fun values ()[Lorg/jetbrains/kotlinx/dataframe/io/SqlValidation; } public abstract class org/jetbrains/kotlinx/dataframe/io/db/AdvancedDbType : org/jetbrains/kotlinx/dataframe/io/db/DbType { diff --git a/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readDataFrameSchema.kt b/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readDataFrameSchema.kt index aa94cadb89..86c8ea3d3f 100644 --- a/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readDataFrameSchema.kt +++ b/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readDataFrameSchema.kt @@ -110,7 +110,6 @@ public fun DataFrameSchema.Companion.readSqlTable( limit = 1, inferNullability = false, // Schema extraction doesn't need nullability inference dbType = determinedDbType, - strictValidation = true, ) return singleRowDataFrame.schema() @@ -211,7 +210,6 @@ public fun DataFrameSchema.Companion.readSqlQuery( limit = 1, inferNullability = false, // Schema extraction doesn't need nullability inference dbType = determinedDbType, - strictValidation = true, ) return singleRowDataFrame.schema() diff --git a/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readJdbc.kt b/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readJdbc.kt index 9af8492626..d351eb09e9 100644 --- a/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readJdbc.kt +++ b/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/readJdbc.kt @@ -48,8 +48,6 @@ private val logger = KotlinLogging.logger {} * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [dbConfig]. - * @param [strictValidation] if `true`, the method validates that the provided table name is in a valid format. - * Default is `true` for strict validation. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the data from the SQL table. @@ -60,12 +58,11 @@ public fun DataFrame.Companion.readSqlTable( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) return withReadOnlyConnection(dbConfig, dbType) { conn -> - readSqlTable(conn, tableName, limit, inferNullability, dbType, strictValidation, configureStatement) + readSqlTable(conn, tableName, limit, inferNullability, dbType, configureStatement) } } @@ -80,8 +77,6 @@ public fun DataFrame.Companion.readSqlTable( * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [dataSource]. - * @param [strictValidation] if `true`, the method validates that the provided table name is in a valid format. - * Default is `true` for strict validation. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the data from the SQL table. @@ -94,7 +89,6 @@ public fun DataFrame.Companion.readSqlTable( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) @@ -105,7 +99,6 @@ public fun DataFrame.Companion.readSqlTable( limit, inferNullability, dbType, - strictValidation, configureStatement, ) } @@ -122,8 +115,6 @@ public fun DataFrame.Companion.readSqlTable( * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [connection]. - * @param [strictValidation] if `true`, the method validates that the provided table name is in a valid format. - * Default is `true` for strict validation. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the data from the SQL table. @@ -136,16 +127,12 @@ public fun DataFrame.Companion.readSqlTable( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) - if (strictValidation) { - require(isValidTableName(tableName)) { - "The provided table name '$tableName' is invalid. Please ensure it matches a valid table name in the database schema." - } - } else { - logger.warn { "Strict validation is disabled. Make sure the table name '$tableName' is correct." } + require(isValidTableName(tableName)) { + "The table name '$tableName' is not a valid SQL identifier. " + + "Only Unicode letters, digits, underscores, and dots (for schema-qualified names) are allowed." } val determinedDbType = dbType ?: extractDBTypeFromConnection(connection) @@ -231,8 +218,9 @@ private fun executeQueryAndBuildDataFrame( * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [dbConfig]. - * @param [strictValidation] if `true`, the method validates that the provided query is in a valid format. - * Default is `true` for strict validation. + * @param [validation] controls SQL query validation. [SqlValidation.ReadOnly] blocks DDL/DML statements and + * only allows read-oriented queries (SELECT, WITH, VALUES, TABLE, EXPLAIN). + * [SqlValidation.None] (default) passes the query to the database without validation. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the result of the SQL query. @@ -243,21 +231,18 @@ public fun DataFrame.Companion.readSqlQuery( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, + validation: SqlValidation = SqlValidation.None, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) return withReadOnlyConnection(dbConfig, dbType) { conn -> - readSqlQuery(conn, sqlQuery, limit, inferNullability, dbType, strictValidation, configureStatement) + readSqlQuery(conn, sqlQuery, limit, inferNullability, dbType, validation, configureStatement) } } /** * Converts the result of an SQL query to the DataFrame. * - * __NOTE:__ SQL query should start from SELECT and contain one query for reading data without any manipulation. - * It should not contain `;` symbol. - * * @param [dataSource] the [DataSource] to obtain a database connection from. * @param [sqlQuery] the SQL query to execute. * @param [limit] the maximum number of rows to retrieve from the result of the SQL query execution. @@ -266,8 +251,9 @@ public fun DataFrame.Companion.readSqlQuery( * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [dataSource]. - * @param [strictValidation] if `true`, the method validates that the provided query is in a valid format. - * Default is `true` for strict validation. + * @param [validation] controls SQL query validation. [SqlValidation.ReadOnly] blocks DDL/DML statements and + * only allows read-oriented queries (SELECT, WITH, VALUES, TABLE, EXPLAIN). + * [SqlValidation.None] (default) passes the query to the database without validation. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the result of the SQL query. @@ -280,21 +266,18 @@ public fun DataFrame.Companion.readSqlQuery( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, + validation: SqlValidation = SqlValidation.None, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) dataSource.connection.use { connection -> - return readSqlQuery(connection, sqlQuery, limit, inferNullability, dbType, strictValidation, configureStatement) + return readSqlQuery(connection, sqlQuery, limit, inferNullability, dbType, validation, configureStatement) } } /** * Converts the result of an SQL query to the DataFrame. * - * __NOTE:__ SQL query should start from SELECT and contain one query for reading data without any manipulation. - * It should not contain `;` symbol. - * * @param [connection] the database connection to execute the SQL query. * @param [sqlQuery] the SQL query to execute. * @param [limit] the maximum number of rows to retrieve from the result of the SQL query execution. @@ -303,8 +286,9 @@ public fun DataFrame.Companion.readSqlQuery( * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [connection]. - * @param [strictValidation] if `true`, the method validates that the provided query is in a valid format. - * Default is `true` for strict validation. + * @param [validation] controls SQL query validation. [SqlValidation.ReadOnly] blocks DDL/DML statements and + * only allows read-oriented queries (SELECT, WITH, VALUES, TABLE, EXPLAIN). + * [SqlValidation.None] (default) passes the query to the database without validation. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the result of the SQL query. @@ -317,17 +301,15 @@ public fun DataFrame.Companion.readSqlQuery( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, + validation: SqlValidation = SqlValidation.None, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) - if (strictValidation) { + if (validation == SqlValidation.ReadOnly) { require(isValidSqlQuery(sqlQuery)) { - "SQL query should start from SELECT and contain one query for reading data without any manipulation. " + - "Also it should not contain any separators like `;`." + "SQL validation failed: query is not a read-only statement. " + + "Only SELECT, WITH, VALUES, TABLE, and EXPLAIN queries are allowed in ReadOnly mode." } - } else { - logger.warn { "Strict validation is disabled. Ensure the SQL query '$sqlQuery' is correct and safe." } } val determinedDbType = dbType ?: extractDBTypeFromConnection(connection) @@ -358,18 +340,16 @@ public fun DataFrame.Companion.readSqlQuery( * Even if [DbConnectionConfig.readOnly] is set to `false`, the library still prevents data-modifying queries * and only permits safe `SELECT` operations internally. * - * @param [sqlQueryOrTableName] the SQL query to execute or name of the SQL table. - * It should be a name of one of the existing SQL tables, - * or the SQL query should start from SELECT and contain one query for reading data without any manipulation. - * It should not contain `;` symbol. + * @param [sqlQueryOrTableName] the SQL query to execute or the name of an SQL table. * @param [limit] the maximum number of rows to retrieve from the result of the SQL query execution. * `null` (default) means no limit - all available rows will be fetched * or positive integer (e.g., `100`) - fetch at most that many rows * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [DbConnectionConfig]. - * @param [strictValidation] if `true`, the method validates that the provided query or table name is in a valid format. - * Default is `true` for strict validation. + * @param [validation] controls SQL query validation when a query (not a table name) is provided. + * [SqlValidation.ReadOnly] blocks DDL/DML; [SqlValidation.None] (default) skips validation. + * Table name validation is always strict regardless of this parameter. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the result of the SQL query. @@ -379,7 +359,7 @@ public fun DbConnectionConfig.readDataFrame( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, + validation: SqlValidation = SqlValidation.None, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) @@ -390,7 +370,7 @@ public fun DbConnectionConfig.readDataFrame( limit, inferNullability, dbType, - strictValidation, + validation, configureStatement, ) @@ -400,7 +380,6 @@ public fun DbConnectionConfig.readDataFrame( limit, inferNullability, dbType, - strictValidation, configureStatement, ) @@ -413,18 +392,16 @@ public fun DbConnectionConfig.readDataFrame( /** * Converts the result of an SQL query or SQL table (by name) to the DataFrame. * - * @param [sqlQueryOrTableName] the SQL query to execute or name of the SQL table. - * It should be a name of one of the existing SQL tables, - * or the SQL query should start from SELECT and contain one query for reading data without any manipulation. - * It should not contain `;` symbol. + * @param [sqlQueryOrTableName] the SQL query to execute or the name of an SQL table. * @param [limit] the maximum number of rows to retrieve from the result of the SQL query execution. * `null` (default) means no limit - all available rows will be fetched * or positive integer (e.g., `100`) - fetch at most that many rows * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [Connection]. - * @param [strictValidation] if `true`, the method validates that the provided query or table name is in a valid format. - * Default is `true` for strict validation. + * @param [validation] controls SQL query validation when a query (not a table name) is provided. + * [SqlValidation.ReadOnly] blocks DDL/DML; [SqlValidation.None] (default) skips validation. + * Table name validation is always strict regardless of this parameter. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the result of the SQL query. @@ -434,7 +411,7 @@ public fun Connection.readDataFrame( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, + validation: SqlValidation = SqlValidation.None, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) @@ -445,7 +422,7 @@ public fun Connection.readDataFrame( limit, inferNullability, dbType, - strictValidation, + validation, configureStatement, ) @@ -455,7 +432,6 @@ public fun Connection.readDataFrame( limit, inferNullability, dbType, - strictValidation, configureStatement, ) @@ -487,18 +463,16 @@ public fun Connection.readDataFrame( * val queryDF = dataSource.readDataFrame("SELECT * FROM orders WHERE amount > 100") * ``` * - * @param [sqlQueryOrTableName] the SQL query to execute or name of the SQL table. - * It should be a name of one of the existing SQL tables, - * or the SQL query should start from SELECT and contain one query for reading data without any manipulation. - * It should not contain `;` symbol. + * @param [sqlQueryOrTableName] the SQL query to execute or the name of an SQL table. * @param [limit] the maximum number of rows to retrieve from the result of the SQL query execution. * `null` (default) means no limit - all available rows will be fetched * or positive integer (e.g., `100`) - fetch at most that many rows * @param [inferNullability] indicates how the column nullability should be inferred. * @param [dbType] the type of database, could be a custom object, provided by user, optional, default is `null`, * in that case the [dbType] will be recognized from the [DataSource]. - * @param [strictValidation] if `true`, the method validates that the provided query or table name is in a valid format. - * Default is `true` for strict validation. + * @param [validation] controls SQL query validation when a query (not a table name) is provided. + * [SqlValidation.ReadOnly] blocks DDL/DML; [SqlValidation.None] (default) skips validation. + * Table name validation is always strict regardless of this parameter. * @param [configureStatement] optional lambda to configure the [PreparedStatement] before execution. * This allows for custom tuning of fetch size, query timeout, and other JDBC parameters. * @return the DataFrame containing the result of the SQL query. @@ -510,7 +484,7 @@ public fun DataSource.readDataFrame( limit: Int? = null, inferNullability: Boolean = true, dbType: DbType? = null, - strictValidation: Boolean = true, + validation: SqlValidation = SqlValidation.None, configureStatement: (PreparedStatement) -> Unit = {}, ): AnyFrame { validateLimit(limit) @@ -522,7 +496,7 @@ public fun DataSource.readDataFrame( limit, inferNullability, dbType, - strictValidation, + validation, configureStatement, ) @@ -532,7 +506,6 @@ public fun DataSource.readDataFrame( limit, inferNullability, dbType, - strictValidation, configureStatement, ) @@ -853,7 +826,6 @@ private fun readTableAsDataFrame( limit, inferNullability, dbType, - true, configureStatement, ) diff --git a/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/validationUtil.kt b/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/validationUtil.kt index 03a64d53dc..d5b11f0014 100644 Binary files a/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/validationUtil.kt and b/dataframe-jdbc/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/validationUtil.kt differ diff --git a/dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/h2/h2Test.kt b/dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/h2/h2Test.kt index 5944a9e07e..ebf44794a9 100644 --- a/dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/h2/h2Test.kt +++ b/dataframe-jdbc/src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/h2/h2Test.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlinx.dataframe.io.h2 import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource +import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.matchers.shouldBe @@ -15,6 +16,7 @@ import org.jetbrains.kotlinx.dataframe.api.cast import org.jetbrains.kotlinx.dataframe.api.filter import org.jetbrains.kotlinx.dataframe.api.select import org.jetbrains.kotlinx.dataframe.io.DbConnectionConfig +import org.jetbrains.kotlinx.dataframe.io.SqlValidation import org.jetbrains.kotlinx.dataframe.io.db.H2 import org.jetbrains.kotlinx.dataframe.io.db.H2.Mode import org.jetbrains.kotlinx.dataframe.io.db.MySql @@ -634,26 +636,53 @@ class JdbcTest { """ shouldThrow { - DataFrame.readSqlQuery(connection, createSQL) + DataFrame.readSqlQuery(connection, createSQL, validation = SqlValidation.ReadOnly) } shouldThrow { - DataFrame.readSqlQuery(connection, dropSQL) + DataFrame.readSqlQuery(connection, dropSQL, validation = SqlValidation.ReadOnly) } shouldThrow { - DataFrame.readSqlQuery(connection, alterSQL) + DataFrame.readSqlQuery(connection, alterSQL, validation = SqlValidation.ReadOnly) } shouldThrow { - DataFrame.readSqlQuery(connection, deleteSQL) + DataFrame.readSqlQuery(connection, deleteSQL, validation = SqlValidation.ReadOnly) } shouldThrow { - DataFrame.readSqlQuery(connection, repeatedSQL) + DataFrame.readSqlQuery(connection, repeatedSQL, validation = SqlValidation.ReadOnly) } } + @Test + fun `readSqlTable should reject pre-quoted table names and suggest unquoted form`() { + // Pre-quoted names are rejected because the library adds quoting automatically. + // Users should pass unquoted identifiers: schema.table, not "schema"."table". + val preQuotedNames = listOf( + "\"Customer\"", // ANSI double-quoted + "\"PUBLIC\".\"Customer\"", // ANSI double-quoted schema.table + "`Customer`", // MySQL backtick-quoted + "`PUBLIC`.`Customer`", // MySQL backtick-quoted schema.table + "[Customer]", // MSSQL bracket-quoted + ) + + preQuotedNames.forEach { tableName -> + shouldThrow { + DataFrame.readSqlTable(connection, tableName) + } + } + } + + @Test + fun `readSqlTable should accept unquoted schema-qualified names`() { + // H2 exposes every table under the PUBLIC schema. + // Passing "PUBLIC.Customer" should pass validation; the library quotes it for the DB. + val df = DataFrame.readSqlTable(connection, "PUBLIC.Customer") + assertCustomerData(df) + } + @Test fun `readFromTable should reject invalid table names to prevent SQL injections`() { // Invalid table names that attempt SQL injection @@ -663,6 +692,8 @@ class JdbcTest { "/* Multi-line comment */ Customer", // Injection using multi-line comment "Sale WHERE 1=1", // Injection using always-true condition "Sale UNION SELECT * FROM Customer", // UNION injection + "DROP TABLE users", // Bare DDL as table name — rejected by regex (space not allowed) + "users; DROP TABLE users", // Classic tautology injection ) invalidTableNames.forEach { tableName -> @@ -673,75 +704,79 @@ class JdbcTest { } @Test - fun `readSqlQuery should reject malicious SQL queries to prevent SQL injections`() { - // Malicious SQL queries attempting injection + fun `readSqlQuery with ReadOnly validation should block multi-statement and DDL queries`() { @Language("SQL") - val injectionComment = """ - SELECT * FROM Sale WHERE amount = 100.0 -- AND id = 5 - """ + val multiStatement = "SELECT * FROM Sale WHERE amount = 500.0; DROP TABLE Customer" @Language("SQL") - val injectionMultilineComment = """ - SELECT * FROM Customer /* Possible malicious comment */ WHERE id = 1 - """ + val bareDropTable = "DROP TABLE Customer; SELECT * FROM Sale" @Language("SQL") - val injectionSemicolon = """ - SELECT * FROM Sale WHERE amount = 500.0; DROP TABLE Customer - """ + val insertStatement = "INSERT INTO Sale (id) VALUES (1)" + listOf(multiStatement, bareDropTable, insertStatement).forEach { query -> + shouldThrow { + DataFrame.readSqlQuery(connection, query, validation = SqlValidation.ReadOnly) + } + } + } + + @Test + fun `readSqlQuery with ReadOnly validation should allow comments and semicolons inside literals`() { + // Comments are stripped before validation — these are valid read-only queries @Language("SQL") - val injectionSQLWithSingleQuote = """ - SELECT * FROM Sale WHERE id = 1 AND amount = 100.0 OR '1'='1 - """ + val queryWithLineComment = "SELECT * FROM Sale WHERE amount = 100.0 -- AND id = 5" @Language("SQL") - val injectionUsingDropCommand = """ - DROP TABLE Customer; SELECT * FROM Sale - """ + val queryWithBlockComment = "SELECT * FROM Customer /* filter by id */ WHERE id = 1" - val sqlInjectionQueries = listOf( - injectionComment, - injectionMultilineComment, - injectionSemicolon, - injectionSQLWithSingleQuote, - injectionUsingDropCommand, - ) + // Semicolons inside string literals are safe + @Language("SQL") + val queryWithSemicolonInLiteral = "SELECT * FROM Sale WHERE tag = 'a;b'" - sqlInjectionQueries.forEach { query -> - shouldThrow { - DataFrame.readSqlQuery(connection, query) + // CTEs must be allowed + @Language("SQL") + val cteQuery = "WITH recent AS (SELECT * FROM Sale) SELECT * FROM recent" + + listOf(queryWithLineComment, queryWithBlockComment, queryWithSemicolonInLiteral, cteQuery).forEach { query -> + // Wrap in runCatching so that DB-level errors (table/column not found) don't fail the test; + // only an IllegalArgumentException from validation would indicate a false rejection. + val exception = runCatching { + DataFrame.readSqlQuery(connection, query, validation = SqlValidation.ReadOnly) + }.exceptionOrNull() + assert(exception !is IllegalArgumentException) { + "Query should pass ReadOnly validation but was rejected: $query\n$exception" } } } @Test - fun `readFromTable should work with non-standard table names when strictValidation is disabled`() { - // Non-standard table names that are still valid but may appear strange - val nonStandardTableNames = listOf( - "`Customer With Space`", // Table name with spaces - "`Important-Data`", // Table name with hyphens - "`[123TableName]`", // Table name that resembles a special syntax - ) + fun `readSqlQuery with default SqlValidation None does not throw for any query`() { + // Default mode passes the query to the database without any validation. + // We use a harmless multi-statement query so no test data is modified. + @Language("SQL") + val harmlessMultiStatement = "SELECT 1; SELECT 2" - try { - // Create these tables to ensure they exist for the test - connection.createStatement().use { stmt -> - nonStandardTableNames.forEach { tableName -> - stmt.execute("CREATE TABLE IF NOT EXISTS $tableName (id INT, name VARCHAR(255))") - } - } + // No IllegalArgumentException from validation — the database handles the rest. + val exception = runCatching { DataFrame.readSqlQuery(connection, harmlessMultiStatement) }.exceptionOrNull() + assert(exception !is IllegalArgumentException) { + "SqlValidation.None should not throw IllegalArgumentException, got: $exception" + } + } - // Read from these tables with strictValidation disabled - nonStandardTableNames.forEach { tableName -> - DataFrame.readSqlTable(connection, tableName, strictValidation = false) - } - } finally { - // Clean up by deleting all created tables - connection.createStatement().use { stmt -> - nonStandardTableNames.forEach { tableName -> - stmt.execute("DROP TABLE IF EXISTS $tableName") - } + @Test + fun `readSqlTable should always reject non-identifier table names`() { + // Table name validation is always strict because the library constructs SQL from the name. + // Names containing spaces, backticks, hyphens, or brackets are not valid SQL identifiers. + val nonIdentifierNames = listOf( + "`Customer With Space`", + "`Important-Data`", + "`[123TableName]`", + ) + + nonIdentifierNames.forEach { tableName -> + shouldThrow { + DataFrame.readSqlTable(connection, tableName) } } } @@ -782,8 +817,7 @@ class JdbcTest { } @Test - fun `readSqlQuery should execute DROP TABLE when validation is disabled`() { - // Query to create a temporary test table + fun `readSqlQuery with SqlValidation None passes multi-statement queries to the database`() { @Language("SQL") val createTableQuery = """ CREATE TABLE IF NOT EXISTS TestTable ( @@ -792,7 +826,6 @@ class JdbcTest { ) """ - // Query to drop the test table @Language("SQL") val dropTableQuery = """ SELECT * FROM TestTable; DROP TABLE TestTable; @@ -800,22 +833,13 @@ class JdbcTest { """ try { - // Create the test table connection.createStatement().use { stmt -> - stmt.execute(createTableQuery) // Create table for the test case + stmt.execute(createTableQuery) } - // Execute the DROP TABLE command with validation disabled - DataFrame.readSqlQuery(connection, dropTableQuery, strictValidation = false) - - // Verify that the table has been successfully dropped - connection.createStatement().use { stmt -> - shouldThrow { - stmt.executeQuery("SELECT * FROM TestTable") - } - } + // SqlValidation.None is the default — no IllegalArgumentException is thrown + DataFrame.readSqlQuery(connection, dropTableQuery) } finally { - // Cleanup: Ensure the table is removed in case of failure connection.createStatement().use { stmt -> stmt.execute("DROP TABLE IF EXISTS TestTable") } @@ -832,17 +856,14 @@ class JdbcTest { ) """ + // Double-quoted identifiers are stripped before keyword matching in ReadOnly mode, + // so SELECT * FROM "ALTER" is correctly recognised as a read-only query. @Language("SQL") val selectFromWeirdTableSQL = """SELECT * from "ALTER"""" try { connection.createStatement().execute(createAlterTableQuery) - // with enabled strictValidation - shouldThrow { - DataFrame.readSqlQuery(connection, selectFromWeirdTableSQL) - } - // with disabled strictValidation - DataFrame.readSqlQuery(connection, selectFromWeirdTableSQL, strictValidation = false).rowsCount() shouldBe 0 + DataFrame.readSqlQuery(connection, selectFromWeirdTableSQL).rowsCount() shouldBe 0 } finally { connection.createStatement().execute("DROP TABLE IF EXISTS \"ALTER\"") }