package org.xbib.elasticsearch.jdbc.strategy.column; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.joda.time.DateTime; import org.testng.annotations.Parameters; import org.testng.annotations.Test; import org.xbib.elasticsearch.jdbc.strategy.Context; import org.xbib.elasticsearch.jdbc.strategy.mock.MockSink; import org.xbib.elasticsearch.common.util.IndexableObject; import org.xbib.elasticsearch.common.util.PlainIndexableObject; import org.xbib.elasticsearch.common.util.SQLCommand; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.HashMap; import java.util.Map; import java.util.Random; public class ColumnStrategySourceTests extends AbstractColumnStrategyTest { private final static Logger logger = LogManager.getLogger("test.column.source"); private Random random = new Random(); private DateTime LAST_RUN_TIME = new DateTime(new DateTime().getMillis() - 60 * 60 * 1000); @Override public ColumnSource newSource() { return new ColumnSource(); } @Override public ColumnContext newContext() { return new ColumnContext(); } @Test @Parameters({"existedWhereClause", "sqlInsert"}) public void testCreateObjects(String resource, String sql) throws Exception { verifyCreateObjects(resource, sql); } @Test @Parameters({"whereClausePlaceholder", "sqlInsert"}) public void testCreateObjects_configurationWithWherePlaceholder(String resource, String sql) throws Exception { verifyCreateObjects(resource, sql); } @Test @Parameters({"sqlparams", "sqlInsert"}) public void testCreateObjects_configurationWithSqlParams(String resource, String sql) throws Exception { verifyCreateObjects(resource, sql); } @Test @Parameters({"sqlForTestDeletions", "sqlInsert"}) public void testRemoveObjects(String resource, String insertSql) throws Exception { verifyDeleteObjects(resource, insertSql); } @Test @Parameters({"sqlForTestDeletionsAndWherePlaceholder", "sqlInsert"}) public void testRemoveObjects_configurationWithWherePlaceholder(String resource, String insertSql) throws Exception { verifyDeleteObjects(resource, insertSql); } @Test @Parameters({"existedWhereClauseWithOverlap", "sqlInsert"}) public void testCreateObjects_withLastRunTimeStampOverlap(String resource, String sql) throws Exception { final int newRecordsOutOfTimeRange = 3; final int newRecordsInTimeRange = 2; final int updatedRecordsInTimeRange = 4; final int updatedRecordsInTimeRangeWithOverlap = 1; testColumnStrategy(new MockSink(), resource, sql, new ProductFixture[]{ ProductFixture.size(newRecordsOutOfTimeRange).createdAt(oldTimestamp()), ProductFixture.size(newRecordsInTimeRange).createdAt(okTimestamp()), ProductFixture.size(updatedRecordsInTimeRange).createdAt(oldTimestamp()).updatedAt(okTimestamp()), ProductFixture.size(updatedRecordsInTimeRangeWithOverlap).createdAt(oldTimestamp()).updatedAt(overlapTimestamp()), }, newRecordsInTimeRange + updatedRecordsInTimeRange + updatedRecordsInTimeRangeWithOverlap); } private void verifyCreateObjects(String resource, String sql) throws Exception { final int newRecordsOutOfTimeRange = 3; final int newRecordsInTimeRange = 2; final int updatedRecordsInTimeRange = 4; testColumnStrategy(new MockSink(), resource, sql, new ProductFixture[]{ new ProductFixture(newRecordsOutOfTimeRange).createdAt(oldTimestamp()), new ProductFixture(newRecordsInTimeRange).createdAt(okTimestamp()), new ProductFixture(updatedRecordsInTimeRange).createdAt(oldTimestamp()).updatedAt(okTimestamp()), }, newRecordsInTimeRange + updatedRecordsInTimeRange); } private void verifyDeleteObjects(String resource, String insertSql) throws Exception { MockSink sink = new MockSink(); boolean[] shouldProductsBeDeleted = new boolean[]{true, true, false}; ProductFixtures productFixtures = createFixturesAndPopulateSink(shouldProductsBeDeleted, sink); testColumnStrategy(sink, resource, insertSql, productFixtures.fixtures, productFixtures.expectedCount); } private ProductFixtures createFixturesAndPopulateSink(boolean[] shouldProductsBeDeleted, MockSink sink) throws IOException { ProductFixture[] fixtures = new ProductFixture[shouldProductsBeDeleted.length]; int expectedExistsCountAfterRun = 0; for (int i = 0; i < shouldProductsBeDeleted.length; i++) { IndexableObject p = new PlainIndexableObject() .id(Integer.toString(i)) .source(createSource(i)) .optype("delete"); sink.index(p, false); Timestamp deletedAt; if (shouldProductsBeDeleted[i]) { deletedAt = okTimestamp(); } else { deletedAt = oldTimestamp(); expectedExistsCountAfterRun++; } fixtures[i] = ProductFixture.one() .setId(i) .createdAt(oldTimestamp()) .updatedAt(oldTimestamp()) .deletedAt(deletedAt); } return new ProductFixtures(fixtures, expectedExistsCountAfterRun); } private Map<String, Object> createSource(int id) { Map<String, Object> map = new HashMap<String, Object>(); map.put("id", id); map.put("name", null); return map; } private void testColumnStrategy(MockSink sink, String resource, String sql, ProductFixture[] fixtures, int expectedHits) throws Exception { createData(sql, fixtures); context = createContext(resource); context.setSink(sink); source.fetch(); assertEquals(sink.data().size(), expectedHits); } protected ColumnContext createContext(String resource) throws IOException { Settings settings = createSettings(resource); ColumnContext context = newContext(); context.setSettings(settings); context.setLastRunTimeStamp(LAST_RUN_TIME); context.setLastRunTimeStampOverlap(getLastRunTimestampOverlap(settings)); source.setContext(context); source.columnCreatedAt(settings.get("column_created_at")) .columnUpdatedAt(settings.get("column_updated_at")) .columnDeletedAt(settings.get("column_deleted_at")) .columnEscape(true); source.setStatements(SQLCommand.parse(settings.getAsStructuredMap())); return context; } private TimeValue getLastRunTimestampOverlap(Settings settings) { TimeValue overlap = TimeValue.timeValueMillis(0); if (settings != null && settings.getAsStructuredMap().containsKey("last_run_timestamp_overlap")) { overlap = settings.getAsTime("last_run_timestamp_overlap", null); } return overlap; } private Timestamp okTimestamp() { return new Timestamp(LAST_RUN_TIME.getMillis() + 60 * 2 * 1000); } private Timestamp oldTimestamp() { return new Timestamp(LAST_RUN_TIME.getMillis() - 60 * 2 * 1000); } private Timestamp overlapTimestamp() { return new Timestamp(LAST_RUN_TIME.getMillis() - 1000); } private void createData(String sql, ProductFixture[] fixtures) throws SQLException { Connection conn = source.getConnectionForWriting(); for (ProductFixture fixture : fixtures) { createData(conn, sql, fixture); } source.closeWriting(); } private void createData(Connection connection, String sql, ProductFixture fixture) throws SQLException { PreparedStatement stmt = connection.prepareStatement(sql); logger.debug("timestamps: [" + fixture.createdAt + ", " + fixture.updatedAt + ", " + fixture.deletedAt + "]"); for (int i = 0; i < fixture.size; i++) { int id = fixture.id >= 0 ? fixture.id : random.nextInt(); logger.debug("id={}", id); stmt.setInt(1, id); stmt.setNull(2, Types.VARCHAR); stmt.setInt(3, 1); stmt.setDouble(4, 1.1); if (fixture.createdAt != null) { stmt.setTimestamp(5, fixture.createdAt); } else { stmt.setNull(5, Types.TIMESTAMP); } if (fixture.updatedAt != null) { stmt.setTimestamp(6, fixture.updatedAt); } else { stmt.setNull(6, Types.TIMESTAMP); } if (fixture.deletedAt != null) { stmt.setTimestamp(7, fixture.deletedAt); } else { stmt.setNull(7, Types.TIMESTAMP); } stmt.execute(); } } private static class ProductFixtures { int expectedCount; ProductFixture[] fixtures; ProductFixtures(ProductFixture[] fixtures, int expectedCount) { this.expectedCount = expectedCount; this.fixtures = fixtures; } } private static class ProductFixture { private int id = -1; private Timestamp createdAt; private Timestamp deletedAt; private Timestamp updatedAt; private int size; static ProductFixture one() { return size(1); } static ProductFixture size(int size) { return new ProductFixture(size); } ProductFixture(int size) { this.size = size; } ProductFixture createdAt(Timestamp ts) { this.createdAt = ts; return this; } ProductFixture deletedAt(Timestamp ts) { this.deletedAt = ts; return this; } ProductFixture updatedAt(Timestamp ts) { this.updatedAt = ts; return this; } ProductFixture setId(int id) { this.id = id; return this; } } }