Author: gk Date: Mon Nov 25 15:22:32 2019 New Revision: 1870385 URL: http://svn.apache.org/viewvc?rev=1870385&view=rev Log: Showcase Java Testcontainer - in profile mysql/docker-resources - used for mysql adapter + just one test class: DataContainerTest.java (from DataTest.java) - no need for a running mysql database installed locally - still support pre-installed database and docker containerized test (cft. pom.xml)
pom.xml: - generation: need to use docker-maven-plugin to init a temporary database image to generate sql (in maven torque-maven-plugin), stopping it after this is done - separate id-table generation and pass on insert into dockerfile (may use some shell script indirection later) - remove not used log4j.properties - TODO use torque.wrapper.configuration.file instead of hard-coded configuration file path, add tests, add adapters Added: db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/ db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/BaseDatabaseContainerTestCase.java (with props) db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/DataContainerTest.java (with props) db/torque/torque4/trunk/torque-test/src/test/profile/mysql/Torque4Test.xml (with props) db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/ db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/ db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/.dockerignore db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/Dockerfile db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/docker-java.properties.template db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/testcontainers.properties (with props) db/torque/torque4/trunk/torque-test/src/test/profile/mysql/torque.usersettings.properties (with props) Removed: db/torque/torque4/trunk/torque-test/src/test/resources/log4j.properties Modified: db/torque/torque4/trunk/torque-test/pom.xml Modified: db/torque/torque4/trunk/torque-test/pom.xml URL: http://svn.apache.org/viewvc/db/torque/torque4/trunk/torque-test/pom.xml?rev=1870385&r1=1870384&r2=1870385&view=diff ============================================================================== --- db/torque/torque4/trunk/torque-test/pom.xml (original) +++ db/torque/torque4/trunk/torque-test/pom.xml Mon Nov 25 15:22:32 2019 @@ -30,6 +30,9 @@ Troubleshooting: Exception org.apache.torque.generator.source.transform.SourceTransformerException: Option torque.database must be set: You did not specify a correct profile name, see above. + + Eclipse classpath generation: + First run mvn test -Pmysql -DskipTests=true, then mvn eclipse:eclipse -Dmaven.antrun.skip=true -Pmysql --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <parent> @@ -54,6 +57,9 @@ <torque.test.mysql.log4j2.version>2.12.1</torque.test.mysql.log4j2.version> <torque.test.oracle.version>10.2.0.3.0</torque.test.oracle.version> <torque.test.postgres.version>9.1-901.jdbc4</torque.test.postgres.version> + <torque.test.testcontainer.version>1.12.3</torque.test.testcontainer.version> + <!-- --> + <torque.test.idmethod>native</torque.test.idmethod> </properties> <scm> @@ -105,6 +111,19 @@ <artifactId>fulcrum-yaafi</artifactId> <scope>test</scope> </dependency> + <!-- testcontainer minimal shared resources --> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <version>${torque.test.testcontainer.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>junit-jupiter</artifactId> + <version>${torque.test.testcontainer.version}</version> + <scope>test</scope> + </dependency> </dependencies> <build> @@ -138,6 +157,7 @@ <copy todir="target/torque/test/schema"> <fileset dir="src/main/schema" /> <filterset> + <filter token="DATABASE_DEFAULT" value="bookstore" /> <filter token="DATABASE_ID_METHOD" value="${torque.test.idmethod}" /> <filter token="DATABASE_SCHEMA" value="${torque.test.databaseSchema}" /> @@ -282,7 +302,7 @@ <packaging>classpath</packaging> <configPackage>org.apache.torque.templates.idtable</configPackage> <sourceDir>target/torque/test/schema</sourceDir> - <defaultOutputDir>target/generated-sql</defaultOutputDir> + <defaultOutputDir>target/generated-sql-init</defaultOutputDir> <defaultOutputDirUsage>none</defaultOutputDirUsage> <loglevel>error</loglevel> <options> @@ -453,7 +473,7 @@ <autocommit>true</autocommit> <orderFile>ascending</orderFile> <fileset> - <basedir>${basedir}/target/generated-sql</basedir> + <basedir>${basedir}/target/generated-sql-init</basedir> <includes> <include>*idtable-init.sql</include> </includes> @@ -493,9 +513,10 @@ <include>**/${torque.test.include.managers}</include> </includes> <excludes> - <exclude>**/BaseRuntimeTestCase.java</exclude> + <exclude>**/BaseDatabaseTestCase.java</exclude> + <exclude>**/*Container*.java</exclude> </excludes> - <forkMode>none</forkMode><!-- normally pertest, if remote debugging with mvnDebug port 8000, setting in console -DforkMode=none seems to have no effect, set here --> + <forkMode>pertest</forkMode><!-- normally pertest, if remote debugging with mvnDebug port 8000, setting in console -DforkMode=none seems to have no effect, set here --> <systemProperties> <property> <name>torque.configuration.file</name> @@ -1149,6 +1170,187 @@ </plugins> </build> </profile> + <!-- + mvn clean test -Pmysql,docker-testcontainer + --> + <profile> + <id>docker-testcontainer</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <plugins> + <!-- requires a started docker host - starts database for sql-maven-plugin. + caution: Cancelling the maven process before life cycle process-classes is reached, the docker has to be stopped manually, docker stop <id>. + --> + <plugin> + <groupId>io.fabric8</groupId> + <artifactId>docker-maven-plugin</artifactId> + <version>0.30.0</version> + <configuration> + <filter>mysql:${torque.test.mysql.version}</filter> + </configuration> + <executions> + <execution> + <id>prepare-mysql-database</id> + <phase>initialize</phase> + <goals> + <goal>start</goal> + </goals> + <configuration> + <images> + <image> + <name>mysql:${torque.test.mysql.version}</name> + <alias>mysql-generate-helper-database</alias> + <run> + <env> + <MYSQL_ROOT_PASSWORD>${torque.database.password}</MYSQL_ROOT_PASSWORD> + <MYSQL_DATABASE>bookstore</MYSQL_DATABASE> + <MYSQL_USER>${torque.database.user}</MYSQL_USER> + <MYSQL_PASSWORD>${torque.database.password}</MYSQL_PASSWORD> + </env> + <ports> + <port>3306:3306</port> + </ports> + <!--wait> + <log>database system is ready to accept connections.</log> + <time>20000</time> + </wait--> + </run> + </image> + </images> + </configuration> + </execution> + <!-- close this init generate sources database outside tests, before starting tests --> + <execution> + <id>remove-mysql-database</id> + <phase>process-classes</phase> + <goals> + <goal>stop</goal> + </goals> + </execution> + </executions> + </plugin> + <!-- skip and use testcontaiers docker --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>sql-maven-plugin</artifactId> + <executions> + <execution> + <id>execute-ext-ddl</id> + <configuration> + <skip>true</skip> + </configuration> + </execution> + <execution> + <id>execute-ddl</id> + <configuration> + <skip>true</skip> + </configuration> + </execution> + <execution> + <id>execute-idtable-sql</id> + <configuration> + <skip>true</skip> + </configuration> + </execution><execution> + <id>shutdown-db-after-ddl</id> + <configuration> + <skip>true</skip> + </configuration> + </execution> + </executions> + </plugin> + <!-- skip database test or do it in a later phase.. --> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>test-database-filled</id> + <phase>generate-sources</phase> + <configuration> + <skip>true</skip> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-resources-plugin</artifactId> + <version>2.6</version> + <executions> + <execution> + <id>copy-resources</id> + <phase>validate</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${basedir}/target</outputDirectory> + <resources> + <resource> + <directory>src/test/profile/mysql/docker-resources/db</directory> + <filtering>false</filtering> + <includes> + <include>Dockerfile</include> + <include>.dockerignore</include> + </includes> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <systemProperties> + <property> + <!-- file is sued, but not this configuration setting, cft. --> + <name>torque.wrapper.configuration.file</name> + <value>src/test/profile/${torque.test.profileDirectory}/Torque4Test.xml</value> + </property> + </systemProperties> + <excludes> + <exclude>**/Base*.java</exclude> + </excludes> + <includes> + <include>**/DataContainerTest.java</include> + </includes> + </configuration> + <executions> + <execution> + <id>default-test</id> + <configuration> + <!--groups>docker</groups--> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + <dependencies> + <!-- docker testcontainer: mvn test -Pmysql,docker-testcontainer --> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <version>${torque.test.testcontainer.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>junit-jupiter</artifactId> + <version>${torque.test.testcontainer.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>mysql</artifactId> + <version>${torque.test.testcontainer.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + </profile> <profile> <id>apache-release</id> Added: db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/BaseDatabaseContainerTestCase.java URL: http://svn.apache.org/viewvc/db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/BaseDatabaseContainerTestCase.java?rev=1870385&view=auto ============================================================================== --- db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/BaseDatabaseContainerTestCase.java (added) +++ db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/BaseDatabaseContainerTestCase.java Mon Nov 25 15:22:32 2019 @@ -0,0 +1,380 @@ +package org.apache.torque.testcontainer; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/* + * 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. + */ + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.torque.Torque; +import org.apache.torque.TorqueException; +import org.apache.torque.adapter.Adapter; +import org.apache.torque.adapter.MysqlAdapter; +import org.apache.torque.criteria.Criteria; +import org.apache.torque.om.mapper.StringMapper; +import org.apache.torque.test.dbobject.Author; +import org.apache.torque.test.dbobject.Book; +import org.apache.torque.test.peer.AuthorPeer; +import org.apache.torque.test.peer.BookPeer; +import org.apache.torque.util.BasePeerImpl; +import org.apache.torque.util.CountHelper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + + + +/** + * Base functionality for Test cases which use standard database setup. + * + * Uses T E S T C O N T A I N E R S + * + * https://www.testcontainers.org/features/configuration/ + * + * Copy docker-java.properties.template to docker-java.properties and adapt it to your needs. + * + * @author <a href="mailto:se...@backstagetech.com.au">Scott Eade</a> + * @author <a href="mailto:g...@apache.org">Georg Kallidis</a> + * @version $Id: BaseDatabaseTestCase.java 1839288 2018-08-27 09:48:33Z tv $ + */ +@Tag("docker") +@Testcontainers +public abstract class BaseDatabaseContainerTestCase +{ + private static final String TORQUE_WRAPPER_CONFIGURATION_FILE = "src/test/profile/mysql/Torque4Test.xml"; + /** TODO: use this key insted of hard coding + * The system property containing the path to the configuration file. + * */ + public static final String CONFIG_FILE_SYSTEM_PROPERTY = "torque.wrapper.configuration.file"; + + protected static Adapter defaultAdapter; + + // copied from src/test/profile/mysql/docker-resources/db/ + public static final String DOCKERFILE = "./target/Dockerfile"; + + private static Logger log = LogManager.getLogger(); + + public static int SERVICE_PORT = 3306; + + public static String DATABASE_NAME = "bookstore"; + + Connection connection; + + @Container + public static GenericContainer MY_SQL_CONTAINER = new GenericContainer<>( + new ImageFromDockerfile() + .withDockerfile(new File(DOCKERFILE).toPath()) + ).withExposedPorts( SERVICE_PORT ) //.withStartupAttempts( 2 ) + .withEnv( "MYSQL_DATABASE", DATABASE_NAME ) + .withEnv( "MYSQL_USER", "torque" ) + .withEnv( "MYSQL_PASSWORD", "torque" ) + .withEnv( "MYSQL_ROOT_PASSWORD","torque" ); + + /** + * Initialize Torque on the first setUp(). Subclasses which override + * setUp() must call super.setUp() as their first action. + * @throws TorqueException + * @throws InterruptedException + * @throws IOException + * @throws UnsupportedOperationException + */ + @BeforeAll + public static void init() throws TorqueException, UnsupportedOperationException, IOException, InterruptedException { + log.info("Starting from dockerfile: {}", DOCKERFILE); + + //before torque init + MY_SQL_CONTAINER.setStartupAttempts( 3 ); + + startDatabaseContainer(); + + //MY_SQL_CONTAINER.execInContainer("mysql -h localhost -u root -ptorque -P 3306 "+ DATABASE_NAME); + + // TODO do resource filtering and read from properties + File targetFile = new File(TORQUE_WRAPPER_CONFIGURATION_FILE); + Path torqueConfBase = Paths.get(targetFile.toURI()).getParent(); + updateTorque(torqueConfBase); + + synchronized (BaseDatabaseContainerTestCase.class) + { + if (!Torque.isInit()) + { + Torque.init( + TORQUE_WRAPPER_CONFIGURATION_FILE); + } + } + defaultAdapter = Torque.getDatabase(Torque.getDefaultDB()).getAdapter(); + log.info("using adapter: {}", defaultAdapter); + + } + + protected static void updateTorque(Path torqueConfBase) { + try { + String jdbcConnectionString = generateJdbcUrl(); + String customUrl = "torque.dsfactory.bookstore.connection.url="+ jdbcConnectionString; + // override and set mapped port in url, which is known only at runtime. + File file = torqueConfBase.resolve("torque.usersettings.properties").toFile(); + try (FileOutputStream fop = new FileOutputStream(file )) { + if (!file.exists()) { + file.createNewFile(); + } + fop.write( customUrl.getBytes() ); + fop.flush(); + } + } catch (Exception e) { + fail(); + } + } + + public static void startDatabaseContainer() { + if (!MY_SQL_CONTAINER.isRunning()) { + MY_SQL_CONTAINER.waitingFor(Wait.forListeningPort()); + MY_SQL_CONTAINER.start(); + } + } + + /** + * + * @returns the JDBC string with mapped port binding + */ + public static String generateJdbcUrl() { + startDatabaseContainer(); + if (!MY_SQL_CONTAINER.isRunning()) { throw new RuntimeException("Could not find RUNNING database container"); } + + //MY_SQL_CONTAINER.withCreateContainerCmdModifier(modifier) // + String serviceHost = MY_SQL_CONTAINER.getContainerIpAddress(); + Integer mappedPort = MY_SQL_CONTAINER.getMappedPort(SERVICE_PORT);// e.g. 32811 + log.info("generate jdbc url from {}, mapped Port: {}, bounded port: {}", serviceHost, mappedPort, MY_SQL_CONTAINER.getBoundPortNumbers()); + + String targetJDBC = //genJDBC; + String.format("jdbc:mysql://%s:%d/%s?loggerLevel=OFF", serviceHost, + mappedPort, DATABASE_NAME); + log.info( "used connect url: {}", targetJDBC); + return targetJDBC; + } + + /** + * Queries mysql for its version. + * @return the version String mysql returns + * @throws TorqueException if the database is not mysql or the query fails. + */ + protected String getMysqlVersion() throws TorqueException + { + Adapter adapter + = Torque.getDatabase(Torque.getDefaultDB()).getAdapter(); + if (!(adapter instanceof MysqlAdapter)) + { + throw new TorqueException( + "getMysqlVersion called but database adapter is " + + adapter.getClass().getName()); + } + List<String> records = new BasePeerImpl<String>().doSelect( + "show variables like \"version\"", + new StringMapper(1), + (String) null); + return records.get(0); + } + + /** + * Queries mysql for its major version. (format is major.minor.release) + * @return the major version of mysql + * @throws TorqueException if the database is not mysql or the query fails. + * @throws NumberFormatException if the mysql major version cannot be + * converted to an int + */ + protected int getMysqlMajorVersion() + throws TorqueException + { + String completeVersion = getMysqlVersion(); + String majorVersion + = completeVersion.substring(0, completeVersion.indexOf('.')); + return Integer.parseInt(majorVersion); + } + + /** + * Queries mysql for its minor version. (format is major.minor.release) + * @return the minor version of mysql + * @throws TorqueException if the database is not mysql or the query fails. + */ + protected int getMysqlMinorVersion() + throws TorqueException + { + String completeVersion = getMysqlVersion(); + if (completeVersion.indexOf('-') != -1) // handle x.y.z-ubuntuSomething + { + completeVersion = completeVersion.substring( + 0, + completeVersion.indexOf('-')); + } + String minorVersion + = completeVersion.substring( + completeVersion.indexOf('.') + 1, + completeVersion.lastIndexOf('.')); + return Integer.parseInt(minorVersion); + } + + /** + * Queries the database for its major version. + * + * @param connection a connection to the database. + * + * @return the version String from the connection metadata + * + * @throws TorqueException if the query fails. + */ + protected int getDatabaseMajorVersion(final Connection connection) + throws TorqueException + { + try + { + return connection.getMetaData().getDatabaseMajorVersion(); + } + catch (SQLException e) + { + throw new TorqueException(e); + } + } + + /** + * Deletes all authors and books in the bookstore tables. + * + * @throws TorqueException if the bookstore could not be cleaned + */ + protected void cleanBookstore() throws TorqueException + { + Criteria criteria = new Criteria(); + BookPeer.doDelete(criteria); + + criteria = new Criteria(); + AuthorPeer.doDelete(criteria); + } + + /** + * Inserts test data into the bookstore tables. + * + * @return the list of added authors, which in turn contain the added books. + * + * @throws TorqueException if filling data fails. + */ + protected List<Author> insertBookstoreData() throws TorqueException + { + List<Author> result = new ArrayList<>(); + for (int i = 1; i <= 10; i++) + { + Author author = new Author(); + author.setName("Author " + i); + author.save(); + result.add(author); + assertTrue("authorId should not be 0 after insert", + author.getAuthorId() != 0); + + for (int j = 1; j <= 10; j++) + { + Book book = new Book(); + author.addBook(book); + book.setTitle("Book " + j + " - Author " + i); + if (j < 10) + { + book.setIsbn("ISBN " + j + " - " + i); + } + else + { + book.setIsbn(null); + } + book.save(); + } + } + return result; + } + + /** + * Checks that the bookstore tables contain exactly the records + * in the passed list. + * The books in the authors are also checked. + * + * @param toVerify the list of authors to check. + * + * @throws TorqueException if reading data fails. + */ + protected void verifyBookstore(final List<Author> toVerify) throws TorqueException + { + int numBooks = 0; + + for (Author author : toVerify) + { + Criteria criteria = new Criteria() + .where(AuthorPeer.NAME, author.getName()) + .and(AuthorPeer.AUTHOR_ID, author.getAuthorId()); + criteria.setSingleRecord(true); + List<Author> selectedAuthorList = AuthorPeer.doSelect(criteria); + assertEquals("Could not find author with id " + + author.getAuthorId() + + " and name " + + author.getName(), + 1, + selectedAuthorList.size()); + + numBooks += author.getBooks().size(); + + for (Book book : author.getBooks()) + { + criteria = new Criteria() + .where(BookPeer.TITLE, book.getTitle()) + .and(BookPeer.BOOK_ID, book.getBookId()) + .and(BookPeer.AUTHOR_ID, book.getAuthorId()) + .and(BookPeer.ISBN, book.getIsbn()); + criteria.setSingleRecord(true); + List<Book> selectedBookList = BookPeer.doSelect(criteria); + assertEquals("Could not find book with id " + + book.getBookId() + + " title " + + book.getTitle() + + " ISBN " + + book.getIsbn() + + " authorId " + + book.getAuthorId(), + 1, + selectedBookList.size()); + } + } + + assertEquals( + toVerify.size(), + new CountHelper().count(AuthorPeer.getTableMap())); + assertEquals( + numBooks, + new CountHelper().count(BookPeer.getTableMap())); + } + +} Propchange: db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/BaseDatabaseContainerTestCase.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/DataContainerTest.java URL: http://svn.apache.org/viewvc/db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/DataContainerTest.java?rev=1870385&view=auto ============================================================================== --- db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/DataContainerTest.java (added) +++ db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/DataContainerTest.java Mon Nov 25 15:22:32 2019 @@ -0,0 +1,1452 @@ +package org.apache.torque.testcontainer; + +/* + * 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. + */ + +import static org.junit.Assert.*; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.torque.ColumnImpl; +import org.apache.torque.ForeignKeySchemaData; +import org.apache.torque.TestInterface; +import org.apache.torque.TestPeerInterface; +import org.apache.torque.Torque; +import org.apache.torque.TorqueException; +import org.apache.torque.adapter.Adapter; +import org.apache.torque.adapter.DerbyAdapter; +import org.apache.torque.adapter.HsqldbAdapter; +import org.apache.torque.adapter.MssqlAdapter; +import org.apache.torque.adapter.MysqlAdapter; +import org.apache.torque.criteria.Criteria; +import org.apache.torque.criteria.Criterion; +import org.apache.torque.om.mapper.CompositeMapper; +import org.apache.torque.om.mapper.IntegerMapper; +import org.apache.torque.om.mapper.RecordMapper; +import org.apache.torque.test.InheritanceClassnameTestChild1; +import org.apache.torque.test.InheritanceClassnameTestChild2; +import org.apache.torque.test.dbobject.Author; +import org.apache.torque.test.dbobject.BigintType; +import org.apache.torque.test.dbobject.Book; +import org.apache.torque.test.dbobject.CompPkContainsFk; +import org.apache.torque.test.dbobject.IfcTable; +import org.apache.torque.test.dbobject.InheritanceChildB; +import org.apache.torque.test.dbobject.InheritanceChildC; +import org.apache.torque.test.dbobject.InheritanceChildD; +import org.apache.torque.test.dbobject.InheritanceClassnameTest; +import org.apache.torque.test.dbobject.InheritanceTest; +import org.apache.torque.test.dbobject.IntegerType; +import org.apache.torque.test.dbobject.LocalIfcTable; +import org.apache.torque.test.dbobject.LocalTestInterface; +import org.apache.torque.test.dbobject.MultiPk; +import org.apache.torque.test.dbobject.Nopk; +import org.apache.torque.test.dbobject.OIntegerPk; +import org.apache.torque.test.dbobject.VarcharType; +import org.apache.torque.test.peer.AuthorPeer; +import org.apache.torque.test.peer.BigintTypePeer; +import org.apache.torque.test.peer.BookPeer; +import org.apache.torque.test.peer.CompPkContainsFkPeer; +import org.apache.torque.test.peer.IfcTablePeer; +import org.apache.torque.test.peer.IfcTablePeerImpl; +import org.apache.torque.test.peer.InheritanceClassnameTestPeer; +import org.apache.torque.test.peer.InheritanceTestPeer; +import org.apache.torque.test.peer.IntegerTypePeer; +import org.apache.torque.test.peer.LocalIfcTablePeer; +import org.apache.torque.test.peer.LocalIfcTablePeerImpl; +import org.apache.torque.test.peer.LocalTestPeerInterface; +import org.apache.torque.test.peer.MultiPkPeer; +import org.apache.torque.test.peer.NopkPeer; +import org.apache.torque.test.peer.VarcharTypePeer; +import org.apache.torque.test.recordmapper.AuthorRecordMapper; +import org.apache.torque.test.recordmapper.BookRecordMapper; +import org.apache.torque.util.BasePeerImpl; +import org.apache.torque.util.CountHelper; +import org.apache.torque.util.Transaction; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Runtime tests. + * + * @author <a href="mailto:se...@backstagetech.com.au">Scott Eade</a> + * @author <a href="mailto:mpoes...@marmot.at">Martin Poeschl</a> + * @author <a href="mailto:fisc...@seitenbau.de">Thomas Fischer</a> + * @author <a href="mailto:patrick.c...@web.de">Patrick Carl</a> + * @author <a href="mailto:t...@apache.org">Thomas Vandahl</a> + * @author <a href="mailto:g...@apache.org">Georg Kallidis</a> + * @version $Id: DataTest.java 1869081 2019-10-28 16:17:11Z gk $ + */ +@Tag("docker") +@Testcontainers +public class DataContainerTest extends BaseDatabaseContainerTestCase +{ + private static Logger log = LogManager.getLogger(); + + /** + * test whether we can connect to the database at all + * @throws Exception if no connection can be established + */ + @Test + @Tag("docker") + public void testConnect() throws Exception + { + Connection connection = null; + try + { + connection = Torque.getConnection(); + connection.close(); + connection = null; + } + finally + { + if (connection != null) + { + connection.close(); + } + } + } + + /** + * multiple pk test (TRQ12) + * @throws Exception if the test fails + */ + @Test + public void testMultiplePk() throws Exception + { + // clean table + Criteria criteria = new Criteria(); + criteria.where(MultiPkPeer.PK1, (Object) null, Criteria.NOT_EQUAL); + MultiPkPeer.doDelete(criteria); + + // do test + MultiPk mpk = new MultiPk(); + mpk.setPrimaryKey("Svarchar:N5:Schar:N3:N-42:N3:N4:N5:N6:D9999999999:"); + mpk.save(); + // TODO assert saved values + } + + private static final String[] validTitles = { + "Book 6 - Author 4", "Book 6 - Author 5", "Book 6 - Author 6", + "Book 6 - Author 7", "Book 6 - Author 8", + "Book 7 - Author 4", "Book 7 - Author 5", "Book 7 - Author 6", + "Book 7 - Author 7", "Book 7 - Author 8" + }; + + /** + * test limit/offset + * + * @throws Exception if the test fails + */ + @Test + public void testLimitOffset() throws Exception + { + cleanBookstore(); + insertBookstoreData(); + Set<String> titleSet = new HashSet<>(); + for (int j = 0; j < validTitles.length; j++) + { + titleSet.add(validTitles[j]); + } + + Criteria crit = new Criteria(); + Criterion c = new Criterion(BookPeer.TITLE, + "Book 6 - Author 1", Criteria.GREATER_EQUAL); + c.and(new Criterion(BookPeer.TITLE, + "Book 8 - Author 3", Criteria.LESS_EQUAL)); + crit.where(c); + crit.addDescendingOrderByColumn(BookPeer.BOOK_ID); + crit.setLimit(10); + crit.setOffset(5); + List<Book> books = BookPeer.doSelect(crit); + assertEquals("List should have 10 books", 10, books.size()); + for (Book book : books) + { + String title = book.getTitle(); + assertTrue("Incorrect title: " + title, + titleSet.contains(title)); + } + + + // Test limit of zero works + if (defaultAdapter instanceof DerbyAdapter || defaultAdapter instanceof HsqldbAdapter) + { + log.info("testLimitOffset(): " + + "A limit of 0 is not supported for Derby or Hsqldb"); + } + else + { + crit = new Criteria(); + crit.setLimit(0); + books = BookPeer.doSelect(crit); + assertEquals("List should have 0 books", 0, books.size()); + } + + // check that Offset also works without limit + crit = new Criteria(); + crit.setOffset(5); + books = BookPeer.doSelect(crit); + assertEquals("List should have 95 books", 95, books.size()); + + // Check that limiting also works if a table with an equal column name + // is joined. This is problematic for oracle, see TORQUE-10. + + crit = new Criteria(); + crit.setLimit(10); + crit.setOffset(5); + books = BookPeer.doSelectJoinAuthor(crit); + assertEquals("List should have 10 books", 10, books.size()); + } + + /** + * Checks whether the setSingleRecord() method in criteria works + */ + @Test + public void testSingleRecord() throws Exception + { + cleanBookstore(); + insertBookstoreData(); + Criteria criteria = new Criteria(); + criteria.setSingleRecord(true); + criteria.setLimit(1); + criteria.setOffset(5); + List<Book> books = BookPeer.doSelect(criteria); + assertTrue("List should have 1 books, not " + books.size(), + books.size() == 1); + + criteria = new Criteria(); + criteria.setSingleRecord(true); + criteria.setLimit(2); + try + { + books = BookPeer.doSelect(criteria); + fail("doSelect should have failed " + + "because two records were selected " + + " and one was expected"); + } + catch (TorqueException e) + { + } + } + + /** + * Tests whether selects work correctly if the value <code>null</code> + * is used. + * @throws Exception if the test fails + */ + @Test + public void testNullSelects() throws Exception + { + // clean table + VarcharTypePeer.doDelete(new Criteria()); + IntegerTypePeer.doDelete(new Criteria()); + + // add test data + VarcharType varcharType = new VarcharType(); + varcharType.setId("text2"); + varcharType.setVarcharValue("text2"); + varcharType.save(); + varcharType = new VarcharType(); + varcharType.setId("text"); + varcharType.save(); + + IntegerType integerTypeNotNull = new IntegerType(); + integerTypeNotNull.setIntegerObjectValue(1); + integerTypeNotNull.save(); + IntegerType integerTypeNull = new IntegerType(); + integerTypeNull.save(); + + // check for comparison NOT_EQUAL and value null + Criteria criteria = new Criteria(); + criteria.where(VarcharTypePeer.ID, null, Criteria.NOT_EQUAL) + .and(VarcharTypePeer.VARCHAR_VALUE, null, Criteria.NOT_EQUAL); + List<VarcharType> varcharResult = VarcharTypePeer.doSelect(criteria); + assertEquals(1, varcharResult.size()); + assertEquals("text2", varcharResult.get(0).getId()); + + criteria = new Criteria(); + criteria.where(IntegerTypePeer.ID, null, Criteria.NOT_EQUAL) + .and(IntegerTypePeer.INTEGER_OBJECT_VALUE, null, Criteria.NOT_EQUAL); + List<IntegerType> integerResult = IntegerTypePeer.doSelect(criteria); + assertEquals(1, integerResult.size()); + assertEquals(integerTypeNotNull.getId(), integerResult.get(0).getId()); + + // check for comparison EQUAL and value null + criteria = new Criteria(); + criteria.where(VarcharTypePeer.VARCHAR_VALUE, null, Criteria.EQUAL); + varcharResult = VarcharTypePeer.doSelect(criteria); + assertEquals(1, varcharResult.size()); + assertEquals("text", varcharResult.get(0).getId()); + + criteria = new Criteria(); + criteria.where(IntegerTypePeer.INTEGER_OBJECT_VALUE, null, Criteria.EQUAL); + integerResult = IntegerTypePeer.doSelect(criteria); + assertEquals(1, integerResult.size()); + assertEquals(integerTypeNull.getId(), integerResult.get(0).getId()); + } + + /** + * Test whether an update works and whether it only affects the + * specified record. + * @throws Exception if anything in the test goes wrong. + */ + @Test + public void testUpdate() throws Exception + { + cleanBookstore(); + + Author otherAuthor = new Author(); + otherAuthor.setName("OtherName"); + otherAuthor.save(); + + Author author = new Author(); + author.setName("Name"); + author.save(); + + + // Test doUpdate methods in Peer explicitly + Connection connection = Transaction.begin(AuthorPeer.DATABASE_NAME); + author.setName("NewName2"); + AuthorPeer.doUpdate(author); + Transaction.commit(connection); + + Criteria criteria = new Criteria(); + criteria.addAscendingOrderByColumn(AuthorPeer.NAME); + + List<Author> authors = AuthorPeer.doSelect(criteria); + assertEquals("List should contain 2 authors", 2, authors.size()); + assertEquals("First Author's name should be \"NewName2\"", + "NewName2", + authors.get(0).getName()); + assertEquals("Second Author's name should be \"OtherName\"", + "OtherName", + authors.get(1).getName()); + + author.setName("NewName3"); + AuthorPeer.doUpdate(author); + + criteria = new Criteria(); + criteria.addAscendingOrderByColumn(AuthorPeer.NAME); + + authors = AuthorPeer.doSelect(criteria); + assertEquals("List should contain 2 authors", 2, authors.size()); + assertEquals("First Author's name should be \"NewName3\"", + "NewName3", + authors.get(0).getName()); + assertEquals("Second Author's name should be \"OtherName\"", + "OtherName", + authors.get(1).getName()); + + Nopk nopk = new Nopk(); + nopk.setName("name"); + nopk.save(); + + // check the doPupdate Peer methods throw exceptions on a modified + // object without primary keys + try + { + NopkPeer.doUpdate(new Nopk()); + fail("A Torque exception should be thrown (2)"); + } + catch (TorqueException e) + { + } + + connection = Transaction.begin(NopkPeer.DATABASE_NAME); + try + { + NopkPeer.doUpdate(new Nopk(),connection); + fail("A Torque exception should be thrown (3)"); + } + catch (TorqueException e) + { + } + Transaction.safeRollback(connection); + + } + + /** + * test special cases in the select clause + * @throws Exception if the test fails + */ + @Test + public void testSelectClause() throws Exception + { + // test double functions in select columns + Criteria criteria = new Criteria(); + criteria.addSelectColumn( + new ColumnImpl("count(distinct(" + BookPeer.BOOK_ID + "))")); + new BasePeerImpl<>().doSelect(criteria, new IntegerMapper()); + + // test qualifiers in function in select columns + criteria = new Criteria(); + criteria.addSelectColumn( + new ColumnImpl("count(distinct " + BookPeer.BOOK_ID + ")")); + new BasePeerImpl<>().doSelect(criteria, new IntegerMapper()); + } + + /** + * test if a select from the "default" database works + * @throws Exception (NPE) if the test fails + */ + @Test + public void testSelectFromDefault() throws Exception + { + Criteria criteria = new Criteria("default"); + + criteria.addSelectColumn(BookPeer.BOOK_ID); + + new BasePeerImpl<>().doSelect(criteria, new IntegerMapper()); + } + + /** + * Test the behaviour if a connection is supplied to access the database, + * but it is null. All methods on the user level should be fail + * because these methods will be only needed if a method should be executed + * in a transaction context. If one assumes that a transaction is open + * (connection is not null), but it is not (connection == null), + * it is a bad idea to silently start one as this behaviour is very + * difficult to tell from the correct one. A clean failure is much easier + * to test for. + */ + @Test + public void testNullConnection() throws Exception + { + try + { + Criteria criteria = new Criteria(); + AuthorPeer.doSelect(criteria, new IntegerMapper(), null); + fail("NullPointerException expected"); + } + catch (NullPointerException e) + { + //expected + } + + try + { + Criteria criteria = new Criteria(); + criteria.where(BookPeer.BOOK_ID, (Long) null, Criteria.NOT_EQUAL); + BookPeer.doDelete(criteria, (Connection) null); + fail("NullPointerException expected"); + } + catch (NullPointerException e) + { + //expected + } + + try + { + Author author = new Author(); + author.setName("name"); + author.save((Connection) null); + fail("TorqueException expected"); + } + catch (TorqueException e) + { + //expected + assertEquals("connection is null", e.getMessage()); + } + } + + /** + * test the order by, especially in joins and with aliases + * @throws Exception if the test fails + */ + @Test + public void testOrderBy() throws Exception + { + cleanBookstore(); + + // insert test data + Author firstAuthor = new Author(); + firstAuthor.setName("Author 1"); + firstAuthor.save(); + Book book = new Book(); + book.setAuthor(firstAuthor); + book.setTitle("Book 1"); + book.setIsbn("unknown"); + book.save(); + + Author secondAuthor = new Author(); + secondAuthor.setName("Author 2"); + secondAuthor.save(); + for (int bookNr = 2; bookNr <=4; bookNr++) + { + book = new Book(); + book.setAuthor(secondAuthor); + book.setTitle("Book " + bookNr); + book.setIsbn("unknown"); + book.save(); + } + + // test simple ascending order by + Criteria criteria = new Criteria(); + criteria.addAscendingOrderByColumn(BookPeer.TITLE); + List<Book> bookList = BookPeer.doSelect(criteria); + if (bookList.size() != 4) + { + fail("Ascending Order By: " + + "incorrect numbers of books found : " + + bookList.size() + + ", should be 4"); + } + if (! "Book 1".equals(bookList.get(0).getTitle())) + { + fail("Ascending Order By: " + + "Title of first Book is " + + bookList.get(0).getTitle() + + ", should be \"Book 1\""); + } + if (! "Book 4".equals(bookList.get(3).getTitle())) + { + fail("Ascending Order By: " + + "Title of fourth Book is " + + bookList.get(3).getTitle() + + ", should be \"Book 4\""); + } + + // test simple descending order by + criteria = new Criteria(); + criteria.addDescendingOrderByColumn(BookPeer.TITLE); + bookList = BookPeer.doSelect(criteria); + if (bookList.size() != 4) + { + fail("Descending Order By: " + + "incorrect numbers of books found : " + + bookList.size() + + ", should be 4"); + } + if (! "Book 1".equals(bookList.get(3).getTitle())) + { + fail("Descending Order By: " + + "Title of fourth Book is " + + bookList.get(3).getTitle() + + ", should be \"Book 1\""); + } + if (! "Book 4".equals((bookList.get(0)).getTitle())) + { + fail("Descending Order By: " + + "Title of first Book is " + + bookList.get(0).getTitle() + + ", should be \"Book 4\""); + } + + criteria = new Criteria(); + criteria.addAlias("b", BookPeer.TABLE_NAME); + criteria.addJoin(BookPeer.AUTHOR_ID, AuthorPeer.AUTHOR_ID); + criteria.addJoin( + AuthorPeer.AUTHOR_ID, + new ColumnImpl("b." + BookPeer.AUTHOR_ID.getColumnName())); + criteria.addAscendingOrderByColumn( + new ColumnImpl("b." + BookPeer.TITLE.getColumnName())); + criteria.addDescendingOrderByColumn(BookPeer.TITLE); + // the retrieved columns are + // author book b + // author1 book1 book1 + // author2 book4 book2 + // author2 book3 book2 + // author2 book2 book2 + // author2 book4 book3 + // ... + bookList = BookPeer.doSelect(criteria); + if (bookList.size() != 10) + { + fail("ordering by Aliases: " + + "incorrect numbers of books found : " + + bookList.size() + + ", should be 10"); + } + if (!"Book 4".equals(bookList.get(1).getTitle())) + { + fail("ordering by Aliases: " + + "Title of second Book is " + + bookList.get(1).getTitle() + + ", should be \"Book 4\""); + } + if (!"Book 3".equals(bookList.get(2).getTitle())) + { + fail("ordering by Aliases: " + + "Title of third Book is " + + bookList.get(2).getTitle() + + ", should be \"Book 3\""); + } + + criteria = new Criteria(); + criteria.addAlias("b", BookPeer.TABLE_NAME); + criteria.addJoin(BookPeer.AUTHOR_ID, AuthorPeer.AUTHOR_ID); + criteria.addJoin( + AuthorPeer.AUTHOR_ID, + new ColumnImpl("b." + BookPeer.AUTHOR_ID.getColumnName())); + criteria.addAscendingOrderByColumn(BookPeer.TITLE); + criteria.addDescendingOrderByColumn( + new ColumnImpl("b." + BookPeer.TITLE.getColumnName())); + // the retrieved columns are + // author book b + // author1 book1 book1 + // author2 book2 book4 + // author2 book2 book3 + // author2 book2 book2 + // author2 book3 book4 + // ... + bookList = BookPeer.doSelect(criteria); + if (bookList.size() != 10) + { + fail("ordering by Aliases (2): " + + "incorrect numbers of books found : " + + bookList.size() + + ", should be 10"); + } + if (!"Book 2".equals(bookList.get(1).getTitle())) + { + fail("ordering by Aliases (2, PS): " + + "Title of second Book is " + + bookList.get(1).getTitle() + + ", should be \"Book 2\""); + } + if (!"Book 2".equals(bookList.get(2).getTitle())) + { + fail("ordering by Aliases (2, PS): " + + "Title of third Book is " + + bookList.get(2).getTitle() + + ", should be \"Book 2\""); + } + + // test usage of Expressions in order by + criteria = new Criteria(); + criteria.addAscendingOrderByColumn( + new ColumnImpl("UPPER(" + BookPeer.TITLE + ")")); + criteria.setIgnoreCase(true); + BookPeer.doSelect(criteria); + } + + + /** + * Tests whether ignoreCase works correctly + * @throws Exception if the test fails + */ + @Test + public void testIgnoreCase() throws Exception + { + cleanBookstore(); + + // check ignore case in selects + Author author = new Author(); + author.setName("AuTHor"); + author.save(); + + Criteria criteria = new Criteria(); + criteria.where(AuthorPeer.NAME, author.getName().toLowerCase()); + criteria.setIgnoreCase(true); + List<Author> result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + // LIKE treatment might be different (e.g. postgres), so check extra + criteria = new Criteria(); + criteria.where( + AuthorPeer.NAME, + author.getName().toLowerCase().replace('r', '%'), + Criteria.LIKE); + criteria.setIgnoreCase(true); + result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + // Test ignore case in criterion + criteria = new Criteria(); + Criterion criterion1 = new Criterion( + AuthorPeer.NAME, + author.getName().toLowerCase(), + Criteria.EQUAL); + criterion1.setIgnoreCase(true); + Criterion criterion2 = new Criterion( + AuthorPeer.AUTHOR_ID, null, Criteria.NOT_EQUAL); + criterion1.and(criterion2); + + result = AuthorPeer.doSelect(criteria); + + // ignore case should not be set either in Criteria + // nor in other criterions + assertFalse(criteria.isIgnoreCase()); + assertFalse(criterion2.isIgnoreCase()); + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + + // Test ignore case in attached criterion + criteria = new Criteria(); + criterion1 = new Criterion( + AuthorPeer.AUTHOR_ID, null, Criteria.NOT_EQUAL); + criterion2 = new Criterion( + AuthorPeer.NAME, + author.getName().toLowerCase(), + Criteria.EQUAL); + criterion2.setIgnoreCase(true); + criterion1.and(criterion2); + + result = AuthorPeer.doSelect(criteria); + + // ignore case should not be set either in Criteria + // nor in other criterions + assertFalse(criteria.isIgnoreCase()); + assertFalse(criterion1.isIgnoreCase()); + + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + // ignore case in "in" query + { + criteria = new Criteria(); + Set<String> names = new HashSet<>(); + names.add(author.getName().toLowerCase()); + criteria.where(AuthorPeer.NAME, names, Criteria.IN); + criteria.setIgnoreCase(true); + + result = AuthorPeer.doSelect(criteria); + assertEquals("Expected result of size 1 but got " + result.size(), + result.size(), + 1); + } + + // Check that case is not ignored if ignoreCase is not set + // This is known not to work for mysql + author = new Author(); + author.setName("author"); + author.save(); + + Adapter adapter = Torque.getAdapter(Torque.getDefaultDB()); + if (adapter instanceof MysqlAdapter + || adapter instanceof MssqlAdapter) + { + log.error("testIgnoreCase(): " + + "Case sensitive comparisons are known not to work" + + " with Mysql and MSSQL"); + // failing is "expected", so bypass without error + } + else + { + criteria = new Criteria(); + criteria.where(AuthorPeer.NAME, author.getName()); + result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + // again check LIKE treatment + criteria = new Criteria(); + criteria.where( + AuthorPeer.NAME, + author.getName().replace('r', '%'), + Criteria.LIKE); + result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + // Test different ignore cases in criterions + criteria = new Criteria(); + criterion1 = new Criterion( + AuthorPeer.NAME, + author.getName().toLowerCase(), + Criteria.NOT_EQUAL); + criterion2 = new Criterion( + AuthorPeer.NAME, + author.getName().toLowerCase(), + Criteria.EQUAL); + criterion2.setIgnoreCase(true); + criterion1.and(criterion2); + criteria.where(criterion1); + + result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 1, but " + result.size(), + result.size() == 1); + + // ignore case in "in" query + { + criteria = new Criteria(); + Set<String> names = new HashSet<>(); + names.add(author.getName()); + criteria.where(AuthorPeer.NAME, names, Criteria.IN); + + result = AuthorPeer.doSelect(criteria); + assertEquals("Expected result of size 1 but got " + result.size(), + result.size(), + 1); + } + } + + cleanBookstore(); + author = new Author(); + author.setName("AA"); + author.save(); + author = new Author(); + author.setName("BB"); + author.save(); + author = new Author(); + author.setName("ba"); + author.save(); + author = new Author(); + author.setName("ab"); + author.save(); + + // check ignoreCase in Criteria + criteria = new Criteria(); + criteria.setIgnoreCase(true); + criteria.addAscendingOrderByColumn(AuthorPeer.NAME); + result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 4, but " + result.size(), + result.size() == 4); + assertEquals("AA", result.get(0).getName()); + assertEquals("ab", result.get(1).getName()); + assertEquals("ba", result.get(2).getName()); + assertEquals("BB", result.get(3).getName()); + + // check ignoreCase in orderBy + criteria = new Criteria(); + criteria.addAscendingOrderByColumn(AuthorPeer.NAME, true); + result = AuthorPeer.doSelect(criteria); + assertTrue("Size of result is not 4, but " + result.size(), + result.size() == 4); + assertEquals(result.get(0).getName(), "AA"); + assertEquals(result.get(1).getName(), "ab"); + assertEquals(result.get(2).getName(), "ba"); + assertEquals(result.get(3).getName(), "BB"); + } + + /** + * tests whether AsColumns produce valid SQL code + * @throws Exception if the test fails + */ + @Test + public void testAsColumn() throws Exception + { + Criteria criteria = new Criteria(); + criteria.addAsColumn("ALIASNAME", AuthorPeer.NAME); + // we need an additional column to select from, + // to indicate the table we want use + criteria.addSelectColumn(AuthorPeer.AUTHOR_ID); + new BasePeerImpl<>().doSelect(criteria, new DoNothingMapper()); + } + + /** + * Test whether same column name in different tables + * are handled correctly + * @throws Exception if the test fails + */ + @Test + public void testSameColumnName() throws Exception + { + cleanBookstore(); + Author author = new Author(); + author.setName("Name"); + author.save(); + + author = new Author(); + author.setName("NotCorrespondingName"); + author.save(); + + Book book = new Book(); + book.setTitle("Name"); + book.setAuthor(author); + book.setIsbn("unknown"); + book.save(); + + Criteria criteria = new Criteria(); + criteria.addJoin(BookPeer.TITLE, AuthorPeer.NAME); + BookPeer.addSelectColumns(criteria); + AuthorPeer.addSelectColumns(criteria); + // basically a BaseBookPeer.setDbName(criteria); + // and BasePeer.doSelect(criteria); + CompositeMapper mapper = new CompositeMapper(); + mapper.addMapper(new BookRecordMapper(), 0); + mapper.addMapper( + new AuthorRecordMapper(), + BookPeer.numColumns); + + List<List<Object>> queryResult + = BookPeer.doSelect(criteria, mapper); + List<Object> mappedRow = queryResult.get(0); + book = (Book) mappedRow.get(0); + author = (Author) mappedRow.get(1); + + if (book.getAuthorId() == author.getAuthorId()) + { + fail("wrong Ids read"); + } + } + + /** + * tests whether large primary keys are inserted and read correctly + * @throws Exception if the test fails + */ + @Test + public void testLargePk() throws Exception + { + if (defaultAdapter instanceof MssqlAdapter) { + log.error("testLargePk(): " + + "MSSQL does not support inserting defined PK values"); + return; + } + BigintTypePeer.doDelete(new Criteria()); + + long longId = 8771507845873286l; + BigintType bigintType = new BigintType(); + bigintType.setId(longId); + bigintType.save(); + + List<BigintType> bigintTypeList = BigintTypePeer.doSelect(new Criteria()); + BigintType readBigintType = bigintTypeList.get(0); + assertEquals(bigintType.getId(), readBigintType.getId()); + assertEquals(longId, readBigintType.getId()); + } + + /** + * tests whether large bigint values are inserted and read correctly + * @throws Exception if the test fails + */ + @Test + public void testLargeValue() throws Exception + { + BigintTypePeer.doDelete(new Criteria()); + + long longValue = 8771507845873286l; + BigintType bigintType = new BigintType(); + bigintType.setBigintValue(longValue); + bigintType.save(); + + List<BigintType> bigintTypeList = BigintTypePeer.doSelect(new Criteria()); + BigintType readBigintType = bigintTypeList.get(0); + assertEquals(bigintType.getId(), readBigintType.getId()); + assertEquals(longValue, readBigintType.getBigintValue()); + } + + /** + * Tests the CountHelper class + * @throws Exception if the test fails + */ + @Test + public void testCountHelper() throws Exception + { + cleanBookstore(); + Author author = new Author(); + author.setName("Name"); + author.save(); + + author = new Author(); + author.setName("Name2"); + author.save(); + + author = new Author(); + author.setName("Name"); + author.save(); + + Criteria criteria = new Criteria(); + int count = new CountHelper().count( + criteria, + null, + AuthorPeer.AUTHOR_ID); + + if (count != 3) { + fail("counted " + count + " datasets, should be 3 "); + } + + criteria = new Criteria(); + criteria.setDistinct(); + count = new CountHelper().count(criteria, null, AuthorPeer.NAME); + + if (count != 2) { + fail("counted " + count + " distinct datasets, should be 2 "); + } + + criteria = new Criteria(); + criteria.where(AuthorPeer.NAME, "Name2"); + count = new CountHelper().count(criteria); + + if (count != 1) { + fail("counted " + count + " datasets with name Name2," + + " should be 1 "); + } + } + + + /** + * Tests whether we can handle multiple primary keys some of which are + * also foreign keys + * @throws Exception if the test fails + */ + @Test + public void testMultiplePrimaryForeignKey() throws Exception + { + ForeignKeySchemaData.clearTablesInDatabase(); + + OIntegerPk oIntegerPk = new OIntegerPk(); + oIntegerPk.save(); + CompPkContainsFk compPkContainsFk = new CompPkContainsFk(); + compPkContainsFk.setId1(oIntegerPk.getId()); + compPkContainsFk.setId2("test"); + compPkContainsFk.save(); + + List<CompPkContainsFk> selectedList + = CompPkContainsFkPeer.doSelect(new Criteria()); + assertEquals(1, selectedList.size()); + CompPkContainsFk selected = selectedList.get(0); + assertEquals(oIntegerPk.getId(), selected.getId1()); + assertEquals("test", selected.getId2()); + } + + /** + * Tests inserting single quotes in Strings. + * This may not crash now, but in a later task like datasql, + * so the data has to be inserted in a table which does not get cleaned + * during the runtime test. + * @throws Exception if inserting the test data fails + */ + @Test + public void testSingleQuotes() throws Exception + { + cleanBookstore(); + + Author author = new Author(); + author.setName("has Single ' Quote"); + author.save(); + } + + /** + * Test whether equals() is working correctly + * @throws Exception + */ + @Test + public void testEquals() throws Exception + { + Author author = new Author(); + author.setAuthorId(1000); + + Book book = new Book(); + book.setBookId(1000); + + Book bookNotEqual = new Book(); + bookNotEqual.setBookId(2000); + + Book bookEqual = new Book(); + bookEqual.setBookId(1000); + + assertFalse("Author and Book should not be equal", + author.equals(book)); + assertTrue("Book compared with itself should be equal", + book.equals(book)); + assertTrue("Book compared with book with same id should be equal", + book.equals(bookEqual)); + assertFalse("Book compared with book with different id " + + "should not be equal", + book.equals(bookNotEqual)); + } + + /** + * Tests whether a table implementing an interface actually + * returns an instance of this interface + * @throws Exception if the test fails + */ + @Test + public void testInterface() throws Exception + { + Criteria criteria = new Criteria(); + criteria.where(IfcTablePeer.ID, -1, Criteria.NOT_EQUAL); + IfcTablePeer.doDelete(criteria); + + IfcTable ifc = new IfcTable(); + + assertTrue("IfcTable should be an instance of TestInterface", + ifc instanceof TestInterface); + + ifc.setID(1); + ifc.setName("John Doe"); + ifc.save(); + + List<IfcTable> results = IfcTablePeer.doSelect(new Criteria()); + + for (IfcTable ifcTable : results) + { + assertTrue("IfcTablePeer.doSelect should return" + + " instances of TestInterface", + ifcTable instanceof TestInterface); + } + + IfcTablePeerImpl peerImpl = IfcTablePeer.getIfcTablePeerImpl(); + assertTrue("IfcTablePeerImpl should be an instance of " + + "TestPeerInterface", + peerImpl instanceof TestPeerInterface); + + LocalIfcTable localIfc = new LocalIfcTable(); + + assertTrue("LocalIfcTable should be an instance of LocalTestInterface", + localIfc instanceof LocalTestInterface); + + List<LocalIfcTable> results2 = LocalIfcTablePeer.doSelect(new Criteria()); + + for (LocalIfcTable readLocalIfcTable : results2) + { + assertTrue("IfcTable2Peer.doSelect should return" + + " instances of LocalTestInterface", + readLocalIfcTable instanceof LocalTestInterface); + } + + LocalIfcTablePeerImpl localPeerImpl = LocalIfcTablePeer.getLocalIfcTablePeerImpl(); + assertTrue("LocalIfcTablePeerImpl should be an instance of " + + "LocalTestPeerInterface", + localPeerImpl instanceof LocalTestPeerInterface); + } + + @Test + public void testInheritanceWithKeys() throws Exception + { + // make sure that the InheritanceTest table is empty before the test + Criteria criteria = new Criteria(); + criteria.where( + InheritanceTestPeer.INHERITANCE_TEST, + (Object) null, + Criteria.ISNOTNULL); + InheritanceTestPeer.doDelete(criteria); + criteria = new Criteria(); + criteria.where( + InheritanceTestPeer.INHERITANCE_TEST, + (Object) null, + Criteria.ISNOTNULL); + assertEquals(0, + new CountHelper().count(criteria)); + + // create & save test data + InheritanceTest inheritanceTest = new InheritanceTest(); + inheritanceTest.setPayload("payload1"); + inheritanceTest.save(); + InheritanceChildB inheritanceChildB = new InheritanceChildB(); + inheritanceChildB.setPayload("payload 2"); + inheritanceChildB.save(); + InheritanceChildC inheritanceChildC = new InheritanceChildC(); + inheritanceChildC.setPayload("payload 3"); + inheritanceChildC.save(); + InheritanceChildD inheritanceChildD = new InheritanceChildD(); + inheritanceChildD.setPayload("payload 4"); + inheritanceChildD.save(); + + // Check that all objects are saved into the InheritanceTest table + criteria = new Criteria(); + criteria.where( + InheritanceTestPeer.INHERITANCE_TEST, + null, + Criteria.ISNOTNULL); + assertEquals("InheritanceTestTable should contain 4 rows", + 4, + new CountHelper().count(criteria)); + criteria = new Criteria(); + criteria.addAscendingOrderByColumn( + InheritanceTestPeer.INHERITANCE_TEST); + + // Check that the class of the object is retained when loading + List<InheritanceTest> inheritanceObjects + = InheritanceTestPeer.doSelect(criteria); + assertEquals( + InheritanceTest.class, + inheritanceObjects.get(0).getClass()); + assertEquals( + InheritanceChildB.class, + inheritanceObjects.get(1).getClass()); + assertEquals( + InheritanceChildC.class, + inheritanceObjects.get(2).getClass()); + assertEquals( + InheritanceChildD.class, + inheritanceObjects.get(3).getClass()); + } + + @Test + public void testInheritanceWithClassname() throws Exception + { + // make sure that the InheritanceTest table is empty before the test + Criteria criteria = new Criteria(); + InheritanceClassnameTestPeer.doDelete(criteria); + criteria = new Criteria(); + criteria.where( + InheritanceClassnameTestPeer.INHERITANCE_TEST, + null, + Criteria.ISNOTNULL); + assertEquals(0, + new CountHelper().count(criteria)); + + // create & save test data + InheritanceClassnameTest inheritanceClassnameTest + = new InheritanceClassnameTest(); + inheritanceClassnameTest.setPayload("0 parent"); + inheritanceClassnameTest.save(); + InheritanceClassnameTestChild1 inheritanceClassnameChild1 + = new InheritanceClassnameTestChild1(); + inheritanceClassnameChild1.setPayload("1 child"); + inheritanceClassnameChild1.save(); + InheritanceClassnameTestChild2 inheritanceClassnameChild2 + = new InheritanceClassnameTestChild2(); + inheritanceClassnameChild2.setPayload("2 child"); + inheritanceClassnameChild2.save(); + + // Check that all objects are saved into the InheritanceTest table + criteria = new Criteria(); + criteria.where( + InheritanceClassnameTestPeer.INHERITANCE_TEST, + null, + Criteria.ISNOTNULL); + assertEquals("InheritanceClassnameTest table should contain 3 rows", + 3, + new CountHelper().count(criteria)); + criteria = new Criteria(); + criteria.addAscendingOrderByColumn( + InheritanceClassnameTestPeer.PAYLOAD); + + // Check that the class of the object is retained when loading + List<InheritanceClassnameTest> inheritanceObjects + = InheritanceClassnameTestPeer.doSelect(criteria); + assertEquals( + InheritanceClassnameTest.class, + inheritanceObjects.get(0).getClass()); + assertEquals("0 parent", inheritanceObjects.get(0).getPayload()); + assertEquals( + InheritanceClassnameTestChild1.class, + inheritanceObjects.get(1).getClass()); + assertEquals("1 child", inheritanceObjects.get(1).getPayload()); + assertEquals( + InheritanceClassnameTestChild2.class, + inheritanceObjects.get(2).getClass()); + assertEquals("2 child", inheritanceObjects.get(2).getPayload()); + } + + /** + * Checks whether selects with unqualified column names work. + * + * @throws Exception if a problem occurs. + */ + @Test + public void testUnqualifiedColumnNames() throws Exception + { + cleanBookstore(); + Author author = new Author(); + author.setName("Joshua Bloch"); + author.save(); + + Criteria criteria = new Criteria(); + criteria.where(AuthorPeer.AUTHOR_ID, (Object) null, Criteria.NOT_EQUAL); + criteria.and(new ColumnImpl("name"), "Joshua Bloch", Criteria.EQUAL); + List<Author> authors = AuthorPeer.doSelect(criteria); + assertEquals(1, authors.size()); + } + + @Test + public void testLikeClauseEscaping() throws Exception + { + String[] authorNames + = {"abc", "bbc", "a_c", "a%c", "a\\c", + "a\"c", "a'c", "a?c", "a*c" }; + + Map<String, String> likeResults = new LinkedHashMap<>(); + + likeResults.put("a\\_c", "a_c"); + likeResults.put("a\\_%", "a_c"); + likeResults.put("%\\_c", "a_c"); + + likeResults.put("a\\%c", "a%c"); + likeResults.put("a\\%%", "a%c"); + likeResults.put("%\\%c", "a%c"); // escaped second % + + likeResults.put("a\\\\c", "a\\c"); // escaped \ three times + likeResults.put("a\\\\%", "a\\c"); + + // mysql: like '%\\c' ESCAPE '|' succeeds, but + // %\\\\c fails, see https://dev.mysql.com/doc/refman/8.0/en/string-comparison-functions.html, + // MySQL uses C escape syntax in strings.. you must double any \ that you use in LIKE strings. + // That is platform specific mysql: + // likeResults.put("%\\\\\\\\c", "a\\c"); // succeeds in mysql + // other platforms ? + //likeResults.put("%\\\\c", "a\\c"); // fails in mysql + + likeResults.put("a\\*c", "a*c"); + likeResults.put("a\\*%", "a*c"); + //likeResults.put("%\\*c", "a*c"); // mysql: %\\*c fails, only underscore (_) is wild card + // this matches multiple users + //likeResults.put("%*c", "a%c"); + likeResults.put("_\\*c", "a*c"); + + likeResults.put("a\\?c", "a?c"); + likeResults.put("a\\?%", "a?c"); + likeResults.put("%\\?c", "a?c"); + + likeResults.put("a\"c", "a\"c"); + likeResults.put("a\"%", "a\"c"); + likeResults.put("%\"c", "a\"c"); + + likeResults.put("a'c", "a'c"); + likeResults.put("a'%", "a'c"); + likeResults.put("%'c", "a'c"); + cleanBookstore(); + + // Save authors + for (int i = 0; i < authorNames.length; ++i) + { + Author author = new Author(); + author.setName(authorNames[i]); + author.save(); + } + + // Check authors are in the database + for (int i = 0; i < authorNames.length; ++i) + { + Criteria criteria = new Criteria(); + criteria.where(AuthorPeer.NAME, authorNames[i]); + List<Author> authorList = AuthorPeer.doSelect(criteria); + assertEquals( + "AuthorList should contain one author" + + " when querying for " + authorNames[i], + 1, + authorList.size()); + Author author = authorList.get(0); + assertEquals("Name of author should be " + authorNames[i], + authorNames[i], + author.getName()); + } + + for (Map.Entry<String, String> likeResult : likeResults.entrySet()) + { + // System.out.println("Key: " + likeResult.getKey() + " - Value: " + likeResult.getValue()); + Criteria criteria = new Criteria(); + criteria.where( + AuthorPeer.NAME, + likeResult.getKey(), + Criteria.LIKE); + List<Author> authorList; + try + { + authorList = AuthorPeer.doSelect(criteria); + } + catch (Exception e) + { + throw new Exception( + "error executing select using like content " + + likeResult.getKey(), + e); + } + assertEquals( + "AuthorList contained " + authorList.size() + ", but should contain one author" + + " when querying for " + likeResult.getKey(), + 1, + authorList.size()); + Author author = authorList.get(0); + assertEquals("Name of author should be " + + likeResult.getValue() + + " when querying for " + + likeResult.getKey(), + likeResult.getValue(), + author.getName()); + } + + // check that case insensitivity is maintained if + // a like is replaced with an equals (no wildcard present) + // This might be a problem for databases which use ILIKE + Criteria criteria = new Criteria(); + criteria.where(AuthorPeer.NAME, "AbC", Criteria.LIKE); + criteria.setIgnoreCase(true); + List<Author> authorList = AuthorPeer.doSelect(criteria); + assertEquals( + "AuthorList should contain one author", + 1, + authorList.size()); + Author author = authorList.get(0); + assertEquals("Name of author should be abc", + "abc", + author.getName()); + + // check that the escape clause (where needed) also works + // with limit, offset and order by + criteria = new Criteria(); + Criterion criterion1 = new Criterion( + AuthorPeer.NAME, + "b%", + Criteria.LIKE); + Criterion criterion2 = new Criterion( + AuthorPeer.NAME, + "a\\%%", + Criteria.LIKE); + Criterion criterion3 = new Criterion( + AuthorPeer.NAME, + "cbc", + Criteria.LIKE); + criteria.where(criterion1.or(criterion2).or(criterion3)); + criteria.addAscendingOrderByColumn(AuthorPeer.NAME); + criteria.setOffset(1); + criteria.setLimit(1); + authorList = AuthorPeer.doSelect(criteria); + assertEquals( + "AuthorList should contain one author", + 1, + authorList.size()); + author = authorList.get(0); + assertEquals("Name of author should be bbc", + "bbc", + author.getName()); + } + + + /** + * Strips the schema and table name from a fully qualified colum name + * This is useful for creating Query with aliases, as the constants + * for the colum names in the data objects are fully qualified. + * @param fullyQualifiedColumnName the fully qualified column name, not null + * @return the column name stripped from the table (and schema) prefixes + */ + public static String getRawColumnName(final String fullyQualifiedColumnName) + { + int dotPosition = fullyQualifiedColumnName.lastIndexOf("."); + if (dotPosition == -1) + { + return fullyQualifiedColumnName; + } + String result = fullyQualifiedColumnName.substring( + dotPosition + 1, + fullyQualifiedColumnName.length()); + return result; + } + + static class DoNothingMapper implements RecordMapper<Object> + { + + /** Serial version */ + private static final long serialVersionUID = 1L; + + @Override + public Object processRow( + final ResultSet resultSet, + final int rowOffset, + final Criteria criteria) + throws TorqueException + { + return null; + } + } +} Propchange: db/torque/torque4/trunk/torque-test/src/test/java/org/apache/torque/testcontainer/DataContainerTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/torque/torque4/trunk/torque-test/src/test/profile/mysql/Torque4Test.xml URL: http://svn.apache.org/viewvc/db/torque/torque4/trunk/torque-test/src/test/profile/mysql/Torque4Test.xml?rev=1870385&view=auto ============================================================================== --- db/torque/torque4/trunk/torque-test/src/test/profile/mysql/Torque4Test.xml (added) +++ db/torque/torque4/trunk/torque-test/src/test/profile/mysql/Torque4Test.xml Mon Nov 25 15:22:32 2019 @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!-- + 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. +--> +<!-- from the docs: CombinedConfigurationBuilder takes care that properties defined in the first file (the user file) are found; other properties which the user has not changed will still be returned from the second file (the defaults file). --> + +<configuration> + <properties config-name="torqueuser" fileName="torque.usersettings.properties" config-optional="true" config-forceCreate="true"/> + <properties config-name="torque" fileName="Torque.properties" throwExceptionOnMissing="true"/> +</configuration> \ No newline at end of file Propchange: db/torque/torque4/trunk/torque-test/src/test/profile/mysql/Torque4Test.xml ------------------------------------------------------------------------------ svn:eol-style = native Added: db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/.dockerignore URL: http://svn.apache.org/viewvc/db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/.dockerignore?rev=1870385&view=auto ============================================================================== --- db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/.dockerignore (added) +++ db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/.dockerignore Mon Nov 25 15:22:32 2019 @@ -0,0 +1,19 @@ +# 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. +*/tmp* +*/*/tmp* +tmp? \ No newline at end of file Added: db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/Dockerfile URL: http://svn.apache.org/viewvc/db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/Dockerfile?rev=1870385&view=auto ============================================================================== --- db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/Dockerfile (added) +++ db/torque/torque4/trunk/torque-test/src/test/profile/mysql/docker-resources/db/Dockerfile Mon Nov 25 15:22:32 2019 @@ -0,0 +1,10 @@ +FROM mysql:8.0.18 + +COPY [ "./generated-createddb-sql/*", "./generated-sql/*", "/docker-entrypoint-initdb.d/" ] + +ENV MYSQL_DATABASE ${MYSQL_DATABASE} +ENV MYSQL_USER=${MYSQL_USER} +ENV MYSQL_PASSWORD=${MYSQL_PASSWORD} +ENV MYSQL_HOST=% + +ENV MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} --------------------------------------------------------------------- To unsubscribe, e-mail: torque-dev-unsubscr...@db.apache.org For additional commands, e-mail: torque-dev-h...@db.apache.org