Repository: komoot/photon
Pull request #773 (2024-02-24 11:05:41)

Fix updates for objects with housenumbers
There has been a long-standing issue that updates of places with housenumbers as well as housenumber interpolation objects do not work properly. These places are added to the Photon database with a special database ID `<place_id>.<housenumber>` in order to allow multiple Photon objects for the same Nominatim place_id. This works fine on import but goes subtly wrong when doing updates, because update have only the information about the new state of a place, not the old one. Thus, it is not really possible to delete the old data for such a place because we don't know what database ID to look it up under.

This PR changes the database ID for such objects to `<place_id>.<seq_nr>`. When a place is inserted that needs to be exploded into multiple Photon documents with different housenumbers, they are simply assigned with a sequential ID. As a place is always updated as a whole, we can now simply delete all documents matching the pattern `<place_id>.<seq_nr>` by sequentially checking if there is such document in the database. If there is, delete it, if not, stop the entire process.

The change do not modify the database schema, so the code happily works with older database dumps. Only when you want to make use of the fixed update function, then you need to start off with a new dump created by this new code or you will see duplicate housenumbers creep into your database.

Tried on a planet to update the database and was able to catch up on OSM data at a rate of about 1day/hour (updating both, the Nominatim DB and the Photon DB).  This should be sufficient performance-wise.

The PR also finally adds tests for the update process and fixes an off-by-one error in the handling of new-style interpolations.


