diff --git a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleDatabaseOnDocker.scala b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleDatabaseOnDocker.scala new file mode 100644 index 0000000000000..9b77469b2f8a1 --- /dev/null +++ b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleDatabaseOnDocker.scala @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.spark.sql.jdbc + +import java.io.{File, PrintWriter} + +import com.github.dockerjava.api.model._ + +import org.apache.spark.internal.Logging +import org.apache.spark.util.Utils + +class OracleDatabaseOnDocker extends DatabaseOnDocker with Logging { + lazy override val imageName = + sys.env.getOrElse("ORACLE_DOCKER_IMAGE_NAME", "gvenzl/oracle-free:23.3") + val oracle_password = "Th1s1sThe0racle#Pass" + override val env = Map( + "ORACLE_PWD" -> oracle_password, // oracle images uses this + "ORACLE_PASSWORD" -> oracle_password // gvenzl/oracle-free uses this + ) + override val usesIpc = false + override val jdbcPort: Int = 1521 + + override def getJdbcUrl(ip: String, port: Int): String = { + s"jdbc:oracle:thin:system/$oracle_password@//$ip:$port/freepdb1" + } + + override def beforeContainerStart( + hostConfigBuilder: HostConfig, + containerConfigBuilder: ContainerConfig): Unit = { + try { + val dir = Utils.createTempDir() + val writer = new PrintWriter(new File(dir, "install.sql")) + // SPARK-46592: gvenzl/oracle-free occasionally fails to start with the following error: + // 'ORA-04021: timeout occurred while waiting to lock object', when initializing the + // SYSTEM user. This is due to the fact that the default DDL_LOCK_TIMEOUT is 0, which + // means that the lock will no wait. We set the timeout to 30 seconds to try again. + // TODO: This workaround should be removed once the issue is fixed in the image. + // https://github.com/gvenzl/oci-oracle-free/issues/35 + writer.write("ALTER SESSION SET DDL_LOCK_TIMEOUT = 30;") + writer.write(s"""ALTER USER SYSTEM IDENTIFIED BY "$oracle_password";""") + writer.close() + val newBind = new Bind( + dir.getAbsolutePath, + new Volume("/docker-entrypoint-initdb.d"), + AccessMode.ro) + hostConfigBuilder.withBinds(hostConfigBuilder.getBinds :+ newBind: _*) + } catch { + case e: Exception => + logWarning("Failed to create install.sql file", e) + } + } +} diff --git a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleIntegrationSuite.scala b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleIntegrationSuite.scala index 4e13d21864fef..0cdff79ef69fc 100644 --- a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleIntegrationSuite.scala +++ b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/OracleIntegrationSuite.scala @@ -65,19 +65,7 @@ import org.apache.spark.tags.DockerTest class OracleIntegrationSuite extends DockerJDBCIntegrationSuite with SharedSparkSession { import testImplicits._ - override val db = new DatabaseOnDocker { - lazy override val imageName = - sys.env.getOrElse("ORACLE_DOCKER_IMAGE_NAME", "gvenzl/oracle-free:23.3") - val oracle_password = "Th1s1sThe0racle#Pass" - override val env = Map( - "ORACLE_PWD" -> oracle_password, // oracle images uses this - "ORACLE_PASSWORD" -> oracle_password // gvenzl/oracle-free uses this - ) - override val usesIpc = false - override val jdbcPort: Int = 1521 - override def getJdbcUrl(ip: String, port: Int): String = - s"jdbc:oracle:thin:system/$oracle_password@//$ip:$port/freepdb1" - } + override val db = new OracleDatabaseOnDocker override val connectionTimeout = timeout(7.minutes) diff --git a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleIntegrationSuite.scala b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleIntegrationSuite.scala index 0c844219aeb5f..1badc110347d6 100644 --- a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleIntegrationSuite.scala +++ b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleIntegrationSuite.scala @@ -26,7 +26,7 @@ import org.apache.spark.{SparkConf, SparkRuntimeException} import org.apache.spark.sql.AnalysisException import org.apache.spark.sql.catalyst.util.CharVarcharUtils.CHAR_VARCHAR_TYPE_STRING_METADATA_KEY import org.apache.spark.sql.execution.datasources.v2.jdbc.JDBCTableCatalog -import org.apache.spark.sql.jdbc.DatabaseOnDocker +import org.apache.spark.sql.jdbc.OracleDatabaseOnDocker import org.apache.spark.sql.types._ import org.apache.spark.tags.DockerTest @@ -75,19 +75,7 @@ class OracleIntegrationSuite extends DockerJDBCIntegrationV2Suite with V2JDBCTes override val catalogName: String = "oracle" override val namespaceOpt: Option[String] = Some("SYSTEM") - override val db = new DatabaseOnDocker { - lazy override val imageName = - sys.env.getOrElse("ORACLE_DOCKER_IMAGE_NAME", "gvenzl/oracle-free:23.3") - val oracle_password = "Th1s1sThe0racle#Pass" - override val env = Map( - "ORACLE_PWD" -> oracle_password, // oracle images uses this - "ORACLE_PASSWORD" -> oracle_password // gvenzl/oracle-free uses this - ) - override val usesIpc = false - override val jdbcPort: Int = 1521 - override def getJdbcUrl(ip: String, port: Int): String = - s"jdbc:oracle:thin:system/$oracle_password@//$ip:$port/freepdb1" - } + override val db = new OracleDatabaseOnDocker override val defaultMetadata: Metadata = new MetadataBuilder() .putLong("scale", 0) diff --git a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleNamespaceSuite.scala b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleNamespaceSuite.scala index daffb5a2d4a3d..05f38102d4101 100644 --- a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleNamespaceSuite.scala +++ b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/OracleNamespaceSuite.scala @@ -21,7 +21,7 @@ import java.sql.Connection import scala.jdk.CollectionConverters._ -import org.apache.spark.sql.jdbc.{DatabaseOnDocker, DockerJDBCIntegrationSuite} +import org.apache.spark.sql.jdbc.{DockerJDBCIntegrationSuite, OracleDatabaseOnDocker} import org.apache.spark.sql.util.CaseInsensitiveStringMap import org.apache.spark.tags.DockerTest @@ -57,19 +57,7 @@ class OracleNamespaceSuite extends DockerJDBCIntegrationSuite with V2JDBCNamespa override def excluded: Seq[String] = Seq("listNamespaces: basic behavior", "Drop namespace") - override val db = new DatabaseOnDocker { - lazy override val imageName = - sys.env.getOrElse("ORACLE_DOCKER_IMAGE_NAME", "gvenzl/oracle-free:23.3") - val oracle_password = "Th1s1sThe0racle#Pass" - override val env = Map( - "ORACLE_PWD" -> oracle_password, // oracle images uses this - "ORACLE_PASSWORD" -> oracle_password // gvenzl/oracle-free uses this - ) - override val usesIpc = false - override val jdbcPort: Int = 1521 - override def getJdbcUrl(ip: String, port: Int): String = - s"jdbc:oracle:thin:system/$oracle_password@//$ip:$port/freepdb1" - } + override val db = new OracleDatabaseOnDocker val map = new CaseInsensitiveStringMap( Map("url" -> db.getJdbcUrl(dockerIp, externalPort),