From 4608f868bf9c79680150f7f9856ac12f19448e63 Mon Sep 17 00:00:00 2001 From: javanna Date: Thu, 9 Apr 2015 16:15:05 +0200 Subject: [PATCH 01/22] add 1.5.2 snapshot version and and make 1.5.1 a released version --- src/main/java/org/elasticsearch/Version.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/elasticsearch/Version.java b/src/main/java/org/elasticsearch/Version.java index 7fab3b517f4..fdd3f5fe3b3 100644 --- a/src/main/java/org/elasticsearch/Version.java +++ b/src/main/java/org/elasticsearch/Version.java @@ -230,7 +230,9 @@ public class Version { public static final int V_1_5_0_ID = 1050099; public static final Version V_1_5_0 = new Version(V_1_5_0_ID, false, org.apache.lucene.util.Version.LUCENE_4_10_4); public static final int V_1_5_1_ID = 1050199; - public static final Version V_1_5_1 = new Version(V_1_5_1_ID, true, org.apache.lucene.util.Version.LUCENE_4_10_4); + public static final Version V_1_5_1 = new Version(V_1_5_1_ID, false, org.apache.lucene.util.Version.LUCENE_4_10_4); + public static final int V_1_5_2_ID = 1050299; + public static final Version V_1_5_2 = new Version(V_1_5_2_ID, true, org.apache.lucene.util.Version.LUCENE_4_10_4); public static final int V_1_6_0_ID = 1060099; public static final Version V_1_6_0 = new Version(V_1_6_0_ID, true, org.apache.lucene.util.Version.LUCENE_4_10_4); public static final int V_2_0_0_ID = 2000099; @@ -252,6 +254,8 @@ public class Version { return V_2_0_0; case V_1_6_0_ID: return V_1_6_0; + case V_1_5_2_ID: + return V_1_5_2; case V_1_5_1_ID: return V_1_5_1; case V_1_5_0_ID: From 5367e04fbce1eb2c7cb93046f74f335d884b5d51 Mon Sep 17 00:00:00 2001 From: javanna Date: Thu, 9 Apr 2015 17:03:35 +0200 Subject: [PATCH 02/22] [TEST] add 1.5.1 index for bw comp tests --- .../org/elasticsearch/bwcompat/index-1.5.1.zip | Bin 0 -> 75671 bytes .../org/elasticsearch/bwcompat/repo-1.5.1.zip | Bin 0 -> 71684 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/test/resources/org/elasticsearch/bwcompat/index-1.5.1.zip create mode 100644 src/test/resources/org/elasticsearch/bwcompat/repo-1.5.1.zip diff --git a/src/test/resources/org/elasticsearch/bwcompat/index-1.5.1.zip b/src/test/resources/org/elasticsearch/bwcompat/index-1.5.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..9b16e6e96dd40c4b059b567e560ead58bec55f6f GIT binary patch literal 75671 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

`60F!jN~-dh1u} zb78Z$dnA`1hXZDcja`C+7YGOf5xa`h|8lZ^fI>vq&)|@0?X}Y{=E~X%3gOtDyvN!< zkPpJPg$}f=EePPljZ$ar<9=HcSS!5iQowg1d6AmH@vBdB4P;dh_|dlq40LH3!24hI zzWBZ$krV?-{<%?-(MtY^yf{c+6{rZ%6*I~!DQ*r}t8DHs|5nw_glz5&*% zICgV@lamsw4E^^d25VC=M?ewGC9DQnLYjDSE!DmOCffQ0_0 z3}sds$T>9?Eg%jDpn&6$h-Sh#$SR|tuZ5BUOh#&@H!^COF;rM(=;Nct4*@Y4d>2O% zj!7JoEDg(SJ{cBIN)^_ya4XZ@X8b;BkvOv z2Cz=W8Uwua#K@3cV8Q?vqF7^qmueUp4xgDYfQ1{@7~qUQBO`E$2?NOgv&I1D^%)s+ z%osqHpEU+JCC|vnT4oXlkf3Lc0ZvUZG8Wm{Ht(;1L=|fca1x1;@sNWF14t>c#sDWz z{$czxMDK!b%J|Oou*L&Vi5PheJe%--kOua?j5IC&b=r4Mn6)^-MvzfC9=5K`@-;&_ TfsPy^20SBpAdvF{YxVy>GOu?H literal 0 HcmV?d00001 From d470bdbff61d9036d76a7530d95d9e458ad90ddf Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Thu, 9 Apr 2015 14:54:40 +0200 Subject: [PATCH 03/22] Only flush for checkindex if we have uncommitted changes Today we force a flush before check index to ensure we have an index to check on. Yet if the index is large and the FS is slow this can have significant impact on the index deletion performance. This commit introduces a check if there are any uncommitted changes in order to skip the additional commit. Closes #10505 --- .../org/elasticsearch/index/engine/Engine.java | 6 ++++++ .../index/engine/InternalEngine.java | 5 +++++ .../elasticsearch/index/engine/ShadowEngine.java | 5 +++++ .../OldIndexBackwardsCompatibilityTests.java | 15 --------------- .../test/store/MockFSDirectoryService.java | 10 ++++------ 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/elasticsearch/index/engine/Engine.java b/src/main/java/org/elasticsearch/index/engine/Engine.java index ca7d10130ad..83ebfb72a7c 100644 --- a/src/main/java/org/elasticsearch/index/engine/Engine.java +++ b/src/main/java/org/elasticsearch/index/engine/Engine.java @@ -1061,4 +1061,10 @@ public abstract class Engine implements Closeable { } } } + + /** + * Returns true the internal writer has any uncommitted changes. Otherwise false + * @return + */ + public abstract boolean hasUncommittedChanges(); } diff --git a/src/main/java/org/elasticsearch/index/engine/InternalEngine.java b/src/main/java/org/elasticsearch/index/engine/InternalEngine.java index 9ae31e24244..2c293d986f3 100644 --- a/src/main/java/org/elasticsearch/index/engine/InternalEngine.java +++ b/src/main/java/org/elasticsearch/index/engine/InternalEngine.java @@ -940,6 +940,11 @@ public class InternalEngine extends Engine { } } + @Override + public boolean hasUncommittedChanges() { + return indexWriter.hasUncommittedChanges(); + } + @Override protected SearcherManager getSearcherManager() { return searcherManager; diff --git a/src/main/java/org/elasticsearch/index/engine/ShadowEngine.java b/src/main/java/org/elasticsearch/index/engine/ShadowEngine.java index ba23903ee4b..c49758398bc 100644 --- a/src/main/java/org/elasticsearch/index/engine/ShadowEngine.java +++ b/src/main/java/org/elasticsearch/index/engine/ShadowEngine.java @@ -216,4 +216,9 @@ public class ShadowEngine extends Engine { } } } + + @Override + public boolean hasUncommittedChanges() { + return false; + } } diff --git a/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java b/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java index e5811c9e5b3..916779f9a59 100644 --- a/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java +++ b/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java @@ -158,7 +158,6 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio } void unloadIndex(String indexName) throws Exception { - client().admin().indices().prepareFlush(indexName).setWaitIfOngoing(true).setForce(true).get(); // temporary for debugging ElasticsearchAssertions.assertAcked(client().admin().indices().prepareDelete(indexName).get()); ElasticsearchAssertions.assertAllFilesClosed(); } @@ -201,20 +200,6 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio Collections.shuffle(indexes, getRandom()); for (String index : indexes) { - if (index.equals("index-0.90.13.zip") == false) { - long startTime = System.currentTimeMillis(); - logger.info("--> Testing old index " + index); - assertOldIndexWorks(index); - logger.info("--> Done testing " + index + ", took " + ((System.currentTimeMillis() - startTime) / 1000.0) + " seconds"); - } - } - } - - @TestLogging("test.engine:TRACE,index.engine:TRACE,test.engine.lucene:TRACE,index.engine.lucene:TRACE") - public void testShitSlowIndex() throws Exception { - setupCluster(); - for (int i = 0; i < 5; i++) { - String index = "index-0.90.13.zip"; long startTime = System.currentTimeMillis(); logger.info("--> Testing old index " + index); assertOldIndexWorks(index); diff --git a/src/test/java/org/elasticsearch/test/store/MockFSDirectoryService.java b/src/test/java/org/elasticsearch/test/store/MockFSDirectoryService.java index 89393fa24ac..718342f4c59 100644 --- a/src/test/java/org/elasticsearch/test/store/MockFSDirectoryService.java +++ b/src/test/java/org/elasticsearch/test/store/MockFSDirectoryService.java @@ -26,17 +26,13 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.LockFactory; import org.apache.lucene.store.StoreRateLimiting; import org.apache.lucene.util.AbstractRandomizedTest; -import org.apache.lucene.util.IOUtils; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.engine.Engine; -import org.elasticsearch.index.engine.InternalEngine; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.IndexShardException; import org.elasticsearch.index.shard.IndexShardState; @@ -91,8 +87,10 @@ public class MockFSDirectoryService extends FsDirectoryService { // When the the internal engine closes we do a rollback, which removes uncommitted segments // By doing a commit flush we perform a Lucene commit, but don't clear the translog, // so that even in tests where don't flush we can check the integrity of the Lucene index - logger.info("{} flushing in order to run checkindex", indexShard.shardId()); - Releasables.close(indexShard.engine().snapshotIndex()); // Keep translog for tests that rely on replaying it + if (indexShard.engine().hasUncommittedChanges()) { // only if we have any changes + logger.info("{} flushing in order to run checkindex", indexShard.shardId()); + Releasables.close(indexShard.engine().snapshotIndex()); // Keep translog for tests that rely on replaying it + } logger.info("{} flush finished in beforeIndexShardClosed", indexShard.shardId()); canRun = true; } From 2f5cbf5f9dfdc16ccba1c4f50ee53b9856080c88 Mon Sep 17 00:00:00 2001 From: Michael McCandless Date: Thu, 9 Apr 2015 13:11:49 -0400 Subject: [PATCH 04/22] Core: log path.data filesystem details to INFO level This change logs total space, free space, usable free space, an estimate of whether the IO system spins (e.g., SSD or not), the mount point and filesystem type, on node startup. It produces log output like this: [2015-04-09 12:09:30,244][INFO ][env ] [node_t0] node data locations details: -> /l/es.logspins/target/J0/data/TEST-haswell-CHILD_VM=[0]-CLUSTER_SEED=[2926863498862121027]-HASH=[AFC194B1B384B]/nodes/0, free_space [260.6gb], usable_space [256.3gb], total_space [465gb], spins? [no], mount [/ (/dev/mapper/haswell--vg-root)], type [btrfs] Closes #10502 --- .../elasticsearch/env/NodeEnvironment.java | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/elasticsearch/env/NodeEnvironment.java b/src/main/java/org/elasticsearch/env/NodeEnvironment.java index 6b770b859e5..f4151c89ecf 100644 --- a/src/main/java/org/elasticsearch/env/NodeEnvironment.java +++ b/src/main/java/org/elasticsearch/env/NodeEnvironment.java @@ -147,18 +147,35 @@ public class NodeEnvironment extends AbstractComponent implements Closeable{ if (logger.isDebugEnabled()) { logger.debug("using node location [{}], local_node_id [{}]", nodePaths, localNodeId); } - if (logger.isTraceEnabled()) { + + // We do some I/O in here, so skip it if INFO is not enabled: + if (logger.isInfoEnabled()) { StringBuilder sb = new StringBuilder("node data locations details:\n"); for (Path file : nodePaths) { - sb.append(" -> ") - .append(file.toAbsolutePath()) - .append(", free_space [") - .append(new ByteSizeValue(Files.getFileStore(file).getUnallocatedSpace())) - .append("], usable_space [") - .append(new ByteSizeValue(Files.getFileStore(file).getUsableSpace())) - .append("]\n"); + // NOTE: FSDirectory.open creates the directory up above so it will exist here: + sb.append(" -> ").append(file.toAbsolutePath()); + try { + FileStore fileStore = getFileStore(file); + boolean spins = IOUtils.spins(file); + sb.append(", free_space [") + .append(new ByteSizeValue(fileStore.getUnallocatedSpace())) + .append("], usable_space [") + .append(new ByteSizeValue(fileStore.getUsableSpace())) + .append("], total_space [") + .append(new ByteSizeValue(fileStore.getTotalSpace())) + .append("], spins? [") + .append(spins ? "possibly" : "no") + .append("], mount [") + .append(fileStore) + .append("], type [") + .append(fileStore.type()) + .append(']'); + } catch (Exception e) { + sb.append(", ignoring exception gathering filesystem details: " + e); + } + sb.append('\n'); } - logger.trace(sb.toString()); + logger.info(sb.toString()); } this.nodeIndicesPaths = new Path[nodePaths.length]; @@ -167,7 +184,32 @@ public class NodeEnvironment extends AbstractComponent implements Closeable{ } } + // NOTE: poached from Lucene's IOUtils: + // Files.getFileStore(Path) useless here! + // don't complain, just try it yourself + static FileStore getFileStore(Path path) throws IOException { + FileStore store = Files.getFileStore(path); + String mount = getMountPoint(store); + + // find the "matching" FileStore from system list, it's the one we want. + for (FileStore fs : path.getFileSystem().getFileStores()) { + if (mount.equals(getMountPoint(fs))) { + return fs; + } + } + + // fall back to crappy one we got from Files.getFileStore + return store; + } + + // NOTE: poached from Lucene's IOUtils: + + // these are hacks that are not guaranteed + static String getMountPoint(FileStore store) { + String desc = store.toString(); + return desc.substring(0, desc.lastIndexOf('(') - 1); + } /** * Deletes a shard data directory iff the shards locks were successfully acquired. From 54b702db07fcf69d5fda7984833d94643d661835 Mon Sep 17 00:00:00 2001 From: Michael McCandless Date: Thu, 9 Apr 2015 14:31:42 -0400 Subject: [PATCH 05/22] Tests: don't fsync so often Most tests don't "really" need to fsync, and this is costly (makes tests slower, wears out our SSDs). This change makes it uncommon to actually fsync when Lucene asks for it. It's just a workaround (in MockDirectoryHelper) until we can cutover Elasticseach to use MockFileSystem like Lucene. Closes #10516 --- .../test/store/MockDirectoryHelper.java | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/elasticsearch/test/store/MockDirectoryHelper.java b/src/test/java/org/elasticsearch/test/store/MockDirectoryHelper.java index a2b32322507..9b4f5efe7da 100644 --- a/src/test/java/org/elasticsearch/test/store/MockDirectoryHelper.java +++ b/src/test/java/org/elasticsearch/test/store/MockDirectoryHelper.java @@ -19,12 +19,14 @@ package org.elasticsearch.test.store; -import com.carrotsearch.randomizedtesting.SeedUtils; import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FilterDirectory; import org.apache.lucene.store.MMapDirectory; -import org.apache.lucene.store.MockDirectoryWrapper; import org.apache.lucene.store.MockDirectoryWrapper.Throttling; +import org.apache.lucene.store.MockDirectoryWrapper; +import org.apache.lucene.store.NRTCachingDirectory; import org.apache.lucene.util.Constants; +import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; @@ -32,8 +34,11 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.store.DirectoryService; import org.elasticsearch.index.store.IndexStore; import org.elasticsearch.index.store.fs.*; +import com.carrotsearch.randomizedtesting.SeedUtils; import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Collection; import java.util.Random; import java.util.Set; @@ -122,11 +127,26 @@ public class MockDirectoryHelper { private final boolean crash; private volatile RuntimeException closeException; private final Object lock = new Object(); + private final Set superUnSyncedFiles; + private final Random superRandomState; public ElasticsearchMockDirectoryWrapper(Random random, Directory delegate, ESLogger logger, boolean crash) { super(random, delegate); this.crash = crash; this.logger = logger; + + // TODO: remove all this and cutover to MockFS (DisableFsyncFS) instead + try { + Field field = MockDirectoryWrapper.class.getDeclaredField("unSyncedFiles"); + field.setAccessible(true); + superUnSyncedFiles = (Set) field.get(this); + + field = MockDirectoryWrapper.class.getDeclaredField("randomState"); + field.setAccessible(true); + superRandomState = (Random) field.get(this); + } catch (ReflectiveOperationException roe) { + throw new RuntimeException(roe); + } } @Override @@ -144,7 +164,32 @@ public class MockDirectoryHelper { } } + /** + * Returns true if {@link #in} must sync its files. + * Currently, only {@link NRTCachingDirectory} requires sync'ing its files + * because otherwise they are cached in an internal {@link RAMDirectory}. If + * other directories require that too, they should be added to this method. + */ + private boolean mustSync() { + Directory delegate = in; + while (delegate instanceof FilterDirectory) { + if (delegate instanceof NRTCachingDirectory) { + return true; + } + delegate = ((FilterDirectory) delegate).getDelegate(); + } + return delegate instanceof NRTCachingDirectory; + } + @Override + public synchronized void sync(Collection names) throws IOException { + // don't wear out our hardware so much in tests. + if (LuceneTestCase.rarely(superRandomState) || mustSync()) { + super.sync(names); + } else { + superUnSyncedFiles.removeAll(names); + } + } public void awaitClosed(long timeout) throws InterruptedException { synchronized (lock) { From 78612cf0a8ec805d6b876e3d51a3ed31b743e378 Mon Sep 17 00:00:00 2001 From: Michael McCandless Date: Thu, 9 Apr 2015 14:47:48 -0400 Subject: [PATCH 06/22] fix indent --- .../elasticsearch/env/NodeEnvironment.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/elasticsearch/env/NodeEnvironment.java b/src/main/java/org/elasticsearch/env/NodeEnvironment.java index f4151c89ecf..8bff8057715 100644 --- a/src/main/java/org/elasticsearch/env/NodeEnvironment.java +++ b/src/main/java/org/elasticsearch/env/NodeEnvironment.java @@ -157,19 +157,19 @@ public class NodeEnvironment extends AbstractComponent implements Closeable{ try { FileStore fileStore = getFileStore(file); boolean spins = IOUtils.spins(file); - sb.append(", free_space [") - .append(new ByteSizeValue(fileStore.getUnallocatedSpace())) - .append("], usable_space [") - .append(new ByteSizeValue(fileStore.getUsableSpace())) - .append("], total_space [") - .append(new ByteSizeValue(fileStore.getTotalSpace())) - .append("], spins? [") - .append(spins ? "possibly" : "no") - .append("], mount [") - .append(fileStore) - .append("], type [") - .append(fileStore.type()) - .append(']'); + sb.append(", free_space [") + .append(new ByteSizeValue(fileStore.getUnallocatedSpace())) + .append("], usable_space [") + .append(new ByteSizeValue(fileStore.getUsableSpace())) + .append("], total_space [") + .append(new ByteSizeValue(fileStore.getTotalSpace())) + .append("], spins? [") + .append(spins ? "possibly" : "no") + .append("], mount [") + .append(fileStore) + .append("], type [") + .append(fileStore.type()) + .append(']'); } catch (Exception e) { sb.append(", ignoring exception gathering filesystem details: " + e); } From 9d0764e5df67f570a6696d10d227a9db44bc5baf Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Thu, 9 Apr 2015 13:31:08 -0700 Subject: [PATCH 07/22] Tests: increasing timeout for replicas in static bwc tests, to account for reallySlowJenkinsWhenIndexHasLotsOfSegments --- .../bwcompat/OldIndexBackwardsCompatibilityTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java b/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java index 916779f9a59..36e95c8ffe9 100644 --- a/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java +++ b/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java @@ -305,7 +305,7 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio assertAcked(client().admin().indices().prepareUpdateSettings(indexName).setSettings(ImmutableSettings.builder() .put("number_of_replicas", numReplicas) ).execute().actionGet()); - ensureGreen(TimeValue.timeValueMinutes(1), indexName); + ensureGreen(TimeValue.timeValueMinutes(2), indexName); logger.debug("--> index [{}] is green, took [{}]", indexName, TimeValue.timeValueMillis(System.currentTimeMillis() - startTime)); logger.debug("--> recovery status:\n{}", XContentHelper.toString(client().admin().indices().prepareRecoveries(indexName).get())); From 224c43564b3c4d94398909927732551192a25fd8 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Thu, 9 Apr 2015 12:06:28 -0700 Subject: [PATCH 08/22] Tests: Add multi data path testing to static bwc tests This randomly chooses to run bwc indexes on single or multi data paths. closes #10519 --- .../OldIndexBackwardsCompatibilityTests.java | 96 ++++++++++++++++--- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java b/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java index 36e95c8ffe9..497d497847d 100644 --- a/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java +++ b/src/test/java/org/elasticsearch/bwcompat/OldIndexBackwardsCompatibilityTests.java @@ -21,6 +21,7 @@ package org.elasticsearch.bwcompat; import com.carrotsearch.randomizedtesting.LifecycleScope; import com.google.common.util.concurrent.ListenableFuture; +import org.apache.lucene.index.IndexWriter; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.TestUtil; import org.elasticsearch.Version; @@ -55,13 +56,18 @@ import org.hamcrest.Matchers; import org.junit.AfterClass; import org.junit.BeforeClass; +import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Modifier; import java.net.URL; import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; @@ -75,7 +81,8 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio // We have a 0.20.6.zip etc for this. static List indexes; - static Path indicesDir; + static Path singleDataPath; + static Path[] multiDataPath; @BeforeClass public static void initIndexesList() throws Exception { @@ -93,7 +100,8 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio @AfterClass public static void tearDownStatics() { indexes = null; - indicesDir = null; + singleDataPath = null; + multiDataPath = null; } @Override @@ -108,17 +116,37 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio void setupCluster() throws Exception { ListenableFuture> replicas = internalCluster().startNodesAsync(1); // for replicas - Path dataDir = newTempDirPath(LifecycleScope.SUITE); + Path baseTempDir = newTempDirPath(LifecycleScope.SUITE); + // start single data path node ImmutableSettings.Builder nodeSettings = ImmutableSettings.builder() - .put("path.data", dataDir.toAbsolutePath()) - .put("node.master", false); // workaround for dangling index loading issue when node is master - String loadingNode = internalCluster().startNode(nodeSettings.build()); + .put("path.data", baseTempDir.resolve("single-path").toAbsolutePath()) + .put("node.master", false); // workaround for dangling index loading issue when node is master + ListenableFuture singleDataPathNode = internalCluster().startNodeAsync(nodeSettings.build()); - Path[] nodePaths = internalCluster().getInstance(NodeEnvironment.class, loadingNode).nodeDataPaths(); + // start multi data path node + nodeSettings = ImmutableSettings.builder() + .put("path.data", baseTempDir.resolve("multi-path1").toAbsolutePath() + "," + baseTempDir.resolve("multi-path2").toAbsolutePath()) + .put("node.master", false); // workaround for dangling index loading issue when node is master + ListenableFuture multiDataPathNode = internalCluster().startNodeAsync(nodeSettings.build()); + + // find single data path dir + Path[] nodePaths = internalCluster().getInstance(NodeEnvironment.class, singleDataPathNode.get()).nodeDataPaths(); assertEquals(1, nodePaths.length); - indicesDir = nodePaths[0].resolve(NodeEnvironment.INDICES_FOLDER); - assertFalse(Files.exists(indicesDir)); - Files.createDirectories(indicesDir); + singleDataPath = nodePaths[0].resolve(NodeEnvironment.INDICES_FOLDER); + assertFalse(Files.exists(singleDataPath)); + Files.createDirectories(singleDataPath); + logger.info("--> Single data path: " + singleDataPath.toString()); + + // find multi data path dirs + nodePaths = internalCluster().getInstance(NodeEnvironment.class, multiDataPathNode.get()).nodeDataPaths(); + assertEquals(2, nodePaths.length); + multiDataPath = new Path[] {nodePaths[0].resolve(NodeEnvironment.INDICES_FOLDER), + nodePaths[1].resolve(NodeEnvironment.INDICES_FOLDER)}; + assertFalse(Files.exists(multiDataPath[0])); + assertFalse(Files.exists(multiDataPath[1])); + Files.createDirectories(multiDataPath[0]); + Files.createDirectories(multiDataPath[1]); + logger.info("--> Multi data paths: " + multiDataPath[0].toString() + ", " + multiDataPath[1].toString()); replicas.get(); // wait for replicas } @@ -143,13 +171,15 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio // the bwc scripts packs the indices under this path Path src = list[0].resolve("nodes/0/indices/" + indexName); - Path dest = indicesDir.resolve(indexName); assertTrue("[" + indexFile + "] missing index dir: " + src.toString(), Files.exists(src)); - logger.info("--> injecting index [{}] into path [{}]", indexName, dest); - Files.move(src, dest); - assertFalse(Files.exists(src)); - assertTrue(Files.exists(dest)); + if (randomBoolean()) { + logger.info("--> injecting index [{}] into single data path", indexName); + copyIndex(src, indexName, singleDataPath); + } else { + logger.info("--> injecting index [{}] into multi data path", indexName); + copyIndex(src, indexName, multiDataPath); + } // force reloading dangling indices with a cluster state republish client().admin().cluster().prepareReroute().get(); @@ -157,6 +187,42 @@ public class OldIndexBackwardsCompatibilityTests extends ElasticsearchIntegratio return indexName; } + // randomly distribute the files from src over dests paths + void copyIndex(final Path src, final String indexName, final Path... dests) throws IOException { + for (Path dest : dests) { + Path indexDir = dest.resolve(indexName); + assertFalse(Files.exists(indexDir)); + Files.createDirectories(indexDir); + } + Files.walkFileTree(src, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path relativeDir = src.relativize(dir); + for (Path dest : dests) { + Path destDir = dest.resolve(indexName).resolve(relativeDir); + Files.createDirectories(destDir); + } + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.getFileName().toString().equals(IndexWriter.WRITE_LOCK_NAME)) { + // skip lock file, we don't need it + logger.trace("Skipping lock file: " + file.toString()); + return FileVisitResult.CONTINUE; + } + + Path relativeFile = src.relativize(file); + Path destFile = dests[randomInt(dests.length - 1)].resolve(indexName).resolve(relativeFile); + logger.trace("--> Moving " + relativeFile.toString() + " to " + destFile.toString()); + Files.move(file, destFile); + assertFalse(Files.exists(file)); + assertTrue(Files.exists(destFile)); + return FileVisitResult.CONTINUE; + } + }); + } + void unloadIndex(String indexName) throws Exception { ElasticsearchAssertions.assertAcked(client().admin().indices().prepareDelete(indexName).get()); ElasticsearchAssertions.assertAllFilesClosed(); From 4a09a98561cb6a1eb7af7b5cd436f6bb6965c71e Mon Sep 17 00:00:00 2001 From: Colin Goodheart-Smithe Date: Thu, 9 Apr 2015 11:43:45 +0100 Subject: [PATCH 09/22] Fix to pom.xml to allow eclipse maven integration using m2e --- pom.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pom.xml b/pom.xml index 7ca27190064..68ca15306f2 100644 --- a/pom.xml +++ b/pom.xml @@ -1599,6 +1599,32 @@ + + + org.apache.maven.plugins + maven-antrun-plugin + [1.0.0,) + + run + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + [1.0.0,) + + copy-resources + + + + + + From 7e353e0768b7ddfc8f204a00f9110906009b2801 Mon Sep 17 00:00:00 2001 From: Colin Goodheart-Smithe Date: Fri, 10 Apr 2015 10:37:26 +0100 Subject: [PATCH 10/22] removed erroneous tab character --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 68ca15306f2..e835d2fe2a6 100644 --- a/pom.xml +++ b/pom.xml @@ -1622,7 +1622,7 @@ - + From bdc4c7f84ca7bbb9931b54331ae601b1cebbb733 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Fri, 10 Apr 2015 14:37:18 +0200 Subject: [PATCH 11/22] [BUILD] Add sigar binaries when running unittests the sigar binaries are not available when running tests today. This commit adds the path to the test run. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index e835d2fe2a6..5d8a2722c73 100644 --- a/pom.xml +++ b/pom.xml @@ -551,6 +551,7 @@ -Xmx${tests.heap.size} -Xms${tests.heap.size} ${java.permGenSpace} + -Djava.library.path=${project.basedir}/lib/sigar -XX:MaxDirectMemorySize=512m -Des.logger.prefix= -XX:+HeapDumpOnOutOfMemoryError From 919589b908983b8bea953ee6143f73aa81ef6a72 Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 2 Apr 2015 11:42:36 +0200 Subject: [PATCH 12/22] Queries: Remove fuzzy-like-this support. The fuzzy-like-this query builds very expensive queries and only serves esoteric use-cases. --- docs/reference/migration/migrate_2_0.asciidoc | 3 + docs/reference/query-dsl/queries.asciidoc | 4 - .../queries/flt-field-query.asciidoc | 47 ----- .../query-dsl/queries/flt-query.asciidoc | 65 ------- .../query/FuzzyLikeThisFieldQueryBuilder.java | 148 ---------------- .../query/FuzzyLikeThisFieldQueryParser.java | 160 ----------------- .../query/FuzzyLikeThisQueryBuilder.java | 160 ----------------- .../index/query/FuzzyLikeThisQueryParser.java | 162 ------------------ .../index/query/QueryBuilders.java | 25 --- .../indices/query/IndicesQueriesModule.java | 2 - .../flt/FuzzyLikeThisActionTests.java | 88 ---------- .../query/SimpleIndexQueryParserTests.java | 69 -------- .../index/query/fuzzyLikeThis.json | 7 - .../index/query/fuzzyLikeThisField.json | 8 - 14 files changed, 3 insertions(+), 945 deletions(-) delete mode 100644 docs/reference/query-dsl/queries/flt-field-query.asciidoc delete mode 100644 docs/reference/query-dsl/queries/flt-query.asciidoc delete mode 100644 src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryBuilder.java delete mode 100644 src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryParser.java delete mode 100644 src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryBuilder.java delete mode 100644 src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryParser.java delete mode 100644 src/test/java/org/elasticsearch/flt/FuzzyLikeThisActionTests.java delete mode 100644 src/test/java/org/elasticsearch/index/query/fuzzyLikeThis.json delete mode 100644 src/test/java/org/elasticsearch/index/query/fuzzyLikeThisField.json diff --git a/docs/reference/migration/migrate_2_0.asciidoc b/docs/reference/migration/migrate_2_0.asciidoc index f875363bb8b..5809a96616f 100644 --- a/docs/reference/migration/migrate_2_0.asciidoc +++ b/docs/reference/migration/migrate_2_0.asciidoc @@ -380,3 +380,6 @@ http.cors.allow-origin: /https?:\/\/localhost(:[0-9]+)?/ The cluster state api doesn't return the `routing_nodes` section anymore when `routing_table` is requested. The newly introduced `routing_nodes` flag can be used separately to control whether `routing_nodes` should be returned. +=== Query DSL + +The `fuzzy_like_this` and `fuzzy_like_this_field` queries have been removed. diff --git a/docs/reference/query-dsl/queries.asciidoc b/docs/reference/query-dsl/queries.asciidoc index 2fec7e3527b..d56d2c719f1 100644 --- a/docs/reference/query-dsl/queries.asciidoc +++ b/docs/reference/query-dsl/queries.asciidoc @@ -22,10 +22,6 @@ include::queries/dis-max-query.asciidoc[] include::queries/filtered-query.asciidoc[] -include::queries/flt-query.asciidoc[] - -include::queries/flt-field-query.asciidoc[] - include::queries/function-score-query.asciidoc[] include::queries/fuzzy-query.asciidoc[] diff --git a/docs/reference/query-dsl/queries/flt-field-query.asciidoc b/docs/reference/query-dsl/queries/flt-field-query.asciidoc deleted file mode 100644 index 205dc61307d..00000000000 --- a/docs/reference/query-dsl/queries/flt-field-query.asciidoc +++ /dev/null @@ -1,47 +0,0 @@ -[[query-dsl-flt-field-query]] -=== Fuzzy Like This Field Query - -The `fuzzy_like_this_field` query is the same as the `fuzzy_like_this` -query, except that it runs against a single field. It provides nicer -query DSL over the generic `fuzzy_like_this` query, and support typed -fields query (automatically wraps typed fields with type filter to match -only on the specific type). - -[source,js] --------------------------------------------------- -{ - "fuzzy_like_this_field" : { - "name.first" : { - "like_text" : "text like this one", - "max_query_terms" : 12 - } - } -} --------------------------------------------------- - -`fuzzy_like_this_field` can be shortened to `flt_field`. - -The `fuzzy_like_this_field` top level parameters include: - -[cols="<,<",options="header",] -|======================================================================= -|Parameter |Description -|`like_text` |The text to find documents like it, *required*. - -|`ignore_tf` |Should term frequency be ignored. Defaults to `false`. - -|`max_query_terms` |The maximum number of query terms that will be -included in any generated query. Defaults to `25`. - -|`fuzziness` |The fuzziness of the term variants. Defaults -to `0.5`. See <>. - -|`prefix_length` |Length of required common prefix on variant terms. -Defaults to `0`. - -|`boost` |Sets the boost value of the query. Defaults to `1.0`. - -|`analyzer` |The analyzer that will be used to analyze the text. -Defaults to the analyzer associated with the field. -|======================================================================= - diff --git a/docs/reference/query-dsl/queries/flt-query.asciidoc b/docs/reference/query-dsl/queries/flt-query.asciidoc deleted file mode 100644 index 231de6b6c04..00000000000 --- a/docs/reference/query-dsl/queries/flt-query.asciidoc +++ /dev/null @@ -1,65 +0,0 @@ -[[query-dsl-flt-query]] -=== Fuzzy Like This Query - -Fuzzy like this query find documents that are "like" provided text by -running it against one or more fields. - -[source,js] --------------------------------------------------- -{ - "fuzzy_like_this" : { - "fields" : ["name.first", "name.last"], - "like_text" : "text like this one", - "max_query_terms" : 12 - } -} --------------------------------------------------- - -`fuzzy_like_this` can be shortened to `flt`. - -The `fuzzy_like_this` top level parameters include: - -[cols="<,<",options="header",] -|======================================================================= -|Parameter |Description -|`fields` |A list of the fields to run the more like this query against. -Defaults to the `_all` field. - -|`like_text` |The text to find documents like it, *required*. - -|`ignore_tf` |Should term frequency be ignored. Defaults to `false`. - -|`max_query_terms` |The maximum number of query terms that will be -included in any generated query. Defaults to `25`. - -|`fuzziness` |The minimum similarity of the term variants. Defaults -to `0.5`. See <>. - -|`prefix_length` |Length of required common prefix on variant terms. -Defaults to `0`. - -|`boost` |Sets the boost value of the query. Defaults to `1.0`. - -|`analyzer` |The analyzer that will be used to analyze the text. -Defaults to the analyzer associated with the field. -|======================================================================= - -[float] -==== How it Works - -Fuzzifies ALL terms provided as strings and then picks the best n -differentiating terms. In effect this mixes the behaviour of FuzzyQuery -and MoreLikeThis but with special consideration of fuzzy scoring -factors. This generally produces good results for queries where users -may provide details in a number of fields and have no knowledge of -boolean query syntax and also want a degree of fuzzy matching and a fast -query. - -For each source term the fuzzy variants are held in a BooleanQuery with -no coord factor (because we are not looking for matches on multiple -variants in any one doc). Additionally, a specialized TermQuery is used -for variants and does not use that variant term's IDF because this would -favor rarer terms, such as misspellings. Instead, all variants use the -same IDF ranking (the one for the source query term) and this is -factored into the variant's boost. If the source query term does not -exist in the index the average IDF of the variants is used. diff --git a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryBuilder.java deleted file mode 100644 index 21f481c9bf9..00000000000 --- a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryBuilder.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import org.elasticsearch.ElasticsearchIllegalArgumentException; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.common.xcontent.XContentBuilder; - -import java.io.IOException; - -/** - * - */ -public class FuzzyLikeThisFieldQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder { - - private final String name; - - private Float boost; - - private String likeText = null; - private Fuzziness fuzziness; - private Integer prefixLength; - private Integer maxQueryTerms; - private Boolean ignoreTF; - private String analyzer; - private Boolean failOnUnsupportedField; - private String queryName; - - /** - * A fuzzy more like this query on the provided field. - * - * @param name the name of the field - */ - public FuzzyLikeThisFieldQueryBuilder(String name) { - this.name = name; - } - - /** - * The text to use in order to find documents that are "like" this. - */ - public FuzzyLikeThisFieldQueryBuilder likeText(String likeText) { - this.likeText = likeText; - return this; - } - - public FuzzyLikeThisFieldQueryBuilder fuzziness(Fuzziness fuzziness) { - this.fuzziness = fuzziness; - return this; - } - - public FuzzyLikeThisFieldQueryBuilder prefixLength(int prefixLength) { - this.prefixLength = prefixLength; - return this; - } - - public FuzzyLikeThisFieldQueryBuilder maxQueryTerms(int maxQueryTerms) { - this.maxQueryTerms = maxQueryTerms; - return this; - } - - public FuzzyLikeThisFieldQueryBuilder ignoreTF(boolean ignoreTF) { - this.ignoreTF = ignoreTF; - return this; - } - - /** - * The analyzer that will be used to analyze the text. Defaults to the analyzer associated with the field. - */ - public FuzzyLikeThisFieldQueryBuilder analyzer(String analyzer) { - this.analyzer = analyzer; - return this; - } - - @Override - public FuzzyLikeThisFieldQueryBuilder boost(float boost) { - this.boost = boost; - return this; - } - - /** - * Whether to fail or return no result when this query is run against a field which is not supported such as binary/numeric fields. - */ - public FuzzyLikeThisFieldQueryBuilder failOnUnsupportedField(boolean fail) { - failOnUnsupportedField = fail; - return this; - } - - /** - * Sets the query name for the filter that can be used when searching for matched_filters per hit. - */ - public FuzzyLikeThisFieldQueryBuilder queryName(String queryName) { - this.queryName = queryName; - return this; - } - - @Override - protected void doXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(FuzzyLikeThisFieldQueryParser.NAME); - builder.startObject(name); - if (likeText == null) { - throw new ElasticsearchIllegalArgumentException("fuzzyLikeThis requires 'likeText' to be provided"); - } - builder.field("like_text", likeText); - if (maxQueryTerms != null) { - builder.field("max_query_terms", maxQueryTerms); - } - if (fuzziness != null) { - fuzziness.toXContent(builder, params); - } - if (prefixLength != null) { - builder.field("prefix_length", prefixLength); - } - if (ignoreTF != null) { - builder.field("ignore_tf", ignoreTF); - } - if (boost != null) { - builder.field("boost", boost); - } - if (analyzer != null) { - builder.field("analyzer", analyzer); - } - if (failOnUnsupportedField != null) { - builder.field("fail_on_unsupported_field", failOnUnsupportedField); - } - if (queryName != null) { - builder.field("_name", queryName); - } - builder.endObject(); - builder.endObject(); - } -} diff --git a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryParser.java b/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryParser.java deleted file mode 100644 index 5f531760e74..00000000000 --- a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisFieldQueryParser.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.sandbox.queries.FuzzyLikeThisQuery; -import org.apache.lucene.search.Query; -import org.elasticsearch.ElasticsearchIllegalArgumentException; -import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.analysis.Analysis; -import org.elasticsearch.index.mapper.MapperService; - -import java.io.IOException; - -/** - *

- * {
- *  fuzzy_like_this_field : {
- *      field1 : {
- *          maxNumTerms : 12,
- *          boost : 1.1,
- *          likeText : "..."
- *      }
- * }
- * 
- */ -public class FuzzyLikeThisFieldQueryParser implements QueryParser { - - public static final String NAME = "flt_field"; - private static final Fuzziness DEFAULT_FUZZINESS = Fuzziness.fromSimilarity(0.5f); - private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("min_similarity"); - - @Inject - public FuzzyLikeThisFieldQueryParser() { - } - - @Override - public String[] names() { - return new String[]{NAME, "fuzzy_like_this_field", Strings.toCamelCase(NAME), "fuzzyLikeThisField"}; - } - - @Override - public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException { - XContentParser parser = parseContext.parser(); - - int maxNumTerms = 25; - float boost = 1.0f; - String likeText = null; - Fuzziness fuzziness = DEFAULT_FUZZINESS; - int prefixLength = 0; - boolean ignoreTF = false; - Analyzer analyzer = null; - boolean failOnUnsupportedField = true; - String queryName = null; - - XContentParser.Token token = parser.nextToken(); - if (token != XContentParser.Token.FIELD_NAME) { - throw new QueryParsingException(parseContext.index(), "[flt_field] query malformed, no field"); - } - String fieldName = parser.currentName(); - - // now, we move after the field name, which starts the object - token = parser.nextToken(); - if (token != XContentParser.Token.START_OBJECT) { - throw new QueryParsingException(parseContext.index(), "[flt_field] query malformed, no start_object"); - } - - - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token.isValue()) { - if ("like_text".equals(currentFieldName) || "likeText".equals(currentFieldName)) { - likeText = parser.text(); - } else if ("max_query_terms".equals(currentFieldName) || "maxQueryTerms".equals(currentFieldName)) { - maxNumTerms = parser.intValue(); - } else if ("boost".equals(currentFieldName)) { - boost = parser.floatValue(); - } else if ("ignore_tf".equals(currentFieldName) || "ignoreTF".equals(currentFieldName)) { - ignoreTF = parser.booleanValue(); - } else if (FUZZINESS.match(currentFieldName, parseContext.parseFlags())) { - fuzziness = Fuzziness.parse(parser); - } else if ("prefix_length".equals(currentFieldName) || "prefixLength".equals(currentFieldName)) { - prefixLength = parser.intValue(); - } else if ("analyzer".equals(currentFieldName)) { - analyzer = parseContext.analysisService().analyzer(parser.text()); - } else if ("fail_on_unsupported_field".equals(currentFieldName) || "failOnUnsupportedField".equals(currentFieldName)) { - failOnUnsupportedField = parser.booleanValue(); - } else if ("_name".equals(currentFieldName)) { - queryName = parser.text(); - } else { - throw new QueryParsingException(parseContext.index(), "[flt_field] query does not support [" + currentFieldName + "]"); - } - } - } - - if (likeText == null) { - throw new QueryParsingException(parseContext.index(), "fuzzy_like_This_field requires 'like_text' to be specified"); - } - - MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - fieldName = smartNameFieldMappers.mapper().names().indexName(); - if (analyzer == null) { - analyzer = smartNameFieldMappers.mapper().searchAnalyzer(); - } - } - } - if (analyzer == null) { - analyzer = parseContext.mapperService().searchAnalyzer(); - } - if (!Analysis.generatesCharacterTokenStream(analyzer, fieldName)) { - if (failOnUnsupportedField) { - throw new ElasticsearchIllegalArgumentException("fuzzy_like_this_field doesn't support binary/numeric fields: [" + fieldName + "]"); - } else { - return null; - } - } - - FuzzyLikeThisQuery fuzzyLikeThisQuery = new FuzzyLikeThisQuery(maxNumTerms, analyzer); - fuzzyLikeThisQuery.addTerms(likeText, fieldName, fuzziness.asSimilarity(), prefixLength); - fuzzyLikeThisQuery.setBoost(boost); - fuzzyLikeThisQuery.setIgnoreTF(ignoreTF); - - // move to the next end object, to close the field name - token = parser.nextToken(); - if (token != XContentParser.Token.END_OBJECT) { - throw new QueryParsingException(parseContext.index(), "[flt_field] query malformed, no end_object"); - } - assert token == XContentParser.Token.END_OBJECT; - - if (queryName != null) { - parseContext.addNamedQuery(queryName, fuzzyLikeThisQuery); - } - return fuzzyLikeThisQuery; - } -} diff --git a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryBuilder.java deleted file mode 100644 index 08f2359c4f2..00000000000 --- a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryBuilder.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import org.elasticsearch.ElasticsearchIllegalArgumentException; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.common.xcontent.XContentBuilder; - -import java.io.IOException; - -/** - * - */ -public class FuzzyLikeThisQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder { - - private final String[] fields; - - private Float boost; - - private String likeText = null; - private Fuzziness fuzziness; - private Integer prefixLength; - private Integer maxQueryTerms; - private Boolean ignoreTF; - private String analyzer; - private Boolean failOnUnsupportedField; - private String queryName; - - /** - * Constructs a new fuzzy like this query which uses the "_all" field. - */ - public FuzzyLikeThisQueryBuilder() { - this.fields = null; - } - - /** - * Sets the field names that will be used when generating the 'Fuzzy Like This' query. - * - * @param fields the field names that will be used when generating the 'Fuzzy Like This' query. - */ - public FuzzyLikeThisQueryBuilder(String... fields) { - this.fields = fields; - } - - /** - * The text to use in order to find documents that are "like" this. - */ - public FuzzyLikeThisQueryBuilder likeText(String likeText) { - this.likeText = likeText; - return this; - } - - public FuzzyLikeThisQueryBuilder fuzziness(Fuzziness fuzziness) { - this.fuzziness = fuzziness; - return this; - } - - public FuzzyLikeThisQueryBuilder prefixLength(int prefixLength) { - this.prefixLength = prefixLength; - return this; - } - - public FuzzyLikeThisQueryBuilder maxQueryTerms(int maxQueryTerms) { - this.maxQueryTerms = maxQueryTerms; - return this; - } - - public FuzzyLikeThisQueryBuilder ignoreTF(boolean ignoreTF) { - this.ignoreTF = ignoreTF; - return this; - } - - /** - * The analyzer that will be used to analyze the text. Defaults to the analyzer associated with the fied. - */ - public FuzzyLikeThisQueryBuilder analyzer(String analyzer) { - this.analyzer = analyzer; - return this; - } - - @Override - public FuzzyLikeThisQueryBuilder boost(float boost) { - this.boost = boost; - return this; - } - - /** - * Whether to fail or return no result when this query is run against a field which is not supported such as binary/numeric fields. - */ - public FuzzyLikeThisQueryBuilder failOnUnsupportedField(boolean fail) { - failOnUnsupportedField = fail; - return this; - } - - /** - * Sets the query name for the filter that can be used when searching for matched_filters per hit. - */ - public FuzzyLikeThisQueryBuilder queryName(String queryName) { - this.queryName = queryName; - return this; - } - - @Override - protected void doXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(FuzzyLikeThisQueryParser.NAME); - if (fields != null) { - builder.startArray("fields"); - for (String field : fields) { - builder.value(field); - } - builder.endArray(); - } - if (likeText == null) { - throw new ElasticsearchIllegalArgumentException("fuzzyLikeThis requires 'likeText' to be provided"); - } - builder.field("like_text", likeText); - if (maxQueryTerms != null) { - builder.field("max_query_terms", maxQueryTerms); - } - if (fuzziness != null) { - fuzziness.toXContent(builder, params); - } - if (prefixLength != null) { - builder.field("prefix_length", prefixLength); - } - if (ignoreTF != null) { - builder.field("ignore_tf", ignoreTF); - } - if (boost != null) { - builder.field("boost", boost); - } - if (analyzer != null) { - builder.field("analyzer", analyzer); - } - if (failOnUnsupportedField != null) { - builder.field("fail_on_unsupported_field", failOnUnsupportedField); - } - if (queryName != null) { - builder.field("_name", queryName); - } - builder.endObject(); - } -} diff --git a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryParser.java b/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryParser.java deleted file mode 100644 index 68d6045521c..00000000000 --- a/src/main/java/org/elasticsearch/index/query/FuzzyLikeThisQueryParser.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.index.query; - -import com.google.common.collect.Lists; -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.sandbox.queries.FuzzyLikeThisQuery; -import org.apache.lucene.search.Query; -import org.elasticsearch.ElasticsearchIllegalArgumentException; -import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.analysis.Analysis; - -import java.io.IOException; -import java.util.Iterator; -import java.util.List; - -/** - *
- * {
- *  fuzzy_like_this : {
- *      maxNumTerms : 12,
- *      boost : 1.1,
- *      fields : ["field1", "field2"]
- *      likeText : "..."
- *  }
- * }
- * 
- */ -public class FuzzyLikeThisQueryParser implements QueryParser { - - public static final String NAME = "flt"; - private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("min_similarity"); - - @Inject - public FuzzyLikeThisQueryParser() { - } - - @Override - public String[] names() { - return new String[]{NAME, "fuzzy_like_this", "fuzzyLikeThis"}; - } - - @Override - public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException { - XContentParser parser = parseContext.parser(); - - int maxNumTerms = 25; - float boost = 1.0f; - List fields = null; - String likeText = null; - Fuzziness fuzziness = Fuzziness.TWO; - int prefixLength = 0; - boolean ignoreTF = false; - Analyzer analyzer = null; - boolean failOnUnsupportedField = true; - String queryName = null; - - XContentParser.Token token; - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token.isValue()) { - if ("like_text".equals(currentFieldName) || "likeText".equals(currentFieldName)) { - likeText = parser.text(); - } else if ("max_query_terms".equals(currentFieldName) || "maxQueryTerms".equals(currentFieldName)) { - maxNumTerms = parser.intValue(); - } else if ("boost".equals(currentFieldName)) { - boost = parser.floatValue(); - } else if ("ignore_tf".equals(currentFieldName) || "ignoreTF".equals(currentFieldName)) { - ignoreTF = parser.booleanValue(); - } else if (FUZZINESS.match(currentFieldName, parseContext.parseFlags())) { - fuzziness = Fuzziness.parse(parser); - } else if ("prefix_length".equals(currentFieldName) || "prefixLength".equals(currentFieldName)) { - prefixLength = parser.intValue(); - } else if ("analyzer".equals(currentFieldName)) { - analyzer = parseContext.analysisService().analyzer(parser.text()); - } else if ("fail_on_unsupported_field".equals(currentFieldName) || "failOnUnsupportedField".equals(currentFieldName)) { - failOnUnsupportedField = parser.booleanValue(); - } else if ("_name".equals(currentFieldName)) { - queryName = parser.text(); - } else { - throw new QueryParsingException(parseContext.index(), "[flt] query does not support [" + currentFieldName + "]"); - } - } else if (token == XContentParser.Token.START_ARRAY) { - if ("fields".equals(currentFieldName)) { - fields = Lists.newLinkedList(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - fields.add(parseContext.indexName(parser.text())); - } - } else { - throw new QueryParsingException(parseContext.index(), "[flt] query does not support [" + currentFieldName + "]"); - } - } - } - - if (likeText == null) { - throw new QueryParsingException(parseContext.index(), "fuzzy_like_this requires 'like_text' to be specified"); - } - - if (analyzer == null) { - analyzer = parseContext.mapperService().searchAnalyzer(); - } - - FuzzyLikeThisQuery query = new FuzzyLikeThisQuery(maxNumTerms, analyzer); - if (fields == null) { - fields = Lists.newArrayList(parseContext.defaultField()); - } else if (fields.isEmpty()) { - throw new QueryParsingException(parseContext.index(), "fuzzy_like_this requires 'fields' to be non-empty"); - } - for (Iterator it = fields.iterator(); it.hasNext(); ) { - final String fieldName = it.next(); - if (!Analysis.generatesCharacterTokenStream(analyzer, fieldName)) { - if (failOnUnsupportedField) { - throw new ElasticsearchIllegalArgumentException("more_like_this doesn't support binary/numeric fields: [" + fieldName + "]"); - } else { - it.remove(); - } - } - } - if (fields.isEmpty()) { - return null; - } - float minSimilarity = fuzziness.asFloat(); - if (minSimilarity >= 1.0f && minSimilarity != (int)minSimilarity) { - throw new ElasticsearchIllegalArgumentException("fractional edit distances are not allowed"); - } - if (minSimilarity < 0.0f) { - throw new ElasticsearchIllegalArgumentException("minimumSimilarity cannot be less than 0"); - } - for (String field : fields) { - query.addTerms(likeText, field, minSimilarity, prefixLength); - } - query.setBoost(boost); - query.setIgnoreTF(ignoreTF); - - if (queryName != null) { - parseContext.addNamedQuery(queryName, query); - } - return query; - } -} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index 9fec3f4326d..415544f3288 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -458,31 +458,6 @@ public abstract class QueryBuilders { return new MoreLikeThisQueryBuilder(); } - /** - * A fuzzy like this query that finds documents that are "like" the provided {@link FuzzyLikeThisQueryBuilder#likeText(String)} - * which is checked against the fields the query is constructed with. - * - * @param fields The fields to run the query against - */ - public static FuzzyLikeThisQueryBuilder fuzzyLikeThisQuery(String... fields) { - return new FuzzyLikeThisQueryBuilder(fields); - } - - /** - * A fuzzy like this query that finds documents that are "like" the provided {@link FuzzyLikeThisQueryBuilder#likeText(String)} - * which is checked against the "_all" field. - */ - public static FuzzyLikeThisQueryBuilder fuzzyLikeThisQuery() { - return new FuzzyLikeThisQueryBuilder(); - } - - /** - * A fuzzy like this query that finds documents that are "like" the provided {@link FuzzyLikeThisFieldQueryBuilder#likeText(String)}. - */ - public static FuzzyLikeThisFieldQueryBuilder fuzzyLikeThisFieldQuery(String name) { - return new FuzzyLikeThisFieldQueryBuilder(name); - } - /** * Constructs a new scoring child query, with the child type and the query to run on the child documents. The * results of this query are the parent docs that those child docs matched. diff --git a/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java b/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java index 06547913939..14bbbc428f2 100644 --- a/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java +++ b/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java @@ -94,8 +94,6 @@ public class IndicesQueriesModule extends AbstractModule { qpBinders.addBinding().to(SpanNearQueryParser.class).asEagerSingleton(); qpBinders.addBinding().to(SpanOrQueryParser.class).asEagerSingleton(); qpBinders.addBinding().to(MoreLikeThisQueryParser.class).asEagerSingleton(); - qpBinders.addBinding().to(FuzzyLikeThisQueryParser.class).asEagerSingleton(); - qpBinders.addBinding().to(FuzzyLikeThisFieldQueryParser.class).asEagerSingleton(); qpBinders.addBinding().to(WrapperQueryParser.class).asEagerSingleton(); qpBinders.addBinding().to(IndicesQueryParser.class).asEagerSingleton(); qpBinders.addBinding().to(CommonTermsQueryParser.class).asEagerSingleton(); diff --git a/src/test/java/org/elasticsearch/flt/FuzzyLikeThisActionTests.java b/src/test/java/org/elasticsearch/flt/FuzzyLikeThisActionTests.java deleted file mode 100644 index d143545f7ff..00000000000 --- a/src/test/java/org/elasticsearch/flt/FuzzyLikeThisActionTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.flt; - -import org.elasticsearch.action.search.SearchPhaseExecutionException; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.test.ElasticsearchIntegrationTest; -import org.junit.Test; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.query.QueryBuilders.fuzzyLikeThisFieldQuery; -import static org.elasticsearch.index.query.QueryBuilders.fuzzyLikeThisQuery; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThrows; -import static org.hamcrest.Matchers.equalTo; - -/** - * - */ -public class FuzzyLikeThisActionTests extends ElasticsearchIntegrationTest { - - @Test - // See issue https://github.com/elasticsearch/elasticsearch/issues/3252 - public void testNumericField() throws Exception { - assertAcked(prepareCreate("test") - .addMapping("type", "int_value", "type=integer")); - ensureGreen(); - client().prepareIndex("test", "type", "1") - .setSource(jsonBuilder().startObject().field("string_value", "lucene index").field("int_value", 1).endObject()) - .execute().actionGet(); - client().prepareIndex("test", "type", "2") - .setSource(jsonBuilder().startObject().field("string_value", "elasticsearch index").field("int_value", 42).endObject()) - .execute().actionGet(); - - refresh(); - - // flt query with no field -> OK - SearchResponse searchResponse = client().prepareSearch().setQuery(fuzzyLikeThisQuery().likeText("index")).execute().actionGet(); - assertThat(searchResponse.getFailedShards(), equalTo(0)); - assertThat(searchResponse.getHits().getTotalHits(), equalTo(2L)); - - // flt query with string fields - searchResponse = client().prepareSearch().setQuery(fuzzyLikeThisQuery("string_value").likeText("index")).execute().actionGet(); - assertThat(searchResponse.getFailedShards(), equalTo(0)); - assertThat(searchResponse.getHits().getTotalHits(), equalTo(2L)); - - // flt query with at least a numeric field -> fail by default - assertThrows(client().prepareSearch().setQuery(fuzzyLikeThisQuery("string_value", "int_value").likeText("index")), SearchPhaseExecutionException.class); - - // flt query with at least a numeric field -> fail by command - assertThrows(client().prepareSearch().setQuery(fuzzyLikeThisQuery("string_value", "int_value").likeText("index").failOnUnsupportedField(true)), SearchPhaseExecutionException.class); - - - // flt query with at least a numeric field but fail_on_unsupported_field set to false - searchResponse = client().prepareSearch().setQuery(fuzzyLikeThisQuery("string_value", "int_value").likeText("index").failOnUnsupportedField(false)).execute().actionGet(); - assertThat(searchResponse.getFailedShards(), equalTo(0)); - assertThat(searchResponse.getHits().getTotalHits(), equalTo(2L)); - - // flt field query on a numeric field -> failure by default - assertThrows(client().prepareSearch().setQuery(fuzzyLikeThisFieldQuery("int_value").likeText("42")), SearchPhaseExecutionException.class); - - // flt field query on a numeric field -> failure by command - assertThrows(client().prepareSearch().setQuery(fuzzyLikeThisFieldQuery("int_value").likeText("42").failOnUnsupportedField(true)), SearchPhaseExecutionException.class); - - // flt field query on a numeric field but fail_on_unsupported_field set to false - searchResponse = client().prepareSearch().setQuery(fuzzyLikeThisFieldQuery("int_value").likeText("42").failOnUnsupportedField(false)).execute().actionGet(); - assertThat(searchResponse.getFailedShards(), equalTo(0)); - assertThat(searchResponse.getHits().getTotalHits(), equalTo(0L)); - } - -} diff --git a/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java b/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java index 48f62cec8d5..6870dd335d1 100644 --- a/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java +++ b/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java @@ -25,7 +25,6 @@ import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.index.*; import org.apache.lucene.index.memory.MemoryIndex; import org.apache.lucene.queries.*; -import org.apache.lucene.sandbox.queries.FuzzyLikeThisQuery; import org.apache.lucene.search.*; import org.apache.lucene.search.spans.*; import org.apache.lucene.spatial.prefix.IntersectsPrefixTreeFilter; @@ -1795,74 +1794,6 @@ public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest { return strings; } - @Test - public void testFuzzyLikeThisBuilder() throws Exception { - IndexQueryParserService queryParser = queryParser(); - Query parsedQuery = queryParser.parse(fuzzyLikeThisQuery("name.first", "name.last").likeText("something").maxQueryTerms(12)).query(); - assertThat(parsedQuery, instanceOf(FuzzyLikeThisQuery.class)); - parsedQuery = queryParser.parse(fuzzyLikeThisQuery("name.first", "name.last").likeText("something").maxQueryTerms(12).fuzziness(Fuzziness.build("4"))).query(); - assertThat(parsedQuery, instanceOf(FuzzyLikeThisQuery.class)); - - Query parsedQuery1 = queryParser.parse(fuzzyLikeThisQuery("name.first", "name.last").likeText("something").maxQueryTerms(12).fuzziness(Fuzziness.build("4.0"))).query(); - assertThat(parsedQuery1, instanceOf(FuzzyLikeThisQuery.class)); - assertThat(parsedQuery, equalTo(parsedQuery1)); - - try { - queryParser.parse(fuzzyLikeThisQuery("name.first", "name.last").likeText("something").maxQueryTerms(12).fuzziness(Fuzziness.build("4.1"))).query(); - fail("exception expected - fractional edit distance"); - } catch (ElasticsearchException ex) { - // - } - - try { - queryParser.parse(fuzzyLikeThisQuery("name.first", "name.last").likeText("something").maxQueryTerms(12).fuzziness(Fuzziness.build("-" + between(1, 100)))).query(); - fail("exception expected - negative edit distance"); - } catch (ElasticsearchException ex) { - // - } - String[] queries = new String[] { - "{\"flt\": {\"fields\": [\"comment\"], \"like_text\": \"FFFdfds\",\"fuzziness\": \"4\"}}", - "{\"flt\": {\"fields\": [\"comment\"], \"like_text\": \"FFFdfds\",\"fuzziness\": \"4.00000000\"}}", - "{\"flt\": {\"fields\": [\"comment\"], \"like_text\": \"FFFdfds\",\"fuzziness\": \"4.\"}}", - "{\"flt\": {\"fields\": [\"comment\"], \"like_text\": \"FFFdfds\",\"fuzziness\": 4}}", - "{\"flt\": {\"fields\": [\"comment\"], \"like_text\": \"FFFdfds\",\"fuzziness\": 4.0}}" - }; - int iters = scaledRandomIntBetween(5, 100); - for (int i = 0; i < iters; i++) { - parsedQuery = queryParser.parse(new BytesArray((String) randomFrom(queries))).query(); - parsedQuery1 = queryParser.parse(new BytesArray((String) randomFrom(queries))).query(); - assertThat(parsedQuery1, instanceOf(FuzzyLikeThisQuery.class)); - assertThat(parsedQuery, instanceOf(FuzzyLikeThisQuery.class)); - assertThat(parsedQuery, equalTo(parsedQuery1)); - } - } - - @Test - public void testFuzzyLikeThis() throws Exception { - IndexQueryParserService queryParser = queryParser(); - String query = copyToStringFromClasspath("/org/elasticsearch/index/query/fuzzyLikeThis.json"); - Query parsedQuery = queryParser.parse(query).query(); - assertThat(parsedQuery, instanceOf(FuzzyLikeThisQuery.class)); -// FuzzyLikeThisQuery fuzzyLikeThisQuery = (FuzzyLikeThisQuery) parsedQuery; - } - - @Test - public void testFuzzyLikeFieldThisBuilder() throws Exception { - IndexQueryParserService queryParser = queryParser(); - Query parsedQuery = queryParser.parse(fuzzyLikeThisFieldQuery("name.first").likeText("something").maxQueryTerms(12)).query(); - assertThat(parsedQuery, instanceOf(FuzzyLikeThisQuery.class)); -// FuzzyLikeThisQuery fuzzyLikeThisQuery = (FuzzyLikeThisQuery) parsedQuery; - } - - @Test - public void testFuzzyLikeThisField() throws Exception { - IndexQueryParserService queryParser = queryParser(); - String query = copyToStringFromClasspath("/org/elasticsearch/index/query/fuzzyLikeThisField.json"); - Query parsedQuery = queryParser.parse(query).query(); - assertThat(parsedQuery, instanceOf(FuzzyLikeThisQuery.class)); -// FuzzyLikeThisQuery fuzzyLikeThisQuery = (FuzzyLikeThisQuery) parsedQuery; - } - @Test public void testGeoDistanceFilterNamed() throws IOException { IndexQueryParserService queryParser = queryParser(); diff --git a/src/test/java/org/elasticsearch/index/query/fuzzyLikeThis.json b/src/test/java/org/elasticsearch/index/query/fuzzyLikeThis.json deleted file mode 100644 index ccd30a98953..00000000000 --- a/src/test/java/org/elasticsearch/index/query/fuzzyLikeThis.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - fuzzy_like_this:{ - fields:["name.first", "name.last"], - like_text:"something", - max_query_terms:12 - } -} \ No newline at end of file diff --git a/src/test/java/org/elasticsearch/index/query/fuzzyLikeThisField.json b/src/test/java/org/elasticsearch/index/query/fuzzyLikeThisField.json deleted file mode 100644 index 114ebe5ffda..00000000000 --- a/src/test/java/org/elasticsearch/index/query/fuzzyLikeThisField.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - fuzzy_like_this_field:{ - "name.first":{ - like_text:"something", - max_query_terms:12 - } - } -} \ No newline at end of file From 5b3cc2f07c471d95efe4cda0eba040dd3059541d Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Fri, 10 Apr 2015 15:41:56 +0200 Subject: [PATCH 13/22] Search: deprecate the limit filter. This is really a Collector instead of a filter. This commit deprecates the `limit` filter, makes it a no-op and recommends to use the `terminate_after` parameter instead that we introduced in the meantime. --- docs/reference/migration/migrate_2_0.asciidoc | 3 + .../query-dsl/filters/limit-filter.asciidoc | 2 + .../common/lucene/search/LimitFilter.java | 79 ------------------- .../index/query/FilterBuilders.java | 3 + .../index/query/LimitFilterBuilder.java | 5 ++ .../index/query/LimitFilterParser.java | 5 +- .../count/query/CountQueryTests.java | 2 +- .../query/SimpleIndexQueryParserTests.java | 14 ---- .../index/query/limit-filter.json | 14 ---- .../search/query/SearchQueryTests.java | 2 +- 10 files changed, 18 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/org/elasticsearch/common/lucene/search/LimitFilter.java delete mode 100644 src/test/java/org/elasticsearch/index/query/limit-filter.json diff --git a/docs/reference/migration/migrate_2_0.asciidoc b/docs/reference/migration/migrate_2_0.asciidoc index 5809a96616f..d51660a6d37 100644 --- a/docs/reference/migration/migrate_2_0.asciidoc +++ b/docs/reference/migration/migrate_2_0.asciidoc @@ -383,3 +383,6 @@ be used separately to control whether `routing_nodes` should be returned. === Query DSL The `fuzzy_like_this` and `fuzzy_like_this_field` queries have been removed. + +The `limit` filter is deprecated and becomes a no-op. You can achieve similar +behaviour using the <> parameter. diff --git a/docs/reference/query-dsl/filters/limit-filter.asciidoc b/docs/reference/query-dsl/filters/limit-filter.asciidoc index a590c2567f7..2282b66c92e 100644 --- a/docs/reference/query-dsl/filters/limit-filter.asciidoc +++ b/docs/reference/query-dsl/filters/limit-filter.asciidoc @@ -1,6 +1,8 @@ [[query-dsl-limit-filter]] === Limit Filter +deprecated[2.0.0, Use <> instead] + A limit filter limits the number of documents (per shard) to execute on. For example: diff --git a/src/main/java/org/elasticsearch/common/lucene/search/LimitFilter.java b/src/main/java/org/elasticsearch/common/lucene/search/LimitFilter.java deleted file mode 100644 index d349db3e66e..00000000000 --- a/src/main/java/org/elasticsearch/common/lucene/search/LimitFilter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.common.lucene.search; - -import java.io.IOException; - -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.search.DocIdSet; -import org.apache.lucene.search.DocValuesDocIdSet; -import org.apache.lucene.util.Bits; -import org.apache.lucene.util.RamUsageEstimator; -import org.elasticsearch.common.Nullable; - -public class LimitFilter extends NoCacheFilter { - - private final int limit; - private int counter; - - public LimitFilter(int limit) { - this.limit = limit; - } - - public int getLimit() { - return limit; - } - - @Override - public DocIdSet getDocIdSet(LeafReaderContext context, Bits acceptDocs) throws IOException { - if (counter > limit) { - return null; - } - return new LimitDocIdSet(context.reader().maxDoc(), acceptDocs, limit); - } - - public class LimitDocIdSet extends DocValuesDocIdSet { - - private final int limit; - - public LimitDocIdSet(int maxDoc, @Nullable Bits acceptDocs, int limit) { - super(maxDoc, acceptDocs); - this.limit = limit; - } - - @Override - protected boolean matchDoc(int doc) { - if (++counter > limit) { - return false; - } - return true; - } - - @Override - public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_INT; - } - } - - @Override - public String toString(String field) { - return "limit(limit=" + limit + ")"; - } -} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java index b2a4fbe98cb..efd48255f1e 100644 --- a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java @@ -19,6 +19,7 @@ package org.elasticsearch.index.query; +import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.geo.GeoPoint; @@ -39,7 +40,9 @@ public abstract class FilterBuilders { /** * A filter that limits the results to the provided limit value (per shard!). + * @deprecated Use {@link SearchRequestBuilder#setTerminateAfter(int)} instead. */ + @Deprecated public static LimitFilterBuilder limitFilter(int limit) { return new LimitFilterBuilder(limit); } diff --git a/src/main/java/org/elasticsearch/index/query/LimitFilterBuilder.java b/src/main/java/org/elasticsearch/index/query/LimitFilterBuilder.java index 31e34462777..552c47b6cf6 100644 --- a/src/main/java/org/elasticsearch/index/query/LimitFilterBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/LimitFilterBuilder.java @@ -19,10 +19,15 @@ package org.elasticsearch.index.query; +import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; +/** + * @deprecated Use {@link SearchRequestBuilder#setTerminateAfter(int)} instead. + */ +@Deprecated public class LimitFilterBuilder extends BaseFilterBuilder { private final int limit; diff --git a/src/main/java/org/elasticsearch/index/query/LimitFilterParser.java b/src/main/java/org/elasticsearch/index/query/LimitFilterParser.java index e82563f5985..f22308f058f 100644 --- a/src/main/java/org/elasticsearch/index/query/LimitFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/LimitFilterParser.java @@ -21,7 +21,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.search.Filter; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.lucene.search.LimitFilter; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; @@ -62,6 +62,7 @@ public class LimitFilterParser implements FilterParser { throw new QueryParsingException(parseContext.index(), "No value specified for limit filter"); } - return new LimitFilter(limit); + // this filter is deprecated and parses to a filter that matches everything + return Queries.MATCH_ALL_FILTER; } } diff --git a/src/test/java/org/elasticsearch/count/query/CountQueryTests.java b/src/test/java/org/elasticsearch/count/query/CountQueryTests.java index 66879cd606b..0f77e83c03a 100644 --- a/src/test/java/org/elasticsearch/count/query/CountQueryTests.java +++ b/src/test/java/org/elasticsearch/count/query/CountQueryTests.java @@ -303,7 +303,7 @@ public class CountQueryTests extends ElasticsearchIntegrationTest { client().prepareIndex("test", "type1", "4").setSource("field3", "value3_4")); CountResponse countResponse = client().prepareCount().setQuery(filteredQuery(matchAllQuery(), limitFilter(2))).get(); - assertHitCount(countResponse, 2l); + assertHitCount(countResponse, 4l); // limit is a no-op } @Test diff --git a/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java b/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java index 6870dd335d1..88fb2a365a2 100644 --- a/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java +++ b/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java @@ -1280,20 +1280,6 @@ public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest { assertThat(getTerm(filteredQuery.getFilter()), equalTo(new Term("name.last", "banon"))); } - @Test - public void testLimitFilter() throws Exception { - IndexQueryParserService queryParser = queryParser(); - String query = copyToStringFromClasspath("/org/elasticsearch/index/query/limit-filter.json"); - Query parsedQuery = queryParser.parse(query).query(); - assertThat(parsedQuery, instanceOf(FilteredQuery.class)); - FilteredQuery filteredQuery = (FilteredQuery) parsedQuery; - assertThat(filteredQuery.getFilter(), instanceOf(LimitFilter.class)); - assertThat(((LimitFilter) filteredQuery.getFilter()).getLimit(), equalTo(2)); - - assertThat(filteredQuery.getQuery(), instanceOf(TermQuery.class)); - assertThat(((TermQuery) filteredQuery.getQuery()).getTerm(), equalTo(new Term("name.first", "shay"))); - } - @Test public void testTermFilterQuery() throws Exception { IndexQueryParserService queryParser = queryParser(); diff --git a/src/test/java/org/elasticsearch/index/query/limit-filter.json b/src/test/java/org/elasticsearch/index/query/limit-filter.json deleted file mode 100644 index 549f33178d7..00000000000 --- a/src/test/java/org/elasticsearch/index/query/limit-filter.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "filtered":{ - "filter":{ - "limit":{ - "value":2 - } - }, - "query":{ - "term":{ - "name.first":"shay" - } - } - } -} \ No newline at end of file diff --git a/src/test/java/org/elasticsearch/search/query/SearchQueryTests.java b/src/test/java/org/elasticsearch/search/query/SearchQueryTests.java index 0caf0cb9457..7471866c5c1 100644 --- a/src/test/java/org/elasticsearch/search/query/SearchQueryTests.java +++ b/src/test/java/org/elasticsearch/search/query/SearchQueryTests.java @@ -677,7 +677,7 @@ public class SearchQueryTests extends ElasticsearchIntegrationTest { client().prepareIndex("test", "type1", "3").setSource("field2", "value2_3"), client().prepareIndex("test", "type1", "4").setSource("field3", "value3_4")); - assertHitCount(client().prepareSearch().setQuery(filteredQuery(matchAllQuery(), limitFilter(2))).get(), 2l); + assertHitCount(client().prepareSearch().setQuery(filteredQuery(matchAllQuery(), limitFilter(2))).get(), 4l); // no-op } @Test From b936ec9a25665526dbef4119b2bc3a6872e89764 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Fri, 10 Apr 2015 11:28:30 -0400 Subject: [PATCH 14/22] allow reflection of MXBean for file descriptor stats --- dev-tools/tests.policy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev-tools/tests.policy b/dev-tools/tests.policy index 82e23fca6fd..fcb6b8ceb63 100644 --- a/dev-tools/tests.policy +++ b/dev-tools/tests.policy @@ -81,6 +81,8 @@ grant { permission java.lang.RuntimePermission "accessClassInPackage.sun.nio.ch"; // needed by groovy engine permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect"; + // needed to get file descriptor statistics + permission java.lang.RuntimePermission "accessClassInPackage.sun.management"; permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "getStackTrace"; From ab8926bc6a4ebce433463c0b33dc4b428ea0af80 Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Fri, 10 Apr 2015 17:38:12 +0200 Subject: [PATCH 15/22] Docs: fix build. --- docs/reference/api-conventions.asciidoc | 3 +-- docs/reference/query-dsl/filters/limit-filter.asciidoc | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/reference/api-conventions.asciidoc b/docs/reference/api-conventions.asciidoc index 3ccda61f21a..bbf78eb1521 100644 --- a/docs/reference/api-conventions.asciidoc +++ b/docs/reference/api-conventions.asciidoc @@ -230,8 +230,7 @@ generates an edit distance based on the length of the term. For lengths: converted into an edit distance using the formula: `length(term) * (1.0 - fuzziness)`, eg a `fuzziness` of `0.6` with a term of length 10 would result -in an edit distance of `4`. Note: in all APIs except for the -<>, the maximum allowed edit distance is `2`. +in an edit distance of `4`. Note: in all APIs the maximum allowed edit distance is `2`. diff --git a/docs/reference/query-dsl/filters/limit-filter.asciidoc b/docs/reference/query-dsl/filters/limit-filter.asciidoc index 2282b66c92e..950c5a93b0b 100644 --- a/docs/reference/query-dsl/filters/limit-filter.asciidoc +++ b/docs/reference/query-dsl/filters/limit-filter.asciidoc @@ -1,7 +1,7 @@ [[query-dsl-limit-filter]] === Limit Filter -deprecated[2.0.0, Use <> instead] +deprecated[1.6.0, Use <> instead] A limit filter limits the number of documents (per shard) to execute on. For example: From 97a9b4ec2fb6691437c86b694b77d56e81efcaa1 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Wed, 8 Apr 2015 16:25:03 +0900 Subject: [PATCH 16/22] Fix m2e Eclipse Integration Update Eclipse core prefs Eclipse Luna overwrites the prefs file, putting all the settings in alphabetical order and removing comments. This causes the prefs files to be modified in the git workspace. Update the file with the version generated by Eclipse to prevent it from being modified every time. No settings values are modified by this change. This also adds another plugin to the lifecycle mapping in the pom.xml which was missed in https://github.com/elastic/elasticsearch/pull/10524. --- .settings/org.eclipse.core.resources.prefs | 5 +++++ .settings/org.eclipse.jdt.core.prefs | 17 +++++++---------- pom.xml | 13 +++++++++++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 99f26c0203a..f4e75a7d41a 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,2 +1,7 @@ eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 encoding/=UTF-8 +encoding/rest-api-spec=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 2d43111397f..f1163fdd583 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,21 +1,18 @@ eclipse.preferences.version=1 -# We target Java 1.7 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.source=1.7 -# Lines should be splitted at 140 chars -org.eclipse.jdt.core.formatter.lineSplit=140 -# Indentation is 4 spaces -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -# Configuration for NPE analysis org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.nullable=org.elasticsearch.common.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning org.eclipse.jdt.core.compiler.problem.nullReference=warning org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.formatter.lineSplit=140 +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 diff --git a/pom.xml b/pom.xml index 5d8a2722c73..35b4a4c17fa 100644 --- a/pom.xml +++ b/pom.xml @@ -1626,6 +1626,19 @@ + + + com.mycila + license-maven-plugin + [1.0.0,) + + check + + + + + + From a8a35d7c291a999e13a56fd2107427453b7c76ba Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Fri, 27 Mar 2015 14:13:30 -0500 Subject: [PATCH 17/22] [GEO] Fix hole intersection at tangential coordinate OGC SFA 2.1.10 assertion 3 allows interior boundaries to touch exterior boundaries provided they intersect at a single point. Issue #9511 provides an example where a valid shape is incorrectly interpreted as invalid (a false violation of assertion 3). When the intersecting point appears as the first and last coordinate of the interior boundary in a polygon, the ShapeBuilder incorrectly counted this as multiple intersecting vertices. The fix required a little more than just a logic check. Passing the duplicate vertices resulted in a connected component in the edge graph causing an invalid self crossing polygon. This required additional logic to the edge assignment in order to correctly segment the connected components. Finally, an additional hole validation has been added along with proper unit tests for testing valid and invalid conditions (including dateline crossing polys). closes #9511 --- .../geo/builders/BasePolygonBuilder.java | 82 +++++++++-- .../common/geo/builders/ShapeBuilder.java | 23 ++- .../common/geo/GeoJSONShapeParserTests.java | 2 +- .../common/geo/ShapeBuilderTests.java | 137 ++++++++++++++---- 4 files changed, 192 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java index 2981145b90a..c1cb9bb32ac 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java +++ b/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java @@ -19,14 +19,18 @@ package org.elasticsearch.common.geo.builders; +import com.google.common.collect.Sets; +import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Shape; import com.vividsolutions.jts.geom.*; -import org.elasticsearch.ElasticsearchParseException; +import org.apache.commons.lang3.tuple.Pair; import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; /** @@ -111,6 +115,18 @@ public abstract class BasePolygonBuilder> extend return shell.close(); } + /** + * Validates only 1 vertex is tangential (shared) between the interior and exterior of a polygon + */ + protected void validateHole(BaseLineStringBuilder shell, BaseLineStringBuilder hole) { + HashSet exterior = Sets.newHashSet(shell.points); + HashSet interior = Sets.newHashSet(hole.points); + exterior.retainAll(interior); + if (exterior.size() >= 2) { + throw new InvalidShapeException("Invalid polygon, interior cannot share more than one point with the exterior"); + } + } + /** * The coordinates setup by the builder will be assembled to a polygon. The result will consist of * a set of polygons. Each of these components holds a list of linestrings defining the polygon: the @@ -125,6 +141,7 @@ public abstract class BasePolygonBuilder> extend int numEdges = shell.points.size()-1; // Last point is repeated for (int i = 0; i < holes.size(); i++) { numEdges += holes.get(i).points.size()-1; + validateHole(shell, this.holes.get(i)); } Edge[] edges = new Edge[numEdges]; @@ -253,28 +270,62 @@ public abstract class BasePolygonBuilder> extend } } - double shift = any.coordinate.x > DATELINE ? DATELINE : (any.coordinate.x < -DATELINE ? -DATELINE : 0); + double shiftOffset = any.coordinate.x > DATELINE ? DATELINE : (any.coordinate.x < -DATELINE ? -DATELINE : 0); if (debugEnabled()) { - LOGGER.debug("shift: {[]}", shift); + LOGGER.debug("shift: {[]}", shiftOffset); } // run along the border of the component, collect the // edges, shift them according to the dateline and // update the component id - int length = 0; + int length = 0, connectedComponents = 0; + // if there are two connected components, splitIndex keeps track of where to split the edge array + // start at 1 since the source coordinate is shared + int splitIndex = 1; Edge current = edge; + Edge prev = edge; + // bookkeep the source and sink of each visited coordinate + HashMap> visitedEdge = new HashMap<>(); do { - - current.coordinate = shift(current.coordinate, shift); + current.coordinate = shift(current.coordinate, shiftOffset); current.component = id; - if(edges != null) { + + if (edges != null) { + // found a closed loop - we have two connected components so we need to slice into two distinct components + if (visitedEdge.containsKey(current.coordinate)) { + if (connectedComponents > 0 && current.next != edge) { + throw new InvalidShapeException("Shape contains more than one shared point"); + } + + // a negative id flags the edge as visited for the edges(...) method. + // since we're splitting connected components, we want the edges method to visit + // the newly separated component + final int visitID = -id; + Edge firstAppearance = visitedEdge.get(current.coordinate).getRight(); + // correct the graph pointers by correcting the 'next' pointer for both the + // first appearance and this appearance of the edge + Edge temp = firstAppearance.next; + firstAppearance.next = current.next; + current.next = temp; + current.component = visitID; + // backtrack until we get back to this coordinate, setting the visit id to + // a non-visited value (anything positive) + do { + prev.component = visitID; + prev = visitedEdge.get(prev.coordinate).getLeft(); + ++splitIndex; + } while (!current.coordinate.equals(prev.coordinate)); + ++connectedComponents; + } else { + visitedEdge.put(current.coordinate, Pair.of(prev, current)); + } edges.add(current); + prev = current; } - length++; - } while((current = current.next) != edge); + } while(connectedComponents == 0 && (current = current.next) != edge); - return length; + return (splitIndex != 1) ? length-splitIndex: length; } /** @@ -364,11 +415,12 @@ public abstract class BasePolygonBuilder> extend // if no intersection is found then the hole is not within the polygon, so // don't waste time calling a binary search final int pos; - if (intersections == 0 || - (pos = Arrays.binarySearch(edges, 0, intersections, current, INTERSECTION_ORDER)) >= 0) { - throw new ElasticsearchParseException("Invalid shape: Hole is not within polygon"); + boolean sharedVertex = false; + if (intersections == 0 || ((pos = Arrays.binarySearch(edges, 0, intersections, current, INTERSECTION_ORDER)) >= 0) + && !(sharedVertex = (edges[pos].intersect.compareTo(current.coordinate) == 0)) ) { + throw new InvalidShapeException("Invalid shape: Hole is not within polygon"); } - final int index = -(pos+2); + final int index = -((sharedVertex) ? 0 : pos+2); final int component = -edges[index].component - numHoles - 1; if(debugEnabled()) { @@ -465,7 +517,7 @@ public abstract class BasePolygonBuilder> extend Edge[] edges, int offset) { // inner rings (holes) have an opposite direction than the outer rings // XOR will invert the orientation for outer ring cases (Truth Table:, T/T = F, T/F = T, F/T = T, F/F = F) - boolean direction = (component != 0 ^ orientation == Orientation.RIGHT); + boolean direction = (component == 0 ^ orientation == Orientation.RIGHT); // set the points array accordingly (shell or hole) Coordinate[] points = (hole != null) ? hole.coordinates(false) : shell.coordinates(false); Edge.ring(component, direction, orientation == Orientation.LEFT, shell, points, 0, edges, offset, points.length-1); diff --git a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java index 11545855077..98779c5f1a8 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java +++ b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java @@ -20,6 +20,7 @@ package org.elasticsearch.common.geo.builders; import com.spatial4j.core.context.jts.JtsSpatialContext; +import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.jts.JtsGeometry; import com.vividsolutions.jts.geom.Coordinate; @@ -446,7 +447,8 @@ public abstract class ShapeBuilder implements ToXContent { protected Edge(Coordinate coordinate, Edge next, Coordinate intersection) { this.coordinate = coordinate; - this.next = next; + // use setter to catch duplicate point cases + this.setNext(next); this.intersect = intersection; if (next != null) { this.component = next.component; @@ -457,6 +459,17 @@ public abstract class ShapeBuilder implements ToXContent { this(coordinate, next, Edge.MAX_COORDINATE); } + protected void setNext(Edge next) { + // don't bother setting next if its null + if (next != null) { + // self-loop throws an invalid shape + if (this.coordinate.equals(next.coordinate)) { + throw new InvalidShapeException("Provided shape has duplicate consecutive coordinates at: " + this.coordinate); + } + this.next = next; + } + } + private static final int top(Coordinate[] points, int offset, int length) { int top = 0; // we start at 1 here since top points to 0 for (int i = 1; i < length; i++) { @@ -522,17 +535,19 @@ public abstract class ShapeBuilder implements ToXContent { if (direction) { edges[edgeOffset + i] = new Edge(points[pointOffset + i], edges[edgeOffset + i - 1]); edges[edgeOffset + i].component = component; - } else { + } else if(!edges[edgeOffset + i - 1].coordinate.equals(points[pointOffset + i])) { edges[edgeOffset + i - 1].next = edges[edgeOffset + i] = new Edge(points[pointOffset + i], null); edges[edgeOffset + i - 1].component = component; + } else { + throw new InvalidShapeException("Provided shape has duplicate consecutive coordinates at: " + points[pointOffset + i]); } } if (direction) { - edges[edgeOffset].next = edges[edgeOffset + length - 1]; + edges[edgeOffset].setNext(edges[edgeOffset + length - 1]); edges[edgeOffset].component = component; } else { - edges[edgeOffset + length - 1].next = edges[edgeOffset]; + edges[edgeOffset + length - 1].setNext(edges[edgeOffset]); edges[edgeOffset + length - 1].component = component; } diff --git a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java index 0b71570403f..28cc9f41cd1 100644 --- a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java +++ b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java @@ -267,7 +267,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { XContentParser parser = JsonXContent.jsonXContent.createParser(multiPolygonGeoJson); parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class); } public void testParse_OGCPolygonWithoutHoles() throws IOException { diff --git a/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java b/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java index bb59c5b582a..d82b1bdd14f 100644 --- a/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java +++ b/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java @@ -28,6 +28,7 @@ import com.spatial4j.core.shape.impl.PointImpl; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Polygon; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.geo.builders.PolygonBuilder; import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.test.ElasticsearchTestCase; @@ -39,14 +40,12 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.*; */ public class ShapeBuilderTests extends ElasticsearchTestCase { - @Test public void testNewPoint() { Point point = ShapeBuilder.newPoint(-100, 45).build(); assertEquals(-100D, point.getX(), 0.0d); assertEquals(45D, point.getY(), 0.0d); } - @Test public void testNewRectangle() { Rectangle rectangle = ShapeBuilder.newEnvelope().topLeft(-45, 30).bottomRight(45, -30).build(); assertEquals(-45D, rectangle.getMinX(), 0.0d); @@ -55,7 +54,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertEquals(30D, rectangle.getMaxY(), 0.0d); } - @Test public void testNewPolygon() { Polygon polygon = ShapeBuilder.newPolygon() .point(-45, 30) @@ -71,7 +69,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); } - @Test public void testNewPolygon_coordinate() { Polygon polygon = ShapeBuilder.newPolygon() .point(new Coordinate(-45, 30)) @@ -87,7 +84,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); } - @Test public void testNewPolygon_coordinates() { Polygon polygon = ShapeBuilder.newPolygon() .points(new Coordinate(-45, 30), new Coordinate(45, 30), new Coordinate(45, -30), new Coordinate(-45, -30), new Coordinate(-45, 30)).toPolygon(); @@ -98,8 +94,7 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); } - - @Test + public void testLineStringBuilder() { // Building a simple LineString ShapeBuilder.newLineString() @@ -141,7 +136,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .build(); } - @Test public void testMultiLineString() { ShapeBuilder.newMultiLinestring() .linestring() @@ -175,7 +169,7 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .end() .build(); } - + @Test(expected = InvalidShapeException.class) public void testPolygonSelfIntersection() { ShapeBuilder.newPolygon() @@ -186,7 +180,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .close().build(); } - @Test public void testGeoCircle() { double earthCircumference = 40075016.69; Circle circle = ShapeBuilder.newCircleBuilder().center(0, 0).radius("100m").build(); @@ -211,8 +204,7 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertEquals((360 * randomRadius) / earthCircumference, circle.getRadius(), 0.00000001); assertEquals(new PointImpl(randomLon, randomLat, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); } - - @Test + public void testPolygonWrapping() { Shape shape = ShapeBuilder.newPolygon() .point(-150.0, 65.0) @@ -224,7 +216,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertMultiPolygon(shape); } - @Test public void testLineStringWrapping() { Shape shape = ShapeBuilder.newLineString() .point(-150.0, 65.0) @@ -232,11 +223,9 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(-250.0, -65.0) .point(-150.0, -65.0) .build(); - assertMultiLineString(shape); } - @Test public void testDatelineOGC() { // tests that the following shape (defined in counterclockwise OGC order) // https://gist.github.com/anonymous/7f1bb6d7e9cd72f5977c crosses the dateline @@ -275,11 +264,9 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(-179,1); Shape shape = builder.close().build(); - assertMultiPolygon(shape); } - @Test public void testDateline() { // tests that the following shape (defined in clockwise non-OGC order) // https://gist.github.com/anonymous/7f1bb6d7e9cd72f5977c crosses the dateline @@ -318,11 +305,9 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(-179,1); Shape shape = builder.close().build(); - assertMultiPolygon(shape); } - - @Test + public void testComplexShapeWithHole() { PolygonBuilder builder = ShapeBuilder.newPolygon() .point(-85.0018514,37.1311314) @@ -393,11 +378,9 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(-85.0000002,37.1317672); Shape shape = builder.close().build(); - - assertPolygon(shape); + assertPolygon(shape); } - @Test public void testShapeWithHoleAtEdgeEndPoints() { PolygonBuilder builder = ShapeBuilder.newPolygon() .point(-4, 2) @@ -416,11 +399,9 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(4, 1); Shape shape = builder.close().build(); - - assertPolygon(shape); + assertPolygon(shape); } - @Test public void testShapeWithPointOnDateline() { PolygonBuilder builder = ShapeBuilder.newPolygon() .point(180, 0) @@ -429,11 +410,9 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(180, 0); Shape shape = builder.close().build(); - - assertPolygon(shape); + assertPolygon(shape); } - @Test public void testShapeWithEdgeAlongDateline() { // test case 1: test the positive side of the dateline PolygonBuilder builder = ShapeBuilder.newPolygon() @@ -456,7 +435,6 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertPolygon(shape); } - @Test public void testShapeWithBoundaryHoles() { // test case 1: test the positive side of the dateline PolygonBuilder builder = ShapeBuilder.newPolygon() @@ -481,7 +459,7 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(179, 10) .point(179, -10) .point(-176, -15) - .point(-172,0); + .point(-172, 0); builder.hole() .point(-176, 10) .point(-176, -10) @@ -492,6 +470,89 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertMultiPolygon(shape); } + public void testShapeWithTangentialHole() { + // test a shape with one tangential (shared) vertex (should pass) + PolygonBuilder builder = ShapeBuilder.newPolygon() + .point(179, 10) + .point(168, 15) + .point(164, 0) + .point(166, -15) + .point(179, -10) + .point(179, 10); + builder.hole() + .point(-177, 10) + .point(-178, -10) + .point(-180, -5) + .point(-180, 5) + .point(-177, 10); + Shape shape = builder.close().build(); + assertMultiPolygon(shape); + } + + @Test(expected = InvalidShapeException.class) + public void testShapeWithInvalidTangentialHole() { + // test a shape with one invalid tangential (shared) vertex (should throw exception) + PolygonBuilder builder = ShapeBuilder.newPolygon() + .point(179, 10) + .point(168, 15) + .point(164, 0) + .point(166, -15) + .point(179, -10) + .point(179, 10); + builder.hole() + .point(164, 0) + .point(175, 10) + .point(175, 5) + .point(179, -10) + .point(164, 0); + Shape shape = builder.close().build(); + assertMultiPolygon(shape); + } + + public void testBoundaryShapeWithTangentialHole() { + // test a shape with one tangential (shared) vertex for each hole (should pass) + PolygonBuilder builder = ShapeBuilder.newPolygon() + .point(-177, 10) + .point(176, 15) + .point(172, 0) + .point(176, -15) + .point(-177, -10) + .point(-177, 10); + builder.hole() + .point(-177, 10) + .point(-178, -10) + .point(-180, -5) + .point(-180, 5) + .point(-177, 10); + builder.hole() + .point(172, 0) + .point(176, 10) + .point(176, -5) + .point(172, 0); + Shape shape = builder.close().build(); + assertMultiPolygon(shape); + } + + @Test(expected = InvalidShapeException.class) + public void testBoundaryShapeWithInvalidTangentialHole() { + // test shape with two tangential (shared) vertices (should throw exception) + PolygonBuilder builder = ShapeBuilder.newPolygon() + .point(-177, 10) + .point(176, 15) + .point(172, 0) + .point(176, -15) + .point(-177, -10) + .point(-177, 10); + builder.hole() + .point(-177, 10) + .point(172, 0) + .point(180, -5) + .point(176, -10) + .point(-177, 10); + Shape shape = builder.close().build(); + assertMultiPolygon(shape); + } + /** * Test an enveloping polygon around the max mercator bounds */ @@ -510,7 +571,7 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { @Test public void testShapeWithAlternateOrientation() { - // ccw: should produce a single polygon spanning hemispheres + // cw: should produce a multi polygon spanning hemispheres PolygonBuilder builder = ShapeBuilder.newPolygon() .point(180, 0) .point(176, 4) @@ -531,4 +592,16 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { assertMultiPolygon(shape); } + + @Test(expected = InvalidShapeException.class) + public void testInvalidShapeWithConsecutiveDuplicatePoints() { + PolygonBuilder builder = ShapeBuilder.newPolygon() + .point(180, 0) + .point(176, 4) + .point(176, 4) + .point(-176, 4) + .point(180, 0); + Shape shape = builder.close().build(); + assertPolygon(shape); + } } From 754856289ec8bbc35e889f1002b93f53acde7940 Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Fri, 10 Apr 2015 08:53:09 -0500 Subject: [PATCH 18/22] [GEO] Add merge conflicts to GeoShapeFieldMapper Prevents the user from changing strategies, tree, tree_level or precision. distance_error_pct changes are allowed as they do not compromise the integrity of the index. A separate issue is open for allowing users to change tree_level or precision. --- .../index/mapper/geo/GeoShapeFieldMapper.java | 46 +++++++++++++ .../mapper/geo/GeoShapeFieldMapperTests.java | 64 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java index d3f9e0e819c..a96a84550f7 100644 --- a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java @@ -41,6 +41,8 @@ import org.elasticsearch.index.fielddata.FieldDataType; import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperParsingException; +import org.elasticsearch.index.mapper.MergeContext; +import org.elasticsearch.index.mapper.MergeMappingException; import org.elasticsearch.index.mapper.ParseContext; import org.elasticsearch.index.mapper.core.AbstractFieldMapper; @@ -262,6 +264,50 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper { } } + @Override + public void merge(Mapper mergeWith, MergeContext mergeContext) throws MergeMappingException { + super.merge(mergeWith, mergeContext); + if (!this.getClass().equals(mergeWith.getClass())) { + mergeContext.addConflict("mapper [" + names.fullName() + "] has different field type"); + return; + } + final GeoShapeFieldMapper fieldMergeWith = (GeoShapeFieldMapper) mergeWith; + if (!mergeContext.mergeFlags().simulate()) { + final PrefixTreeStrategy mergeWithStrategy = fieldMergeWith.defaultStrategy; + + // prevent user from changing strategies + if (!(this.defaultStrategy.getClass().equals(mergeWithStrategy.getClass()))) { + mergeContext.addConflict("mapper [" + names.fullName() + "] has different strategy"); + } + + final SpatialPrefixTree grid = this.defaultStrategy.getGrid(); + final SpatialPrefixTree mergeGrid = mergeWithStrategy.getGrid(); + + // prevent user from changing trees (changes encoding) + if (!grid.getClass().equals(mergeGrid.getClass())) { + mergeContext.addConflict("mapper [" + names.fullName() + "] has different tree"); + } + + // TODO we should allow this, but at the moment levels is used to build bookkeeping variables + // in lucene's SpatialPrefixTree implementations, need a patch to correct that first + if (grid.getMaxLevels() != mergeGrid.getMaxLevels()) { + mergeContext.addConflict("mapper [" + names.fullName() + "] has different tree_levels or precision"); + } + + // bail if there were merge conflicts + if (mergeContext.hasConflicts()) { + return; + } + + // change distance error percent + this.defaultStrategy.setDistErrPct(mergeWithStrategy.getDistErrPct()); + + // change orientation - this is allowed because existing dateline spanning shapes + // have already been unwound and segmented + this.shapeOrientation = fieldMergeWith.shapeOrientation; + } + } + @Override protected void parseCreateField(ParseContext context, List fields) throws IOException { } diff --git a/src/test/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapperTests.java b/src/test/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapperTests.java index 933767e7c99..b823d0ac809 100644 --- a/src/test/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapperTests.java +++ b/src/test/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapperTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.index.mapper.geo; import org.apache.lucene.spatial.prefix.PrefixTreeStrategy; +import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; import org.elasticsearch.common.geo.GeoUtils; @@ -31,9 +32,13 @@ import org.elasticsearch.test.ElasticsearchSingleNodeTest; import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import static org.elasticsearch.index.mapper.DocumentMapper.MergeFlags.mergeFlags; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.isIn; public class GeoShapeFieldMapperTests extends ElasticsearchSingleNodeTest { @@ -291,4 +296,63 @@ public class GeoShapeFieldMapperTests extends ElasticsearchSingleNodeTest { assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(50d))); } } + + @Test + public void testGeoShapeMapperMerge() throws Exception { + String stage1Mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties") + .startObject("shape").field("type", "geo_shape").field("tree", "geohash").field("strategy", "recursive") + .field("precision", "1m").field("tree_levels", 8).field("distance_error_pct", 0.01).field("orientation", "ccw") + .endObject().endObject().endObject().endObject().string(); + DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser(); + DocumentMapper stage1 = parser.parse(stage1Mapping); + String stage2Mapping = XContentFactory.jsonBuilder().startObject().startObject("type") + .startObject("properties").startObject("shape").field("type", "geo_shape").field("tree", "quadtree") + .field("strategy", "term").field("precision", "1km").field("tree_levels", 26).field("distance_error_pct", 26) + .field("orientation", "cw").endObject().endObject().endObject().endObject().string(); + DocumentMapper stage2 = parser.parse(stage2Mapping); + + DocumentMapper.MergeResult mergeResult = stage1.merge(stage2, mergeFlags().simulate(false)); + // check correct conflicts + assertThat(mergeResult.hasConflicts(), equalTo(true)); + assertThat(mergeResult.conflicts().length, equalTo(3)); + ArrayList conflicts = new ArrayList<>(Arrays.asList(mergeResult.conflicts())); + assertThat("mapper [shape] has different strategy", isIn(conflicts)); + assertThat("mapper [shape] has different tree", isIn(conflicts)); + assertThat("mapper [shape] has different tree_levels or precision", isIn(conflicts)); + + // verify nothing changed + FieldMapper fieldMapper = stage1.mappers().name("shape").mapper(); + assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class)); + + GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper; + PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy(); + + assertThat(strategy, instanceOf(RecursivePrefixTreeStrategy.class)); + assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class)); + assertThat(strategy.getDistErrPct(), equalTo(0.01)); + assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(1d))); + assertThat(geoShapeFieldMapper.orientation(), equalTo(ShapeBuilder.Orientation.CCW)); + + // correct mapping + stage2Mapping = XContentFactory.jsonBuilder().startObject().startObject("type") + .startObject("properties").startObject("shape").field("type", "geo_shape").field("precision", "1m") + .field("distance_error_pct", 0.001).field("orientation", "cw").endObject().endObject().endObject().endObject().string(); + stage2 = parser.parse(stage2Mapping); + mergeResult = stage1.merge(stage2, mergeFlags().simulate(false)); + + // verify mapping changes, and ensure no failures + assertThat(mergeResult.hasConflicts(), equalTo(false)); + + fieldMapper = stage1.mappers().name("shape").mapper(); + assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class)); + + geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper; + strategy = geoShapeFieldMapper.defaultStrategy(); + + assertThat(strategy, instanceOf(RecursivePrefixTreeStrategy.class)); + assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class)); + assertThat(strategy.getDistErrPct(), equalTo(0.001)); + assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(1d))); + assertThat(geoShapeFieldMapper.orientation(), equalTo(ShapeBuilder.Orientation.CW)); + } } From 90e1775a2ba8eb5682675a5bf247b07b06beaf25 Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Fri, 10 Apr 2015 13:49:59 -0500 Subject: [PATCH 19/22] [GEO] Correct ShapeBuilder coordinate parser to ignore values in 3rd+ dimension ShapeBuilder's coordinate parser expected 2 double values for every coordinate array. If > 2 doubles were provided the parser terminated parsing of the coordinate array. This resulted in an invalid Shape state leaving LineStrings, LinearRings, and Polygons with a single coordinate. An incorrect parse exception was thrown. This corrects the parser to ignore those values in the 3rd+ dimension, correctly parsing the rest of the coordinate array. Unit tests have been updated to verify the fix. closes #10510 --- .../common/geo/builders/ShapeBuilder.java | 3 ++ .../common/geo/GeoJSONShapeParserTests.java | 31 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java index 98779c5f1a8..22b6ee074ff 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java +++ b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java @@ -251,6 +251,9 @@ public abstract class ShapeBuilder implements ToXContent { token = parser.nextToken(); double lat = parser.doubleValue(); token = parser.nextToken(); + while (token == XContentParser.Token.VALUE_NUMBER) { + token = parser.nextToken(); + } return new CoordinateNode(new Coordinate(lon, lat)); } else if (token == XContentParser.Token.VALUE_NULL) { throw new ElasticsearchIllegalArgumentException("coordinates cannot contain NULL values)"); diff --git a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java index 28cc9f41cd1..2c767d645bd 100644 --- a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java +++ b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java @@ -115,6 +115,32 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { assertGeometryEquals(expected, multilinesGeoJson); } + public void testParse_multiDimensionShapes() throws IOException { + // multi dimension point + String pointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Point") + .startArray("coordinates").value(100.0).value(0.0).value(15.0).value(18.0).endArray() + .endObject().string(); + + Point expectedPt = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); + assertGeometryEquals(new JtsPoint(expectedPt, SPATIAL_CONTEXT), pointGeoJson); + + // multi dimension linestring + String lineGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "LineString") + .startArray("coordinates") + .startArray().value(100.0).value(0.0).value(15.0).endArray() + .startArray().value(101.0).value(1.0).value(18.0).value(19.0).endArray() + .endArray() + .endObject().string(); + + List lineCoordinates = new ArrayList<>(); + lineCoordinates.add(new Coordinate(100, 0)); + lineCoordinates.add(new Coordinate(101, 1)); + + LineString expectedLS = GEOMETRY_FACTORY.createLineString( + lineCoordinates.toArray(new Coordinate[lineCoordinates.size()])); + assertGeometryEquals(jtsGeom(expectedLS), lineGeoJson); + } + public void testParse_envelope() throws IOException { // test #1: envelope with expected coordinate order (TopLeft, BottomRight) String multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope") @@ -567,11 +593,12 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endArray() .endObject().string(); + // add 3d point to test ISSUE #10501 List shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(100, 0)); + shellCoordinates.add(new Coordinate(100, 0, 15.0)); shellCoordinates.add(new Coordinate(101, 0)); shellCoordinates.add(new Coordinate(101, 1)); - shellCoordinates.add(new Coordinate(100, 1)); + shellCoordinates.add(new Coordinate(100, 1, 10.0)); shellCoordinates.add(new Coordinate(100, 0)); List holeCoordinates = new ArrayList<>(); From 3cc3390f1762be06747439cd39c9c3f9ae4e6d46 Mon Sep 17 00:00:00 2001 From: Michael McCandless Date: Sat, 11 Apr 2015 06:02:59 -0400 Subject: [PATCH 20/22] don't return negative percentage when max < 0 --- .../org/elasticsearch/rest/action/cat/RestNodesAction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java b/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java index b712fd945fb..79111d59b69 100644 --- a/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java +++ b/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java @@ -358,9 +358,9 @@ public class RestNodesAction extends AbstractCatAction { * Calculate the percentage of {@code used} from the {@code max} number. * @param used The currently used number. * @param max The maximum number. - * @return 0 if {@code max} is 0. Otherwise 100 * {@code used} / {@code max}. + * @return 0 if {@code max} is <= 0. Otherwise 100 * {@code used} / {@code max}. */ private short calculatePercentage(long used, long max) { - return max == 0 ? 0 : (short)((100d * used) / max); + return max <= 0 ? 0 : (short)((100d * used) / max); } } From 4934def035bfda538b8a6070c65f0e5da601cec3 Mon Sep 17 00:00:00 2001 From: Britta Weber Date: Sat, 11 Apr 2015 13:13:56 +0200 Subject: [PATCH 21/22] [TEST] better test start and end messages print when cleaning up also for SingleNodeTests distinguish between after suite and after test cleanup --- .../test/ElasticsearchIntegrationTest.java | 16 ++++++++++++---- .../test/ElasticsearchSingleNodeTest.java | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java b/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java index 28f92be21ef..5ab3d216ee3 100644 --- a/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java +++ b/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java @@ -290,7 +290,7 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase cluster().beforeTest(getRandom(), getPerTestTransportClientRatio()); cluster().wipe(); randomIndexTemplate(); - logger.info("[{}#{}]: before test", getTestClass().getSimpleName(), getTestName()); + printTestMessage("before"); } catch (OutOfMemoryError e) { if (e.getMessage().contains("unable to create new native thread")) { ElasticsearchTestCase.printStackDump(logger); @@ -299,6 +299,14 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase } } + private void printTestMessage(String message) { + if (isSuiteScopedTest(getClass())) { + logger.info("[{}]: {} suite", getTestClass().getSimpleName(), message); + } else { + logger.info("[{}#{}]: {} test", getTestClass().getSimpleName(), getTestName(), message); + } + } + private Loading randomLoadingValues() { return randomFrom(Loading.values()); } @@ -590,9 +598,9 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase protected final void afterInternal(boolean afterClass) throws Exception { boolean success = false; try { - logger.info("[{}#{}]: cleaning up after test", getTestClass().getSimpleName(), getTestName()); - clearDisruptionScheme(); final Scope currentClusterScope = getCurrentClusterScope(); + printTestMessage("cleaning up after"); + clearDisruptionScheme(); try { if (cluster() != null) { if (currentClusterScope != Scope.TEST) { @@ -614,7 +622,7 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase clearClusters(); // it is ok to leave persistent / transient cluster state behind if scope is TEST } } - logger.info("[{}#{}]: cleaned up after test", getTestClass().getSimpleName(), getTestName()); + printTestMessage("cleaned up after"); success = true; } finally { if (!success) { diff --git a/src/test/java/org/elasticsearch/test/ElasticsearchSingleNodeTest.java b/src/test/java/org/elasticsearch/test/ElasticsearchSingleNodeTest.java index a638c820939..61145f773fc 100644 --- a/src/test/java/org/elasticsearch/test/ElasticsearchSingleNodeTest.java +++ b/src/test/java/org/elasticsearch/test/ElasticsearchSingleNodeTest.java @@ -87,6 +87,7 @@ public abstract class ElasticsearchSingleNodeTest extends ElasticsearchTestCase @After public void tearDown() throws Exception { + logger.info("[{}#{}]: cleaning up after test", getTestClass().getSimpleName(), getTestName()); super.tearDown(); cleanup(resetNodeAfterTest()); } @@ -230,7 +231,7 @@ public abstract class ElasticsearchSingleNodeTest extends ElasticsearchTestCase * It is useful to ensure that all action on the cluster have finished and all shards that were currently relocating * are now allocated and started. */ - public ClusterHealthStatus ensureGreen(String... indices) { + public ClusterHealthStatus ensureGreen(String... indices) { return ensureGreen(TimeValue.timeValueSeconds(30), indices); } From df118214c4c067ae3a1c3065cc9565788f1f2b97 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Mon, 13 Apr 2015 01:27:56 +0200 Subject: [PATCH 22/22] Update forbiddenapis to version 1.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 35b4a4c17fa..e838bb299dd 100644 --- a/pom.xml +++ b/pom.xml @@ -1410,7 +1410,7 @@ de.thetaphi forbiddenapis - 1.7 + 1.8