Patch:
diff --git a/src/main/java/de/komoot/photon/Importer.java b/src/main/java/de/komoot/photon/Importer.java
index bb1cccd..de04209 100644
--- a/src/main/java/de/komoot/photon/Importer.java
+++ b/src/main/java/de/komoot/photon/Importer.java
@@ -11,7 +11,7 @@ public interface Importer {
      *
      * @param doc
      */
-    public void add(PhotonDoc doc);
+    public void add(PhotonDoc doc, int object_id);
 
     /**
      * import is finished
diff --git a/src/main/java/de/komoot/photon/JsonDumper.java b/src/main/java/de/komoot/photon/JsonDumper.java
index 9ab056e..2dbd138 100644
--- a/src/main/java/de/komoot/photon/JsonDumper.java
+++ b/src/main/java/de/komoot/photon/JsonDumper.java
@@ -24,7 +24,7 @@ public class JsonDumper implements Importer {
     }
 
     @Override
-    public void add(PhotonDoc doc) {
+    public void add(PhotonDoc doc, int object_id) {
         try {
             writer.println("{\"index\": {}}");
             writer.println(Utils.convert(doc, languages, extraTags).string());
diff --git a/src/main/java/de/komoot/photon/PhotonDoc.java b/src/main/java/de/komoot/photon/PhotonDoc.java
index 2cb0fed..61facae 100644
--- a/src/main/java/de/komoot/photon/PhotonDoc.java
+++ b/src/main/java/de/komoot/photon/PhotonDoc.java
@@ -161,11 +161,11 @@ public class PhotonDoc {
         return this;
     }
 
-    public String getUid() {
-        if (houseNumber == null)
+    public String getUid(int object_id) {
+        if (object_id <= 0)
             return String.valueOf(placeId);
 
-        return String.valueOf(placeId) + "." + houseNumber;
+        return String.format("%d.%d", placeId, object_id);
     }
 
     public void copyName(Map<String, String> target, String target_field, String name_field) {
diff --git a/src/main/java/de/komoot/photon/Updater.java b/src/main/java/de/komoot/photon/Updater.java
index 908494f..1995c1d 100644
--- a/src/main/java/de/komoot/photon/Updater.java
+++ b/src/main/java/de/komoot/photon/Updater.java
@@ -1,12 +1,14 @@
 package de.komoot.photon;
 
 /**
- * @author felix
+ * Interface for classes accepting database updates.
  */
 public interface Updater {
-    public void create(PhotonDoc doc);
+    void create(PhotonDoc doc, int object_id);
 
-    public void delete(Long id);
+    void delete(long doc_id, int object_id);
 
-    public void finish();
+    boolean exists(long doc_id, int object_id);
+
+    void finish();
 }
diff --git a/src/main/java/de/komoot/photon/elasticsearch/Importer.java b/src/main/java/de/komoot/photon/elasticsearch/Importer.java
index cdc04e8..a445874 100644
--- a/src/main/java/de/komoot/photon/elasticsearch/Importer.java
+++ b/src/main/java/de/komoot/photon/elasticsearch/Importer.java
@@ -10,9 +10,7 @@ import org.elasticsearch.client.Client;
 import java.io.IOException;
 
 /**
- * elasticsearch importer
- *
- * @author felix
+ * Elasticsearch importer
  */
 @Slf4j
 public class Importer implements de.komoot.photon.Importer {
@@ -31,12 +29,13 @@ public class Importer implements de.komoot.photon.Importer {
     }
 
     @Override
-    public void add(PhotonDoc doc) {
+    public void add(PhotonDoc doc, int object_id) {
+        String uid = doc.getUid(object_id);
         try {
             this.bulkRequest.add(this.esClient.prepareIndex(PhotonIndex.NAME, PhotonIndex.TYPE).
-                    setSource(Utils.convert(doc, languages, extraTags)).setId(doc.getUid()));
+                    setSource(Utils.convert(doc, languages, extraTags)).setId(uid));
         } catch (IOException e) {
-            log.error("could not bulk add document " + doc.getUid(), e);
+            log.error("could not bulk add document " + uid, e);
             return;
         }
         this.documentCount += 1;
diff --git a/src/main/java/de/komoot/photon/elasticsearch/Updater.java b/src/main/java/de/komoot/photon/elasticsearch/Updater.java
index 01dd63e..4f8b378 100644
--- a/src/main/java/de/komoot/photon/elasticsearch/Updater.java
+++ b/src/main/java/de/komoot/photon/elasticsearch/Updater.java
@@ -33,16 +33,28 @@ public class Updater implements de.komoot.photon.Updater {
     }
 
     @Override
-    public void create(PhotonDoc doc) {
+    public void create(PhotonDoc doc, int object_id) {
+        String uid = doc.getUid(object_id);
         try {
-            bulkRequest.add(esClient.prepareIndex(PhotonIndex.NAME, PhotonIndex.TYPE).setSource(Utils.convert(doc, languages, extraTags)).setId(String.valueOf(doc.getPlaceId())));
+            bulkRequest.add(esClient.prepareIndex(PhotonIndex.NAME, PhotonIndex.TYPE).setSource(Utils.convert(doc, languages, extraTags)).setId(uid));
         } catch (IOException e) {
-            log.error(String.format("creation of new doc [%s] failed", doc), e);
+            log.error(String.format("creation of new doc [%s] failed", uid), e);
         }
     }
 
-    public void delete(Long id) {
-        this.bulkRequest.add(this.esClient.prepareDelete(PhotonIndex.NAME, PhotonIndex.TYPE, String.valueOf(id)));
+    public void delete(long doc_id, int object_id) {
+        this.bulkRequest.add(this.esClient.prepareDelete(PhotonIndex.NAME, PhotonIndex.TYPE, makeUid(doc_id, object_id)));
+    }
+
+    public boolean exists(long doc_id, int object_id) {
+        return esClient.prepareGet(PhotonIndex.NAME, PhotonIndex.TYPE, makeUid(doc_id, object_id)).execute().actionGet().isExists();
+    }
+
+    private String makeUid(Long doc_id, int object_id) {
+        if (object_id <= 0) {
+            return String.valueOf(doc_id);
+        }
+        return String.format("%d.%d", doc_id, object_id);
     }
 
     private void updateDocuments() {
diff --git a/src/main/java/de/komoot/photon/nominatim/ImportThread.java b/src/main/java/de/komoot/photon/nominatim/ImportThread.java
index 1442b38..2d3c6e7 100644
--- a/src/main/java/de/komoot/photon/nominatim/ImportThread.java
+++ b/src/main/java/de/komoot/photon/nominatim/ImportThread.java
@@ -4,6 +4,7 @@ import de.komoot.photon.Importer;
 import de.komoot.photon.PhotonDoc;
 import lombok.extern.slf4j.Slf4j;
 
+import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.atomic.AtomicLong;
@@ -11,8 +12,8 @@ import java.util.concurrent.atomic.AtomicLong;
 @Slf4j
 class ImportThread {
     private static final int PROGRESS_INTERVAL = 50000;
-    private static final PhotonDoc FINAL_DOCUMENT = new PhotonDoc(0, null, 0, null, null);
-    private final BlockingQueue<PhotonDoc> documents = new LinkedBlockingDeque<>(20);
+    private static final NominatimResult FINAL_DOCUMENT = new NominatimResult(new PhotonDoc(0, null, 0, null, null));
+    private final BlockingQueue<NominatimResult> documents = new LinkedBlockingDeque<>(20);
     private final AtomicLong counter = new AtomicLong();
     private final Importer importer;
     private final Thread thread;
@@ -31,22 +32,22 @@ class ImportThread {
      * @param docs Fully filled nominatim document.
      */
     public void addDocument(NominatimResult docs) {
-        for (PhotonDoc doc : docs.getDocsWithHousenumber()) {
-            while (true) {
-                try {
-                    documents.put(doc);
-                    break;
-                } catch (InterruptedException e) {
-                    log.warn("Thread interrupted while placing document in queue.");
-                    // Restore interrupted state.
-                    Thread.currentThread().interrupt();
-                }
-            }
-            if (counter.incrementAndGet() % PROGRESS_INTERVAL == 0) {
-                final double documentsPerSecond = 1000d * counter.longValue() / (System.currentTimeMillis() - startMillis);
-                log.info(String.format("imported %d documents [%.1f/second]", counter.longValue(), documentsPerSecond));
+        assert docs != null;
+        while (true) {
+            try {
+                documents.put(docs);
+                break;
+            } catch (InterruptedException e) {
+                log.warn("Thread interrupted while placing document in queue.");
+                // Restore interrupted state.
+                Thread.currentThread().interrupt();
             }
         }
+
+        if (counter.incrementAndGet() % PROGRESS_INTERVAL == 0) {
+            final double documentsPerSecond = 1000d * counter.longValue() / (System.currentTimeMillis() - startMillis);
+            log.info(String.format("imported %d documents [%.1f/second]", counter.longValue(), documentsPerSecond));
+        }
     }
 
     /**
@@ -74,12 +75,15 @@ class ImportThread {
         @Override
         public void run() {
             while (true) {
-                PhotonDoc doc;
                 try {
-                    doc = documents.take();
-                    if (doc == FINAL_DOCUMENT)
+                    NominatimResult docs = documents.take();
+                    if (docs == FINAL_DOCUMENT) {
                         break;
-                    importer.add(doc);
+                    }
+                    int object_id = 0;
+                    for (PhotonDoc doc : docs.getDocsWithHousenumber()) {
+                        importer.add(doc, object_id++);
+                    }
                 } catch (InterruptedException e) {
                     log.info("interrupted exception ", e);
                     // Restore interrupted state.
diff --git a/src/main/java/de/komoot/photon/nominatim/NominatimConnector.java b/src/main/java/de/komoot/photon/nominatim/NominatimConnector.java
index f5fd0ae..1c53ddc 100644
--- a/src/main/java/de/komoot/photon/nominatim/NominatimConnector.java
+++ b/src/main/java/de/komoot/photon/nominatim/NominatimConnector.java
@@ -171,7 +171,7 @@ public class NominatimConnector {
     }
 
     public List<PhotonDoc> getByPlaceId(long placeId) {
-        List<NominatimResult> result = template.query(SELECT_COLS_PLACEX + " FROM placex WHERE place_id = ?",
+        List<NominatimResult> result = template.query(SELECT_COLS_PLACEX + " FROM placex WHERE place_id = ? and indexed_status = 0",
                                                          placeRowMapper, placeId);
         if (result.size() == 0)
             return null;
@@ -181,7 +181,7 @@ public class NominatimConnector {
 
     public List<PhotonDoc> getInterpolationsByPlaceId(long placeId) {
         List<NominatimResult> result = template.query(selectOsmlineSql
-                                                          + " FROM location_property_osmline WHERE place_id = ?",
+                                                          + " FROM location_property_osmline WHERE place_id = ? and indexed_status = 0",
                                                           osmlineRowMapper, placeId);
         if (result.size() == 0)
             return null;
diff --git a/src/main/java/de/komoot/photon/nominatim/NominatimResult.java b/src/main/java/de/komoot/photon/nominatim/NominatimResult.java
index 062dd3e..8962aad 100644
--- a/src/main/java/de/komoot/photon/nominatim/NominatimResult.java
+++ b/src/main/java/de/komoot/photon/nominatim/NominatimResult.java
@@ -5,6 +5,7 @@ import com.vividsolutions.jts.geom.GeometryFactory;
 import com.vividsolutions.jts.geom.Point;
 import com.vividsolutions.jts.linearref.LengthIndexedLine;
 import de.komoot.photon.PhotonDoc;
+import lombok.extern.slf4j.Slf4j;
 
 import java.util.*;
 import java.util.regex.Pattern;
@@ -13,6 +14,7 @@ import java.util.regex.Pattern;
  * A Nominatim result consisting of the basic PhotonDoc for the object
  * and a map of attached house numbers together with their respective positions.
  */
+@Slf4j
 class NominatimResult {
     private PhotonDoc doc;
     private Map<String, Point> housenumbers;
@@ -144,20 +146,24 @@ class NominatimResult {
      * @param geom Geometry of the interpolation line.
      */
     public void addHouseNumbersFromInterpolation(long first, long last, long step, Geometry geom) {
-         if (last <= first || (last - first) > 1000)
+         if (last < first || (last - first) > 1000)
             return;
 
         if (housenumbers == null)
             housenumbers = new HashMap<>();
 
-        LengthIndexedLine line = new LengthIndexedLine(geom);
-        double si = line.getStartIndex();
-        double ei = line.getEndIndex();
-        double lstep = (ei - si) / (double) (last - first);
-
-        GeometryFactory fac = geom.getFactory();
-        for (long num = 1; first + num <= last; num += step) {
-            housenumbers.put(String.valueOf(num + first), fac.createPoint(line.extractPoint(si + lstep * num)));
+        if (last == first) {
+            housenumbers.put(String.valueOf(first), geom.getCentroid());
+        } else {
+            LengthIndexedLine line = new LengthIndexedLine(geom);
+            double si = line.getStartIndex();
+            double ei = line.getEndIndex();
+            double lstep = (ei - si) / (double) (last - first);
+
+            GeometryFactory fac = geom.getFactory();
+            for (long num = 0; first + num <= last; num += step) {
+                housenumbers.put(String.valueOf(num + first), fac.createPoint(line.extractPoint(si + lstep * num)));
+            }
         }
     }
 }
diff --git a/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java b/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java
index 07aeea9..41e0b20 100644
--- a/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java
+++ b/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java
@@ -6,6 +6,7 @@ import de.komoot.photon.nominatim.model.UpdateRow;
 import org.apache.commons.dbcp2.BasicDataSource;
 import org.springframework.jdbc.core.JdbcTemplate;
 
+import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.locks.ReentrantLock;
@@ -94,22 +95,29 @@ public class NominatimUpdater {
         int deletedPlaces = 0;
         for (UpdateRow place : getPlaces("placex")) {
             long placeId = place.getPlaceId();
+            int object_id = -1;
+            boolean check_for_multidoc = true;
+
+            if (!place.isToDelete()) {
+                final List<PhotonDoc> updatedDocs = exporter.getByPlaceId(placeId);
+                if (updatedDocs != null && !updatedDocs.isEmpty() && updatedDocs.get(0).isUsefulForIndex()) {
+                    check_for_multidoc = updatedDocs.get(0).getRankAddress() == 30;
+                    ++updatedPlaces;
+                    for (PhotonDoc updatedDoc : updatedDocs) {
+                            updater.create(updatedDoc, ++object_id);
+                    }
+                }
+            }
 
-            // Always delete to catch some corner cases around places with exploded housenumbers.
-            updater.delete(placeId);
-
-            if (place.isToDelete()) {
-                deletedPlaces++;
-                continue;
+            if (object_id < 0) {
+                ++deletedPlaces;
+                updater.delete(placeId, 0);
+                object_id = 0;
             }
 
-            final List<PhotonDoc> updatedDocs = exporter.getByPlaceId(placeId);
-            if (updatedDocs != null) {
-                updatedPlaces++;
-                for (PhotonDoc updatedDoc : updatedDocs) {
-                    if (updatedDoc.isUsefulForIndex()) {
-                        updater.create(updatedDoc);
-                    }
+            if (check_for_multidoc) {
+                while (updater.exists(placeId, ++object_id)) {
+                    updater.delete(placeId, object_id);
                 }
             }
         }
@@ -126,28 +134,31 @@ public class NominatimUpdater {
         LOGGER.info("Starting interpolations");
         int updatedInterpolations = 0;
         int deletedInterpolations = 0;
-        int interpolationDocuments = 0;
         for (UpdateRow place : getPlaces("location_property_osmline")) {
             long placeId = place.getPlaceId();
+            int object_id = -1;
+
+            if (!place.isToDelete()) {
+                final List<PhotonDoc> updatedDocs = exporter.getInterpolationsByPlaceId(placeId);
+                if (updatedDocs != null) {
+                    ++updatedInterpolations;
+                    for (PhotonDoc updatedDoc : updatedDocs) {
+                        updater.create(updatedDoc, ++object_id);
+                    }
+                }
+            }
 
-            updater.delete(placeId);
-            if (place.isToDelete()) {
-                deletedInterpolations++;
-                continue;
+            if (object_id < 0) {
+                ++deletedInterpolations;
             }
 
-            final List<PhotonDoc> updatedDocs = exporter.getInterpolationsByPlaceId(place.getPlaceId());
-            if (updatedDocs != null) {
-                updatedInterpolations++;
-                for (PhotonDoc updatedDoc : updatedDocs) {
-                    updater.create(updatedDoc);
-                    interpolationDocuments++;
-                }
+            while (updater.exists(placeId, ++object_id)) {
+                updater.delete(placeId, object_id);
             }
         }
-        LOGGER.info(String.format("%d interpolations created or updated, %d deleted, %d documents added or updated", updatedInterpolations,
-                deletedInterpolations, interpolationDocuments));
 
+        LOGGER.info(String.format("%d interpolations created or updated, %d deleted",
+                updatedInterpolations, deletedInterpolations));
     }
 
     private List<UpdateRow> getPlaces(String table) {
@@ -155,12 +166,24 @@ public class NominatimUpdater {
                 "DELETE FROM photon_updates WHERE rel = ?", "place_id, operation, indexed_date"),
                 (rs, rowNum) -> {
                     boolean isDelete = "DELETE".equals(rs.getString("operation"));
-                    return new UpdateRow(rs.getLong("place_id"), isDelete, rs.getDate("indexed_date"));
-                }, new Object[]{table});
-
-        results.sort(Comparator.comparing(UpdateRow::getUpdateDate));
+                    return new UpdateRow(rs.getLong("place_id"), isDelete, rs.getTimestamp("indexed_date"));
+                }, table);
+
+        // For each place only keep the newest item.
+        // Order doesn't really matter because updates of each place are independent now.
+        results.sort(Comparator.comparing(UpdateRow::getPlaceId).thenComparing(
+                     Comparator.comparing(UpdateRow::getUpdateDate).reversed()));
+
+        ArrayList<UpdateRow> todo = new ArrayList<>();
+        long prev_id = -1;
+        for (UpdateRow row: results) {
+            if (row.getPlaceId() != prev_id) {
+                prev_id = row.getPlaceId();
+                todo.add(row);
+            }
+        }
 
-        return results;
+        return todo;
     }
 
 


Test patch:
diff --git a/src/test/java/de/komoot/photon/ApiIntegrationTest.java b/src/test/java/de/komoot/photon/ApiIntegrationTest.java
index 0da6d4b..4c07595 100644
--- a/src/test/java/de/komoot/photon/ApiIntegrationTest.java
+++ b/src/test/java/de/komoot/photon/ApiIntegrationTest.java
@@ -25,8 +25,8 @@ public class ApiIntegrationTest extends ESBaseTester {
     public void setUp() throws Exception {
         setUpES();
         Importer instance = makeImporter();
-        instance.add(createDoc(13.38886, 52.51704, 1000, 1000, "place", "city").importance(0.6));
-        instance.add(createDoc(13.39026, 52.54714, 1001, 1001, "place", "town").importance(0.3));
+        instance.add(createDoc(13.38886, 52.51704, 1000, 1000, "place", "city").importance(0.6), 0);
+        instance.add(createDoc(13.39026, 52.54714, 1001, 1001, "place", "town").importance(0.3), 0);
         instance.finish();
         refresh();
     }
diff --git a/src/test/java/de/komoot/photon/api/ApiLanguagesTest.java b/src/test/java/de/komoot/photon/api/ApiLanguagesTest.java
index 4b25dc4..5fb23bf 100644
--- a/src/test/java/de/komoot/photon/api/ApiLanguagesTest.java
+++ b/src/test/java/de/komoot/photon/api/ApiLanguagesTest.java
@@ -51,9 +51,9 @@ public class ApiLanguagesTest extends ESBaseTester {
         setUpES(dataDirectory, languages);
         Importer instance = makeImporterWithLanguages(languages);
         instance.add(createDoc(1000, "place", "city",
-                "name:en", "thething", "name:fr", "letruc", "name:ch", "dasding"));
+                "name:en", "thething", "name:fr", "letruc", "name:ch", "dasding"), 0);
         instance.add(createDoc(1001, "place", "town",
-                "name:ch", "thething", "name:fr", "letruc", "name:en", "dasding"));
+                "name:ch", "thething", "name:fr", "letruc", "name:en", "dasding"), 0);
         instance.finish();
         refresh();
     }
diff --git a/src/test/java/de/komoot/photon/elasticsearch/ElasticResultTest.java b/src/test/java/de/komoot/photon/elasticsearch/ElasticResultTest.java
index 90b35a6..9261fae 100644
--- a/src/test/java/de/komoot/photon/elasticsearch/ElasticResultTest.java
+++ b/src/test/java/de/komoot/photon/elasticsearch/ElasticResultTest.java
@@ -52,16 +52,16 @@ public class ElasticResultTest  extends ESBaseTester {
                 .names(makeMap("name", "München", "name:it", "Monacco", "name:en", "Munich"))
                 .address(Collections.singletonMap("state", "Bavaria"))
                 .countryCode("de")
-                .extraTags(makeMap("population", "many", "capital", "yes", "maxage", "99")));
+                .extraTags(makeMap("population", "many", "capital", "yes", "maxage", "99")), 0);
         instance.add(createDoc(0, 0, 99, 11999, "place", "locality")
-                .names(makeMap("name", "null island")));
+                .names(makeMap("name", "null island")), 0);
         instance.add(createDoc(-179, 1.0001, 923, 1923, "place", "house")
                 .houseNumber("34")
                 .bbox(FACTORY.createMultiPoint(new Coordinate[]{new Coordinate(-179.5, 1.0),
                         new Coordinate(-178.5, 1.1)}))
-                .address(makeMap("street", "Hauptstr", "city", "Hamburg")));
+                .address(makeMap("street", "Hauptstr", "city", "Hamburg")), 0);
         instance.add(new PhotonDoc(42, "N", 42, "place", "hamlet")
-                .names(makeMap("name", "nowhere")));
+                .names(makeMap("name", "nowhere")), 0);
 
         instance.finish();
         refresh();
diff --git a/src/test/java/de/komoot/photon/elasticsearch/ImporterTest.java b/src/test/java/de/komoot/photon/elasticsearch/ImporterTest.java
index f73f283..04fefbe 100644
--- a/src/test/java/de/komoot/photon/elasticsearch/ImporterTest.java
+++ b/src/test/java/de/komoot/photon/elasticsearch/ImporterTest.java
@@ -26,7 +26,7 @@ public class ImporterTest extends ESBaseTester {
         Importer instance = makeImporterWithExtra("");
 
         instance.add(new PhotonDoc(1234, "N", 1000, "place", "city")
-                .extraTags(Collections.singletonMap("maxspeed", "100")));
+                .extraTags(Collections.singletonMap("maxspeed", "100")), 0);
         instance.finish();
         refresh();
 
@@ -43,14 +43,15 @@ public class ImporterTest extends ESBaseTester {
     }
 
     @Test
-    public void testAddHousenumberDoc() {
+    public void testAddHousenumberMultiDoc() {
         Importer instance = makeImporterWithExtra("");
 
-        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("34"));
+        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("34"), 0);
+        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("35"), 1);
         instance.finish();
         refresh();
 
-        PhotonResult response = getById("4432.34");
+        PhotonResult response = getById("4432");
 
         assertNotNull(response);
 
@@ -60,6 +61,15 @@ public class ImporterTest extends ESBaseTester {
         assertEquals("yes", response.get("osm_value"));
         assertEquals("34", response.get("housenumber"));
 
+        response = getById("4432.1");
+
+        assertNotNull(response);
+
+        assertEquals("N", response.get("osm_type"));
+        assertEquals(100, response.get("osm_id"));
+        assertEquals("building", response.get("osm_key"));
+        assertEquals("yes", response.get("osm_value"));
+        assertEquals("35", response.get("housenumber"));
     }
 
     @Test
@@ -71,9 +81,9 @@ public class ImporterTest extends ESBaseTester {
         extratags.put("maxspeed", "100 mph");
         extratags.put("source", "survey");
 
-        instance.add(new PhotonDoc(1234, "N", 1000, "place", "city").extraTags(extratags));
+        instance.add(new PhotonDoc(1234, "N", 1000, "place", "city").extraTags(extratags), 0);
         instance.add(new PhotonDoc(1235, "N", 1001, "place", "city")
-                .extraTags(Collections.singletonMap("wikidata", "100")));
+                .extraTags(Collections.singletonMap("wikidata", "100")), 0);
         instance.finish();
         refresh();
 
diff --git a/src/test/java/de/komoot/photon/elasticsearch/UpdaterTest.java b/src/test/java/de/komoot/photon/elasticsearch/UpdaterTest.java
index f8b1179..1e72cc7 100644
--- a/src/test/java/de/komoot/photon/elasticsearch/UpdaterTest.java
+++ b/src/test/java/de/komoot/photon/elasticsearch/UpdaterTest.java
@@ -24,13 +24,13 @@ public class UpdaterTest extends ESBaseTester {
 
         setUpES();
         Importer instance = makeImporter();
-        instance.add(doc);
+        instance.add(doc, 0);
         instance.finish();
         refresh();
 
         names.put("name:en", "Enfoo");
         Updater updater = makeUpdater();
-        updater.create(doc);
+        updater.create(doc, 0);
         updater.finish();
         refresh();
 
@@ -51,13 +51,13 @@ public class UpdaterTest extends ESBaseTester {
 
         setUpES();
         Importer instance = makeImporter();
-        instance.add(doc);
+        instance.add(doc, 0);
         instance.finish();
         refresh();
 
         names.remove("name");
         Updater updater = makeUpdater();
-        updater.create(doc);
+        updater.create(doc, 0);
         updater.finish();
         refresh();
 
@@ -77,7 +77,7 @@ public class UpdaterTest extends ESBaseTester {
 
         setUpES();
         Importer instance = makeImporterWithExtra("website");
-        instance.add(doc);
+        instance.add(doc, 0);
         instance.finish();
         refresh();
 
@@ -88,7 +88,7 @@ public class UpdaterTest extends ESBaseTester {
 
         doc.extraTags(Collections.singletonMap("website", "http://site.foo"));
         Updater updater = makeUpdaterWithExtra("website");
-        updater.create(doc);
+        updater.create(doc, 0);
         updater.finish();
         refresh();
 
@@ -100,4 +100,43 @@ public class UpdaterTest extends ESBaseTester {
         assertNotNull(extra);
         assertEquals(Collections.singletonMap("website", "http://site.foo"), extra);
     }
+
+    @Test void deleteDoc() throws IOException {
+        setUpES();
+        Importer instance = makeImporterWithExtra("website");
+        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("34"), 0);
+        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("35"), 1);
+        instance.finish();
+        refresh();
+
+        assertNotNull(getById("4432"));
+        assertNotNull(getById("4432.1"));
+
+        Updater updater = makeUpdaterWithExtra("website");
+        updater.delete(4432L, 1);
+        updater.finish();
+        refresh();
+
+        assertNotNull(getById("4432"));
+        assertNull(getById("4432.1"));
+    }
+
+    @Test
+    public void checkExistence() throws IOException {
+        setUpES();
+        Importer instance = makeImporterWithExtra("website");
+        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("34"), 0);
+        instance.add(new PhotonDoc(4432, "N", 100, "building", "yes").houseNumber("35"), 1);
+        instance.finish();
+        refresh();
+
+        Updater updater = makeUpdaterWithExtra("website");
+        assertTrue(updater.exists(4432L, 0));
+        assertTrue(updater.exists(4432L, 1));
+        assertFalse(updater.exists(4432L, 2));
+        assertFalse(updater.exists(4433L, 0));
+        assertFalse(updater.exists(4433L, 1));
+        updater.finish();
+        refresh();
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java b/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java
index 3b6d281..7412697 100644
--- a/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java
+++ b/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java
@@ -9,6 +9,7 @@ import de.komoot.photon.nominatim.testdb.CollectingImporter;
 import de.komoot.photon.nominatim.testdb.H2DataAdapter;
 import de.komoot.photon.nominatim.testdb.OsmlineTestRow;
 import de.komoot.photon.nominatim.testdb.PlacexTestRow;
+import lombok.extern.slf4j.Slf4j;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
@@ -17,7 +18,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
 
-
+@Slf4j
 public class NominatimConnectorDBTest {
     private EmbeddedDatabase db;
     private NominatimConnector connector;
@@ -121,21 +122,59 @@ public class NominatimConnectorDBTest {
         AssertUtil.assertNoAddress(doc, AddressType.STATE);
     }
 
+    @Test
+    public void testInterpolationPoint() throws ParseException {
+        PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
+
+        OsmlineTestRow osmline =
+                new OsmlineTestRow().number(45, 45, 1).parent(street).geom("POINT(45 23)").add(jdbc);
+
+        connector.readEntireDatabase();
+
+        assertEquals(2, importer.size());
+
+        PlacexTestRow expect = new PlacexTestRow("place", "house_number")
+                .id(osmline.getPlaceId())
+                .parent(street)
+                .osm("W", 23)
+                .centroid(45.0, 23.0);
+
+        importer.assertContains(expect, "45");
+    }
+
     @Test
     public void testInterpolationAny() throws ParseException {
         PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
 
         OsmlineTestRow osmline =
-                new OsmlineTestRow().number(1, 11, "all").parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
+                new OsmlineTestRow().number(1, 11, 1).parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
+
+        connector.readEntireDatabase();
+
+        assertEquals(12, importer.size());
+
+        PlacexTestRow expect = new PlacexTestRow("place", "house_number").id(osmline.getPlaceId()).parent(street).osm("W", 23);
+
+        for (int i = 1; i <= 11; ++i) {
+            importer.assertContains(expect.centroid(0, (i - 1) * 0.1), String.valueOf(i));
+        }
+    }
+
+    @Test
+    public void testInterpolationWithSteps() throws ParseException {
+        PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
+
+        OsmlineTestRow osmline =
+                new OsmlineTestRow().number(10, 20, 2).parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
 
         connector.readEntireDatabase();
 
-        assertEquals(10, importer.size());
+        assertEquals(7, importer.size());
 
         PlacexTestRow expect = new PlacexTestRow("place", "house_number").id(osmline.getPlaceId()).parent(street).osm("W", 23);
 
-        for (int i = 2; i < 11; ++i) {
-            importer.assertContains(expect.centroid(0, (i - 1) * 0.1), i);
+        for (int i = 0; i < 6; ++i) {
+            importer.assertContains(expect.centroid(0, i * 0.2), String.valueOf(10 + i * 2));
         }
     }
 
@@ -204,15 +243,15 @@ public class NominatimConnectorDBTest {
     @Test
     public void testObjectWithHousenumberList() throws ParseException {
         PlacexTestRow parent = PlacexTestRow.make_street("Main St").add(jdbc);
-        PlacexTestRow place = new PlacexTestRow("building", "yes").addr("housenumber", "1;2;3").parent(parent).add(jdbc);
+        PlacexTestRow place = new PlacexTestRow("building", "yes").addr("housenumber", "1;2a;3").parent(parent).add(jdbc);
 
         connector.readEntireDatabase();
 
         assertEquals(4, importer.size());
 
-        importer.assertContains(place, 1);
-        importer.assertContains(place, 2);
-        importer.assertContains(place, 3);
+        importer.assertContains(place, "1");
+        importer.assertContains(place, "2a");
+        importer.assertContains(place, "3");
     }
 
     /**
@@ -230,8 +269,8 @@ public class NominatimConnectorDBTest {
 
         assertEquals(3, importer.size());
 
-        importer.assertContains(place, 34);
-        importer.assertContains(place, 99521);
+        importer.assertContains(place, "34");
+        importer.assertContains(place, "99521");
     }
     /**
      * Unnamed objects are ignored when they do not have a housenumber.
diff --git a/src/test/java/de/komoot/photon/nominatim/NominatimUpdaterDBTest.java b/src/test/java/de/komoot/photon/nominatim/NominatimUpdaterDBTest.java
new file mode 100644
index 0000000..4c7c509
--- /dev/null
+++ b/src/test/java/de/komoot/photon/nominatim/NominatimUpdaterDBTest.java
@@ -0,0 +1,291 @@
+package de.komoot.photon.nominatim;
+
+import com.vividsolutions.jts.io.ParseException;
+import de.komoot.photon.ReflectionTestUtil;
+import de.komoot.photon.nominatim.testdb.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class NominatimUpdaterDBTest {
+    private EmbeddedDatabase db;
+    private NominatimUpdater connector;
+    private CollectingUpdater updater;
+    private JdbcTemplate jdbc;
+
+    @BeforeEach
+    public void setup() {
+        db = new EmbeddedDatabaseBuilder()
+                .setType(EmbeddedDatabaseType.H2)
+                .generateUniqueName(true)
+                .addScript("/test-schema.sql")
+                .build();
+
+
+        connector = new NominatimUpdater(null, 0, null, null, null, new H2DataAdapter());
+        updater = new CollectingUpdater();
+        connector.setUpdater(updater);
+
+        jdbc = new JdbcTemplate(db);
+        ReflectionTestUtil.setFieldValue(connector, "template", jdbc);
+        ReflectionTestUtil.setFieldValue(connector, "exporter", "template", jdbc);
+    }
+
+    @Test
+    public void testSimpleInsert() {
+        PlacexTestRow place = new PlacexTestRow("place", "city").name("Town").add(jdbc);
+        (new PhotonUpdateRow("placex", place.getPlaceId(), "UPDATE")).add(jdbc);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(1, updater.numCreated());
+
+        updater.assertHasCreated(place.getPlaceId());
+    }
+
+    @Test
+    public void testSimpleDelete() {
+        final long place_id = 47836;
+        (new PhotonUpdateRow("placex", place_id, "DELETE")).add(jdbc);
+
+        updater.add_existing(place_id, 0);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(1, updater.numDeleted());
+        assertEquals(0, updater.numCreated());
+
+        updater.assertHasDeleted(place_id);
+    }
+
+    @Test
+    public void testSimpleDeleteNonExisting() {
+        final long place_id = 28119;
+        (new PhotonUpdateRow("placex", place_id, "DELETE")).add(jdbc);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(1, updater.numDeleted());
+        assertEquals(0, updater.numCreated());
+
+        updater.assertHasDeleted(place_id);
+    }
+
+    @Test
+    public void testDeleteMultiDocument() {
+        final long place_id = 887;
+        (new PhotonUpdateRow("placex", place_id, "DELETE")).add(jdbc);
+
+        updater.add_existing(place_id, 0, 1, 2, 3);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(4, updater.numDeleted());
+        assertEquals(0, updater.numCreated());
+
+        updater.assertHasDeleted(place_id, 4);
+    }
+
+    @Test
+    public void testUpdateLowerRanks() {
+        PlacexTestRow place = new PlacexTestRow("place", "city").name("Town").rankAddress(12).add(jdbc);
+        (new PhotonUpdateRow("placex", place.getPlaceId(), "UPDATE")).add(jdbc);
+
+        // Pretending to have two documents for the place in question to check that
+        // the algorithm stops at the first.
+        // In practise, a rankAddress<30 document cannot have duplicates.
+        updater.add_existing(place.getPlaceId(), 0, 1);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(1, updater.numCreated());
+
+        updater.assertHasCreated(place.getPlaceId());
+    }
+
+    @Test
+    public void testUpdateSimpleRank30() {
+        PlacexTestRow place = new PlacexTestRow("building", "yes").housenumber(23).rankAddress(30).add(jdbc);
+        (new PhotonUpdateRow("placex", place.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(place.getPlaceId(), 0);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(1, updater.numCreated());
+
+        updater.assertHasCreated(place.getPlaceId());
+    }
+
+    @Test
+    public void testUpdateRank30MoreHousenumbers() {
+        PlacexTestRow place = new PlacexTestRow("building", "yes").addr("housenumber", "1;2a;3").rankAddress(30).add(jdbc);
+        (new PhotonUpdateRow("placex", place.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(place.getPlaceId(), 0);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(3, updater.numCreated());
+
+        updater.assertHasCreated(place.getPlaceId(), "1");
+        updater.assertHasCreated(place.getPlaceId(), "2a");
+        updater.assertHasCreated(place.getPlaceId(), "3");
+    }
+
+    @Test
+    public void testUpdateRank30SameHousenumbers() {
+        PlacexTestRow place = new PlacexTestRow("building", "yes").addr("housenumber", "1;2a;3").rankAddress(30).add(jdbc);
+        (new PhotonUpdateRow("placex", place.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(place.getPlaceId(), 0, 1, 2);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(3, updater.numCreated());
+
+        updater.assertHasCreated(place.getPlaceId(), "1");
+        updater.assertHasCreated(place.getPlaceId(), "2a");
+        updater.assertHasCreated(place.getPlaceId(), "3");
+    }
+
+    @Test
+    public void testUpdateRank30LessHousenumbers() {
+        PlacexTestRow place = new PlacexTestRow("building", "yes").addr("housenumber", "1").rankAddress(30).add(jdbc);
+        (new PhotonUpdateRow("placex", place.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(place.getPlaceId(), 0, 1, 2);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(2, updater.numDeleted());
+        assertEquals(1, updater.numCreated());
+
+        updater.assertHasCreated(place.getPlaceId(), "1");
+        updater.assertHasDeleted(place.getPlaceId(), 2);
+    }
+
+    @Test
+    public void testInsertInterpolation() {
+        PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
+
+        OsmlineTestRow osmline =
+                new OsmlineTestRow().number(6, 8, 1).parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
+        (new PhotonUpdateRow("location_property_osmline", osmline.getPlaceId(), "UPDATE")).add(jdbc);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(3, updater.numCreated());
+
+        updater.assertHasCreated(osmline.getPlaceId(), "6");
+        updater.assertHasCreated(osmline.getPlaceId(), "7");
+        updater.assertHasCreated(osmline.getPlaceId(), "8");
+    }
+
+    @Test
+    public void testUpdateInterpolationSameLength() {
+        PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
+
+        OsmlineTestRow osmline =
+                new OsmlineTestRow().number(6, 8, 1).parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
+        (new PhotonUpdateRow("location_property_osmline", osmline.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(osmline.getPlaceId(), 0, 1, 2);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(3, updater.numCreated());
+
+        updater.assertHasCreated(osmline.getPlaceId(), "6");
+        updater.assertHasCreated(osmline.getPlaceId(), "7");
+        updater.assertHasCreated(osmline.getPlaceId(), "8");
+    }
+
+     @Test
+    public void testUpdateInterpolationLonger() {
+        PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
+
+        OsmlineTestRow osmline =
+                new OsmlineTestRow().number(6, 8, 1).parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
+        (new PhotonUpdateRow("location_property_osmline", osmline.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(osmline.getPlaceId(), 0);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(0, updater.numDeleted());
+        assertEquals(3, updater.numCreated());
+
+        updater.assertHasCreated(osmline.getPlaceId(), "6");
+        updater.assertHasCreated(osmline.getPlaceId(), "7");
+        updater.assertHasCreated(osmline.getPlaceId(), "8");
+    }
+   @Test
+    public void testUpdateInterpolationShorter() {
+        PlacexTestRow street = PlacexTestRow.make_street("La strada").add(jdbc);
+
+        OsmlineTestRow osmline =
+                new OsmlineTestRow().number(6, 8, 1).parent(street).geom("LINESTRING(0 0, 0 1)").add(jdbc);
+        (new PhotonUpdateRow("location_property_osmline", osmline.getPlaceId(), "UPDATE")).add(jdbc);
+
+        updater.add_existing(osmline.getPlaceId(), 0, 1, 2, 3, 4);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(2, updater.numDeleted());
+        assertEquals(3, updater.numCreated());
+
+        updater.assertHasCreated(osmline.getPlaceId(), "6");
+        updater.assertHasCreated(osmline.getPlaceId(), "7");
+        updater.assertHasCreated(osmline.getPlaceId(), "8");
+        updater.assertHasDeleted(osmline.getPlaceId(), 2);
+    }
+
+    @Test
+    public void testUpdateWithDuplicatesDeleteLast() throws InterruptedException {
+        PlacexTestRow place1 = new PlacexTestRow("place", "city").name("Town").rankAddress(12).add(jdbc);
+        PlacexTestRow place2 = new PlacexTestRow("building", "yes").housenumber(23).rankAddress(30).add(jdbc);
+
+        (new PhotonUpdateRow("placex", place2.getPlaceId(), "UPDATE")).add(jdbc);
+        (new PhotonUpdateRow("placex", place1.getPlaceId(), "UPDATE")).add(jdbc);
+        Thread.sleep(2L);
+        (new PhotonUpdateRow("placex", place2.getPlaceId(), "UPDATE")).add(jdbc);
+        Thread.sleep(2L);
+        (new PhotonUpdateRow("placex", place1.getPlaceId(), "UPDATE")).add(jdbc);
+        (new PhotonUpdateRow("placex", place2.getPlaceId(), "DELETE")).add(jdbc);
+
+        connector.update();
+        updater.assertFinishCalled();
+
+        assertEquals(1, updater.numDeleted());
+        assertEquals(1, updater.numCreated());
+
+        updater.assertHasCreated(place1.getPlaceId());
+        updater.assertHasDeleted(place2.getPlaceId());
+    }
+}
diff --git a/src/test/java/de/komoot/photon/nominatim/testdb/CollectingImporter.java b/src/test/java/de/komoot/photon/nominatim/testdb/CollectingImporter.java
index 90db98d..4d052c8 100644
--- a/src/test/java/de/komoot/photon/nominatim/testdb/CollectingImporter.java
+++ b/src/test/java/de/komoot/photon/nominatim/testdb/CollectingImporter.java
@@ -7,18 +7,20 @@ import lombok.extern.slf4j.Slf4j;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.*;
 
 @Slf4j
 public class CollectingImporter implements Importer {
-    private List<PhotonDoc> docs = new ArrayList<>();
+    private List<Map.Entry<Integer, PhotonDoc>> docs = new ArrayList<>();
     private int finishCalled = 0;
 
 
     @Override
-    public void add(PhotonDoc doc) {
-        docs.add(doc);
+    public void add(PhotonDoc doc, int object_id)
+    {
+        docs.add(Map.entry(object_id, doc));
     }
 
     @Override
@@ -39,9 +41,9 @@ public class CollectingImporter implements Importer {
     }
 
     public PhotonDoc get(long placeId) {
-        for (PhotonDoc doc : docs) {
-            if (doc.getPlaceId() == placeId) {
-                return doc;
+        for (Map.Entry<Integer, PhotonDoc> doc : docs) {
+            if (doc.getValue().getPlaceId() == placeId) {
+                return doc.getValue();
             }
         }
 
@@ -51,10 +53,10 @@ public class CollectingImporter implements Importer {
 
     public void assertContains(PlacexTestRow row) throws ParseException {
         PhotonDoc doc = null;
-        for (PhotonDoc outdoc : docs) {
-            if (outdoc.getPlaceId() == row.getPlaceId()) {
+        for (Map.Entry<Integer, PhotonDoc> outdoc : docs) {
+            if (outdoc.getValue().getPlaceId() == row.getPlaceId()) {
                 assertNull(doc, "Row is contained multiple times");
-                doc = outdoc;
+                doc = outdoc.getValue();
             }
         }
 
@@ -63,13 +65,13 @@ public class CollectingImporter implements Importer {
         row.assertEquals(doc);
     }
 
-    public void assertContains(PlacexTestRow row, int housenumber) throws ParseException {
-        String hnrstr = Integer.toString(housenumber);
+    public void assertContains(PlacexTestRow row, String housenumber) throws ParseException {
         PhotonDoc doc = null;
-        for (PhotonDoc outdoc : docs) {
-            if (outdoc.getPlaceId() == row.getPlaceId() && hnrstr.equals(outdoc.getHouseNumber())) {
+        for (Map.Entry<Integer, PhotonDoc> outdoc : docs) {
+            if (outdoc.getValue().getPlaceId() == row.getPlaceId()
+                    && housenumber.equals(outdoc.getValue().getHouseNumber())) {
                 assertNull(doc, "Row is contained multiple times");
-                doc = outdoc;
+                doc = outdoc.getValue();
             }
         }
 
diff --git a/src/test/java/de/komoot/photon/nominatim/testdb/CollectingUpdater.java b/src/test/java/de/komoot/photon/nominatim/testdb/CollectingUpdater.java
new file mode 100644
index 0000000..d0c0f7f
--- /dev/null
+++ b/src/test/java/de/komoot/photon/nominatim/testdb/CollectingUpdater.java
@@ -0,0 +1,107 @@
+package de.komoot.photon.nominatim.testdb;
+
+import de.komoot.photon.PhotonDoc;
+import de.komoot.photon.Updater;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Slf4j
+public class CollectingUpdater implements Updater {
+    private List<Map.Entry<Integer, PhotonDoc>> created = new ArrayList<>();
+    private List<Map.Entry<Integer, Long>> deleted = new ArrayList<>();
+    private List<Map.Entry<Integer, Long>> existing = new ArrayList<>();
+    private int finishCalled = 0;
+
+    @Override
+    public void create(PhotonDoc doc, int object_id) {
+        created.add(Map.entry(object_id, doc));
+    }
+
+    @Override
+    public void delete(long id, int object_id) {
+        deleted.add(Map.entry(object_id, id));
+    }
+
+    @Override
+    public boolean exists(long id, int object_id) {
+        for (Map.Entry<Integer, Long> entry: existing) {
+            if (entry.getKey() == object_id && id == entry.getValue()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void finish() { ++finishCalled; }
+
+
+    public void add_existing(long place_id, int ... object_id)
+    {
+        for (int o: object_id) {
+            existing.add(Map.entry(o, place_id));
+        }
+    }
+
+
+    public void assertFinishCalled() {
+        assertEquals(finishCalled, 1);
+    }
+
+    public int numDeleted() {
+        return deleted.size();
+    }
+
+    public int numCreated() {
+        return created.size();
+    }
+
+
+    public void assertHasCreated(long id) {
+        int object_id = -1;
+        for (Map.Entry<Integer, PhotonDoc> outdoc : created) {
+            if (outdoc.getValue().getPlaceId() == id) {
+                assertTrue(object_id == -1, "Row is contained multiple times");
+                object_id = outdoc.getKey();
+            }
+        }
+
+        assertTrue(object_id >= 0, "Row not found");
+        assertTrue(object_id == 0, "Row inserted with a non-zero object id");
+    }
+
+    public void assertHasCreated(long id, String housenumber) {
+        int object_id = -1;
+        for (Map.Entry<Integer, PhotonDoc> outdoc : created) {
+            PhotonDoc doc = outdoc.getValue();
+            if (doc.getPlaceId() == id && housenumber.equals(doc.getHouseNumber())) {
+                assertTrue(object_id == -1, "Row is contained multiple times");
+                object_id = outdoc.getKey();
+            }
+        }
+
+        assertTrue(object_id >= 0, "Row not found");
+    }
+
+    public void assertHasDeleted(long id) {
+        assertHasDeleted(id, 1);
+    }
+
+    public void assertHasDeleted(long id, int num) {
+        int numFound = 0;
+        for (Map.Entry<Integer, Long> outdoc : deleted) {
+            if (outdoc.getValue() == id) {
+                ++numFound;
+            }
+        }
+
+        assertEquals(num, numFound);
+    }
+
+}
diff --git a/src/test/java/de/komoot/photon/nominatim/testdb/H2DataAdapter.java b/src/test/java/de/komoot/photon/nominatim/testdb/H2DataAdapter.java
index 40b2a0d..82de92c 100644
--- a/src/test/java/de/komoot/photon/nominatim/testdb/H2DataAdapter.java
+++ b/src/test/java/de/komoot/photon/nominatim/testdb/H2DataAdapter.java
@@ -43,7 +43,11 @@ public class H2DataAdapter implements DBDataAdapter {
     }
 
     @Override
-    public boolean hasColumn(JdbcTemplate template, String table, String column) {
+    public boolean hasColumn(JdbcTemplate template, String table, String column)
+    {
+        if ("location_property_osmline".equals(table) && "step".equals(column)) {
+            return true;
+        }
         return false;
     }
 
diff --git a/src/test/java/de/komoot/photon/nominatim/testdb/OsmlineTestRow.java b/src/test/java/de/komoot/photon/nominatim/testdb/OsmlineTestRow.java
index b8107ee..93c516e 100644
--- a/src/test/java/de/komoot/photon/nominatim/testdb/OsmlineTestRow.java
+++ b/src/test/java/de/komoot/photon/nominatim/testdb/OsmlineTestRow.java
@@ -12,7 +12,7 @@ public class OsmlineTestRow {
     private Long osmId = 23L;
     private Integer startnumber;
     private Integer endnumber;
-    private String interpolationtype;
+    private Integer step;
     private String countryCode = "de";
     private String lineGeo;
 
@@ -21,10 +21,10 @@ public class OsmlineTestRow {
         lineGeo = "LINESTRING(0 0, 0.1 0.1, 0 0.2)";
     }
 
-    public OsmlineTestRow number(int start, int end, String type) {
+    public OsmlineTestRow number(int start, int end, int step) {
         startnumber = start;
         endnumber = end;
-        interpolationtype = type;
+        this.step = step;
         return this;
     }
 
@@ -40,9 +40,9 @@ public class OsmlineTestRow {
 
     public OsmlineTestRow add(JdbcTemplate jdbc) {
         jdbc.update("INSERT INTO location_property_osmline (place_id, parent_place_id, osm_id,"
-                        + " startnumber, endnumber, interpolationtype, linegeo, country_code)"
-                        + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
-                placeId, parentPlaceId, osmId, startnumber, endnumber, interpolationtype, lineGeo, countryCode);
+                        + " startnumber, endnumber, step, linegeo, country_code, indexed_status)"
+                        + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)",
+                placeId, parentPlaceId, osmId, startnumber, endnumber, step, lineGeo, countryCode);
 
         return this;
     }
diff --git a/src/test/java/de/komoot/photon/nominatim/testdb/PhotonUpdateRow.java b/src/test/java/de/komoot/photon/nominatim/testdb/PhotonUpdateRow.java
new file mode 100644
index 0000000..39cc107
--- /dev/null
+++ b/src/test/java/de/komoot/photon/nominatim/testdb/PhotonUpdateRow.java
@@ -0,0 +1,26 @@
+package de.komoot.photon.nominatim.testdb;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.Date;
+
+public class PhotonUpdateRow {
+    private String rel;
+    private Long placeId;
+    private String operation;
+
+
+    public PhotonUpdateRow(String rel, Long placeId, String operation) {
+        this.rel = rel;
+        this.placeId = placeId;
+        this.operation = operation;
+    }
+
+     public PhotonUpdateRow add(JdbcTemplate jdbc) {
+        jdbc.update("INSERT INTO photon_updates (rel, place_id, operation, indexed_date)"
+                        + "VALUES (?, ?, ?, ?)",
+                    rel, placeId, operation, new Date());
+
+        return this;
+    }
+}
diff --git a/src/test/java/de/komoot/photon/nominatim/testdb/PlacexTestRow.java b/src/test/java/de/komoot/photon/nominatim/testdb/PlacexTestRow.java
index 08e9218..5f9419f 100644
--- a/src/test/java/de/komoot/photon/nominatim/testdb/PlacexTestRow.java
+++ b/src/test/java/de/komoot/photon/nominatim/testdb/PlacexTestRow.java
@@ -114,8 +114,8 @@ public class PlacexTestRow {
 
     public PlacexTestRow add(JdbcTemplate jdbc) {
         jdbc.update("INSERT INTO placex (place_id, parent_place_id, osm_type, osm_id, class, type, rank_search, rank_address,"
-                        + " centroid, name, country_code, importance, address)"
-                        + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? FORMAT JSON, ?, ?, ? FORMAT JSON)",
+                        + " centroid, name, country_code, importance, address, indexed_status)"
+                        + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? FORMAT JSON, ?, ?, ? FORMAT JSON, 0)",
                 placeId, parentPlaceId, osmType, osmId, key, value, rankSearch, rankAddress, centroid,
                 asJson(names), countryCode, importance, asJson(address));
 
diff --git a/src/test/java/de/komoot/photon/query/QueryBasicSearchTest.java b/src/test/java/de/komoot/photon/query/QueryBasicSearchTest.java
index e63bced..ca9a1d4 100644
--- a/src/test/java/de/komoot/photon/query/QueryBasicSearchTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryBasicSearchTest.java
@@ -46,7 +46,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
     @Test
     public void testSearchByDefaultName() {
         Importer instance = makeImporter();
-        instance.add(createDoc("name", "Muffle Flu"));
+        instance.add(createDoc("name", "Muffle Flu"), 0);
         instance.finish();
         refresh();
 
@@ -63,7 +63,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
     @Test
     public void testSearchNameSkipTerms() {
         Importer instance = makeImporter();
-        instance.add(createDoc("name", "Hunted House Hotel"));
+        instance.add(createDoc("name", "Hunted House Hotel"), 0);
         instance.finish();
         refresh();
 
@@ -80,7 +80,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
         Importer instance = makeImporter();
         instance.add(createDoc("name", "original", "alt_name", "alt", "old_name", "older", "int_name", "int",
                                "loc_name", "local", "reg_name", "regional", "addr:housename", "house",
-                               "other_name", "other"));
+                               "other_name", "other"), 0);
         instance.finish();
         refresh();
 
@@ -107,7 +107,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
         address.put("state", "Estado");
 
         Importer instance = makeImporter();
-        instance.add(createDoc("name", "Castillo").address(address));
+        instance.add(createDoc("name", "Castillo").address(address), 0);
         instance.finish();
         refresh();
 
@@ -124,7 +124,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
     @Test
     public void testSearchMustContainANameTerm() {
         Importer instance = makeImporter();
-        instance.add(createDoc("name", "Palermo").address(Collections.singletonMap("state", "Sicilia")));
+        instance.add(createDoc("name", "Palermo").address(Collections.singletonMap("state", "Sicilia")), 0);
         instance.finish();
         refresh();
 
@@ -141,7 +141,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
     @Test
     public void testSearchWithHousenumberNamed() {
         Importer instance = makeImporter();
-        instance.add(createDoc("name", "Edeka").houseNumber("5").address(Collections.singletonMap("street", "Hauptstrasse")));
+        instance.add(createDoc("name", "Edeka").houseNumber("5").address(Collections.singletonMap("street", "Hauptstrasse")), 0);
         instance.finish();
         refresh();
 
@@ -156,7 +156,7 @@ public class QueryBasicSearchTest extends ESBaseTester {
     @Test
     public void testSearchWithHousenumberUnnamed() {
         Importer instance = makeImporter();
-        instance.add(createDoc().houseNumber("5").address(Collections.singletonMap("street", "Hauptstrasse")));
+        instance.add(createDoc().houseNumber("5").address(Collections.singletonMap("street", "Hauptstrasse")), 0);
         instance.finish();
         refresh();
 
diff --git a/src/test/java/de/komoot/photon/query/QueryByClassificationTest.java b/src/test/java/de/komoot/photon/query/QueryByClassificationTest.java
index 8c9acf3..e6b52f3 100644
--- a/src/test/java/de/komoot/photon/query/QueryByClassificationTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryByClassificationTest.java
@@ -72,7 +72,7 @@ public class QueryByClassificationTest extends ESBaseTester {
     @Test
     public void testQueryByClassificationString() {
         Importer instance = makeImporter();
-        instance.add(createDoc("amenity", "restaurant", "curliflower"));
+        instance.add(createDoc("amenity", "restaurant", "curliflower"), 0);
         instance.finish();
         refresh();
 
@@ -93,7 +93,7 @@ public class QueryByClassificationTest extends ESBaseTester {
     @Test
     public void testQueryByClassificationSynonym() {
         Importer instance = makeImporter();
-        instance.add(createDoc("amenity", "restaurant", "curliflower"));
+        instance.add(createDoc("amenity", "restaurant", "curliflower"), 0);
         instance.finish();
         refresh();
 
@@ -113,8 +113,8 @@ public class QueryByClassificationTest extends ESBaseTester {
     @Test
     public void testSynonymDoNotInterfereWithWords() {
         Importer instance = makeImporter();
-        instance.add(createDoc("amenity", "restaurant", "airport"));
-        instance.add(createDoc("aeroway", "terminal", "Houston"));
+        instance.add(createDoc("amenity", "restaurant", "airport"), 0);
+        instance.add(createDoc("aeroway", "terminal", "Houston"), 0);
         instance.finish();
         refresh();
 
@@ -133,8 +133,8 @@ public class QueryByClassificationTest extends ESBaseTester {
     @Test
     public void testSameSynonymForDifferentTags() {
         Importer instance = makeImporter();
-        instance.add(createDoc("railway", "halt", "Newtown"));
-        instance.add(createDoc("railway", "station", "King's Cross"));
+        instance.add(createDoc("railway", "halt", "Newtown"), 0);
+        instance.add(createDoc("railway", "station", "King's Cross"), 0);
         instance.finish();
         refresh();
 
diff --git a/src/test/java/de/komoot/photon/query/QueryByLanguageTest.java b/src/test/java/de/komoot/photon/query/QueryByLanguageTest.java
index 29e3b8e..aaf0205 100644
--- a/src/test/java/de/komoot/photon/query/QueryByLanguageTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryByLanguageTest.java
@@ -50,7 +50,7 @@ public class QueryByLanguageTest extends ESBaseTester {
     public void queryNonStandardLanguages() throws IOException {
         Importer instance = setup("en", "fi");
 
-        instance.add(createDoc("name", "original", "name:fi", "finish", "name:ru", "russian"));
+        instance.add(createDoc("name", "original", "name:fi", "finish", "name:ru", "russian"), 0);
 
         instance.finish();
         refresh();
@@ -68,7 +68,7 @@ public class QueryByLanguageTest extends ESBaseTester {
     @Test
     public void queryAltNames() throws IOException {
         Importer instance = setup("de");
-        instance.add(createDoc("name", "simple", "alt_name", "ancient", "name:de", "einfach"));
+        instance.add(createDoc("name", "simple", "alt_name", "ancient", "name:de", "einfach"), 0);
         instance.finish();
         refresh();
 
@@ -92,7 +92,7 @@ public class QueryByLanguageTest extends ESBaseTester {
 
         doc.setAddressPartIfNew(addressType, address_names);
 
-        instance.add(doc);
+        instance.add(doc, 0);
         instance.finish();
         refresh();
 
@@ -104,7 +104,7 @@ public class QueryByLanguageTest extends ESBaseTester {
     @ValueSource(strings = {"default", "de", "en"})
     public void queryAltNamesFuzzy(String lang) throws IOException {
         Importer instance = setup("de", "en");
-        instance.add(createDoc("name", "simple", "alt_name", "ancient", "name:de", "einfach"));
+        instance.add(createDoc("name", "simple", "alt_name", "ancient", "name:de", "einfach"), 0);
         instance.finish();
         refresh();
 
diff --git a/src/test/java/de/komoot/photon/query/QueryFilterLayerTest.java b/src/test/java/de/komoot/photon/query/QueryFilterLayerTest.java
index 552f933..55d6899 100644
--- a/src/test/java/de/komoot/photon/query/QueryFilterLayerTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryFilterLayerTest.java
@@ -32,7 +32,7 @@ public class QueryFilterLayerTest extends ESBaseTester {
 
         int[] docRanks = {10, 13, 14, 22}; // state, city * 2, locality
         for (int rank : docRanks) {
-            instance.add(createDoc(++id, rank));
+            instance.add(createDoc(++id, rank), 0);
         }
 
         instance.finish();
diff --git a/src/test/java/de/komoot/photon/query/QueryFilterTagValueTest.java b/src/test/java/de/komoot/photon/query/QueryFilterTagValueTest.java
index f26520c..d5bb0fe 100644
--- a/src/test/java/de/komoot/photon/query/QueryFilterTagValueTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryFilterTagValueTest.java
@@ -43,11 +43,11 @@ public class QueryFilterTagValueTest extends ESBaseTester {
             String key = TAGS[i];
             String value = TAGS[++i];
             PhotonDoc doc = this.createDoc(lon, lat, i, i, key, value);
-            instance.add(doc);
+            instance.add(doc, 0);
             lon += 0.00004;
             lat += 0.00086;
             doc = this.createDoc(lon, lat, i + 1, i + 1, key, value);
-            instance.add(doc);
+            instance.add(doc, 0);
             lon += 0.00004;
             lat += 0.00086;
         }
diff --git a/src/test/java/de/komoot/photon/query/QueryRelevanceTest.java b/src/test/java/de/komoot/photon/query/QueryRelevanceTest.java
index 2653031..537685b 100644
--- a/src/test/java/de/komoot/photon/query/QueryRelevanceTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryRelevanceTest.java
@@ -49,8 +49,8 @@ public class QueryRelevanceTest extends ESBaseTester {
     @Test
     public void testRelevanceByImportance() {
         Importer instance = makeImporter();
-        instance.add(createDoc("amenity", "restuarant", 1001, "name", "New York").importance(0.0));
-        instance.add(createDoc("place", "city", 2000, "name", "New York").importance(0.5));
+        instance.add(createDoc("amenity", "restuarant", 1001, "name", "New York").importance(0.0), 0);
+        instance.add(createDoc("place", "city", 2000, "name", "New York").importance(0.5), 0);
         instance.finish();
         refresh();
 
@@ -63,8 +63,8 @@ public class QueryRelevanceTest extends ESBaseTester {
     @Test
     public void testFullNameOverPartialName() {
         Importer instance = makeImporter();
-        instance.add(createDoc("place", "hamlet", 1000, "name", "Ham"));
-        instance.add(createDoc("place", "hamlet", 1001, "name", "Hamburg"));
+        instance.add(createDoc("place", "hamlet", 1000, "name", "Ham"), 0);
+        instance.add(createDoc("place", "hamlet", 1001, "name", "Hamburg"), 0);
         instance.finish();
         refresh();
 
@@ -77,8 +77,8 @@ public class QueryRelevanceTest extends ESBaseTester {
     @Test
     public void testPartialNameWithImportanceOverFullName() {
         Importer instance = makeImporter();
-        instance.add(createDoc("place", "hamlet", 1000, "name", "Ham").importance(0.1));
-        instance.add(createDoc("place", "city", 1001, "name", "Hamburg").importance(0.5));
+        instance.add(createDoc("place", "hamlet", 1000, "name", "Ham").importance(0.1), 0);
+        instance.add(createDoc("place", "city", 1001, "name", "Hamburg").importance(0.5), 0);
         instance.finish();
         refresh();
 
@@ -93,9 +93,9 @@ public class QueryRelevanceTest extends ESBaseTester {
     void testLocationPreferenceForEqualImportance(String placeName) {
         Importer instance = makeImporter();
         instance.add(createDoc("place", "hamlet", 1000, "name", "Ham")
-                .centroid(FACTORY.createPoint(new Coordinate(10, 10))));
+                .centroid(FACTORY.createPoint(new Coordinate(10, 10))), 0);
         instance.add(createDoc("place", "hamlet", 1001, "name", placeName)
-                .centroid(FACTORY.createPoint(new Coordinate(-10, -10))));
+                .centroid(FACTORY.createPoint(new Coordinate(-10, -10))), 0);
         instance.finish();
         refresh();
 
@@ -111,10 +111,10 @@ public class QueryRelevanceTest extends ESBaseTester {
         Importer instance = makeImporter();
         instance.add(createDoc("place", "hamlet", 1000, "name", "Ham")
                         .importance(0.8)
-                .centroid(FACTORY.createPoint(new Coordinate(10, 10))));
+                .centroid(FACTORY.createPoint(new Coordinate(10, 10))), 0);
         instance.add(createDoc("place", "hamlet", 1001, "name", "Ham")
                         .importance(0.01)
-                .centroid(FACTORY.createPoint(new Coordinate(-10, -10))));
+                .centroid(FACTORY.createPoint(new Coordinate(-10, -10))), 0);
         instance.finish();
         refresh();
 
diff --git a/src/test/java/de/komoot/photon/query/QueryReverseFilterLayerTest.java b/src/test/java/de/komoot/photon/query/QueryReverseFilterLayerTest.java
index d750c5a..d853500 100644
--- a/src/test/java/de/komoot/photon/query/QueryReverseFilterLayerTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryReverseFilterLayerTest.java
@@ -36,7 +36,7 @@ public class QueryReverseFilterLayerTest extends ESBaseTester {
 
         int[] docRanks = {10, 13, 14, 22}; // state, city * 2, locality
         for (int rank : docRanks) {
-            instance.add(createDoc(++id, rank));
+            instance.add(createDoc(++id, rank), 0);
         }
 
         instance.finish();
diff --git a/src/test/java/de/komoot/photon/query/QueryReverseFilterTagValueTest.java b/src/test/java/de/komoot/photon/query/QueryReverseFilterTagValueTest.java
index cd655fd..ec034fd 100644
--- a/src/test/java/de/komoot/photon/query/QueryReverseFilterTagValueTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryReverseFilterTagValueTest.java
@@ -48,11 +48,11 @@ public class QueryReverseFilterTagValueTest extends ESBaseTester {
             String key = TAGS[i];
             String value = TAGS[++i];
             PhotonDoc doc = this.createDoc(lon, lat, i, i, key, value);
-            instance.add(doc);
+            instance.add(doc, 0);
             lon += 0.00004;
             lat += 0.00006;
             doc = this.createDoc(lon, lat, i + 1, i + 1, key, value);
-            instance.add(doc);
+            instance.add(doc, 0);
             lon += 0.00004;
             lat += 0.00006;
         }
diff --git a/src/test/java/de/komoot/photon/query/QueryReverseTest.java b/src/test/java/de/komoot/photon/query/QueryReverseTest.java
index fbfdfca..7c82fe7 100644
--- a/src/test/java/de/komoot/photon/query/QueryReverseTest.java
+++ b/src/test/java/de/komoot/photon/query/QueryReverseTest.java
@@ -30,10 +30,10 @@ public class QueryReverseTest extends ESBaseTester {
         setUpES(instanceTestDirectory, "en");
 
         Importer instance = makeImporter();
-        instance.add(createDoc(10,10, 100, 100, "place", "house"));
-        instance.add(createDoc(10,10.1, 101, 101, "place", "house"));
-        instance.add(createDoc(10,10.2, 102, 102, "place", "house"));
-        instance.add(createDoc(-10,-10, 202, 102, "place", "house"));
+        instance.add(createDoc(10,10, 100, 100, "place", "house"), 0);
+        instance.add(createDoc(10,10.1, 101, 101, "place", "house"), 0);
+        instance.add(createDoc(10,10.2, 102, 102, "place", "house"), 0);
+        instance.add(createDoc(-10,-10, 202, 102, "place", "house"), 0);
         instance.finish();
         refresh();
     }
diff --git a/src/test/resources/test-schema.sql b/src/test/resources/test-schema.sql
index f808d3e..b0c3ea4 100644
--- a/src/test/resources/test-schema.sql
+++ b/src/test/resources/test-schema.sql
@@ -42,10 +42,10 @@ CREATE TABLE location_property_osmline (
     indexed_date TIMESTAMP,
     startnumber INTEGER,
     endnumber INTEGER,
+    step SMALLINT,
     partition SMALLINT,
     indexed_status SMALLINT,
     linegeo GEOMETRY,
-    interpolationtype TEXT,
     address JSON,
     postcode TEXT,
     country_code VARCHAR(2)