Repository: Ninja-Squad/DbSetup Branch: master Commit: e520dc610ecd Files: 67 Total size: 287.8 KB Directory structure: gitextract_dbqecuzp/ ├── .circleci/ │ └── config.yml ├── .gitignore ├── DbSetup-core/ │ ├── build.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── ninja_squad/ │ │ └── dbsetup/ │ │ ├── DbSetup.java │ │ ├── DbSetupRuntimeException.java │ │ ├── DbSetupTracker.java │ │ ├── Operations.java │ │ ├── bind/ │ │ │ ├── Binder.java │ │ │ ├── BinderConfiguration.java │ │ │ ├── Binders.java │ │ │ └── DefaultBinderConfiguration.java │ │ ├── destination/ │ │ │ ├── DataSourceDestination.java │ │ │ ├── Destination.java │ │ │ └── DriverManagerDestination.java │ │ ├── generator/ │ │ │ ├── DateSequenceValueGenerator.java │ │ │ ├── SequenceValueGenerator.java │ │ │ ├── StringSequenceValueGenerator.java │ │ │ ├── ValueGenerator.java │ │ │ └── ValueGenerators.java │ │ ├── operation/ │ │ │ ├── CompositeOperation.java │ │ │ ├── DeleteAll.java │ │ │ ├── Insert.java │ │ │ ├── Operation.java │ │ │ ├── SqlOperation.java │ │ │ └── Truncate.java │ │ ├── overview.html │ │ └── util/ │ │ └── Preconditions.java │ └── test/ │ └── java/ │ └── com/ │ └── ninja_squad/ │ └── dbsetup/ │ ├── DbSetupTest.java │ ├── DbSetupTrackerTest.java │ ├── bind/ │ │ ├── BindersTest.java │ │ └── DefaultBinderConfigurationTest.java │ ├── destination/ │ │ ├── DataSourceDestinationTest.java │ │ └── DriverManagerDestinationTest.java │ ├── generator/ │ │ ├── DateSequenceValueGeneratorTest.java │ │ ├── SequenceValueGeneratorTest.java │ │ ├── StringSequenceValueGeneratorTest.java │ │ └── ValueGeneratorsTest.java │ ├── integration/ │ │ ├── CommonOperations.java │ │ ├── Database.java │ │ ├── DeleteAllIntegrationTest.java │ │ ├── InsertIntegrationTest.java │ │ ├── RollbackIntegrationTest.java │ │ └── TruncateIntegrationTest.java │ └── operation/ │ ├── CompositeOperationTest.java │ ├── DeleteAllTest.java │ ├── InsertTest.java │ ├── SqlOperationTest.java │ └── TruncateTest.java ├── DbSetup-kotlin/ │ ├── build.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── com/ │ │ └── ninja_squad/ │ │ └── dbsetup_kotlin/ │ │ ├── DbSetup.kt │ │ ├── DbSetupBuilder.kt │ │ ├── Functions.kt │ │ └── Insert.Builder.kt │ └── test/ │ └── kotlin/ │ └── com/ │ └── ninja_squad/ │ └── dbsetup_kotlin/ │ ├── DbSetupBuilderTest.kt │ ├── InsertBuilderTest.kt │ └── MockitoExtensions.kt ├── README.md ├── build.gradle.kts ├── buildSrc/ │ ├── build.gradle.kts │ ├── settings.grade.kts │ └── src/ │ └── main/ │ └── kotlin/ │ └── java-library-convention.gradle.kts ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ version: 2 jobs: build: working_directory: ~/DbSetup docker: - image: circleci/openjdk:8-jdk steps: - checkout - run: ./gradlew build - store_test_results: path: DbSetup-core/build/test-results/test - store_test_results: path: DbSetup-kotlin/build/test-results/test ================================================ FILE: .gitignore ================================================ /.project .gradle /.settings /bin build/ /.classpath /.classpath.idea/ /.java-version *.iml .idea out classes .sdkmanrc ================================================ FILE: DbSetup-core/build.gradle.kts ================================================ plugins { `java-library-convention` } project.description = "Helps you setup your database with test data" java { withJavadocJar() } dependencies { compileOnly("com.google.code.findbugs:jsr305:2.0.0") compileOnly("net.sourceforge.findbugs:annotations:1.3.2") testImplementation("junit:junit:4.+") testImplementation("org.mockito:mockito-all:1.9.0") testImplementation("org.hsqldb:hsqldb:2.3.3") } tasks { javadoc { (options as StandardJavadocDocletOptions).apply { overview(file("src/main/java/com/ninja_squad/dbsetup/overview.html").path) noTimestamp(true) linkSource(true) addBooleanOption("Xdoclint:all,-missing", true) } } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/DbSetup.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup; import java.sql.Connection; import java.sql.SQLException; import javax.annotation.Nonnull; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration; import com.ninja_squad.dbsetup.destination.Destination; import com.ninja_squad.dbsetup.operation.Operation; import com.ninja_squad.dbsetup.util.Preconditions; /** * Allows executing a sequence of database operations. This object is reusable, and can thus be used several times * to launch the same sequence of database operations. Here's a typical usage scenario in a unit test: *
 * @Before
 * public void setUp() throws Exception {
 *     Operation operation =
 *         Operations.sequenceOf(
 *             CommonOperations.DELETE_ALL,
 *             CommonOperations.INSERT_REFERENCE_DATA,
 *             Operations.insertInto("CLIENT")
 *                       .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "COUNTRY_ID")
 *                       .values(1L, "John", "Doe", "1975-07-19", 1L)
 *                       .values(2L, "Jack", "Smith", "1969-08-22", 2L)
 *                       .build());
 *     DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
 *     dbSetup.launch();
 * }
 * 
* In the above code, CommonOperations.DELETE_ALL and CommonOperations.INSERT_REFERENCE_DATA * are operations shared by multiple test classes. *

* Note that, to speed up test executions, a {@link DbSetupTracker} can be used, at the price of a slightly * bigger complexity. * @author JB Nizet */ public final class DbSetup { private final Destination destination; private final Operation operation; private final BinderConfiguration binderConfiguration; /** * Constructor which uses the {@link DefaultBinderConfiguration#INSTANCE default binder configuration}. * @param destination the destination of the sequence of database operations * @param operation the operation to execute (most of the time, an instance of * {@link com.ninja_squad.dbsetup.operation.CompositeOperation} */ public DbSetup(@Nonnull Destination destination, @Nonnull Operation operation) { this(destination, operation, DefaultBinderConfiguration.INSTANCE); } /** * Constructor allowing to use a custom {@link BinderConfiguration}. * @param destination the destination of the sequence of database operations * @param operation the operation to execute (most of the time, an instance of * {@link com.ninja_squad.dbsetup.operation.CompositeOperation} * @param binderConfiguration the binder configuration to use. */ public DbSetup(@Nonnull Destination destination, @Nonnull Operation operation, @Nonnull BinderConfiguration binderConfiguration) { Preconditions.checkNotNull(destination, "destination may not be null"); Preconditions.checkNotNull(operation, "operation may not be null"); Preconditions.checkNotNull(binderConfiguration, "binderConfiguration may not be null"); this.destination = destination; this.operation = operation; this.binderConfiguration = binderConfiguration; } /** * Executes the sequence of operations. All the operations use the same connection, and are grouped * in a single transaction. The transaction is rolled back if any exception occurs. */ public void launch() { try { Connection connection = destination.getConnection(); try { connection.setAutoCommit(false); operation.execute(connection, binderConfiguration); connection.commit(); } catch (SQLException e) { connection.rollback(); throw e; } catch (RuntimeException e) { connection.rollback(); throw e; } finally { connection.close(); } } catch (SQLException e) { throw new DbSetupRuntimeException(e); } } @Override public String toString() { return "DbSetup [destination=" + destination + ", operation=" + operation + ", binderConfiguration=" + binderConfiguration + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + binderConfiguration.hashCode(); result = prime * result + destination.hashCode(); result = prime * result + operation.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } DbSetup other = (DbSetup) obj; return binderConfiguration.equals(other.binderConfiguration) && destination.equals(other.destination) && operation.equals(other.operation); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/DbSetupRuntimeException.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup; /** * A runtime exception indicating that a DbSetup failed. * @author JB Nizet */ public class DbSetupRuntimeException extends RuntimeException { public DbSetupRuntimeException() { super(); } public DbSetupRuntimeException(String message, Throwable cause) { super(message, cause); } public DbSetupRuntimeException(String message) { super(message); } public DbSetupRuntimeException(Throwable cause) { super(cause); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/DbSetupTracker.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup; import javax.annotation.Nonnull; /** *

* This class allows speeding up test execution, by avoiding re-executing the same sequence of database operations * before each test method even if each of these test methods leaves the database as it is (and only performs read-only * operations, which is the most frequent case). *

*

Example usage:

*
 * // the tracker is static because JUnit uses a separate Test instance for every test method.
 * private static DbSetupTracker dbSetupTracker = new DbSetupTracker();
 *
 * @Before
 * public void setUp() throws Exception {
 *     Operation operation =
 *         Operations.sequenceOf(
 *             CommonOperations.DELETE_ALL,
 *             CommonOperations.INSERT_REFERENCE_DATA,
 *             Operations.insertInto("CLIENT")
 *                       .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "COUNTRY_ID")
 *                       .values(1L, "John", "Doe", "1975-07-19", 1L)
 *                       .values(2L, "Jack", "Smith", "1969-08-22", 2L)
 *                       .build());
 *     DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
 *     dbSetupTracker.launchIfNecessary(dbSetup);
 * }
 *
 * @Test
 * public void readOnlyTest1() {
 *     dbSetupTracker.skipNextLaunch();
 *     ...
 * }
 *
 * @Test
 * public void readOnlyTest2() {
 *     dbSetupTracker.skipNextLaunch();
 *     ...
 * }
 *
 * @Test
 * public void readOnlyTest3() {
 *     dbSetupTracker.skipNextLaunch();
 *     ...
 * }
 *
 * @Test
 * public void readWriteTest1() {
 *     // No call to dbSetupTracker.skipNextLaunch();
 *     ...
 * }
 * 
* @author JB Nizet */ public final class DbSetupTracker { private DbSetup lastSetupLaunched; private boolean nextLaunchSkipped; /** * Executes the given DbSetup unless all the following conditions are true: * * This method resets the skipNextLaunch flag to false. * @param dbSetup the DbSetup to execute (or skip) */ public void launchIfNecessary(@Nonnull DbSetup dbSetup) { boolean skipLaunch = nextLaunchSkipped && dbSetup.equals(lastSetupLaunched); nextLaunchSkipped = false; if (skipLaunch) { return; } dbSetup.launch(); lastSetupLaunched = dbSetup; } /** * Marks the current test method as read-only, and thus the need for the next test method to re-execute the same * sequence of database setup operations. */ public void skipNextLaunch() { this.nextLaunchSkipped = true; } @Override public String toString() { return "DbSetupTracker [lastSetupLaunched=" + lastSetupLaunched + ", nextLaunchSkipped=" + nextLaunchSkipped + "]"; } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/Operations.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup; import java.util.List; import javax.annotation.Nonnull; import com.ninja_squad.dbsetup.operation.CompositeOperation; import com.ninja_squad.dbsetup.operation.DeleteAll; import com.ninja_squad.dbsetup.operation.Insert; import com.ninja_squad.dbsetup.operation.Operation; import com.ninja_squad.dbsetup.operation.SqlOperation; import com.ninja_squad.dbsetup.operation.Truncate; /** * A static factory class for operations. Static import of this class can help make the code more readable. * @author JB Nizet */ public final class Operations { private Operations() { } /** * Creates a delete from table operation. * @param table the table to delete all from * @see DeleteAll */ public static DeleteAll deleteAllFrom(@Nonnull String table) { return DeleteAll.from(table); } /** * Creates a sequence of delete from table operations. * @param tables the tables to delete all from * @see DeleteAll */ public static Operation deleteAllFrom(@Nonnull String... tables) { return DeleteAll.from(tables); } /** * Creates a sequence of delete from ... operations. * @param tables the tables to delete all from * @see DeleteAll */ public static Operation deleteAllFrom(@Nonnull List tables) { return DeleteAll.from(tables); } /** * Creates a truncate table ... operation. * @param table the table to truncate * @see Truncate */ public static Truncate truncate(@Nonnull String table) { return Truncate.table(table); } /** * Creates a sequence of truncate table ... operations. * @param tables the tables to truncate * @see Truncate */ public static Operation truncate(@Nonnull String... tables) { return Truncate.tables(tables); } /** * Creates a sequence of truncate table ... operations. * @param tables the tables to truncate * @see Truncate */ public static Operation truncate(@Nonnull List tables) { return Truncate.tables(tables); } /** * Creates a SQL operation. * @param sqlStatement the SQL statement to execute (using {@link java.sql.Statement#executeUpdate(String)}) * @see SqlOperation */ public static SqlOperation sql(@Nonnull String sqlStatement) { return SqlOperation.of(sqlStatement); } /** * Creates a sequence of SQL operations. * @param sqlStatements the SQL statements to execute (using {@link java.sql.Statement#executeUpdate(String)}) * @see SqlOperation */ public static Operation sql(@Nonnull String... sqlStatements) { return SqlOperation.of(sqlStatements); } /** * Creates a sequence of SQL operations. * @param sqlStatements the SQL statements to execute (using {@link java.sql.Statement#executeUpdate(String)}) * @see SqlOperation */ public static Operation sql(@Nonnull List sqlStatements) { return SqlOperation.of(sqlStatements); } /** * Creates a builder for a sequence of insert operations. * @param table the table to insert into * @see Insert */ public static Insert.Builder insertInto(@Nonnull String table) { return Insert.into(table); } /** * Creates a sequence of operations. * @param operations the operations to put in a sequence * @see CompositeOperation */ public static Operation sequenceOf(@Nonnull Operation... operations) { return CompositeOperation.sequenceOf(operations); } /** * Creates a sequence of operations. * @param operations the operations to put in a sequence * @see CompositeOperation */ public static Operation sequenceOf(@Nonnull List operations) { return CompositeOperation.sequenceOf(operations); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/bind/Binder.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.bind; import java.sql.PreparedStatement; import java.sql.SQLException; import com.ninja_squad.dbsetup.DbSetupTracker; /** * An object which binds a value to a prepared statement parameter. It's advised to make implementations of this * interface immutable, and to make them implement equals and hashCode in order for {@link DbSetupTracker} to function * properly, or to make them singletons. * @author JB Nizet */ public interface Binder { /** * Binds the given value to the given parameter in the given prepared statement. * @param statement the statement to bind the parameter to * @param param The index of the parameter to bind in the statement * @param value The value to bind (may be null) * @throws SQLException if the binding throws a {@link SQLException} */ void bind(PreparedStatement statement, int param, Object value) throws SQLException; } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/bind/BinderConfiguration.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.bind; import com.ninja_squad.dbsetup.DbSetup; import com.ninja_squad.dbsetup.DbSetupTracker; import javax.annotation.Nullable; import java.sql.ParameterMetaData; import java.sql.SQLException; /** * An object which returns the appropriate {@link Binder} based on the metadata of the prepared statement. * The default instance of this interface is {@link DefaultBinderConfiguration}. If the binders returned by this * default configuration don't fit for the particular database you're using, or if you would like the binders * returned by the configuration to support additional data types, you might want to provide a different implementation * of this interface to the {@link DbSetup}. *

* It's advised to make implementations of this interface immutable, and to make them implement equals and hashCode * in order for {@link DbSetupTracker} to function properly, or to make them singletons. * @author JB Nizet */ public interface BinderConfiguration { /** * Returns the appropriate {@link Binder} for the given parameter, based on the given metadata. * @param metadata the metadata allowing to decide which Binder to return. null if the Insert has been * configured to not use metadata, or if the JDBC driver returned null metadata, or the JDBC driver threw a * SQLException when asked for the metadata * @param param the param for which a binder is requested * @return the binder for the given param and its metadata * @throws SQLException if a SQLException occurs while using the metadata */ Binder getBinder(@Nullable ParameterMetaData metadata, int param) throws SQLException; } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/bind/Binders.java ================================================ /* * The MIT License * * Copyright (c) 2012-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.bind; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.TimeZone; /** * Utility class allowing to get various kinds of binders. The {@link DefaultBinderConfiguration} uses binders * returned by this class, based on the type of the parameter. * @author JB Nizet */ public final class Binders { private static final Binder DEFAULT_BINDER = new DefaultBinder(); private static final Binder DATE_BINDER = new DateBinder(); private static final Binder TIMESTAMP_BINDER = new TimestampBinder(); private static final Binder DECIMAL_BINDER = new DecimalBinder(); private static final Binder INTEGER_BINDER = new IntegerBinder(); private static final Binder TIME_BINDER = new TimeBinder(); private static final Binder STRING_BINDER = new StringBinder(); private Binders() { } /** * Returns the default binder. This binder is normally used for columns of a type that is not handled by the other * binders. It is also used when the metadata are not used and the Insert thus doesn't know the type of the column. * It simply uses stmt.setObject() to bind the parameter, except if the value being bound is of some * some well-known type not handled by JDBC: *

    *
  • enum: the name of the enum is bound
  • *
  • java.util.Date: the date is transformed to a java.sql.Timestamp
  • *
  • java.util.Calendar: the calendar is transformed to a java.sql.Timestamp, * and is passed as third argument of * PreparedStatement.setTimestamp() to pass the timezone
  • *
  • java.time.LocalDate: transformed to a java.sql.Date
  • *
  • java.time.LocalTime: transformed to a java.sql.Time
  • *
  • java.time.LocalDateTime: transformed to a java.sql.Timestamp
  • *
  • java.time.Instant: transformed to a java.sql.Timestamp
  • *
  • java.time.ZonedDateTime and OffsetDateTime: transformed to a * java.sql.Timestamp. The time zone is also used to create a Calendar passed as * third argument of PreparedStatement.setTimestamp() to pass the timezone
  • *
  • java.time.OffsetTime: transformed to a * java.sql.Time. The time zone is also used to create a Calendar passed as third * argument of PreparedStatement.setTime() to pass the timezone
  • *
*/ public static Binder defaultBinder() { return DEFAULT_BINDER; } /** * Returns a binder suitable for columns of type CHAR and VARCHAR. The returned binder supports values of type *
    *
  • String
  • *
  • enum: the name of the enum is used as bound value
  • *
  • Object: the toString() of the object is used as bound value
  • *
*/ public static Binder stringBinder() { return STRING_BINDER; } /** * Returns a binder suitable for columns of type DATE. The returned binder supports values of type *
    *
  • java.sql.Date
  • *
  • java.util.Date: the milliseconds of the date are used to construct a * java.sql.Date.
  • *
  • java.util.Calendar: the milliseconds of the calendar are used to construct a * java.sql.Date, and the calendar is passed as third argument of * PreparedStatement.setDate() to pass the timezone *
  • *
  • String: the string is transformed to a java.sql.Date using the Date.valueOf() * method
  • *
  • java.time.LocalDate: transformed to a java.sql.Date using * Date.valueOf()
  • *
  • java.time.LocalDateTime: transformed to a LocalDate (and thus ignoring the time), * and then transformed to a java.sql.Date using Date.valueOf()
  • *
  • java.time.Instantthe milliseconds of the instant are used to construct a * java.sql.Date.
  • *
  • java.time.ZonedDateTime and java.time.OffsetDateTime: transformed to an Instant * and then to a java.sql.Date. The time zone is also used to create a Calendar * passed as third argument of PreparedStatement.setDate() to pass the timezone
  • *
* If the value is none of these types, stmt.setObject() is used to bind the value. */ public static Binder dateBinder() { return DATE_BINDER; } /** * Returns a binder suitable for columns of type TIMESTAMP and TIMESTAMP_WITH_TIMEZONE. The returned binder * supports values of type *
    *
  • java.sql.Timestamp
  • *
  • java.util.Date: the milliseconds of the date are used to construct a * java.sql.Timestamp
  • *
  • java.util.Calendar: the milliseconds of the calendar are used to construct a * java.sql.Timestamp, and the calendar is passed as third argument of * PreparedStatement.setTimestamp() to pass the timezone
  • *
  • String: the string is transformed to a java.sql.Timestamp using the * Timestamp.valueOf() method, or using the java.sql.Date.valueOf() method if the * string has less than 19 characters
  • *
  • java.time.LocalDateTime: transformed to a java.sql.Timestamp using * Timestamp.valueOf()
  • *
  • java.time.LocalDate: transformed to a LocalDateTime with the time at start of day, * and then transformed to a java.sql.Timestamp using Timestamp.valueOf()
  • *
  • java.time.Instant: transformed to a java.sql.Timestamp using * Timestamp.from()
  • *
  • java.time.ZonedDateTime and java.time.OffsetDateTime: transformed to an Instant * and then to a java.sql.Timestamp using Timestamp.from(). The time zone is also * used to create a Calendar passed as third argument of * PreparedStatement.setTimestamp() to pass the timezone
  • *
* If the value is none of these types, stmt.setObject() is used to bind the value. */ public static Binder timestampBinder() { return TIMESTAMP_BINDER; } /** * Returns a binder suitable for columns of type TIME or TIME_WITH_TIMEZONE. The returned binder supports values * of type *
    *
  • java.sql.Time
  • *
  • java.util.Date: the milliseconds of the date are used to construct a * java.sql.Time
  • *
  • java.util.Calendar: the milliseconds of the calendar are used to construct a * java.sql.Time, and the calendar is passed as third argument of * PreparedStatement.setTimestamp() to pass the timezone *
  • *
  • String: the string is transformed to a java.sql.Time using the * Time.valueOf() method
  • *
  • java.time.LocalTime: transformed to a java.sql.Time using * Time.valueOf()
  • *
  • java.time.OffsetTime: transformed to a LocalTime and then to a * java.sql.Time using Time.valueOf(). The time zone is also * used to create a Calendar passed as third argument of * PreparedStatement.setTime() to pass the timezone
  • *
* If the value is none of these types, stmt.setObject() is used to bind the value. */ public static Binder timeBinder() { return TIME_BINDER; } /** * Returns a binder suitable for numeric, decimal columns. The returned binder supports values of type *
    *
  • String: the string is transformed to a java.math.BigDecimal using its constructor
  • *
* If the value is none of these types, stmt.setObject() is used to bind the value. */ public static Binder decimalBinder() { return DECIMAL_BINDER; } /** * Returns a binder suitable for numeric, integer columns. The returned binder supports values of type *
    *
  • BigInteger: the object is transformed to a String and bound using * stmt.setObject(), with BIGINT as target type. *
  • *
  • enum: the enum is transformed into an integer by taking its ordinal
  • *
  • String: the string is bound using stmt.setObject(), with BIGINT as * target type. *
  • *
* If the value is none of these types, stmt.setObject() is used to bind the value. */ public static Binder integerBinder() { return INTEGER_BINDER; } /** * The implementation for {@link Binders#stringBinder()} * @author JB Nizet */ private static final class StringBinder implements Binder { @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof String) { stmt.setString(param, (String) value); } else if (value instanceof Enum) { stmt.setString(param, ((Enum) value).name()); } else if (value == null) { stmt.setObject(param, null); } else { stmt.setString(param, value.toString()); } } @Override public String toString() { return "Binders.stringBinder"; } } /** * The implementation for {@link Binders#timeBinder()} * @author JB Nizet */ private static final class TimeBinder implements Binder { @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof Time) { stmt.setTime(param, (Time) value); } else if (value instanceof java.util.Date) { stmt.setTime(param, new Time(((java.util.Date) value).getTime())); } else if (value instanceof Calendar) { Calendar calendar = (Calendar) value; stmt.setTime(param, new Time(calendar.getTimeInMillis()), calendar); } else if (value instanceof String) { stmt.setTime(param, Time.valueOf((String) value)); } else if (value instanceof LocalTime) { stmt.setTime(param, Time.valueOf((LocalTime) value)); } else if (value instanceof OffsetTime) { OffsetTime offsetTime = (OffsetTime) value; stmt.setTime(param, Time.valueOf(offsetTime.toLocalTime()), Calendar.getInstance(TimeZone.getTimeZone(offsetTime.getOffset()))); } else { stmt.setObject(param, value); } } @Override public String toString() { return "Binders.timeBinder"; } } /** * The implementation for {@link Binders#integerBinder()} * @author JB Nizet */ private static final class IntegerBinder implements Binder { @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof BigInteger) { stmt.setObject(param, value.toString(), Types.BIGINT); } else if (value instanceof Enum) { stmt.setInt(param, ((Enum) value).ordinal()); } else if (value instanceof String) { stmt.setObject(param, value, Types.BIGINT); } else { stmt.setObject(param, value); } } @Override public String toString() { return "Binders.integerBinder"; } } /** * The implementation for {@link Binders#decimalBinder()} * @author JB Nizet */ private static final class DecimalBinder implements Binder { @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof String) { stmt.setBigDecimal(param, new BigDecimal((String) value)); } else { stmt.setObject(param, value); } } @Override public String toString() { return "Binders.decimalBinder"; } } /** * The implementation for {@link Binders#timestampBinder()} * @author JB Nizet */ private static final class TimestampBinder implements Binder { // the number of chars in yyyy-mm-dd hh:mm:ss private static final int MIN_NUMBER_OF_CHARS_FOR_TIMESTAMP = 19; @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof Timestamp) { stmt.setTimestamp(param, (Timestamp) value); } else if (value instanceof java.util.Date) { stmt.setTimestamp(param, new Timestamp(((java.util.Date) value).getTime())); } else if (value instanceof Calendar) { stmt.setTimestamp(param, new Timestamp(((Calendar) value).getTimeInMillis()), (Calendar) value); } else if (value instanceof String) { String valueAsString = (String) value; if (valueAsString.length() >= MIN_NUMBER_OF_CHARS_FOR_TIMESTAMP) { stmt.setTimestamp(param, Timestamp.valueOf(valueAsString)); } else { Date valueAsDate = Date.valueOf(valueAsString); stmt.setTimestamp(param, new Timestamp(valueAsDate.getTime())); } } else if (value instanceof LocalDateTime) { LocalDateTime localDateTime = (LocalDateTime) value; stmt.setTimestamp(param, Timestamp.valueOf(localDateTime)); } else if (value instanceof Instant) { Instant instant = (Instant) value; stmt.setTimestamp(param, Timestamp.from(instant)); } else if (value instanceof ZonedDateTime) { ZonedDateTime zonedDateTime = (ZonedDateTime) value; stmt.setTimestamp(param, Timestamp.from(zonedDateTime.toInstant()), Calendar.getInstance(TimeZone.getTimeZone(zonedDateTime.getZone()))); } else if (value instanceof OffsetDateTime) { OffsetDateTime offsetDateTime = (OffsetDateTime) value; stmt.setTimestamp(param, Timestamp.from(offsetDateTime.toInstant()), Calendar.getInstance(TimeZone.getTimeZone(offsetDateTime.getOffset()))); } else if (value instanceof LocalDate) { LocalDate localDate = (LocalDate) value; stmt.setTimestamp(param, Timestamp.valueOf(localDate.atStartOfDay())); } else { stmt.setObject(param, value); } } @Override public String toString() { return "Binders.timestampBinder"; } } /** * The implementation for {@link Binders#dateBinder()} * @author JB Nizet */ private static final class DateBinder implements Binder { @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof Date) { stmt.setDate(param, (Date) value); } else if (value instanceof java.util.Date) { stmt.setDate(param, new Date(((java.util.Date) value).getTime())); } else if (value instanceof Calendar) { Calendar calendar = (Calendar) value; stmt.setDate(param, new Date(calendar.getTimeInMillis()), calendar); } else if (value instanceof String) { stmt.setDate(param, Date.valueOf((String) value)); } else if (value instanceof LocalDate) { LocalDate localDate = (LocalDate) value; stmt.setDate(param, Date.valueOf(localDate)); } else if (value instanceof LocalDateTime) { LocalDateTime localDateTime = (LocalDateTime) value; stmt.setDate(param, Date.valueOf(localDateTime.toLocalDate())); } else if (value instanceof Instant) { Instant instant = (Instant) value; stmt.setDate(param, new Date(instant.toEpochMilli())); } else if (value instanceof ZonedDateTime) { ZonedDateTime zonedDateTime = (ZonedDateTime) value; stmt.setDate(param, new Date(zonedDateTime.toInstant().toEpochMilli()), Calendar.getInstance(TimeZone.getTimeZone(zonedDateTime.getZone()))); } else if (value instanceof OffsetDateTime) { OffsetDateTime offsetDateTime = (OffsetDateTime) value; stmt.setDate(param, new Date(offsetDateTime.toInstant().toEpochMilli()), Calendar.getInstance(TimeZone.getTimeZone(offsetDateTime.getOffset()))); } else { stmt.setObject(param, value); } } @Override public String toString() { return "Binders.dateBinder"; } } /** * The implementation for {@link Binders#defaultBinder()} * @author JB Nizet */ private static final class DefaultBinder implements Binder { @Override public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException { if (value instanceof Enum) { stmt.setString(param, ((Enum) value).name()); } else if (value instanceof java.util.Date) { stmt.setTimestamp(param, new Timestamp(((java.util.Date) value).getTime())); } else if (value instanceof Calendar) { Calendar calendar = (Calendar) value; stmt.setTimestamp(param, new Timestamp(calendar.getTime().getTime()), calendar); } else if (value instanceof LocalDate) { stmt.setDate(param, Date.valueOf((LocalDate) value)); } else if (value instanceof LocalTime) { stmt.setTime(param, Time.valueOf((LocalTime) value)); } else if (value instanceof LocalDateTime) { stmt.setTimestamp(param, Timestamp.valueOf((LocalDateTime) value)); } else if (value instanceof Instant) { stmt.setTimestamp(param, Timestamp.from((Instant) value)); } else if (value instanceof ZonedDateTime) { ZonedDateTime zonedDateTime = (ZonedDateTime) value; stmt.setTimestamp(param, Timestamp.from(zonedDateTime.toInstant()), Calendar.getInstance(TimeZone.getTimeZone(zonedDateTime.getZone()))); } else if (value instanceof OffsetDateTime) { OffsetDateTime offsetDateTime = (OffsetDateTime) value; stmt.setTimestamp(param, Timestamp.from(offsetDateTime.toInstant()), Calendar.getInstance(TimeZone.getTimeZone(offsetDateTime.getOffset()))); } else if (value instanceof OffsetTime) { OffsetTime offsetTime = (OffsetTime) value; stmt.setTime(param, Time.valueOf(offsetTime.toLocalTime()), Calendar.getInstance(TimeZone.getTimeZone(offsetTime.getOffset()))); } else { stmt.setObject(param, value); } } @Override public String toString() { return "Binders.defaultBinder"; } } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/bind/DefaultBinderConfiguration.java ================================================ /* * The MIT License * * Copyright (c) 2012-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.bind; import java.sql.ParameterMetaData; import java.sql.SQLException; import java.sql.Types; import com.ninja_squad.dbsetup.DbSetup; /** * Default implementation of {@link BinderConfiguration}, used by default by {@link DbSetup}. * @author JB Nizet */ public class DefaultBinderConfiguration implements BinderConfiguration { /** * A shareable, reusable instance of this class. */ public static final DefaultBinderConfiguration INSTANCE = new DefaultBinderConfiguration(); /** * Constructor. Protected because it doesn't make much sense to instantiate this class, * but extending it can be useful. */ protected DefaultBinderConfiguration() { } /** * Uses the parameter type of the given parameter and returns the following Binders depending on the type * got from the metadata. *
    *
  • null metadata (i.e. metadata not used or not returned): {@link Binders#defaultBinder()}
  • *
  • VARCHAR, CHAR, LONGNVARCHAR, LONGVARCHAR, NCHAR, NVARCHAR : * {@link Binders#stringBinder()}
  • *
  • DATE : {@link Binders#dateBinder()}
  • *
  • TIME : {@link Binders#timeBinder()}
  • *
  • TIMESTAMP : {@link Binders#timestampBinder()}
  • *
  • INTEGER, BIGINT, SMALLINT, TINYINT : {@link Binders#integerBinder()}
  • *
  • DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL : {@link Binders#decimalBinder()}
  • *
  • other : {@link Binders#defaultBinder()}
  • *
* * If the parameter type can't be obtained from the metadata, the default binder is returned. */ @Override public Binder getBinder(ParameterMetaData metadata, int param) throws SQLException { if (metadata == null) { return Binders.defaultBinder(); } try { int sqlType = metadata.getParameterType(param); if (sqlType == Types.DATE) { return Binders.dateBinder(); } if (sqlType == Types.TIME || sqlType == Types.TIME_WITH_TIMEZONE) { return Binders.timeBinder(); } if (sqlType == Types.TIMESTAMP || sqlType == Types.TIMESTAMP_WITH_TIMEZONE) { return Binders.timestampBinder(); } if (sqlType == Types.BIGINT || sqlType == Types.INTEGER || sqlType == Types.SMALLINT || sqlType == Types.TINYINT) { return Binders.integerBinder(); } if (sqlType == Types.DECIMAL || sqlType == Types.DOUBLE || sqlType == Types.FLOAT || sqlType == Types.NUMERIC || sqlType == Types.REAL) { return Binders.decimalBinder(); } if (sqlType == Types.VARCHAR || sqlType == Types.CHAR || sqlType == Types.LONGNVARCHAR || sqlType == Types.LONGVARCHAR || sqlType == Types.NCHAR || sqlType == Types.NVARCHAR) { return Binders.stringBinder(); } return Binders.defaultBinder(); } catch (SQLException e) { // the database can't return types from parameters. Fall back to default binder. return Binders.defaultBinder(); } } @Override public String toString() { return "DefaultBinderConfiguration"; } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/destination/DataSourceDestination.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.destination; import com.ninja_squad.dbsetup.util.Preconditions; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * A destination which wraps a DataSource and gets its connection from the wrapped DataSource * @author JB Nizet */ @Immutable public final class DataSourceDestination implements Destination { private final DataSource dataSource; /** * Constructor * @param dataSource the wrapped DataSource */ public DataSourceDestination(@Nonnull DataSource dataSource) { Preconditions.checkNotNull(dataSource, "dataSource may not be null"); this.dataSource = dataSource; } /** * Factory method creating a new DataSourceDestination. This allows a more readable style than using the * constructor: * *
     *    DbSetup dbSetup = new DbSetup(DataSourceDestination.with(dataSource), operation);
     * 
* * or, if this method is statically imported: * *
     *    DbSetup dbSetup = new DbSetup(with(dataSource), operation);
     * 
* * instead of * *
     *    DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
     * 
* * @param dataSource the wrapped DataSource */ public static DataSourceDestination with(@Nonnull DataSource dataSource) { return new DataSourceDestination(dataSource); } @Override public Connection getConnection() throws SQLException { return dataSource.getConnection(); } @Override public String toString() { return "DataSourceDestination [dataSource=" + dataSource + "]"; } @Override public int hashCode() { return dataSource.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } DataSourceDestination other = (DataSourceDestination) obj; return dataSource.equals(other.dataSource); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/destination/Destination.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.destination; import java.sql.Connection; import java.sql.SQLException; import com.ninja_squad.dbsetup.DbSetupTracker; /** * The destination of a database setup. It's advised to make implementations of this * interface immutable, and to make them implement equals and hashCode in order for {@link DbSetupTracker} to function * properly, or to make them singletons. * @author JB Nizet */ public interface Destination { /** * Returns a connection to the destination database * @return a connection to the destination database * @throws SQLException if a connection can't be returned */ Connection getConnection() throws SQLException; } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/destination/DriverManagerDestination.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.destination; import com.ninja_squad.dbsetup.util.Preconditions; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * A destination which uses the {@link DriverManager} to get a connection * @author JB Nizet */ @Immutable public final class DriverManagerDestination implements Destination { private final String url; private final String user; private final String password; /** * Constructor * @param url the URL of the database * @param user the user used to get a connection * @param password the password used to get a connection */ public DriverManagerDestination(@Nonnull String url, String user, String password) { Preconditions.checkNotNull(url, "url may not be null"); this.url = url; this.user = user; this.password = password; } /** * Factory method creating a new DriverManagerDestination. This allows a more readable style than using the * constructor: * *
     *    DbSetup dbSetup = new DbSetup(DriverManagerDestination.with(url, user, password), operation);
     * 
* * or, if this method is statically imported: * *
     *    DbSetup dbSetup = new DbSetup(with(url, user, password), operation);
     * 
* * instead of * *
     *    DbSetup dbSetup = new DbSetup(new DriverManagerDestination(url, user, password), operation);
     * 
* * @param url the URL of the database * @param user the user used to get a connection * @param password the password used to get a connection */ public static DriverManagerDestination with(@Nonnull String url, String user, String password) { return new DriverManagerDestination(url, user, password); } @Override public Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } @Override public String toString() { return "DriverManagerDestination [url=" + url + ", user=" + user + ", password=" + password + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + url.hashCode(); result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((user == null) ? 0 : user.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } DriverManagerDestination other = (DriverManagerDestination) obj; if (password == null) { if (other.password != null) { return false; } } else if (!password.equals(other.password)) { return false; } if (!url.equals(other.url)) { return false; } if (user == null) { if (other.user != null) { return false; } } else if (!user.equals(other.user)) { return false; } return true; } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/generator/DateSequenceValueGenerator.java ================================================ /* * The MIT License * * Copyright (c) 2013-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; import javax.annotation.Nonnull; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import com.ninja_squad.dbsetup.util.Preconditions; /** * A {@link ValueGenerator} that returns a sequence of dates, starting at a given zoned date time and incremented by a * given time, specified as an increment and a temporal unit. * @author JB */ public final class DateSequenceValueGenerator implements ValueGenerator { // the number of chars in yyyy-mm-dd hh:mm:ss private static final int MIN_NUMBER_OF_CHARS_FOR_TIMESTAMP = 19; /** * The available units for the increment of this sequence * @deprecated use ChronoField instead. This enum is only kept to maintain backward compatibility */ @Deprecated public enum CalendarField { YEAR(ChronoUnit.YEARS), MONTH(ChronoUnit.MONTHS), DAY(ChronoUnit.DAYS), HOUR(ChronoUnit.HOURS), MINUTE(ChronoUnit.MINUTES), SECOND(ChronoUnit.SECONDS), MILLISECOND(ChronoUnit.MILLIS); private TemporalUnit unit; CalendarField(TemporalUnit unit) { this.unit = unit; } private TemporalUnit toTemporalUnit() { return unit; } } private ZonedDateTime next; private int increment; private TemporalUnit unit; DateSequenceValueGenerator() { this(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()), 1, ChronoUnit.DAYS); } private DateSequenceValueGenerator(ZonedDateTime next, int increment, TemporalUnit unit) { this.next = next; this.increment = increment; this.unit = unit; } /** * Restarts the sequence at the given date, in the given time zone * @return this instance, for chaining * @deprecated use one of the other startingAt() methods taking java.time types as argument */ @Deprecated public DateSequenceValueGenerator startingAt(@Nonnull Date startDate, @Nonnull TimeZone timeZone) { Preconditions.checkNotNull(startDate, "startDate may not be null"); Preconditions.checkNotNull(timeZone, "timeZone may not be null"); next = startDate.toInstant().atZone(timeZone.toZoneId()); return this; } /** * Restarts the sequence at the given date, in the default time zone * @return this instance, for chaining * @deprecated use one of the other startingAt() methods taking java.time types as argument */ @Deprecated public DateSequenceValueGenerator startingAt(@Nonnull Date startDate) { return startingAt(startDate, TimeZone.getDefault()); } /** * Restarts the sequence at the given date * @return this instance, for chaining * @deprecated use one of the other startingAt() methods taking java.time types as argument */ @Deprecated public DateSequenceValueGenerator startingAt(@Nonnull Calendar startDate) { Preconditions.checkNotNull(startDate, "startDate may not be null"); next = startDate.toInstant().atZone(startDate.getTimeZone().toZoneId()); return this; } /** * Restarts the sequence at the given date, in the default time zone * @param startDate the starting date, as a String. The supported formats are the same as the ones supported by * {@link com.ninja_squad.dbsetup.bind.Binders#timestampBinder()}, i.e. the formats supported by * java.sql.Timestamp.valueOf() and java.sql.Date.valueOf() * @return this instance, for chaining */ public DateSequenceValueGenerator startingAt(@Nonnull String startDate) { Preconditions.checkNotNull(startDate, "startDate may not be null"); if (startDate.length() >= MIN_NUMBER_OF_CHARS_FOR_TIMESTAMP) { return startingAt(new Date(Timestamp.valueOf(startDate).getTime())); } else { return startingAt(new Date(java.sql.Date.valueOf(startDate).getTime())); } } /** * Restarts the sequence at the given local date, in the default time zone * @return this instance, for chaining */ public DateSequenceValueGenerator startingAt(@Nonnull LocalDate startDate) { return startingAt(startDate.atStartOfDay()); } /** * Restarts the sequence at the given local date time, in the default time zone * @return this instance, for chaining */ public DateSequenceValueGenerator startingAt(@Nonnull LocalDateTime startDate) { return startingAt(startDate.atZone(ZoneId.systemDefault())); } /** * Restarts the sequence at the given zoned date time * @return this instance, for chaining */ public DateSequenceValueGenerator startingAt(@Nonnull ZonedDateTime startDate) { next = startDate; return this; } /** * Increments the date by the given increment of the given unit. * @return this instance, for chaining * @deprecated use the other {@link #incrementingBy(int, TemporalUnit)} method */ @Deprecated public DateSequenceValueGenerator incrementingBy(int increment, @Nonnull CalendarField unit) { Preconditions.checkNotNull(unit, "unit may not be null"); return incrementingBy(increment, unit.toTemporalUnit()); } /** * Increments the date by the given increment of the given unit. One of the constants of ChronoField is typically * used for the unit. * @return this instance, for chaining */ public DateSequenceValueGenerator incrementingBy(int increment, @Nonnull TemporalUnit unit) { Preconditions.checkNotNull(unit, "unit may not be null"); this.increment = increment; this.unit = unit; return this; } @Override public ZonedDateTime nextValue() { ZonedDateTime result = next; next = next.plus(increment, unit); return result; } @Override public String toString() { return "DateSequenceValueGenerator[" + "next=" + next + ", increment=" + increment + ", unit=" + unit + "]"; } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/generator/SequenceValueGenerator.java ================================================ /* * The MIT License * * Copyright (c) 2013, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; /** * A {@link ValueGenerator} which generates a sequence of long values. By default, the sequence starts at 1 and * increments by 1, but this can be customized. Instances of this class are created using * {@link ValueGenerators#sequence()}. * @author JB Nizet */ public final class SequenceValueGenerator implements ValueGenerator { private long next = 1L; private int increment = 1; SequenceValueGenerator() { this(1, 1); } private SequenceValueGenerator(long start, int increment) { this.next = start; this.increment = increment; } /** * Restarts the sequence at the given value * @param start the starting value of the created generator * @return this instance, for chaining */ public SequenceValueGenerator startingAt(long start) { this.next = start; return this; } /** * Increments the value by the given increment. * @return this instance, for chaining */ public SequenceValueGenerator incrementingBy(int increment) { this.increment = increment; return this; } @Override public Long nextValue() { long result = next; next += increment; return result; } @Override public String toString() { return "SequenceValueGenerator[" + "next=" + next + ", increment=" + increment + "]"; } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/generator/StringSequenceValueGenerator.java ================================================ /* * The MIT License * * Copyright (c) 2013-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; import com.ninja_squad.dbsetup.util.Preconditions; /** * A {@link ValueGenerator} that returns a string prefix followed by a sequence number, optionally left-padded * with 0 to ensure a correct ordering (for example: CODE_001, CODE_002, etc.). Instances of this generator * are created by {@link ValueGenerators#stringSequence(String)}. * @author JB */ public final class StringSequenceValueGenerator implements ValueGenerator { private String prefix; private long next; private int increment; /** * The length of the number once padded. 0 if no padding must be applied */ private int paddedNumberLength; StringSequenceValueGenerator(String prefix) { this(prefix, 1L, 1, 0); } private StringSequenceValueGenerator(String prefix, long next, int increment, int paddedNumberLength) { this.prefix = prefix; this.next = next; this.increment = increment; this.paddedNumberLength = paddedNumberLength; } /** * Tells the generator to left-pad the number it generates with 0 until the length of the number is the given * length. For example, passing 3 to this method will generate numbers 001, 002, 003, 004, etc. If the generated * number, before padding, has a length already equal or larger that the given length, the number is not padded. * @param paddedNumberLength the length of the number once padded. Must be > 0. * @return this instance, for chaining */ public StringSequenceValueGenerator withLeftPadding(int paddedNumberLength) { Preconditions.checkArgument(paddedNumberLength > 0, "paddedNumberLength must be > 0"); this.paddedNumberLength = paddedNumberLength; return this; } /** * Tells the generator to avoid left-padding the number it generates with 0 * @return this instance, for chaining */ public StringSequenceValueGenerator withoutLeftPadding() { this.paddedNumberLength = 0; return this; } /** * Restarts the sequence at the given value * @param start the new starting value of the sequence * @return this instance, for chaining */ public StringSequenceValueGenerator startingAt(long start) { this.next = start; return this; } /** * Increments the number by the given increment. * @return this instance, for chaining */ public StringSequenceValueGenerator incrementingBy(int increment) { this.increment = increment; return this; } @Override public String nextValue() { long number = next; next += increment; return prefix + leftPadIfNecessary(number); } private String leftPadIfNecessary(long number) { String numberAsString = Long.toString(number); if (numberAsString.length() >= paddedNumberLength) { return numberAsString; } StringBuilder builder = new StringBuilder(paddedNumberLength); for (int i = 0; i < paddedNumberLength - numberAsString.length(); i++) { builder.append('0'); } return builder.append(numberAsString).toString(); } @Override public String toString() { return "StringSequenceValueGenerator[" + "prefix='" + prefix + '\'' + ", next=" + next + ", increment=" + increment + ", paddedNumberLength=" + paddedNumberLength + "]"; } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/generator/ValueGenerator.java ================================================ /* * The MIT License * * Copyright (c) 2013, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; /** * A value generator allows generating values for a specific column in a sequence of inserts. This is useful when you * don't want to specify a value for each of the inserted rows in a table, and when a default value is not an option * either because, for example, the column has a unique constraint. * @param the type of value that this generator generates * * @see ValueGenerators for useful implementations of this interface * @author JB Nizet */ public interface ValueGenerator { /** * Called each time a new row is inserted, to get the value to insert in the column using this value generator. * @return the value to insert in the column associated with this generator. */ T nextValue(); } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/generator/ValueGenerators.java ================================================ /* * The MIT License * * Copyright (c) 2013, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; import com.ninja_squad.dbsetup.util.Preconditions; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Utility class containing factory methods for {@link ValueGenerator} * @author JB Nizet */ public final class ValueGenerators { private ValueGenerators() { } /** * Returns a value generator which generates a sequence of long values starting with 1, with an increment of 1. * The starting value and increment can be customized using *
     *     ValueGenerators.increment().startingAt(1000).incrementingBy(5)
     * 
*/ public static SequenceValueGenerator sequence() { return new SequenceValueGenerator(); } /** * Returns a value generator which always returns the same, given value. */ public static ValueGenerator constant(@Nullable final T constant) { return new ValueGenerator() { @Override public T nextValue() { return constant; } @Override public String toString() { return "ValueGenerators.constant(" + constant + ")"; } }; } /** * Returns a value generator that returns a string prefix followed by a sequence number, optionally left-padded * with 0 to ensure a correct ordering (for example: CODE_001, CODE_002, etc.). The returned generator starts the * sequence at 1 and increments by 1, and doesn't pad the numbers. * @param prefix the prefix before the generated number (for example: "CODE_"). */ public static StringSequenceValueGenerator stringSequence(@Nonnull String prefix) { Preconditions.checkNotNull(prefix, "prefix may not be null"); return new StringSequenceValueGenerator(prefix); } /** * Returns a value generator that returns a sequence of dates, starting at a given date and incremented by a given * time, specified as an increment and a calendar field. The returned generator starts today at 00:00:00 * and increments by 1 day by default. */ public static DateSequenceValueGenerator dateSequence() { return new DateSequenceValueGenerator(); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/operation/CompositeOperation.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.ninja_squad.dbsetup.bind.BinderConfiguration; /** * A composite operation or, in other words, an operation which consists in executing a sequence of other operations. * @author JB Nizet */ @Immutable public final class CompositeOperation implements Operation { private static final Operation NOP = new Operation() { @Override public void execute(Connection connection, BinderConfiguration configuration) { // does nothing since it's a NOP } @Override public String toString() { return "NOP"; } }; private final List operations; private CompositeOperation(List operations) { this.operations = new ArrayList(operations); } /** * Creates a new Operation containing all the given operations * @param operations the sequence of operations */ public static Operation sequenceOf(@Nonnull Operation... operations) { return sequenceOf(Arrays.asList(operations)); } /** * Creates a new Operation containing all the given operations * @param operations the sequence of operations */ public static Operation sequenceOf(@Nonnull List operations) { if (operations.isEmpty()) { return NOP; } else if (operations.size() == 1) { return operations.get(0); } return new CompositeOperation(operations); } /** * Executes the sequence of operations * @throws SQLException as soon as one of the operations in the sequence throws a SQLException */ @Override public void execute(Connection connection, BinderConfiguration configuration) throws SQLException { for (Operation operation : operations) { operation.execute(connection, configuration); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); boolean first = true; for (Operation operation : operations) { if (!first) { builder.append('\n'); } else { first = false; } builder.append(operation); } return builder.toString(); } @Override public int hashCode() { return operations.hashCode(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null) { return false; } if (getClass() != o.getClass()) { return false; } CompositeOperation other = (CompositeOperation) o; return this.operations.equals(other.operations); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/operation/DeleteAll.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.util.Preconditions; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * An operation which deletes everything from a given database table. * @author JB Nizet */ @Immutable public final class DeleteAll implements Operation { private final String table; private DeleteAll(String table) { Preconditions.checkNotNull(table, "table may not be null"); this.table = table; } @Override public void execute(Connection connection, BinderConfiguration configuration) throws SQLException { Statement stmt = connection.createStatement(); try { stmt.executeUpdate("delete from " + table); } finally { stmt.close(); } } /** * Returns an operation which deletes all the rows from the given table. * @param table the table to delete everything from. */ public static DeleteAll from(@Nonnull String table) { return new DeleteAll(table); } /** * Returns a composite operation which deletes all the rows from the given tables, in the same order as the * tables. If A has a foreign key to B, which has a foreign key to C, tables should be listed in the following * order: A, B, C. Otherwise, referential constraint will break. If there is a cycle in the dependencies, you might * want to use a sequence of {@link SqlOperation} to disable the foreign key constraints, then delete everything * from the tables, then use another sequence of {@link SqlOperation} to re-enable the foreign key constraints. * @param tables the tables to delete everything from. */ public static Operation from(@Nonnull String... tables) { return from(Arrays.asList(tables)); } /** * Returns a composite operation which deletes all the rows from the given tables, in the same order as the * tables. If A has a foreign key to B, which has a foreign key to C, tables should be listed in the following * order: A, B, C. Otherwise, referential constraint will break. If there is a cycle in the dependencies, you might * want to use a sequence of {@link SqlOperation} to disable the foreign key constraints, then delete everything * from the tables, then use another sequence of {@link SqlOperation} to re-enable the foreign key constraints. * @param tables the tables to delete everything from. */ public static Operation from(@Nonnull List tables) { List operations = new ArrayList(tables.size()); for (String table : tables) { operations.add(new DeleteAll(table)); } return CompositeOperation.sequenceOf(operations); } @Override public String toString() { return "delete from " + table; } @Override public int hashCode() { return table.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } DeleteAll other = (DeleteAll) obj; return this.table.equals(other.table); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/operation/Insert.java ================================================ /* * The MIT License * * Copyright (c) 2012-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import java.sql.Connection; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.ninja_squad.dbsetup.bind.Binder; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.bind.Binders; import com.ninja_squad.dbsetup.generator.ValueGenerator; import com.ninja_squad.dbsetup.generator.ValueGenerators; import com.ninja_squad.dbsetup.util.Preconditions; /** * Operation which inserts one or several rows into a table. Example usage: *
 *   Insert insert =
 *       Insert.into("CLIENT")
 *             .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "CLIENT_TYPE")
 *             .values(1L, "John", "Doe", "1975-07-19", ClientType.NORMAL)
 *             .values(2L, "Jack", "Smith", "1969-08-22", ClientType.HIGH_PRIORITY)
 *             .withDefaultValue("DELETED", false)
 *             .withDefaultValue("VERSION", 1)
 *             .withBinder(new ClientTypeBinder(), "CLIENT_TYPE")
 *             .build();
 * 
* * The above operation will insert two rows inside the CLIENT table. For each row, the column DELETED will be set to * false and the column VERSION will be set to 1. For the column CLIENT_TYPE, instead of using the * {@link Binder} associated to the type of the column found in the metadata of the table, a custom binder will be used. *

* Instead of specifying values as an ordered sequence which must match the sequence of column names, some might prefer * passing a map of column/value associations. This makes things more verbose, but can be more readable in some cases, * when the number of columns is high. This also allows not specifying any value for columns that must stay null. * The map can be constructed like any other map and passed to the builder, or it can be added using a fluent builder. * The following snippet: * *

 *   Insert insert =
 *       Insert.into("CLIENT")
 *             .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "CLIENT_TYPE")
 *             .row().column("CLIENT_ID", 1L)
 *                   .column("FIRST_NAME", "John")
 *                   .column("LAST_NAME", "Doe")
 *                   .column("DATE_OF_BIRTH", "1975-07-19")
 *                   .end()
 *             .row().column("CLIENT_ID", 2L)
 *                   .column("FIRST_NAME", "Jack")
 *                   .column("LAST_NAME", "Smith")
 *                   .end() // null date of birth, because it's not in the row
 *             .build();
 * 
* * is thus equivalent to: * *
 *   Map<String, Object> johnDoe = new HashMap<String, Object>();
 *   johnDoe.put("CLIENT_ID", 1L);
 *   johnDoe.put("FIRST_NAME", "John");
 *   johnDoe.put("LAST_NAME", "Doe");
 *   johnDoe.put("DATE_OF_BIRTH", "1975-07-19");
 *
 *   Map<String, Object> jackSmith = new HashMap<String, Object>();
 *   jackSmith.put("CLIENT_ID", 2L);
 *   jackSmith.put("FIRST_NAME", "Jack");
 *   jackSmith.put("LAST_NAME", "Smith");
 *
 *   Insert insert =
 *       Insert.into("CLIENT")
 *             .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "CLIENT_TYPE")
 *             .values(johnDoe)
 *             .values(jackSmith)
 *             .build();
 * 
* * When building the Insert using column/value associations, it might seem redundant to specify the set of column names * before inserting the rows. Remember, though, that all the rows of an Insert are inserted using the same * parameterized SQL query. We thus need a robust and easy way to know all the columns to insert for every row of the * insert. To be able to spot errors easily and early, and to avoid complex rules, the rule is thus simple: the set of * columns (excluding the generated ones) is specified either by columns(), or by the columns of the first row. All the * subsequent rows may not have additional columns. And null is inserted for all the absent columns of the * subsequent rows. The above example can thus be written as * *
 *   Insert insert =
 *       Insert.into("CLIENT")
 *             .row().column("CLIENT_ID", 1L)
 *                   .column("FIRST_NAME", "John")
 *                   .column("LAST_NAME", "Doe")
 *                   .column("DATE_OF_BIRTH", "1975-07-19")
 *                   .end()
 *             .row().column("CLIENT_ID", 2L)
 *                   .column("FIRST_NAME", "Jack")
 *                   .column("LAST_NAME", "Smith")
 *                   .end() // null date of birth, because it's not in the row
 *             .build();
 * 
* * but the following will throw an exception, because the DATE_OF_BIRTH column is not part of the first row: * *
 *   Insert insert =
 *       Insert.into("CLIENT")
 *             .row().column("CLIENT_ID", 2L)
 *                   .column("FIRST_NAME", "Jack")
 *                   .column("LAST_NAME", "Smith")
 *                   .column("CLIENT_TYPE", ClientType.HIGH_PRIORITY)
 *                   .end()
 *             .row().column("CLIENT_ID", 1L)
 *                   .column("FIRST_NAME", "John")
 *                   .column("LAST_NAME", "Doe")
 *                   .column("DATE_OF_BIRTH", "1975-07-19")
 *                   .column("CLIENT_TYPE", ClientType.NORMAL)
 *                   .end()
 *             .build();
 * 
* * @author JB Nizet */ @Immutable public final class Insert implements Operation { private final String table; private final List columnNames; private final Map> generatedValues; private final List> rows; private final boolean metadataUsed; private final Map binders; private Insert(Builder builder) { this.table = builder.table; this.columnNames = builder.columnNames; this.rows = builder.rows; this.generatedValues = generateValues(builder.valueGenerators, rows.size()); this.binders = builder.binders; this.metadataUsed = builder.metadataUsed; } private Map> generateValues(Map> valueGenerators, int count) { Map> result = new LinkedHashMap>(); for (Map.Entry> entry : valueGenerators.entrySet()) { result.put(entry.getKey(), generateValues(entry.getValue(), count)); } return result; } private List generateValues(ValueGenerator valueGenerator, int count) { List result = new ArrayList(count); for (int i = 0; i < count; i++) { result.add(valueGenerator.nextValue()); } return result; } /** * Inserts the values and generated values in the table. Unless useMetadata has been set to * false, the given configuration is used to get the appropriate binder. Nevertheless, if a binder * has explicitly been associated to a given column, this binder will always be used for this column. */ @edu.umd.cs.findbugs.annotations.SuppressWarnings( value = "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING", justification = "The point here is precisely to compose a SQL String from column names coming from the user") @Override public void execute(Connection connection, BinderConfiguration configuration) throws SQLException { List allColumnNames = new ArrayList(columnNames); allColumnNames.addAll(generatedValues.keySet()); String query = generateSqlQuery(allColumnNames); PreparedStatement stmt = connection.prepareStatement(query); try { Map usedBinders = initializeBinders(stmt, allColumnNames, configuration); int rowIndex = 0; for (List row : rows) { int i = 0; for (Object value : row) { String columnName = columnNames.get(i); Binder binder = usedBinders.get(columnName); binder.bind(stmt, i + 1, value); i++; } for (Map.Entry> entry : generatedValues.entrySet()) { String columnName = entry.getKey(); List rowValues = entry.getValue(); Binder binder = usedBinders.get(columnName); binder.bind(stmt, i + 1, rowValues.get(rowIndex)); i++; } stmt.executeUpdate(); rowIndex++; } } finally { stmt.close(); } } /** * Gets the number of rows that are inserted in the database table when this insert operation is executed. */ public int getRowCount() { return rows.size(); } private String generateSqlQuery(List allColumnNames) { StringBuilder sql = new StringBuilder("insert into ").append(table).append(" ("); for (Iterator it = allColumnNames.iterator(); it.hasNext(); ) { String columnName = it.next(); sql.append(columnName); if (it.hasNext()) { sql.append(", "); } } sql.append(") values ("); for (Iterator it = allColumnNames.iterator(); it.hasNext(); ) { it.next(); sql.append('?'); if (it.hasNext()) { sql.append(", "); } } sql.append(')'); return sql.toString(); } private Map initializeBinders(PreparedStatement stmt, List allColumnNames, BinderConfiguration configuration) throws SQLException { Map result = new HashMap(); ParameterMetaData metadata = null; if (metadataUsed) { try { metadata = stmt.getParameterMetaData(); } catch (SQLException e) { metadata = null; // the parameter metadata are probably not supported by the database. Pass null to the configuration. // The default configuration will return the default binder, just as if useMetadata(false) had been used } } int i = 1; for (String columnName : allColumnNames) { Binder binder = this.binders.get(columnName); if (binder == null) { binder = configuration.getBinder(metadata, i); if (binder == null) { throw new IllegalStateException("null binder returned from configuration " + configuration.getClass()); } } result.put(columnName, binder); i++; } return result; } @Override public String toString() { return "insert into " + table + " [columns=" + columnNames + ", generatedValues=" + generatedValues + ", rows=" + rows + ", metadataUsed=" + metadataUsed + ", binders=" + binders + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + binders.hashCode(); result = prime * result + columnNames.hashCode(); result = prime * result + generatedValues.hashCode(); result = prime * result + Boolean.valueOf(metadataUsed).hashCode(); result = prime * result + rows.hashCode(); result = prime * result + table.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Insert other = (Insert) obj; return binders.equals(other.binders) && columnNames.equals(other.columnNames) && generatedValues.equals(other.generatedValues) && metadataUsed == other.metadataUsed && rows.equals(other.rows) && table.equals(other.table); } /** * Creates a new Builder instance, in order to build an Insert operation into the given table * @param table the name of the table to insert into * @return the created Builder */ public static Builder into(@Nonnull String table) { Preconditions.checkNotNull(table, "table may not be null"); return new Builder(table); } /** * A builder used to create an Insert operation. Such a builder may only be used once. Once it has built its Insert * operation, all its methods throw an {@link IllegalStateException}. * @see Insert * @see Insert#into(String) * @author JB Nizet */ public static final class Builder { private final String table; private final List columnNames = new ArrayList(); private final Map> valueGenerators = new LinkedHashMap>(); private final List> rows = new ArrayList>(); private boolean metadataUsed = true; private final Map binders = new HashMap(); private boolean built; private Builder(String table) { this.table = table; } /** * Specifies the list of columns into which values will be inserted. The values must the be specified, after, * using the {@link #values(Object...)} method, or with the {@link #values(java.util.Map)} method, or by adding * a row with named columns fluently using {@link #row()}. * @param columns the names of the columns to insert into. * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built, or if this method has already been * called, or if one of the given columns is also specified as one of the generated value columns, or if the * set of columns has already been defined by adding a first row to the builder. */ public Builder columns(@Nonnull String... columns) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkState(columnNames.isEmpty(), "columns have already been specified"); for (String column : columns) { Preconditions.checkNotNull(column, "column may not be null"); Preconditions.checkState(!valueGenerators.containsKey(column), "column " + column + " has already been specified as generated value column"); } columnNames.addAll(Arrays.asList(columns)); return this; } /** * Adds a row of values to insert. * @param values the values to insert. * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built, or if the number of values doesn't match * the number of columns. */ public Builder values(@Nonnull Object... values) { return addRepeatingValues(Arrays.asList(values), 1); } /** * Allows adding many rows with the same non-generated values to insert. * @param values the values to insert. * @return A RowRepeater, allowing to choose how many similar rows to add. * @throws IllegalStateException if the Insert has already been built, or if the number of values doesn't match * the number of columns. */ public RowRepeater repeatingValues(@Nonnull Object... values) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkArgument(values.length == columnNames.size(), "The number of values doesn't match the number of columns"); return new ListRowRepeater(this, Arrays.asList(values)); } /** * Starts building a new row with named columns to insert. If the row is the first one being added and the * columns haven't been set yet by calling columns(), then the columns of this row constitute the * column names (excluding the generated ones) of the Insert being built * @return a {@link RowBuilder} instance, which, when built, will add a row (or several ones) to this insert * builder. * @throws IllegalStateException if the Insert has already been built. * @see RowBuilder */ public RowBuilder row() { Preconditions.checkState(!built, "The insert has already been built"); return new RowBuilder(this); } /** * Adds a row to this builder. If no row has been added yet and the columns haven't been set yet by calling * columns(), then the keys of this map constitute the column names (excluding the generated ones) * of the Insert being built, in the order of the keys in the map (which is arbitrary unless an ordered or * sorted map is used). * @param row the row to add. The keys of the map are the column names, which must match with * the column names specified in the call to {@link #columns(String...)}, or with the column names of the first * added row. If a column name is not present in the map, null is inserted for this column. * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built. * @throws IllegalArgumentException if a column name of the map doesn't match with any of the column names * specified with {@link #columns(String...)} */ public Builder values(@Nonnull Map row) { return addRepeatingValues(row, 1); } /** * Allows adding many rows with the same non-generated values to insert. * @return A RowRepeater, allowing to choose how many similar rows to add. * @throws IllegalStateException if the Insert has already been built. * @see #values(Map) */ public RowRepeater repeatingValues(@Nonnull Map row) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkNotNull(row, "The row may not be null"); return new MapRowRepeater(this, row); } /** * Associates a Binder to one or several columns. * @param binder the binder to use, regardless of the metadata, for the given columns * @param columns the name of the columns to associate with the given Binder * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built, * @throws IllegalArgumentException if any of the given columns is not * part of the columns or "generated value" columns. */ public Builder withBinder(@Nonnull Binder binder, @Nonnull String... columns) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkNotNull(binder, "binder may not be null"); for (String columnName : columns) { Preconditions.checkArgument(this.columnNames.contains(columnName) || this.valueGenerators.containsKey(columnName), "column " + columnName + " is not one of the registered column names"); binders.put(columnName, binder); } return this; } /** * Specifies a default value to be inserted in a column for all the rows inserted by the Insert operation. * Calling this method is equivalent to calling * withGeneratedValue(column, ValueGenerators.constant(value)) * @param column the name of the column * @param value the default value to insert into the column * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built, or if the given column is part * of the columns to insert. */ public Builder withDefaultValue(@Nonnull String column, Object value) { return withGeneratedValue(column, ValueGenerators.constant(value)); } /** * Allows the given column to be populated by a value generator, which will be called for every row of the * Insert operation being built. * @param column the name of the column * @param valueGenerator the generator generating values for the given column of every row * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built, or if the given column is part * of the columns to insert. */ public Builder withGeneratedValue(@Nonnull String column, @Nonnull ValueGenerator valueGenerator) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkNotNull(column, "column may not be null"); Preconditions.checkNotNull(valueGenerator, "valueGenerator may not be null"); Preconditions.checkArgument(!columnNames.contains(column), "column " + column + " is already listed in the list of column names"); valueGenerators.put(column, valueGenerator); return this; } /** * Determines if the metadata must be used to get the appropriate binder for each inserted column (except * the ones which have been associated explicitly with a Binder). The default is true. The insert * can be faster if set to false, but in this case, the binder used will be the one returned * by the {@link BinderConfiguration} for a null metadata (which is, by default, the * {@link Binders#defaultBinder() default binder}), except the ones which have been associated explicitly with * a Binder.
* Before version 1.3.0, a SQLException was thrown if the database doesn't support parameter metadata and * useMetadata(false) wasn't called. Since version 1.3.0, if useMetadata is true * (the default) but the database doesn't support metadata, then the default binder configuration returns the * default binder. Using this method is thus normally unnecessary as of 1.3.0. * @return this Builder instance, for chaining. * @throws IllegalStateException if the Insert has already been built. */ public Builder useMetadata(boolean useMetadata) { Preconditions.checkState(!built, "The insert has already been built"); this.metadataUsed = useMetadata; return this; } /** * Builds the Insert operation. * @return the created Insert operation. * @throws IllegalStateException if the Insert has already been built, or if no column and no generated value * column has been specified. */ public Insert build() { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkState(!this.columnNames.isEmpty() || !this.valueGenerators.isEmpty(), "no column and no generated value column has been specified"); built = true; return new Insert(this); } @Override public String toString() { return "insert into " + table + " [columns=" + columnNames + ", rows=" + rows + ", valueGenerators=" + valueGenerators + ", metadataUsed=" + metadataUsed + ", binders=" + binders + ", built=" + built + "]"; } private Builder addRepeatingValues(List values, int times) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkArgument(values.size() == columnNames.size(), "The number of values doesn't match the number of columns"); List row = new ArrayList(values); for (int i = 0; i < times; i++) { rows.add(row); } return this; } private Builder addRepeatingValues(@Nonnull Map row, int times) { Preconditions.checkState(!built, "The insert has already been built"); Preconditions.checkNotNull(row, "The row may not be null"); List values = mapToRow(row); for (int i = 0; i < times; i++) { rows.add(values); } return this; } private List mapToRow(@Nonnull Map row) { boolean setColumns = rows.isEmpty() && columnNames.isEmpty(); if (setColumns) { columns(row.keySet().toArray(new String[row.size()])); } else { Set rowColumnNames = new HashSet(row.keySet()); rowColumnNames.removeAll(columnNames); if (!rowColumnNames.isEmpty()) { throw new IllegalArgumentException( "The following columns of the row don't match with any column name: " + rowColumnNames); } } List values = new ArrayList(columnNames.size()); for (String columnName : columnNames) { values.add(row.get(columnName)); } return values; } } /** * A row builder, constructed with {@link com.ninja_squad.dbsetup.operation.Insert.Builder#row()}. This builder * allows adding a row with named columns to an Insert: * *
     *   Insert insert =
     *       Insert.into("CLIENT")
     *             .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "CLIENT_TYPE")
     *             .row().column("CLIENT_ID", 1L)
     *                   .column("FIRST_NAME", "John")
     *                   .column("LAST_NAME", "Doe")
     *                   .column("DATE_OF_BIRTH", "1975-07-19")
     *                   .column("CLIENT_TYPE", ClientType.NORMAL)
     *                   .end()
     *             .row().column("CLIENT_ID", 2L)
     *                   .column("FIRST_NAME", "Jack")
     *                   .column("LAST_NAME", "Smith")
     *                   .column("DATE_OF_BIRTH", "1969-08-22")
     *                   .column("CLIENT_TYPE", ClientType.HIGH_PRIORITY)
     *                   .end()
     *             .build();
     * 
* * You may omit the call to columns(). In that case, the columns of the Insert will be the columns * specified in the first added row. */ public static final class RowBuilder { private final Builder builder; private final Map row; private boolean ended; private RowBuilder(Builder builder) { this.builder = builder; // note: very important to use a LinkedHashMap here, to guarantee the ordering of the columns. this.row = new LinkedHashMap(); } /** * Adds a new named column to the row. If a previous value has already been added for the same column, it's * replaced by this new value. * @param name the name of the column, which must match with a column name defined in the Insert Builder * @param value the value of the column for the constructed row * @return this builder, for chaining * @throws IllegalArgumentException if the given name is not the name of one of the columns to insert */ public RowBuilder column(@Nonnull String name, Object value) { Preconditions.checkState(!ended, "The row has already been ended and added to the Insert Builder"); if (!builder.columnNames.isEmpty()) { Preconditions.checkNotNull(name, "the column name may not be null"); Preconditions.checkArgument(builder.columnNames.contains(name), "column " + name + " is not one of the registered column names"); } row.put(name, value); return this; } /** * Ends the row, adds it to the Insert Builder and returns it, for chaining. * @return the Insert Builder */ public Builder end() { Preconditions.checkState(!ended, "The row has already been ended and added to the Insert Builder"); ended = true; return builder.values(row); } /** * Ends the row, adds it to the Insert Builder the given amount of times, and returns it, for chaining. * @param times the number of rows to add. Must be >= 0. If zero, no row is added. * @return the Insert Builder */ public Builder times(int times) { Preconditions.checkArgument(times >= 0, "the number of repeating values must be >= 0"); Preconditions.checkState(!ended, "The row has already been ended and added to the Insert Builder"); ended = true; return builder.addRepeatingValues(row, times); } } /** * Allows inserting the same list of non-generated values several times. */ public interface RowRepeater { /** * Adds several rows with the same non-generated values to the insert. This method can only be called once. * @param times the number of rows to add. Must be >= 0. If zero, no row is added. * @return the Insert Builder, for chaining * @throws IllegalStateException if the rows have already been added */ Builder times(int times); } /** * Base abstract class for row repeaters. */ private abstract static class AbstractRowRepeater implements RowRepeater { protected final Builder builder; private boolean ended; public AbstractRowRepeater(Builder builder) { this.builder = builder; } protected abstract Builder doTimes(int times); @Override public Builder times(int times) { Preconditions.checkArgument(times >= 0, "the number of repeating values must be >= 0"); Preconditions.checkState(!ended, "The rows have already been ended and added to the Insert Builder"); ended = true; return doTimes(times); } } /** * Allows inserting the same list of non-generated values as list several times. */ private static final class ListRowRepeater extends AbstractRowRepeater { private final List values; private ListRowRepeater(Builder builder, List values) { super(builder); this.values = values; } @Override public Builder doTimes(int times) { return builder.addRepeatingValues(values, times); } } /** * Allows inserting the same list of non-generated values as map several times. */ private static final class MapRowRepeater extends AbstractRowRepeater { private final Map values; private MapRowRepeater(Builder builder, Map values) { super(builder); this.values = values; } @Override public Builder doTimes(int times) { return builder.addRepeatingValues(values, times); } } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/operation/Operation.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import java.sql.Connection; import java.sql.SQLException; import com.ninja_squad.dbsetup.DbSetupTracker; import com.ninja_squad.dbsetup.bind.BinderConfiguration; /** * An operation that the database setup executes. It's advised to make implementations of this interface * immutable, and to make them implement equals and hashCode in order for {@link DbSetupTracker} to function * properly. * @author JB Nizet */ public interface Operation { /** * Executes the operation * @param connection the connection used to execute the operation * @param configuration the binder configuration, used to get appropriate binders based on the metadata of * the prepared statements * @throws SQLException if the execution throws a SQLException */ void execute(Connection connection, BinderConfiguration configuration) throws SQLException; } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/operation/SqlOperation.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.util.Preconditions; /** * An operation which simply executes a SQL statement (using {@link Statement#executeUpdate(String)}). It can be useful, * for example, to disable or re-enable constraints before/after deleting everything from tables, or inserting into * tables having cross references. * @author JB Nizet */ @Immutable public final class SqlOperation implements Operation { private final String sql; /** * Constructor * @param sql the SQL query to execute */ private SqlOperation(String sql) { Preconditions.checkNotNull(sql, "sql may not be null"); this.sql = sql; } @Override public void execute(Connection connection, BinderConfiguration configuration) throws SQLException { Statement stmt = connection.createStatement(); try { stmt.executeUpdate(sql); } finally { stmt.close(); } } /** * Creates a SqlOperation for the given SQL statement * @param sqlStatement the SQL statement to execute * @return the created SqlOperation */ public static SqlOperation of(@Nonnull String sqlStatement) { return new SqlOperation(sqlStatement); } /** * Creates a sequence of SqlOperation for the given SQL statements. * @param sqlStatements the SQL statements to execute * @return the created sequence of operations */ public static Operation of(@Nonnull String... sqlStatements) { return of(Arrays.asList(sqlStatements)); } /** * Creates a sequence of SqlOperation for the given SQL statements. * @param sqlStatements the SQL statements to execute * @return the created sequence of operations */ public static Operation of(@Nonnull List sqlStatements) { List operations = new ArrayList(sqlStatements.size()); for (String sql : sqlStatements) { operations.add(new SqlOperation(sql)); } return CompositeOperation.sequenceOf(operations); } @Override public String toString() { return sql; } @Override public int hashCode() { return sql.hashCode(); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o == null) { return false; } if (o.getClass() != this.getClass()) { return false; } SqlOperation other = (SqlOperation) o; return this.sql.equals(other.sql); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/operation/Truncate.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import com.ninja_squad.dbsetup.bind.BinderConfiguration; /** * An operation which deletes everything from a given database table using a TRUNCATE statement., which is sometimes * faster that using a DELETE statement. * @author JB Nizet */ @Immutable public final class Truncate implements Operation { private final String tableToTruncate; private Truncate(String table) { this.tableToTruncate = table; } @Override public void execute(Connection connection, BinderConfiguration configuration) throws SQLException { Statement stmt = connection.createStatement(); try { stmt.executeUpdate("truncate table " + tableToTruncate); } finally { stmt.close(); } } /** * Returns an operation which truncates the given table. * @param table the table to delete everything from. */ public static Truncate table(@Nonnull String table) { return new Truncate(table); } /** * Returns a composite operation which truncates the given tables, in the same order as the * tables. If A has a foreign key to B, which has a foreign key to C, tables should be listed in the following * order: A, B, C. Otherwise, referential constraint will break. If there is a cycle in the dependencies, you might * want to use a sequence of {@link SqlOperation} to disable the foreign key constraints, then truncate the tables, * then use another sequence of {@link SqlOperation} to re-enable the foreign key constraints. * @param tables the tables to truncate. */ public static Operation tables(String... tables) { return tables(Arrays.asList(tables)); } /** * Returns a composite operation which truncates the given tables, in the same order as the * tables. If A has a foreign key to B, which has a foreign key to C, tables should be listed in the following * order: A, B, C. Otherwise, referential constraint will break. If there is a cycle in the dependencies, you might * want to use a sequence of {@link SqlOperation} to disable the foreign key constraints, then truncate the tables, * then use another sequence of {@link SqlOperation} to re-enable the foreign key constraints. * @param tables the tables to truncate. */ public static Operation tables(List tables) { List operations = new ArrayList(tables.size()); for (String table : tables) { operations.add(new Truncate(table)); } return CompositeOperation.sequenceOf(operations); } @Override public String toString() { return "truncate table " + tableToTruncate; } @Override public int hashCode() { return tableToTruncate.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Truncate other = (Truncate) obj; return this.tableToTruncate.equals(other.tableToTruncate); } } ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/overview.html ================================================ DbSetup allows populating a database before executing automated integration tests (typically, DAO/Repository automated tests). Although DBUnit, which is a great project, allows doing the same thing and much more, it's also harder to use and setup. And in our experience, in 98% of the cases, DBUnit is only used to pre-populate a database before executing every test method. This is the task on which DbSetup concentrates.

The philosophy of DbSetup is that DAO tests should not have to setup the database, execute tests, and then remove everything from the database. Instead, a single setup method should be used to delete everything from the database (whatever the previous test put in it, or the initial state of the database tables), and then populate it with the data necessary to execute the test.

Another design choice of DbSetup is to provide an easy to use and simple Java API to populate the database, rather than loading data from external XML files. Using a Java API has several advantages:

  • It allows using real Java types as data (longs, enums, etc.)
  • It allows defining default values, looping to generate several similar rows, storing data sets in variables or factorizing their creation using reusable methods
  • It allows viewing the data sets easily, without having to open external files, by storing the data set directly into the test class, or by navigating through classes and methods using the IDE shortcuts.
  • For more complex situations, like cyclic referential integrity constraints between rows, the Java API allows easily integrating SQL statements into the sequence of operations to execute to pre-populate the database. These SQL statements can, for example, disable constraints and re-enable them, or update rows after their insertion.
The {@link com.ninja_squad.dbsetup.DbSetup} class and the {@link com.ninja_squad.dbsetup.DbSetupTracker} classes are the main entry points to the API that an automated test will use. Look at their javadoc below for example usage. ================================================ FILE: DbSetup-core/src/main/java/com/ninja_squad/dbsetup/util/Preconditions.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.util; /** * Utility class to help verifying preconditions * @author JB Nizet */ public final class Preconditions { private Preconditions() { } /** * Throws a NullPointerException with the given message if the given argument is null. * @param argument the argument to check for null * @param message the message of the thrown NullPointerException * @throws NullPointerException if argument is null. */ public static void checkNotNull(Object argument, String message) throws NullPointerException { if (argument == null) { throw new NullPointerException(message); } } /** * Throws an IllegalStateException with the given message if the given condition is false. * @param condition the condition to check * @param message the message of the thrown IllegalStateException * @throws IllegalStateException if the condition is false. */ public static void checkState(boolean condition, String message) throws IllegalStateException { if (!condition) { throw new IllegalStateException(message); } } /** * Throws an IllegalARgumentException with the given message if the given condition is false. * @param condition the condition to check * @param message the message of the thrown IllegalArgumentException * @throws IllegalArgumentException if the condition is false. */ public static void checkArgument(boolean condition, String message) throws IllegalStateException { if (!condition) { throw new IllegalArgumentException(message); } } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/DbSetupTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.SQLException; import org.junit.Test; import org.mockito.InOrder; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration; import com.ninja_squad.dbsetup.destination.Destination; import com.ninja_squad.dbsetup.operation.Operation; /** * @author JB Nizet */ public class DbSetupTest { @Test public void launchWorks() throws SQLException { Destination destination = mock(Destination.class); Connection connection = mock(Connection.class); when(destination.getConnection()).thenReturn(connection); Operation operation = mock(Operation.class); DbSetup setup = new DbSetup(destination, operation); setup.launch(); InOrder inOrder = inOrder(destination, connection, operation); inOrder.verify(destination).getConnection(); inOrder.verify(operation).execute(connection, DefaultBinderConfiguration.INSTANCE); inOrder.verify(connection).commit(); } @Test public void launchWorksWithCustomConfiguration() throws SQLException { Destination destination = mock(Destination.class); Connection connection = mock(Connection.class); when(destination.getConnection()).thenReturn(connection); Operation operation = mock(Operation.class); BinderConfiguration config = mock(BinderConfiguration.class); DbSetup setup = new DbSetup(destination, operation, config); setup.launch(); InOrder inOrder = inOrder(destination, connection, operation); inOrder.verify(destination).getConnection(); inOrder.verify(operation).execute(connection, config); inOrder.verify(connection).commit(); } @Test public void launchRollbacksIfSQLException() throws SQLException { Destination destination = mock(Destination.class); Connection connection = mock(Connection.class); when(destination.getConnection()).thenReturn(connection); Operation operation = mock(Operation.class); doThrow(new SQLException()).when(operation).execute(connection, DefaultBinderConfiguration.INSTANCE); DbSetup setup = new DbSetup(destination, operation); try { setup.launch(); fail("Expected a DbSetupRuntimeException"); } catch (DbSetupRuntimeException e) { // expected } InOrder inOrder = inOrder(destination, connection, operation); inOrder.verify(destination).getConnection(); inOrder.verify(operation).execute(connection, DefaultBinderConfiguration.INSTANCE); inOrder.verify(connection).rollback(); } @Test public void launchRollbacksIfOtherException() throws SQLException { Destination destination = mock(Destination.class); Connection connection = mock(Connection.class); when(destination.getConnection()).thenReturn(connection); Operation operation = mock(Operation.class); doThrow(new NullPointerException()).when(operation).execute(connection, DefaultBinderConfiguration.INSTANCE); DbSetup setup = new DbSetup(destination, operation); try { setup.launch(); fail("Expected a DbSetupRuntimeException"); } catch (NullPointerException e) { // expected } InOrder inOrder = inOrder(destination, connection, operation); inOrder.verify(destination).getConnection(); inOrder.verify(operation).execute(connection, DefaultBinderConfiguration.INSTANCE); inOrder.verify(connection).rollback(); } @Test public void equalsAndHashCodeWork() throws SQLException { Destination destination1 = mock(Destination.class); Operation operation1 = mock(Operation.class); BinderConfiguration config1 = DefaultBinderConfiguration.INSTANCE; Destination destination2 = mock(Destination.class); Operation operation2 = mock(Operation.class); BinderConfiguration config2 = mock(BinderConfiguration.class); DbSetup setup1 = new DbSetup(destination1, operation1, config1); assertEquals(setup1, setup1); assertEquals(setup1, new DbSetup(destination1, operation1, config1)); assertEquals(setup1.hashCode(), new DbSetup(destination1, operation1, config1).hashCode()); assertFalse(setup1.equals(null)); assertFalse(setup1.equals("hello")); assertFalse(setup1.equals(new DbSetup(destination2, operation1, config1))); assertFalse(setup1.equals(new DbSetup(destination1, operation2, config1))); assertFalse(setup1.equals(new DbSetup(destination1, operation1, config2))); } @Test public void toStringWorks() throws SQLException { Destination destination1 = mock(Destination.class); when(destination1.toString()).thenReturn("destination1"); Operation operation1 = mock(Operation.class); when(operation1.toString()).thenReturn("operation1"); BinderConfiguration config1 = mock(BinderConfiguration.class); when(config1.toString()).thenReturn("config1"); DbSetup setup1 = new DbSetup(destination1, operation1, config1); assertEquals("DbSetup [destination=destination1, operation=operation1, binderConfiguration=config1]", setup1.toString()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/DbSetupTrackerTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup; import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.SQLException; import org.junit.Before; import org.junit.Test; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.destination.Destination; import com.ninja_squad.dbsetup.operation.Operation; /** * @author JB Nizet */ public class DbSetupTrackerTest { private Operation operation1; private DbSetup dbSetup1; private Operation operation2; private DbSetup dbSetup2; @Before public void prepare() throws SQLException { Destination destination = mock(Destination.class); Connection connection = mock(Connection.class); when(destination.getConnection()).thenReturn(connection); operation1 = mock(Operation.class); operation2 = mock(Operation.class); dbSetup1 = new DbSetup(destination, operation1); dbSetup2 = new DbSetup(destination, operation2); } @Test public void launchIfNecessaryLaunchesTheFirstTime() throws SQLException { DbSetupTracker tracker = new DbSetupTracker(); tracker.launchIfNecessary(dbSetup1); verify(operation1).execute(any(Connection.class), any(BinderConfiguration.class)); } @Test public void launchIfNecessaryLaunchesIfNotSkipped() throws SQLException { DbSetupTracker tracker = new DbSetupTracker(); tracker.launchIfNecessary(dbSetup1); tracker.launchIfNecessary(dbSetup1); verify(operation1, times(2)).execute(any(Connection.class), any(BinderConfiguration.class)); } @Test public void launchIfNecessaryDoesntLaunchIfSkipped() throws SQLException { DbSetupTracker tracker = new DbSetupTracker(); tracker.launchIfNecessary(dbSetup1); tracker.skipNextLaunch(); tracker.launchIfNecessary(dbSetup1); verify(operation1, times(1)).execute(any(Connection.class), any(BinderConfiguration.class)); } @Test public void launchIfNecessaryResetsTheSkipFlag() throws SQLException { DbSetupTracker tracker = new DbSetupTracker(); tracker.launchIfNecessary(dbSetup1); tracker.skipNextLaunch(); tracker.launchIfNecessary(dbSetup1); tracker.launchIfNecessary(dbSetup1); verify(operation1, times(2)).execute(any(Connection.class), any(BinderConfiguration.class)); } @Test public void launchIfNecessaryDoesntLaunchIfDifferentSetup() throws SQLException { DbSetupTracker tracker = new DbSetupTracker(); tracker.launchIfNecessary(dbSetup1); tracker.skipNextLaunch(); tracker.launchIfNecessary(dbSetup2); verify(operation1, times(1)).execute(any(Connection.class), any(BinderConfiguration.class)); verify(operation2, times(1)).execute(any(Connection.class), any(BinderConfiguration.class)); } @Test public void toStringWorks() { DbSetupTracker tracker = new DbSetupTracker(); assertEquals("DbSetupTracker [lastSetupLaunched=null, nextLaunchSkipped=false]", tracker.toString()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/bind/BindersTest.java ================================================ /* * The MIT License * * Copyright (c) 2012-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.bind; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.TimeZone; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; public class BindersTest { private PreparedStatement stmt; @Before public void prepare() { stmt = mock(PreparedStatement.class); } @Test public void defaultBinderBindsObject() throws SQLException { Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, Boolean.TRUE); verify(stmt).setObject(1, Boolean.TRUE); } @Test public void defaultBinderBindsNull() throws SQLException { Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } @Test public void defaultBinderBindsEnum() throws SQLException { Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, TestEnum.BAR); verify(stmt).setString(1, TestEnum.BAR.name()); } @Test public void defaultBinderBindsUtilDate() throws SQLException { java.util.Date date = new java.util.Date(Date.valueOf("1975-07-19").getTime()); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, date); verify(stmt).setTimestamp(1, new Timestamp(date.getTime())); } @Test public void defaultBinderBindsCalendar() throws SQLException { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(Date.valueOf("1975-07-19").getTime()); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, calendar); verify(stmt).setTimestamp(1, new Timestamp(calendar.getTime().getTime()), calendar); } @Test public void defaultBinderBindsLocalDate() throws SQLException { LocalDate localDate = LocalDate.parse("1975-07-19"); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, localDate); verify(stmt).setDate(1, Date.valueOf(localDate)); } @Test public void defaultBinderBindsLocalTime() throws SQLException { LocalTime localTime = LocalTime.parse("01:02:03.000"); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, localTime); verify(stmt).setTime(1, Time.valueOf("01:02:03")); } @Test public void defaultBinderBindsLocalDateTime() throws SQLException { LocalDateTime localDateTime = LocalDateTime.parse("1975-07-19T01:02:03.000"); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, localDateTime); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 01:02:03")); } @Test public void defaultBinderBindsInstant() throws SQLException { Instant instant = LocalDateTime.parse("1975-07-19T01:02:03.000").atZone(ZoneId.systemDefault()).toInstant(); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, instant); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 01:02:03")); } @Test public void defaultBinderBindsZonedDateTime() throws SQLException { ZonedDateTime zonedDateTime = LocalDateTime.parse("1975-07-19T01:02:03.000").atZone(ZoneOffset.UTC); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, zonedDateTime); verify(stmt).setTimestamp(eq(1), eq(Timestamp.from(zonedDateTime.toInstant())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void defaultBinderBindsOffsetDateTime() throws SQLException { OffsetDateTime offsetDateTime = LocalDateTime.parse("1975-07-19T01:02:03.000").atOffset(ZoneOffset.UTC); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, offsetDateTime); verify(stmt).setTimestamp(eq(1), eq(Timestamp.from(offsetDateTime.toInstant())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void defaultBinderBindsOffsetTime() throws SQLException { OffsetTime offsetTime = LocalTime.parse("01:02:03.000").atOffset(ZoneOffset.UTC); Binder binder = Binders.defaultBinder(); binder.bind(stmt, 1, offsetTime); verify(stmt).setTime(eq(1), eq(Time.valueOf(offsetTime.toLocalTime())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void stringBinderBindsString() throws SQLException { Binder binder = Binders.stringBinder(); binder.bind(stmt, 1, "hello"); verify(stmt).setString(1, "hello"); } @Test public void stringBinderBindsEnum() throws SQLException { Binder binder = Binders.stringBinder(); binder.bind(stmt, 1, TestEnum.BAR); verify(stmt).setString(1, "BAR"); } @Test public void stringBinderBindsObject() throws SQLException { Binder binder = Binders.stringBinder(); binder.bind(stmt, 1, new Foo()); verify(stmt).setString(1, "foo"); } @Test public void stringBinderBindsNull() throws SQLException { Binder binder = Binders.stringBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } @Test public void dateBinderBindsSqlDate() throws SQLException { Date date = Date.valueOf("1975-07-19"); Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, date); verify(stmt).setDate(1, date); } @Test public void dateBinderBindsUtilDate() throws SQLException { java.util.Date date = new java.util.Date(Date.valueOf("1975-07-19").getTime()); Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, date); verify(stmt).setDate(1, Date.valueOf("1975-07-19")); } @Test public void dateBinderBindsCalendar() throws SQLException { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(Date.valueOf("1975-07-19").getTime()); Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, calendar); verify(stmt).setDate(1, Date.valueOf("1975-07-19"), calendar); } @Test public void dateBinderBindsString() throws SQLException { Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, "1975-07-19"); verify(stmt).setDate(1, Date.valueOf("1975-07-19")); } @Test public void dateBinderBindsLocalDate() throws SQLException { Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, LocalDate.parse("1975-07-19")); verify(stmt).setDate(1, Date.valueOf("1975-07-19")); } @Test public void dateBinderBindsLocalDateTime() throws SQLException { Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, LocalDateTime.parse("1975-07-19T01:02:03.000")); verify(stmt).setDate(1, Date.valueOf("1975-07-19")); } @Test public void dateBinderBindsInstant() throws SQLException { Binder binder = Binders.dateBinder(); Instant value = LocalDateTime.parse("1975-07-19T01:02:03.000").atZone(ZoneId.systemDefault()).toInstant(); binder.bind(stmt, 1, value); verify(stmt).setDate(1, new Date(value.toEpochMilli())); } @Test public void dateBinderBindsZonedDateTime() throws SQLException { Binder binder = Binders.dateBinder(); ZonedDateTime value = LocalDateTime.parse("1975-07-19T01:02:03.000").atZone(ZoneOffset.UTC); binder.bind(stmt, 1, value); verify(stmt).setDate(eq(1), eq(new Date(value.toInstant().toEpochMilli())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void dateBinderBindsOffsetDateTime() throws SQLException { Binder binder = Binders.dateBinder(); OffsetDateTime value = LocalDateTime.parse("1975-07-19T01:02:03.000").atOffset(ZoneOffset.UTC); binder.bind(stmt, 1, value); verify(stmt).setDate(eq(1), eq(new Date(value.toInstant().toEpochMilli())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void dateBinderBindsNull() throws SQLException { Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } @Test public void dateBinderBindsObject() throws SQLException { Binder binder = Binders.dateBinder(); binder.bind(stmt, 1, Boolean.TRUE); verify(stmt).setObject(1, Boolean.TRUE); } @Test public void timestampBinderBindsTimestamp() throws SQLException { Timestamp ts = Timestamp.valueOf("1975-07-19 13:14:15"); Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, ts); verify(stmt).setTimestamp(1, ts); } @Test public void timestampBinderBindsUtilDate() throws SQLException { Timestamp ts = Timestamp.valueOf("1975-07-19 13:14:15"); Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, new java.util.Date(ts.getTime())); verify(stmt).setTimestamp(1, ts); } @Test public void timestampBinderBindsCalendar() throws SQLException { Timestamp ts = Timestamp.valueOf("1975-07-19 13:14:15"); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(ts.getTime()); Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, calendar); verify(stmt).setTimestamp(1, ts, calendar); } @Test public void timestampBinderBindsStringWithTimestampFormat() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, "1975-07-19 13:14:15"); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 13:14:15")); } @Test public void timestampBinderBindsStringWithDateFormat() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, "1975-07-19"); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 00:00:00")); } @Test public void timestampBinderBindsLocalDateTime() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, LocalDateTime.parse("1975-07-19T01:02:03.000")); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 01:02:03")); } @Test public void timestampBinderBindsLocalDate() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, LocalDate.parse("1975-07-19")); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 00:00:00")); } @Test public void timestampBinderBindsInstant() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, LocalDateTime.parse("1975-07-19T01:02:03.000").atZone(ZoneId.systemDefault()).toInstant()); verify(stmt).setTimestamp(1, Timestamp.valueOf("1975-07-19 01:02:03")); } @Test public void timestampBinderBindsZonedDateTime() throws SQLException { Binder binder = Binders.timestampBinder(); ZonedDateTime value = LocalDateTime.parse("1975-07-19T01:02:03.000").atZone(ZoneOffset.UTC); binder.bind(stmt, 1, value); verify(stmt).setTimestamp(eq(1), eq(Timestamp.from(value.toInstant())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void timestampBinderBindsOffsetDateTime() throws SQLException { Binder binder = Binders.timestampBinder(); OffsetDateTime value = LocalDateTime.parse("1975-07-19T01:02:03.000").atOffset(ZoneOffset.UTC); binder.bind(stmt, 1, value); verify(stmt).setTimestamp(eq(1), eq(Timestamp.from(value.toInstant())), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void timestampBinderBindsNull() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } @Test public void timestampBinderBindsObject() throws SQLException { Binder binder = Binders.timestampBinder(); binder.bind(stmt, 1, Boolean.TRUE); verify(stmt).setObject(1, Boolean.TRUE); } @Test public void timeBinderBindsTime() throws SQLException { Time time = Time.valueOf("13:14:15"); Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, time); verify(stmt).setTime(1, time); } @Test public void timeBinderBindsUtilDate() throws SQLException { Time time = Time.valueOf("13:14:15"); Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, new java.util.Date(time.getTime())); verify(stmt).setTime(1, time); } @Test public void timeBinderBindsCalendar() throws SQLException { Time time = Time.valueOf("13:14:15"); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(time.getTime()); Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, calendar); verify(stmt).setTime(1, time, calendar); } @Test public void timeBinderBindsString() throws SQLException { Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, "13:14:15"); verify(stmt).setTime(1, Time.valueOf("13:14:15")); } @Test public void timeBinderBindsLocalTime() throws SQLException { Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, LocalTime.parse("01:02:03.000")); verify(stmt).setTime(1, Time.valueOf("01:02:03")); } @Test public void timeBinderBindsOffsetTime() throws SQLException { Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, OffsetTime.of(LocalTime.parse("01:02:03.000"), ZoneOffset.UTC)); verify(stmt).setTime(eq(1), eq(Time.valueOf("01:02:03")), calendarWithTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC))); } @Test public void timeBinderBindsNull() throws SQLException { Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } @Test public void timeBinderBindsObject() throws SQLException { Binder binder = Binders.timeBinder(); binder.bind(stmt, 1, Boolean.TRUE); verify(stmt).setObject(1, Boolean.TRUE); } @Test public void decimalBinderBindsString() throws SQLException { Binder binder = Binders.decimalBinder(); binder.bind(stmt, 1, "12.45"); verify(stmt).setBigDecimal(1, new BigDecimal("12.45")); } @Test public void decimalBinderBindsObject() throws SQLException { Binder binder = Binders.decimalBinder(); binder.bind(stmt, 1, 12.45); verify(stmt).setObject(1, 12.45); } @Test public void decimalBinderBindsNull() throws SQLException { Binder binder = Binders.decimalBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } @Test public void integerBinderBindsBigInteger() throws SQLException { Binder binder = Binders.integerBinder(); binder.bind(stmt, 1, new BigInteger("12")); verify(stmt).setObject(1, "12", Types.BIGINT); } @Test public void integerBinderBindsString() throws SQLException { Binder binder = Binders.integerBinder(); binder.bind(stmt, 1, "12"); verify(stmt).setObject(1, "12", Types.BIGINT); } @Test public void integerBinderBindsEnum() throws SQLException { Binder binder = Binders.integerBinder(); binder.bind(stmt, 1, TestEnum.FOO); verify(stmt).setInt(1, 0); } @Test public void integerBinderBindsObject() throws SQLException { Binder binder = Binders.integerBinder(); binder.bind(stmt, 1, 27); verify(stmt).setObject(1, 27); } @Test public void integerBinderBindsNull() throws SQLException { Binder binder = Binders.integerBinder(); binder.bind(stmt, 1, null); verify(stmt).setObject(1, null); } private enum TestEnum { FOO, BAR; } private static class Foo { @Override public String toString() { return "foo"; } } private static Calendar calendarWithTimeZone(TimeZone timeZone) { return argThat(new BaseMatcher() { @Override public boolean matches(Object item) { return (item instanceof Calendar) && ((Calendar) item).getTimeZone().equals(timeZone); } @Override public void describeTo(Description description) { description.appendText("a calendar with timezone " + timeZone); } }); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/bind/DefaultBinderConfigurationTest.java ================================================ package com.ninja_squad.dbsetup.bind; import org.junit.Test; import java.sql.ParameterMetaData; import java.sql.SQLException; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; public class DefaultBinderConfigurationTest { @Test public void shouldReturnDefaultBinderIfNoParameterMetadata() throws SQLException { assertEquals(Binders.defaultBinder(), DefaultBinderConfiguration.INSTANCE.getBinder(null, 1)); } @Test public void shouldReturnDefaultBinderIfParameterTypeCantBeObtained() throws SQLException { ParameterMetaData mockMetaData = mock(ParameterMetaData.class); when(mockMetaData.getParameterType(1)).thenThrow(new SQLException()); assertEquals(Binders.defaultBinder(), DefaultBinderConfiguration.INSTANCE.getBinder(mockMetaData, 1)); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/destination/DataSourceDestinationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.destination; import org.junit.Test; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author JB Nizet */ public class DataSourceDestinationTest { @Test public void getConnectionWorks() throws SQLException { DataSource dataSource = mock(DataSource.class); Connection connection = mock(Connection.class); when(dataSource.getConnection()).thenReturn(connection); assertSame(connection, new DataSourceDestination(dataSource).getConnection()); } @Test public void equalsAndHashCodeWork() throws SQLException { DataSource dataSource1 = mock(DataSource.class); DataSource dataSource2 = mock(DataSource.class); Destination dest1 = new DataSourceDestination(dataSource1); Destination dest1bis = DataSourceDestination.with(dataSource1); Destination dest2 = new DataSourceDestination(dataSource2); assertEquals(dest1, dest1); assertEquals(dest1, dest1bis); assertEquals(dest1.hashCode(), dest1bis.hashCode()); assertFalse(dest1.equals(dest2)); assertFalse(dest1.equals(null)); assertFalse(dest1.equals("hello")); } @Test public void toStringWorks() { DataSource dataSource1 = mock(DataSource.class); when(dataSource1.toString()).thenReturn("dataSource1"); assertEquals("DataSourceDestination [dataSource=dataSource1]", new DataSourceDestination(dataSource1).toString()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/destination/DriverManagerDestinationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.destination; import static org.junit.Assert.*; import org.junit.Test; /** * @author JB Nizet */ public class DriverManagerDestinationTest { @Test public void equalsAndHashCodeWork() { Destination dest1 = new DriverManagerDestination("url", "user", "password"); Destination dest1bis = DriverManagerDestination.with("url", "user", "password"); assertEquals(dest1, dest1); assertEquals(dest1, dest1bis); assertEquals(dest1.hashCode(), dest1bis.hashCode()); assertFalse(dest1.equals(new DriverManagerDestination("url2", "user", "password"))); assertFalse(dest1.equals(new DriverManagerDestination("url", "user2", "password"))); assertFalse(dest1.equals(new DriverManagerDestination("url", "user", "password2"))); assertFalse(dest1.equals(new DriverManagerDestination("url", null, "password"))); assertFalse(dest1.equals(new DriverManagerDestination("url", "user", null))); assertFalse(new DriverManagerDestination("url", null, "password").equals(dest1)); assertFalse(new DriverManagerDestination("url", "user", null).equals(dest1)); assertFalse(dest1.equals(null)); assertFalse(dest1.equals("hello")); assertEquals(new DriverManagerDestination("url", null, null), new DriverManagerDestination("url", null, null)); assertEquals(new DriverManagerDestination("url", null, null).hashCode(), new DriverManagerDestination("url", null, null).hashCode()); } @Test public void toStringWorks() { assertEquals("DriverManagerDestination [url=theUrl, user=theUser, password=thePassword]", new DriverManagerDestination("theUrl", "theUser", "thePassword").toString()); assertEquals("DriverManagerDestination [url=theUrl, user=null, password=null]", new DriverManagerDestination("theUrl", null, null).toString()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/generator/DateSequenceValueGeneratorTest.java ================================================ /* * The MIT License * * Copyright (c) 2013-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; import static org.junit.Assert.assertEquals; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import org.junit.Test; /** * @author JB */ public class DateSequenceValueGeneratorTest { @Test public void startsAtToday() { ZonedDateTime date = ValueGenerators.dateSequence().nextValue(); assertEquals(LocalDate.now().atStartOfDay(ZoneId.systemDefault()), date); } @Test @SuppressWarnings("deprecation") public void incrementsByOneDay() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence().startingAt(july19Of2013AtMidnight()); sequence.nextValue(); ZonedDateTime date = sequence.nextValue(); assertEquals(LocalDateTime.parse("2013-07-20T00:00:00.000").atZone(ZoneId.systemDefault()), date); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewStartAsDate() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence().startingAt(july19Of2013AtMidnight()); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); sequence.startingAt(july19Of1975AtMidnight()); assertEquals("1975-07-19T00:00:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewStartAsDateWithTimeZone() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnightInParisTimeZone(), TimeZone.getTimeZone("UTC")); assertEquals("2013-07-18T22:00:00.000", toLongStringInUTC(sequence.nextValue())); } @Test public void allowsSettingNewStartAsString() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt("2013-07-19"); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewStartAsCalendar() throws ParseException { Calendar start = Calendar.getInstance(); DateSequenceValueGenerator sequence = ValueGenerators.dateSequence().startingAt(start); assertEquals(start.getTime().getTime(), sequence.nextValue().toInstant().toEpochMilli()); } @Test public void allowsSettingNewStartAsLocalDate() throws ParseException { LocalDate start = LocalDate.parse("2000-01-01"); DateSequenceValueGenerator sequence = ValueGenerators.dateSequence().startingAt(start); assertEquals(start.atStartOfDay(ZoneId.systemDefault()), sequence.nextValue()); } @Test public void allowsSettingNewStartAsLocalDateTime() throws ParseException { LocalDateTime start = LocalDateTime.parse("2000-01-01T01:02:03.000"); DateSequenceValueGenerator sequence = ValueGenerators.dateSequence().startingAt(start); assertEquals(start.atZone(ZoneId.systemDefault()), sequence.nextValue()); } @Test public void allowsSettingNewStartAsZonedDateTime() throws ParseException { ZonedDateTime start = ZonedDateTime.parse("2000-01-01T01:02:03.000Z"); DateSequenceValueGenerator sequence = ValueGenerators.dateSequence().startingAt(start); assertEquals(start, sequence.nextValue()); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrement() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(2, DateSequenceValueGenerator.CalendarField.DAY); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2013-07-21T00:00:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrementInYears() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(1, DateSequenceValueGenerator.CalendarField.YEAR); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2014-07-19T00:00:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrementInMonths() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(1, DateSequenceValueGenerator.CalendarField.MONTH); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2013-08-19T00:00:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrementInHours() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(1, DateSequenceValueGenerator.CalendarField.HOUR); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2013-07-19T01:00:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrementInMinutes() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(1, DateSequenceValueGenerator.CalendarField.MINUTE); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2013-07-19T00:01:00.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrementInSeconds() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(1, DateSequenceValueGenerator.CalendarField.SECOND); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2013-07-19T00:00:01.000", toLongString(sequence.nextValue())); } @Test @SuppressWarnings("deprecation") public void allowsSettingNewIncrementInMilliseconds() throws ParseException { DateSequenceValueGenerator sequence = ValueGenerators.dateSequence() .startingAt(july19Of2013AtMidnight()) .incrementingBy(1, DateSequenceValueGenerator.CalendarField.MILLISECOND); assertEquals("2013-07-19T00:00:00.000", toLongString(sequence.nextValue())); assertEquals("2013-07-19T00:00:00.001", toLongString(sequence.nextValue())); } private String toLongString(ZonedDateTime date) { return DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS").format(date); } private String toLongStringInUTC(ZonedDateTime date) { return toLongString(date.withZoneSameInstant(ZoneOffset.UTC)); } private Date july19Of2013AtMidnight() throws ParseException { return new SimpleDateFormat("yyyy-MM-dd").parse("2013-07-19"); } private Date july19Of1975AtMidnight() throws ParseException { return new SimpleDateFormat("yyyy-MM-dd").parse("1975-07-19"); } // offset is +02:00 in Paris at this date private Date july19Of2013AtMidnightInParisTimeZone() throws ParseException { TimeZone zone = TimeZone.getTimeZone("Europe/Paris"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); simpleDateFormat.setTimeZone(zone); return simpleDateFormat.parse("2013-07-19"); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/generator/SequenceValueGeneratorTest.java ================================================ /* * The MIT License * * Copyright (c) 2013, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; import org.junit.Test; import static org.junit.Assert.*; /** * @author JB */ public class SequenceValueGeneratorTest { @Test public void startsAtOne() { assertEquals(1L, ValueGenerators.sequence().nextValue().longValue()); } @Test public void incrementsByOne() { SequenceValueGenerator sequence = ValueGenerators.sequence(); sequence.nextValue(); assertEquals(2L, sequence.nextValue().longValue()); } @Test public void allowsSettingNewStart() { SequenceValueGenerator sequence = ValueGenerators.sequence().startingAt(12L); assertEquals(12L, sequence.nextValue().longValue()); sequence.startingAt(5L); assertEquals(5L, sequence.nextValue().longValue()); } @Test public void allowsSettingNewIncrement() { SequenceValueGenerator sequence = ValueGenerators.sequence().incrementingBy(10); assertEquals(1L, sequence.nextValue().longValue()); assertEquals(11L, sequence.nextValue().longValue()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/generator/StringSequenceValueGeneratorTest.java ================================================ /* * The MIT License * * Copyright (c) 2013, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.generator; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * @author JB */ public class StringSequenceValueGeneratorTest { @Test public void startsAtOne() { assertEquals("CODE_1", ValueGenerators.stringSequence("CODE_").nextValue()); } @Test public void incrementsByOne() { StringSequenceValueGenerator sequence = ValueGenerators.stringSequence("CODE_"); sequence.nextValue(); assertEquals("CODE_2", sequence.nextValue()); } @Test public void allowsSettingNewStart() { StringSequenceValueGenerator sequence = ValueGenerators.stringSequence("CODE_").startingAt(12L); assertEquals("CODE_12", sequence.nextValue()); sequence.startingAt(5L); assertEquals("CODE_5", sequence.nextValue()); } @Test public void allowsSettingNewIncrement() { StringSequenceValueGenerator sequence = ValueGenerators.stringSequence("CODE_").incrementingBy(10); assertEquals("CODE_1", sequence.nextValue()); assertEquals("CODE_11", sequence.nextValue()); } @Test public void allowsSettingLeftPadding() { StringSequenceValueGenerator sequence = ValueGenerators.stringSequence("CODE_").withLeftPadding(2); assertEquals("CODE_01", sequence.nextValue()); assertEquals("CODE_02", sequence.nextValue()); sequence.startingAt(10L); assertEquals("CODE_10", sequence.nextValue()); assertEquals("CODE_11", sequence.nextValue()); sequence.startingAt(100L); assertEquals("CODE_100", sequence.nextValue()); assertEquals("CODE_101", sequence.nextValue()); } @Test public void allowsUnsettingLeftPadding() { StringSequenceValueGenerator sequence = ValueGenerators.stringSequence("CODE_").withLeftPadding(2); sequence.withoutLeftPadding(); assertEquals("CODE_1", sequence.nextValue()); assertEquals("CODE_2", sequence.nextValue()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/generator/ValueGeneratorsTest.java ================================================ package com.ninja_squad.dbsetup.generator; import org.junit.Test; import static org.junit.Assert.*; /** * @author JB */ public class ValueGeneratorsTest { @Test public void contantShouldReturnGeneratorWhichGeneratesAConstantValue() { ValueGenerator constantGenerator = ValueGenerators.constant("hello"); for (int i = 0; i < 3; i++) { assertEquals("hello", constantGenerator.nextValue()); } } // other methods are tested by the test for the class returned by the method } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/integration/CommonOperations.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.integration; import com.ninja_squad.dbsetup.operation.Operation; import static com.ninja_squad.dbsetup.Operations.*; /** * @author JB Nizet */ public class CommonOperations { public static final Operation DROP_TABLES = sequenceOf(sql("drop table if exists A cascade"), sql("drop table if exists B cascade")); public static final Operation CREATE_TABLES = sequenceOf(sql("create table A (a_id bigint primary key, va varchar(100), nu numeric(10, 2), bo boolean, da date, tis timestamp, tim time, seq numeric)"), sql("create table B (b_id bigint primary key, a_id SMALLINT, va varchar(100), foreign key (a_id) references A (a_id))")); public static final Operation INSERT_ROWS = sequenceOf( insertInto("A").columns("a_id").values(1L).build(), insertInto("B").columns("b_id", "a_id").values(1L, 1L).build()); } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/integration/Database.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.integration; import java.sql.Connection; import java.sql.SQLException; import com.ninja_squad.dbsetup.destination.Destination; import com.ninja_squad.dbsetup.destination.DriverManagerDestination; /** * @author JB Nizet */ public class Database { static { try { Class.forName("org.hsqldb.jdbc.JDBCDriver"); } catch (Exception e) { throw new RuntimeException(e); } } public static final String URL = "jdbc:hsqldb:mem:mymemdb"; public static final String USER = "SA"; public static final String PASSWORD = ""; public static final Destination DESTINATION = new DriverManagerDestination(URL, USER, PASSWORD); public static Connection getConnection() throws SQLException { return DESTINATION.getConnection(); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/integration/DeleteAllIntegrationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.integration; import static org.junit.Assert.*; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.ninja_squad.dbsetup.DbSetup; import com.ninja_squad.dbsetup.Operations; /** * @author JB Nizet */ public class DeleteAllIntegrationTest { private Connection connection; @Before public void prepare() throws SQLException { new DbSetup(Database.DESTINATION, Operations.sequenceOf(CommonOperations.DROP_TABLES, CommonOperations.CREATE_TABLES, CommonOperations.INSERT_ROWS)).launch(); connection = Database.getConnection(); } @After public void cleanup() throws SQLException { connection.close(); } @Test public void testDeleteAll() throws SQLException { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from A"); assertTrue(rs.next()); rs.close(); rs = stmt.executeQuery("select * from B"); assertTrue(rs.next()); connection.close(); new DbSetup(Database.DESTINATION, Operations.deleteAllFrom("B", "A")).launch(); connection = Database.getConnection(); stmt = connection.createStatement(); rs = stmt.executeQuery("select * from A"); assertFalse(rs.next()); rs.close(); rs = stmt.executeQuery("select * from B"); assertFalse(rs.next()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/integration/InsertIntegrationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012-2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.integration; import static org.junit.Assert.*; import java.math.BigDecimal; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.Calendar; import com.ninja_squad.dbsetup.DbSetup; import com.ninja_squad.dbsetup.DbSetupRuntimeException; import com.ninja_squad.dbsetup.Operations; import com.ninja_squad.dbsetup.bind.Binder; import com.ninja_squad.dbsetup.bind.Binders; import com.ninja_squad.dbsetup.generator.ValueGenerators; import com.ninja_squad.dbsetup.operation.Insert; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * @author JB Nizet */ public class InsertIntegrationTest { private Connection connection; @Before public void prepare() throws SQLException { new DbSetup(Database.DESTINATION, Operations.sequenceOf(CommonOperations.DROP_TABLES, CommonOperations.CREATE_TABLES)).launch(); connection = Database.getConnection(); } @After public void cleanup() throws SQLException { connection.close(); } @Test public void testInsert() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "nu", "bo", "da", "tim", "tis") .values(1L, 12.6, true, "1975-07-19", "14:15:22", "2012-12-25 13:05:12") .values(2L, 13.6, false, "1976-10-16", "14:15:23", "2012-12-25 13:05:13") .withDefaultValue("va", "hello") .withGeneratedValue("seq", ValueGenerators.sequence().startingAt(10L).incrementingBy(5)) .build(); Insert insertB1 = Insert.into("B") .columns("b_id", "a_id", "va") .values("1", 1, new Foo("foo")) .withBinder(new FooBinder(), "va") .build(); Insert insertB2 = Insert.into("B") .columns("b_id", "a_id", "va") .values(2, TestEnum.BAT, TestEnum.BAR) .build(); new DbSetup(Database.DESTINATION, Operations.sequenceOf(insertA, insertB1, insertB2)).launch(); Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from A order by a_id"); assertTrue(rs.next()); assertEquals(1L, rs.getLong("a_id")); assertTrue(new BigDecimal("12.6").compareTo(rs.getBigDecimal("nu")) == 0); assertEquals(true, rs.getBoolean("bo")); assertEquals(Date.valueOf("1975-07-19"), rs.getDate("da")); assertEquals(Time.valueOf("14:15:22"), rs.getTime("tim")); assertEquals(Timestamp.valueOf("2012-12-25 13:05:12"), rs.getTimestamp("tis")); assertEquals("hello", rs.getString("va")); assertEquals(10L, rs.getLong("seq")); assertTrue(rs.next()); assertEquals(2L, rs.getLong("a_id")); assertTrue(new BigDecimal("13.6").compareTo(rs.getBigDecimal("nu")) == 0); assertEquals(false, rs.getBoolean("bo")); assertEquals(Date.valueOf("1976-10-16"), rs.getDate("da")); assertEquals(Time.valueOf("14:15:23"), rs.getTime("tim")); assertEquals(Timestamp.valueOf("2012-12-25 13:05:13"), rs.getTimestamp("tis")); assertEquals("hello", rs.getString("va")); assertEquals(15L, rs.getLong("seq")); rs = stmt.executeQuery("select * from B order by b_id"); assertTrue(rs.next()); assertEquals(1L, rs.getLong("b_id")); assertEquals(1L, rs.getLong("a_id")); assertEquals("foo", rs.getString("va")); assertTrue(rs.next()); assertEquals(2L, rs.getLong("b_id")); assertEquals(2L, rs.getLong("a_id")); assertEquals("BAR", rs.getString("va")); } @Test public void testWithoutMetadata() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "nu", "bo", "da", "tim", "tis") .values(TestEnum.BAT, "13.6", false, "1976-10-16", "14:15:23", "2012-12-25 13:05:13") .withDefaultValue("va", "hello") .useMetadata(false) .build(); try { new DbSetup(Database.DESTINATION, insertA).launch(); fail("expected an exception due to the metadata not being used"); } catch (DbSetupRuntimeException e) { // expected } insertA = Insert.into("A") .columns("a_id", "nu", "bo", "da", "tim", "tis") .values("2", "13.6", false, "1976-10-16", "14:15:23", "2012-12-25 13:05:13") .withDefaultValue("va", "hello") .useMetadata(false) .withBinder(Binders.integerBinder(), "a_id") .withBinder(Binders.decimalBinder(), "nu") .withBinder(Binders.dateBinder(), "da") .withBinder(Binders.timeBinder(), "tim") .withBinder(Binders.timestampBinder(), "tis") .build(); new DbSetup(Database.DESTINATION, insertA).launch(); Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from A order by a_id"); assertTrue(rs.next()); assertEquals(2L, rs.getLong("a_id")); assertTrue(new BigDecimal("13.6").compareTo(rs.getBigDecimal("nu")) == 0); assertEquals(false, rs.getBoolean("bo")); assertEquals(Date.valueOf("1976-10-16"), rs.getDate("da")); assertEquals(Time.valueOf("14:15:23"), rs.getTime("tim")); assertEquals(Timestamp.valueOf("2012-12-25 13:05:13"), rs.getTimestamp("tis")); assertEquals("hello", rs.getString("va")); } @Test public void insertEnumToVarcharColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "va") .values(1, TestEnum.BAR) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertJavaUtilDateToDateColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "da") .values(1, new java.util.Date()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertJavaUtilCalendarToDateColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "da") .values(1, Calendar.getInstance()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertJavaUtilDateToTimestampColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tis") .values(1, new java.util.Date()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertJavaUtilCalendarToTimestampColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tis") .values(1, Calendar.getInstance()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertLocalDateTimeToTimestampColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tis") .values(1, LocalDateTime.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertLocalDateToDateColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "da") .values(1, LocalDate.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertLocalTimeToTimeColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tim") .values(1, LocalTime.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertInstantToTimestampColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tis") .values(1, Instant.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertZonedDateTimeToTimestampColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tis") .values(1, ZonedDateTime.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertOffsetDateTimeToTimestampColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tis") .values(1, OffsetDateTime.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } @Test public void insertOffsetTimeToTimeColumnWithoutMetadataShouldWork() throws SQLException { Insert insertA = Insert.into("A") .columns("a_id", "tim") .values(1, OffsetTime.now()) .useMetadata(false) .build(); new DbSetup(Database.DESTINATION, insertA).launch(); } private static class Foo { private String label; public Foo(String label) { this.label = label; } public String getLabel() { return label; } } private static class FooBinder implements Binder { @Override public void bind(PreparedStatement statement, int param, Object value) throws SQLException { statement.setString(param, ((Foo) value).getLabel()); } } private enum TestEnum { BAR, BAZ, BAT; } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/integration/RollbackIntegrationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.integration; import static org.junit.Assert.*; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.ninja_squad.dbsetup.DbSetup; import com.ninja_squad.dbsetup.DbSetupRuntimeException; import com.ninja_squad.dbsetup.Operations; import com.ninja_squad.dbsetup.operation.Insert; /** * @author JB Nizet */ public class RollbackIntegrationTest { private Connection connection; @Before public void prepare() throws SQLException { new DbSetup(Database.DESTINATION, Operations.sequenceOf(CommonOperations.DROP_TABLES, CommonOperations.CREATE_TABLES)).launch(); connection = Database.getConnection(); } @After public void cleanup() throws SQLException { connection.close(); } @Test public void testRollbackIfSQLException() throws SQLException { Insert insertA1 = Insert.into("A") .columns("a_id", "nu") .values(1L, 12.6) .build(); Insert insertA2 = Insert.into("A") .columns("a_id", "fooo") .values(1L, "hello") .build(); try { new DbSetup(Database.DESTINATION, Operations.sequenceOf(insertA1, insertA2)).launch(); fail("expected a DbSetupRuntimeException"); } catch (DbSetupRuntimeException e) { // expected } Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from A order by a_id"); assertFalse(rs.next()); } @Test public void testRollbackIfOtherException() throws SQLException { Insert insertA1 = Insert.into("A") .columns("a_id", "nu") .values(1L, 12.6) .build(); Insert insertA2 = Insert.into("A") .columns("a_id", "nu") .values(1L, "hello") .build(); try { new DbSetup(Database.DESTINATION, Operations.sequenceOf(insertA1, insertA2)).launch(); fail("expected a NumberFormatException"); } catch (NumberFormatException e) { // expected } Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from A order by a_id"); assertFalse(rs.next()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/integration/TruncateIntegrationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.integration; import static org.junit.Assert.*; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.ninja_squad.dbsetup.DbSetup; import com.ninja_squad.dbsetup.Operations; /** * @author JB Nizet */ public class TruncateIntegrationTest { private Connection connection; @Before public void prepare() throws SQLException { new DbSetup(Database.DESTINATION, Operations.sequenceOf(CommonOperations.DROP_TABLES, CommonOperations.CREATE_TABLES, CommonOperations.INSERT_ROWS)).launch(); connection = Database.getConnection(); } @After public void cleanup() throws SQLException { connection.close(); } @Test public void testDeleteAll() throws SQLException { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from A"); assertTrue(rs.next()); rs.close(); rs = stmt.executeQuery("select * from B"); assertTrue(rs.next()); connection.close(); new DbSetup(Database.DESTINATION, Operations.truncate("B", "A")).launch(); connection = Database.getConnection(); stmt = connection.createStatement(); rs = stmt.executeQuery("select * from A"); assertFalse(rs.next()); rs.close(); rs = stmt.executeQuery("select * from B"); assertFalse(rs.next()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/operation/CompositeOperationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.SQLException; import java.util.Arrays; import java.util.Collections; import org.junit.Test; import org.mockito.InOrder; import com.ninja_squad.dbsetup.Operations; import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration; /** * @author JB Nizet */ public class CompositeOperationTest { @Test public void sequenceOfWorksWhenNop() throws SQLException { testNoArgOpWorks(CompositeOperation.sequenceOf()); testNoArgOpWorks(CompositeOperation.sequenceOf(Collections.emptyList())); testNoArgOpWorks(Operations.sequenceOf()); testNoArgOpWorks(Operations.sequenceOf(Collections.emptyList())); } private void testNoArgOpWorks(Operation nop) throws SQLException { Connection connection = mock(Connection.class); nop.execute(connection, DefaultBinderConfiguration.INSTANCE); assertEquals("NOP", nop.toString()); } @Test public void sequenceOfWorksWhenSingleArg() throws SQLException { Operation a = mock(Operation.class); assertSame(a, CompositeOperation.sequenceOf(a)); assertSame(a, CompositeOperation.sequenceOf(Collections.singletonList(a))); assertSame(a, Operations.sequenceOf(a)); assertSame(a, Operations.sequenceOf(Collections.singletonList(a))); } @Test public void sequenceOfWorksWhenSeveralArgs() throws SQLException { Operation a = mock(Operation.class); Operation b = mock(Operation.class); testSequenceOfWorksWhenMultipleArgs(CompositeOperation.sequenceOf(a, b), a, b); testSequenceOfWorksWhenMultipleArgs(CompositeOperation.sequenceOf(Arrays.asList(a, b)), a, b); testSequenceOfWorksWhenMultipleArgs(Operations.sequenceOf(a, b), a, b); testSequenceOfWorksWhenMultipleArgs(Operations.sequenceOf(Arrays.asList(a, b)), a, b); } private void testSequenceOfWorksWhenMultipleArgs(Operation composite, Operation a, Operation b) throws SQLException { Connection connection = mock(Connection.class); composite.execute(connection, DefaultBinderConfiguration.INSTANCE); InOrder inOrder = inOrder(a, b); inOrder.verify(a).execute(connection, DefaultBinderConfiguration.INSTANCE); inOrder.verify(b).execute(connection, DefaultBinderConfiguration.INSTANCE); } @Test public void equalsAndHashCodeWork() { SqlOperation a = SqlOperation.of("A"); SqlOperation b = SqlOperation.of("B"); Operation c1 = CompositeOperation.sequenceOf(a, b); Operation c2 = CompositeOperation.sequenceOf(a, b); assertEquals(c1, c2); assertEquals(c1.hashCode(), c2.hashCode()); assertEquals(c1, c1); assertFalse(c1.equals(null)); assertFalse(c1.equals("hello")); } @Test public void toStringWorks() { Operation a = mock(Operation.class); when(a.toString()).thenReturn("a"); Operation b = mock(Operation.class); when(b.toString()).thenReturn("b"); assertEquals("a\nb", CompositeOperation.sequenceOf(a, b).toString()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/operation/DeleteAllTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import org.junit.Test; import org.mockito.InOrder; import com.ninja_squad.dbsetup.Operations; import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration; /** * @author JB Nizet */ public class DeleteAllTest { @Test public void fromWorks() throws SQLException { testFrom(DeleteAll.from("A", "B")); testFrom(DeleteAll.from(Arrays.asList("A", "B"))); testFrom(Operations.deleteAllFrom("A", "B")); testFrom(Operations.deleteAllFrom(Arrays.asList("A", "B"))); } @Test public void toStringWorks() { assertEquals("delete from A", DeleteAll.from("A").toString()); } @Test public void equalsAndHashCodeWork() { assertEquals(DeleteAll.from("A"), Operations.deleteAllFrom("A")); assertEquals(DeleteAll.from("A").hashCode(), DeleteAll.from("A").hashCode()); assertFalse(DeleteAll.from("A").equals(DeleteAll.from("B"))); assertFalse(DeleteAll.from("A").equals(null)); assertFalse(DeleteAll.from("A").equals("hello")); DeleteAll a = DeleteAll.from("A"); assertEquals(a, a); } private void testFrom(Operation deleteAllFromAandB) throws SQLException { Connection connection = mock(Connection.class); Statement stmt = mock(Statement.class); when(connection.createStatement()).thenReturn(stmt); deleteAllFromAandB.execute(connection, DefaultBinderConfiguration.INSTANCE); InOrder inOrder = inOrder(stmt); inOrder.verify(stmt).executeUpdate("delete from A"); inOrder.verify(stmt).close(); inOrder.verify(stmt).executeUpdate("delete from B"); inOrder.verify(stmt).close(); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/operation/InsertTest.java ================================================ /* * The MIT License * * Copyright (c) 2012-2015, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import com.ninja_squad.dbsetup.bind.Binder; import com.ninja_squad.dbsetup.bind.BinderConfiguration; import com.ninja_squad.dbsetup.bind.Binders; import com.ninja_squad.dbsetup.generator.ValueGenerators; import org.junit.Test; import org.mockito.InOrder; /** * @author JB Nizet */ public class InsertTest { @Test public void insertWorks() throws SQLException { Binder aBinder = mock(Binder.class); Binder bBinder = mock(Binder.class); Binder cBinder = mock(Binder.class); Binder dBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); ParameterMetaData metadata = mock(ParameterMetaData.class); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b, c, d) values (?, ?, ?, ?)")).thenReturn(statement); when(statement.getParameterMetaData()).thenReturn(metadata); when(config.getBinder(metadata, 1)).thenReturn(aBinder); when(config.getBinder(metadata, 3)).thenReturn(cBinder); Insert insert = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .row().column("b", "b3") .column("a", "a3") .end() .row().column("a", "a4") .end() .withDefaultValue("c", "c3") .withDefaultValue("d", "d4") .withBinder(bBinder, "b") .withBinder(dBinder, "d") .build(); insert.execute(connection, config); InOrder inOrder = inOrder(aBinder, bBinder, cBinder, dBinder, statement); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a2"); inOrder.verify(bBinder).bind(statement, 2, "b2"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a3"); inOrder.verify(bBinder).bind(statement, 2, "b3"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a4"); inOrder.verify(bBinder).bind(statement, 2, null); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void insertWorksWithoutMetadata() throws SQLException { Binder bBinder = mock(Binder.class); Binder dBinder = mock(Binder.class); Binder defaultBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); when(config.getBinder(isNull(ParameterMetaData.class), anyInt())).thenReturn(defaultBinder); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b, c, d) values (?, ?, ?, ?)")).thenReturn(statement); Insert insert = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withDefaultValue("d", "d4") .withBinder(bBinder, "b") .withBinder(dBinder, "d") .useMetadata(false) .build(); insert.execute(connection, config); InOrder inOrder = inOrder(defaultBinder, bBinder, dBinder, statement); inOrder.verify(defaultBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(defaultBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(defaultBinder).bind(statement, 1, "a2"); inOrder.verify(bBinder).bind(statement, 2, "b2"); inOrder.verify(defaultBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void insertWorksWhenMetadataNotSupported() throws SQLException { Binder defaultBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); PreparedStatement statement = mock(PreparedStatement.class); when(config.getBinder(null, 1)).thenReturn(defaultBinder); when(config.getBinder(null, 2)).thenReturn(defaultBinder); when(connection.prepareStatement("insert into A (a, b) values (?, ?)")).thenReturn(statement); Insert insert = Insert.into("A") .columns("a", "b") .values("a1", "b1") .build(); insert.execute(connection, config); InOrder inOrder = inOrder(statement, defaultBinder); inOrder.verify(defaultBinder).bind(statement, 1, "a1"); inOrder.verify(defaultBinder).bind(statement, 2, "b1"); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void insertWorksWhenColumnsSpecifiedByFirstRow() throws SQLException { Binder aBinder = mock(Binder.class); Binder bBinder = mock(Binder.class); Binder cBinder = mock(Binder.class); Binder dBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); ParameterMetaData metadata = mock(ParameterMetaData.class); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b, c, d) values (?, ?, ?, ?)")).thenReturn(statement); when(statement.getParameterMetaData()).thenReturn(metadata); when(config.getBinder(metadata, 1)).thenReturn(aBinder); when(config.getBinder(metadata, 3)).thenReturn(cBinder); Insert insert = Insert.into("A") .row().column("a", "a1") .column("b", "b1") .end() .row().column("b", "b2") .end() .values("a3", "b3") .withDefaultValue("c", "c3") .withDefaultValue("d", "d4") .withBinder(bBinder, "b") .withBinder(dBinder, "d") .build(); insert.execute(connection, config); InOrder inOrder = inOrder(aBinder, bBinder, cBinder, dBinder, statement); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, null); inOrder.verify(bBinder).bind(statement, 2, "b2"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a3"); inOrder.verify(bBinder).bind(statement, 2, "b3"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(dBinder).bind(statement, 4, "d4"); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void insertWorksWhenColumnsSpecifiedByFirstRepeatedRow() throws SQLException { Binder aBinder = mock(Binder.class); Binder bBinder = mock(Binder.class); Binder cBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); ParameterMetaData metadata = mock(ParameterMetaData.class); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b, c) values (?, ?, ?)")).thenReturn(statement); when(statement.getParameterMetaData()).thenReturn(metadata); when(config.getBinder(metadata, 1)).thenReturn(aBinder); when(config.getBinder(metadata, 2)).thenReturn(bBinder); when(config.getBinder(metadata, 3)).thenReturn(cBinder); Insert insert = Insert.into("A") .row().column("a", "a1") .column("b", "b1") .times(2) .withDefaultValue("c", "c3") .build(); insert.execute(connection, config); InOrder inOrder = inOrder(aBinder, bBinder, cBinder, statement); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, "c3"); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test(expected = IllegalArgumentException.class) public void rowBuilderColumnFailsWhenMapContainsUnknownColumnName() { Insert.into("A") .columns("a", "b") .row().column("c", "value of c"); } @Test(expected = IllegalArgumentException.class) public void valuesFailsWhenMapContainsUnknownColumnName() { Map map1 = new LinkedHashMap(); map1.put("c", "value of c"); Map map2 = new LinkedHashMap(); map2.put("b", "value of b"); Insert.into("A").values(map1).values(map2); } @Test(expected = IllegalArgumentException.class) public void endRowFailsWhenItContainsUnknownColumnNameAndColumnNamesSpecifiedByFirstRow() { Insert.into("A") .row().column("c", "value of c").end() .row().column("b", "value of b").end(); } @Test public void repeatingValuesWork() throws SQLException { Binder aBinder = mock(Binder.class); Binder bBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); ParameterMetaData metadata = mock(ParameterMetaData.class); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b) values (?, ?)")).thenReturn(statement); when(statement.getParameterMetaData()).thenReturn(metadata); when(config.getBinder(metadata, 1)).thenReturn(aBinder); when(config.getBinder(metadata, 2)).thenReturn(bBinder); Insert insert = Insert.into("A") .columns("a", "b") .repeatingValues("a1", "b1").times(2) .build(); insert.execute(connection, config); InOrder inOrder = inOrder(aBinder, bBinder, statement); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void rowAsMapCanBeAddedSeveralTimes() throws SQLException { Binder aBinder = mock(Binder.class); Binder bBinder = mock(Binder.class); Binder cBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); ParameterMetaData metadata = mock(ParameterMetaData.class); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b, c) values (?, ?, ?)")).thenReturn(statement); when(statement.getParameterMetaData()).thenReturn(metadata); when(config.getBinder(metadata, 1)).thenReturn(aBinder); when(config.getBinder(metadata, 2)).thenReturn(bBinder); when(config.getBinder(metadata, 3)).thenReturn(cBinder); Map row = new HashMap(); row.put("a", "a1"); row.put("b", "b1"); Insert insert = Insert.into("A") .columns("a", "b", "c") .repeatingValues(row).times(2) .build(); insert.execute(connection, config); InOrder inOrder = inOrder(aBinder, bBinder, cBinder, statement); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, null); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, null); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void dynamicRowCanBeAddedSeveralTimes() throws SQLException { Binder aBinder = mock(Binder.class); Binder bBinder = mock(Binder.class); Binder cBinder = mock(Binder.class); Connection connection = mock(Connection.class); BinderConfiguration config = mock(BinderConfiguration.class); ParameterMetaData metadata = mock(ParameterMetaData.class); PreparedStatement statement = mock(PreparedStatement.class); when(connection.prepareStatement("insert into A (a, b, c) values (?, ?, ?)")).thenReturn(statement); when(statement.getParameterMetaData()).thenReturn(metadata); when(config.getBinder(metadata, 1)).thenReturn(aBinder); when(config.getBinder(metadata, 2)).thenReturn(bBinder); when(config.getBinder(metadata, 3)).thenReturn(cBinder); Insert insert = Insert.into("A") .columns("a", "b", "c") .row() .column("a", "a1") .column("b", "b1") .times(2) .build(); insert.execute(connection, config); InOrder inOrder = inOrder(aBinder, bBinder, cBinder, statement); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, null); inOrder.verify(statement).executeUpdate(); inOrder.verify(aBinder).bind(statement, 1, "a1"); inOrder.verify(bBinder).bind(statement, 2, "b1"); inOrder.verify(cBinder).bind(statement, 3, null); inOrder.verify(statement).executeUpdate(); inOrder.verify(statement).close(); } @Test public void toStringWorks() { Insert insert = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withDefaultValue("d", "d4") .withBinder(Binders.decimalBinder(), "b") .withBinder(Binders.dateBinder(), "d") .useMetadata(false) .build(); assertNotNull(insert.toString()); } @Test public void builderToStringWorks() { Insert.Builder builder = Insert.into("A"); assertNotNull(builder.toString()); builder.columns("a", "b"); assertNotNull(builder.toString()); builder.values("a1", "b1") .values("a2", "b2"); assertNotNull(builder.toString()); builder.withDefaultValue("c", "c3") .withDefaultValue("d", "d4"); assertNotNull(builder.toString()); builder.withBinder(Binders.decimalBinder(), "b") .withBinder(Binders.dateBinder(), "d"); assertNotNull(builder.toString()); builder.useMetadata(false); assertNotNull(builder.toString()); builder.build(); assertNotNull(builder.toString()); } @Test public void equalsAndHashCodeWork() { Insert insertA = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.decimalBinder(), "b") .useMetadata(false) .build(); Insert insertB = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.decimalBinder(), "b") .useMetadata(false) .build(); assertEquals(insertA, insertA); assertEquals(insertA, insertB); assertEquals(insertA.hashCode(), insertB.hashCode()); assertFalse(insertA.equals(null)); assertFalse(insertA.equals("hello")); insertB = Insert.into("A") .columns("e", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.decimalBinder(), "b") .useMetadata(false) .build(); assertFalse(insertA.equals(insertB)); insertB = Insert.into("A") .columns("a", "b") .values("a1", "b2") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.decimalBinder(), "b") .useMetadata(false) .build(); assertFalse(insertA.equals(insertB)); insertB = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c4") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.decimalBinder(), "b") .useMetadata(false) .build(); assertFalse(insertA.equals(insertB)); insertB = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.integerBinder(), "b") .useMetadata(false) .build(); assertFalse(insertA.equals(insertB)); insertB = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence()) .withBinder(Binders.decimalBinder(), "b") .useMetadata(true) .build(); assertFalse(insertA.equals(insertB)); insertB = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .withDefaultValue("c", "c3") .withGeneratedValue("d", ValueGenerators.sequence().startingAt(2L)) .withBinder(Binders.decimalBinder(), "b") .useMetadata(false) .build(); assertFalse(insertA.equals(insertB)); } @Test public void getRowCountWorks() { Insert insert = Insert.into("A") .columns("a", "b") .values("a1", "b1") .values("a2", "b2") .build(); assertEquals(2, insert.getRowCount()); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/operation/SqlOperationTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import org.junit.Test; import org.mockito.InOrder; import com.ninja_squad.dbsetup.Operations; import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration; /** * @author JB Nizet */ public class SqlOperationTest { @Test public void ofWorks() throws SQLException { testOf(SqlOperation.of("A", "B")); testOf(SqlOperation.of(Arrays.asList("A", "B"))); testOf(Operations.sql("A", "B")); testOf(Operations.sql(Arrays.asList("A", "B"))); } @Test public void toStringWorks() { assertEquals("A", SqlOperation.of("A").toString()); } @Test public void equalsAndHashCodeWork() { assertEquals(SqlOperation.of("A"), Operations.sql("A")); assertEquals(SqlOperation.of("A").hashCode(), SqlOperation.of("A").hashCode()); assertFalse(SqlOperation.of("A").equals(SqlOperation.of("B"))); assertFalse(SqlOperation.of("A").equals(null)); assertFalse(SqlOperation.of("A").equals("hello")); SqlOperation a = SqlOperation.of("A"); assertEquals(a, a); } private void testOf(Operation aAndB) throws SQLException { Connection connection = mock(Connection.class); Statement stmt = mock(Statement.class); when(connection.createStatement()).thenReturn(stmt); aAndB.execute(connection, DefaultBinderConfiguration.INSTANCE); InOrder inOrder = inOrder(stmt); inOrder.verify(stmt).executeUpdate("A"); inOrder.verify(stmt).close(); inOrder.verify(stmt).executeUpdate("B"); inOrder.verify(stmt).close(); } } ================================================ FILE: DbSetup-core/src/test/java/com/ninja_squad/dbsetup/operation/TruncateTest.java ================================================ /* * The MIT License * * Copyright (c) 2012, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup.operation; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import org.junit.Test; import org.mockito.InOrder; import com.ninja_squad.dbsetup.Operations; import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration; /** * @author JB Nizet */ public class TruncateTest { @Test public void tablesWorks() throws SQLException { testFrom(Truncate.tables("A", "B")); testFrom(Truncate.tables(Arrays.asList("A", "B"))); testFrom(Operations.truncate("A", "B")); testFrom(Operations.truncate(Arrays.asList("A", "B"))); } @Test public void toStringWorks() { assertEquals("truncate table A", Truncate.tables("A").toString()); } @Test public void equalsAndHashCodeWork() { assertEquals(Truncate.table("A"), Operations.truncate("A")); assertEquals(Truncate.table("A").hashCode(), Truncate.table("A").hashCode()); assertFalse(Truncate.table("A").equals(Truncate.table("B"))); assertFalse(Truncate.table("A").equals(null)); assertFalse(Truncate.table("A").equals("hello")); Truncate a = Truncate.table("A"); assertEquals(a, a); } private void testFrom(Operation truncateAandB) throws SQLException { Connection connection = mock(Connection.class); Statement stmt = mock(Statement.class); when(connection.createStatement()).thenReturn(stmt); truncateAandB.execute(connection, DefaultBinderConfiguration.INSTANCE); InOrder inOrder = inOrder(stmt); inOrder.verify(stmt).executeUpdate("truncate table A"); inOrder.verify(stmt).close(); inOrder.verify(stmt).executeUpdate("truncate table B"); inOrder.verify(stmt).close(); } } ================================================ FILE: DbSetup-kotlin/build.gradle.kts ================================================ import java.time.Duration plugins { `java-library-convention` kotlin("jvm") version "1.4.20" id("org.jetbrains.dokka") version "1.4.20" } repositories { // TODO replace this by other repo once dokka allows it jcenter() } project.description = "Kotlin extensions for DbSetup" dependencies { api(project(":DbSetup")) testImplementation("junit:junit:4.+") testImplementation("org.mockito:mockito-all:1.9.0") } val javadocJar by tasks.registering(Jar::class) { archiveFileName.set("dokka-html.jar") from(tasks.dokkaJavadoc) } val dokkaJar by tasks.registering(Jar::class) { archiveFileName.set("dokka-javadoc.jar") from(tasks.dokkaHtml) } publishing { publications.named("maven") { artifact(dokkaJar) { classifier = "dokka" } artifact(javadocJar) { classifier = "javadoc" } } } ================================================ FILE: DbSetup-kotlin/src/main/kotlin/com/ninja_squad/dbsetup_kotlin/DbSetup.kt ================================================ /* * The MIT License * * Copyright (c) 2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup_kotlin import com.ninja_squad.dbsetup.DbSetup import com.ninja_squad.dbsetup.DbSetupTracker /** * Extension function of DbSetup allowing to launch it with a tracker. This allows launching the setup in an easier way. * * Instead of doing * * ``` * val theSetup = dbSetup { * ... * } * tracker.launchIfNecessary(theSetup) * ``` * * you can simply do * * ``` * dbSetup { * ... * }.launchWith(tracker) * ``` * * @param tracker the tracker used to launch the DbSetup, if necessary. * * @author JB Nizet */ fun DbSetup.launchWith(tracker: DbSetupTracker) = tracker.launchIfNecessary(this) ================================================ FILE: DbSetup-kotlin/src/main/kotlin/com/ninja_squad/dbsetup_kotlin/DbSetupBuilder.kt ================================================ /* * The MIT License * * Copyright (c) 2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup_kotlin import com.ninja_squad.dbsetup.DbSetup import com.ninja_squad.dbsetup.Operations import com.ninja_squad.dbsetup.bind.BinderConfiguration import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration import com.ninja_squad.dbsetup.destination.Destination import com.ninja_squad.dbsetup.operation.Insert import com.ninja_squad.dbsetup.operation.Operation /** * A builder allowing to configure a DbSetup from a lambda expression whose receiver type is this builder. * The intended usage is to use the [dbSetup] top level function. * * @param to The destination of the DbSetup * @property binderConfiguration The binder configuration of the DbSetup. It not set, the default configuration is used */ class DbSetupBuilder(private val to: Destination, var binderConfiguration: BinderConfiguration = DefaultBinderConfiguration.INSTANCE) { private val operations = mutableListOf() /** * Adds an Insert operation to the DbSetup, using a lambda expression to configure it. * * Example usage: * * ``` * dbSetup { * insertInto("user") { * columns("id", "name") * values(1, "John Doe") * ... * } * } * ``` * * @param table the name of the table to insert into * @param configure the function used to configure the insert. */ inline fun insertInto(table: String, configure: Insert.Builder.() -> Unit) { val builder = Insert.into(table) builder.configure() execute(builder.build()) } /** * Adds a DeleteAll operation to the DbSetup * @param table the table to delete from */ fun deleteAllFrom(table: String) = execute(Operations.deleteAllFrom(table)) /** * Adds DeleteAll operations to the DbSetup * @param tables the tables to delete from */ fun deleteAllFrom(vararg tables: String) = execute(Operations.deleteAllFrom(*tables)) /** * Adds DeleteAll operations to the DbSetup * @param tables the tables to delete from */ fun deleteAllFrom(tables: List) = execute(Operations.deleteAllFrom(tables)) /** * Adds a Truncate operation to the DbSetup * @param table the table to truncate */ fun truncate(table: String) = execute(Operations.truncate(table)) /** * Adds Truncate operations to the DbSetup * @param tables the tables to delete from */ fun truncate(vararg tables: String) = execute(Operations.truncate(*tables)) /** * Adds Truncate operations to the DbSetup * @param tables the tables to truncate */ fun truncate(tables: List) = execute(Operations.truncate(tables)) /** * Adds a SqlOperation to the DbSetup * @param statement the SQL statement to execute */ fun sql(statement: String) = execute(Operations.sql(statement)) /** * Adds SqlOperations to the DbSetup * @param statements the SQL statements to execute */ fun sql(vararg statements: String) = execute(Operations.sql(*statements)) /** * Adds SqlOperations to the DbSetup * @param statements the SQL statements to execute */ fun sql(statements: List) = execute(Operations.sql(statements)) /** * Adds an operation to the DbSetup. Custom extension functions typically delegate to this method to * add the operation they want. * @param operation the operation to add */ fun execute(operation: Operation) = operations.add(operation) /** * Builds the DbSetup. This method is called by the dbSetup function after the builder has been configured. */ internal fun build() = DbSetup(to, Operations.sequenceOf(operations), binderConfiguration) } ================================================ FILE: DbSetup-kotlin/src/main/kotlin/com/ninja_squad/dbsetup_kotlin/Functions.kt ================================================ /* * The MIT License * * Copyright (c) 2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup_kotlin import com.ninja_squad.dbsetup.DbSetup import com.ninja_squad.dbsetup.bind.BinderConfiguration import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration import com.ninja_squad.dbsetup.destination.DataSourceDestination import com.ninja_squad.dbsetup.destination.Destination import com.ninja_squad.dbsetup.operation.Insert import javax.sql.DataSource /** * Top-level function allowing to create a DbSetup by passing a destination and a lambda expression used to configure * the operations that must be made. * * Example usage: * * ``` * val setup = dbSetup(to = DriverManagerDestination(url, user, password)) { * deleteAllFrom("user", "country") * insertInto("country") { * ... * } * insertInto("user") { * ... * } * sql(...) * } * ``` * * @param to the destination of the DbSetup * @param binderConfiguration a custom binder configuration. The default one is used if not specified * @param configure the function used to configure the DbSetup * @return the created DbSetup * * @author JB Nizet */ fun dbSetup(to: Destination, binderConfiguration: BinderConfiguration = DefaultBinderConfiguration.INSTANCE, configure: DbSetupBuilder.() -> Unit): DbSetup { val builder = DbSetupBuilder(to, binderConfiguration) builder.configure() return builder.build() } /** * Top-level function allowing to create a DbSetup by passing a DataSource and a lambda expression used to configure * the operations that must be made. * * Example usage: * * ``` * val setup = dbSetup(to = dataSource) { * deleteAllFrom("user", "country") * insertInto("country") { * ... * } * insertInto("user") { * ... * } * sql(...) * } * ``` * * @param to the destination of the DbSetup * @param binderConfiguration a custom binder configuration. The default one is used if not specified * @param configure the function used to configure the DbSetup * @return the created DbSetup * * @author JB Nizet */ fun dbSetup(to: DataSource, binderConfiguration: BinderConfiguration = DefaultBinderConfiguration.INSTANCE, configure: DbSetupBuilder.() -> Unit) = dbSetup(DataSourceDestination(to), binderConfiguration, configure) /** * Top-level function allowing to create an Insert operation by passing a lambda expression configuring it. * * Example usage: * * ``` * val insertUsers = insertInto("user") { * columns("id", "name") * values(1, "John Doe") * values(2, "Jane Doe") * } * ``` * @param table the name of the table to insert into * @param configure the function used to configure the Insert * @return the created Insert operation * * @author JB Nizet */ inline fun insertInto(table: String, configure: Insert.Builder.() -> Unit): Insert { val builder = Insert.into(table) builder.configure() return builder.build() } ================================================ FILE: DbSetup-kotlin/src/main/kotlin/com/ninja_squad/dbsetup_kotlin/Insert.Builder.kt ================================================ /* * The MIT License * * Copyright (c) 2016, Ninja Squad * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.ninja_squad.dbsetup_kotlin import com.ninja_squad.dbsetup.operation.Insert /** * Extension function of InsertBuilder allowing to specify values as columns/value pairs instead of a Map. * * Example usage: * * ``` * insertInto("user") { * columns("id", "name", "birthDate") * mappedValues("id" to 1, "name" to "John Doe") * } * ``` * * This is equivalent to * * ``` * insertInto("user") { * columns("id", "name", "birthDate") * values(mapOf("id" to 1, "name" to "John Doe")) * } * ``` * * but is a bit shorter. Beware to NOT use * * ``` * insertInto("user") { * columns("id", "name", "birthDate") * values("id" to 1, "name" to "John Doe") * } * ``` * * because that would try to insert the Pairs themselves into the table, rather than the values of the pairs. * * @param entries the column/value pairs of the row to insert * @return the Insert Builder for chaining (although that is usually not necessary with the Kotlin DSL) * * @author JB Nizet */ fun Insert.Builder.mappedValues(vararg entries: Pair): Insert.Builder { return this.values(mapOf(*entries)) } /** * Allows inserting the same values multiple times, by specifying them as colum/value pairs instead of a Map. * * Example usage: * * ``` * insertInto("user") { * columns("id", "firstName", "lastName") * withGeneratedValue("id", ValueGenerators.sequence()) * repeatingMappedValues("firstName" to "John", "lastName" to "Doe").times(100) * } * ``` * * This is equivalent to * * ``` * insertInto("user") { * columns("id", "firstName", "lastName") * withGeneratedValue("id", ValueGenerators.sequence()) * repeatingValues(mapOf("firstName" to "John", "lastName" to "Doe")).times(100) * } * ``` * * but is a bit shorter. Beware to NOT use * * ``` * insertInto("user") { * columns("id", "firstName", "lastName") * withGeneratedValue("id", ValueGenerators.sequence()) * repeatingValues("firstName" to "John", "lastName" to "Doe").times(100) * } * ``` * * because that would try to insert the Pairs themselves into the table, rather than the values of the pairs. * * @param entries the column/value pairs of the row to insert * @return the RowRepeater, on which you must call times(N) to specify how many similar rows to insert * * @author JB Nizet */ fun Insert.Builder.repeatingMappedValues(vararg entries: Pair) = this.repeatingValues(mapOf(*entries)) ================================================ FILE: DbSetup-kotlin/src/test/kotlin/com/ninja_squad/dbsetup_kotlin/DbSetupBuilderTest.kt ================================================ package com.ninja_squad.dbsetup_kotlin; import com.ninja_squad.dbsetup.bind.BinderConfiguration import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration import com.ninja_squad.dbsetup.destination.Destination import com.ninja_squad.dbsetup.operation.Operation import org.junit.Before import org.junit.Test import org.mockito.Mockito.verify import java.sql.Connection import java.sql.PreparedStatement import java.sql.Statement import javax.sql.DataSource class DbSetupBuilderTest { private lateinit var mockDestination: Destination private lateinit var mockConnection: Connection private lateinit var mockOperation: Operation private lateinit var mockConfig: BinderConfiguration private lateinit var mockStatement: Statement @Before fun prepare() { mockDestination = mock() mockConnection = mock() whenever(mockDestination.connection).thenReturn(mockConnection) mockOperation = mock() mockConfig = mock() mockStatement = mock() whenever(mockConnection.createStatement()).thenReturn(mockStatement) } @Test fun `should execute an operation with the binder configuration specified as property`() { dbSetup(to = mockDestination) { binderConfiguration = mockConfig execute(mockOperation) }.launch() verify(mockOperation).execute(mockConnection, mockConfig) } @Test fun `should execute an operation with the binder configuration specified as argument`() { dbSetup(to = mockDestination, binderConfiguration = mockConfig) { execute(mockOperation) }.launch() verify(mockOperation).execute(mockConnection, mockConfig) } @Test fun `should execute an operation with a DataSource specified as argument`() { val mockDataSource = mock() whenever(mockDataSource.connection).thenReturn(mockConnection) dbSetup(to = mockDataSource, binderConfiguration = mockConfig) { execute(mockOperation) }.launch() verify(mockOperation).execute(mockConnection, mockConfig) } @Test fun `should use the default binder configuration if not set`() { dbSetup(to = mockDestination) { execute(mockOperation) }.launch() verify(mockOperation).execute(mockConnection, DefaultBinderConfiguration.INSTANCE) } @Test fun `should delete all from one table`() { dbSetup(to = mockDestination) { deleteAllFrom("user") }.launch() verify(mockStatement).executeUpdate("delete from user") } @Test fun `should delete all from multiple tables passed as vararg`() { dbSetup(to = mockDestination) { deleteAllFrom("country", "user") }.launch() verify(mockStatement).executeUpdate("delete from country") verify(mockStatement).executeUpdate("delete from user") } @Test fun `should delete all from multiple tables passed as list`() { dbSetup(to = mockDestination) { deleteAllFrom(listOf("country", "user")) }.launch() verify(mockStatement).executeUpdate("delete from country") verify(mockStatement).executeUpdate("delete from user") } @Test fun `should truncate one table`() { dbSetup(to = mockDestination) { truncate("user") }.launch() verify(mockStatement).executeUpdate("truncate table user") } @Test fun `should truncate multiple tables passed as vararg`() { dbSetup(to = mockDestination) { truncate("country", "user") }.launch() verify(mockStatement).executeUpdate("truncate table country") verify(mockStatement).executeUpdate("truncate table user") } @Test fun `should truncate multiple tables passed as list`() { dbSetup(to = mockDestination) { truncate(listOf("country", "user")) }.launch() verify(mockStatement).executeUpdate("truncate table country") verify(mockStatement).executeUpdate("truncate table user") } @Test fun `should execute one sql statement`() { val query = "update foo where bar = 1" dbSetup(to = mockDestination) { sql(query) }.launch() verify(mockStatement).executeUpdate(query) } @Test fun `should execute multiple sql statements passed as vararg`() { val query1 = "update foo where bar = 1" val query2 = "update baz where qux = 1" dbSetup(to = mockDestination) { sql(query1, query2) }.launch() verify(mockStatement).executeUpdate(query1) verify(mockStatement).executeUpdate(query2) } @Test fun `should execute multiple sql statements passed as list`() { val query1 = "update foo where bar = 1" val query2 = "update baz where qux = 1" dbSetup(to = mockDestination) { sql(listOf(query1, query2)) }.launch() verify(mockStatement).executeUpdate(query1) verify(mockStatement).executeUpdate(query2) } @Test fun `should insert`() { val mockPreparedStatement = mock() whenever(mockConnection.prepareStatement("insert into user (id, name) values (?, ?)")) .thenReturn(mockPreparedStatement) dbSetup(to = mockDestination) { insertInto("user") { columns("id", "name") values(1, "John") } }.launch() verify(mockPreparedStatement).setObject(1, 1) verify(mockPreparedStatement).setObject(2, "John") verify(mockPreparedStatement).executeUpdate() } } ================================================ FILE: DbSetup-kotlin/src/test/kotlin/com/ninja_squad/dbsetup_kotlin/InsertBuilderTest.kt ================================================ package com.ninja_squad.dbsetup_kotlin import com.ninja_squad.dbsetup.generator.ValueGenerators import org.junit.Assert.assertEquals import org.junit.Test /** * @author JB Nizet */ class InsertBuilderTest { @Test fun `should construct an insert with mapped values`() { val insert = insertInto("user") { mappedValues("id" to 1L, "name" to "John") } val expected = insertInto("user") { values(mapOf("id" to 1L, "name" to "John")) } assertEquals(expected, insert); } @Test fun `should construct an insert with repeating mapped values`() { val insert = insertInto("user") { withGeneratedValue("id", ValueGenerators.sequence()) repeatingMappedValues("name" to "John").times(10) } val expected = insertInto("user") { withGeneratedValue("id", ValueGenerators.sequence()) repeatingValues(mapOf("name" to "John")).times(10) } assertEquals(expected, insert); } } ================================================ FILE: DbSetup-kotlin/src/test/kotlin/com/ninja_squad/dbsetup_kotlin/MockitoExtensions.kt ================================================ package com.ninja_squad.dbsetup_kotlin import org.mockito.Mockito import org.mockito.stubbing.OngoingStubbing /** * @author JB Nizet */ fun whenever(methodCall: T): OngoingStubbing = Mockito.`when`(methodCall) inline fun mock(): T = Mockito.mock(T::class.java) ================================================ FILE: README.md ================================================ [![CircleCI](https://circleci.com/gh/Ninja-Squad/DbSetup.svg?style=svg)](https://circleci.com/gh/Ninja-Squad/DbSetup) # Overview DbSetup allows populating a database before executing automated integration tests (typically, DAO/Repository automated tests). Although DBUnit, which is a great project, allows doing the same thing and much more, it's also harder to use and setup. And in our experience, in 98% of the cases, DBUnit is only used to pre-populate a database before executing every test method. This is the task on which DbSetup concentrates. The philosophy of DbSetup is that DAO tests should not have to setup the database, execute tests, and then remove everything from the database. Instead, a single setup method should be used to delete everything from the database (whatever the previous test put in it, or the initial state of the database tables), and then populate it with the data necessary to execute the test. Another design choice of DbSetup is to provide an easy to use and simple Java API to populate the database, rather than loading data from external XML files. Using a Java API has several advantages: - It allows using real Java types as data (longs, enums, etc.) - It allows defining default values, looping to generate several similar rows, storing data sets in variables or factorizing their creation using resusable methods - It allows viewing the data sets easily, without having to open external files, by storing the data set directly into the test class, or by navigating through classes and methods using the IDE shortcuts. - For more complex situations, like cyclic referential integrity constraints between rows, the Java API allows easily integrating SQL statements into the sequence of operations to execute to pre-populate the database. These SQL statements can, for example, disable constraints and re-enable them. # Documentation The documentation is available at [dbsetup.ninja-squad.com](http://dbsetup.ninja-squad.com). It's hosted by github, from the gh-pages branch. # Bugs and RFEs To submit bugs or RFEs, use [Github](https://github.com/Ninja-Squad/DbSetup/issues). # How to... ## build ./gradlew build ## install the artifacts in your own local Maven repository ./gradlew publishToMavenLocal ## release a new version of DbSetup - Make sure you have a file named `gradle.properties` under `HOME/.gradle`, and this file contains the following properties: sonatypeUsername= sonatypePassword= signing.keyId= signing.password= signing.secretKeyRingFile= - Make sure the version of DbSetup in the project's `gradle.properties` file is the right one. It must not end with `-SNAPSHOT` - Execute ./gradlew clean build publishToSonatype closeAndReleaseSonatypeStagingRepository That should create a new staging repo in Sonatype OSSRS, then upload the artefacts to this staging repo, sign close the repo and release (i.e. sync it to Maven central). To only close the repo to test without syncing to Maven central, run ./gradlew clean build publishToSonatype closeSonatypeStagingRepository Note: to check the jar file and pom publication before uploading, you can use ./gradlew publishMavenPublicationToBuildRepository which will simply publish under the `build/repo` directory of the root project. See the publishing guide at https://github.com/rwinch/gradle-publish-ossrh-sample. # License DbSetup is released under the [MIT License](http://en.wikipedia.org/wiki/MIT_License). The MIT License Copyright (c) 2012-2013, Ninja Squad Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: build.gradle.kts ================================================ import java.time.Duration plugins { id("io.github.gradle-nexus.publish-plugin") version "1.0.0" } group = "com.ninja-squad" nexusPublishing { repositories { sonatype { username.set(project.findProperty("sonatypeUsername")?.toString() ?: "") password.set(project.findProperty("sonatypePassword")?.toString() ?: "") } } connectTimeout.set(Duration.ofMinutes(3)) clientTimeout.set(Duration.ofMinutes(3)) } ================================================ FILE: buildSrc/build.gradle.kts ================================================ plugins { `kotlin-dsl` } repositories { mavenCentral() gradlePluginPortal() } ================================================ FILE: buildSrc/settings.grade.kts ================================================ ================================================ FILE: buildSrc/src/main/kotlin/java-library-convention.gradle.kts ================================================ import org.gradle.api.tasks.testing.logging.TestExceptionFormat plugins { `java-library` signing `maven-publish` jacoco } group = rootProject.group version = rootProject.version java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 withSourcesJar() } repositories { mavenCentral() } tasks { val checkJavaVersion by registering { doLast { if (!JavaVersion.current().isJava8()) { val message = "ERROR: Java 1.8 required but ${JavaVersion.current()} found. Change your JAVA_HOME environment variable."; throw IllegalStateException(message); } } } compileJava { dependsOn(checkJavaVersion) } withType { options.encoding = "UTF-8" } withType { manifest { attributes( "Implementation-Title" to project.name, "Implementation-Version" to project.version, "Implementation-Vendor" to "ninja-squad.com", "Bundle-Vendor" to "ninja-squad.com" ) } } test { testLogging { exceptionFormat = TestExceptionFormat.FULL } } check { dependsOn(jacocoTestReport) } } publishing { publications { create("maven") { from(components["java"]) pom { name.set(project.name) description.set(project.provider(Callable { project.description })) url.set("https://dbsetup.ninja-squad.com/") organization { name.set("Ninja Squad") url.set("https://ninja-squad.com") } licenses { license { name.set("MIT License") url.set("https://dbsetup.ninja-squad.com/license.html") distribution.set("repo") } } developers { developer { id.set("jnizet") name.set("Jean-Baptiste Nizet") email.set("jb@ninja-squad.com") } } scm { connection.set("scm:git:git://github.com/Ninja-Squad/DbSetup") developerConnection.set("scm:git:git://github.com/Ninja-Squad/DbSetup") url.set("https://github.com/Ninja-Squad/DbSetup") } } } } repositories { maven { name = "build" url = uri("${rootProject.buildDir}/repo") } } } signing { sign(publishing.publications["maven"]) } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Sun Feb 07 17:02:40 CET 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip ================================================ FILE: gradle.properties ================================================ version = 2.1.1 ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle.kts ================================================ rootProject.name = "DbSetupParent" include("DbSetup-core", "DbSetup-kotlin") project(":DbSetup-core").name = "DbSetup"