1 from itertools import chain
5 from place_inserter import PlaceColumn
6 from table_compare import NominatimID, DBRow
9 def check_database_integrity(context):
10 """ Check some generic constraints on the tables.
12 # place_addressline should not have duplicate (place_id, address_place_id)
13 cur = context.db.cursor()
14 cur.execute("""SELECT count(*) FROM
15 (SELECT place_id, address_place_id, count(*) as c
16 FROM place_addressline GROUP BY place_id, address_place_id) x
18 assert cur.fetchone()[0] == 0, "Duplicates found in place_addressline"
21 ################################ GIVEN ##################################
23 @given("the (?P<named>named )?places")
24 def add_data_to_place_table(context, named):
25 """ Add entries into the place table. 'named places' makes sure that
26 the entries get a random name when none is explicitly given.
28 with context.db.cursor() as cur:
29 cur.execute('ALTER TABLE place DISABLE TRIGGER place_before_insert')
30 for row in context.table:
31 PlaceColumn(context).add_row(row, named is not None).db_insert(cur)
32 cur.execute('ALTER TABLE place ENABLE TRIGGER place_before_insert')
34 @given("the relations")
35 def add_data_to_planet_relations(context):
36 """ Add entries into the osm2pgsql relation middle table. This is needed
37 for tests on data that looks up members.
39 with context.db.cursor() as cur:
40 for r in context.table:
46 for m in r['members'].split(','):
49 parts.insert(last_node, int(mid.oid))
53 parts.insert(last_way, int(mid.oid))
56 parts.append(int(mid.oid))
58 members.extend((mid.typ.lower() + mid.oid, mid.cls or ''))
62 tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings if h.startswith("tags+")])
64 cur.execute("""INSERT INTO planet_osm_rels (id, way_off, rel_off, parts, members, tags)
65 VALUES (%s, %s, %s, %s, %s, %s)""",
66 (r['id'], last_node, last_way, parts, members, list(tags)))
69 def add_data_to_planet_ways(context):
70 """ Add entries into the osm2pgsql way middle table. This is necessary for
71 tests on that that looks up node ids in this table.
73 with context.db.cursor() as cur:
74 for r in context.table:
75 tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings if h.startswith("tags+")])
76 nodes = [ int(x.strip()) for x in r['nodes'].split(',') ]
78 cur.execute("INSERT INTO planet_osm_ways (id, nodes, tags) VALUES (%s, %s, %s)",
79 (r['id'], nodes, list(tags)))
81 ################################ WHEN ##################################
84 def import_and_index_data_from_place_table(context):
85 """ Import data previously set up in the place table.
87 context.nominatim.copy_from_place(context.db)
88 context.nominatim.run_setup_script('calculate-postcodes', 'index', 'index-noanalyse')
89 check_database_integrity(context)
91 @when("updating places")
92 def update_place_table(context):
93 """ Update the place table with the given data. Also runs all triggers
94 related to updates and reindexes the new data.
96 context.nominatim.run_setup_script(
97 'create-functions', 'create-partition-functions', 'enable-diff-updates')
98 with context.db.cursor() as cur:
99 for row in context.table:
100 PlaceColumn(context).add_row(row, False).db_insert(cur)
102 context.nominatim.reindex_placex(context.db)
103 check_database_integrity(context)
105 @when("updating postcodes")
106 def update_postcodes(context):
107 """ Rerun the calculation of postcodes.
109 context.nominatim.run_update_script('calculate-postcodes')
111 @when("marking for delete (?P<oids>.*)")
112 def delete_places(context, oids):
113 """ Remove entries from the place table. Multiple ids may be given
114 separated by commas. Also runs all triggers
115 related to updates and reindexes the new data.
117 context.nominatim.run_setup_script(
118 'create-functions', 'create-partition-functions', 'enable-diff-updates')
119 with context.db.cursor() as cur:
120 for oid in oids.split(','):
121 NominatimID(oid).query_osm_id(cur, 'DELETE FROM place WHERE {}')
123 context.nominatim.reindex_placex(context.db)
125 ################################ THEN ##################################
127 @then("(?P<table>placex|place) contains(?P<exact> exactly)?")
128 def check_place_contents(context, table, exact):
129 """ Check contents of place/placex tables. Each row represents a table row
130 and all data must match. Data not present in the expected table, may
131 be arbitry. The rows are identified via the 'object' column which must
132 have an identifier of the form '<NRW><osm id>[:<class>]'. When multiple
133 rows match (for example because 'class' was left out and there are
134 multiple entries for the given OSM object) then all must match. All
135 expected rows are expected to be present with at least one database row.
136 When 'exactly' is given, there must not be additional rows in the database.
138 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
139 expected_content = set()
140 for row in context.table:
141 nid = NominatimID(row['object'])
142 query = 'SELECT *, ST_AsText(geometry) as geomtxt, ST_GeometryType(geometry) as geometrytype'
143 if table == 'placex':
144 query += ' ,ST_X(centroid) as cx, ST_Y(centroid) as cy'
145 query += " FROM %s WHERE {}" % (table, )
146 nid.query_osm_id(cur, query)
147 assert cur.rowcount > 0, "No rows found for " + row['object']
151 expected_content.add((res['osm_type'], res['osm_id'], res['class']))
153 DBRow(nid, res, context).assert_row(row, ['object'])
156 cur.execute('SELECT osm_type, osm_id, class from {}'.format(table))
157 assert expected_content == set([(r[0], r[1], r[2]) for r in cur])
160 @then("(?P<table>placex|place) has no entry for (?P<oid>.*)")
161 def check_place_has_entry(context, table, oid):
162 """ Ensure that no database row for the given object exists. The ID
163 must be of the form '<NRW><osm id>[:<class>]'.
165 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
166 NominatimID(oid).query_osm_id(cur, "SELECT * FROM %s where {}" % table)
167 assert cur.rowcount == 0, \
168 "Found {} entries for ID {}".format(cur.rowcount, oid)
171 @then("search_name contains(?P<exclude> not)?")
172 def check_search_name_contents(context, exclude):
173 """ Check contents of place/placex tables. Each row represents a table row
174 and all data must match. Data not present in the expected table, may
175 be arbitry. The rows are identified via the 'object' column which must
176 have an identifier of the form '<NRW><osm id>[:<class>]'. All
177 expected rows are expected to be present with at least one database row.
179 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
180 for row in context.table:
181 nid = NominatimID(row['object'])
182 nid.row_by_place_id(cur, 'search_name',
183 ['ST_X(centroid) as cx', 'ST_Y(centroid) as cy'])
184 assert cur.rowcount > 0, "No rows found for " + row['object']
187 db_row = DBRow(nid, res, context)
188 for name, value in zip(row.headings, row.cells):
189 if name in ('name_vector', 'nameaddress_vector'):
190 items = [x.strip() for x in value.split(',')]
191 with context.db.cursor() as subcur:
192 subcur.execute(""" SELECT word_id, word_token
193 FROM word, (SELECT unnest(%s::TEXT[]) as term) t
194 WHERE word_token = make_standard_name(t.term)
195 and class is null and country_code is null
198 SELECT word_id, word_token
199 FROM word, (SELECT unnest(%s::TEXT[]) as term) t
200 WHERE word_token = ' ' || make_standard_name(t.term)
201 and class is null and country_code is null
204 (list(filter(lambda x: not x.startswith('#'), items)),
205 list(filter(lambda x: x.startswith('#'), items))))
207 assert subcur.rowcount >= len(items), \
208 "No word entry found for {}. Entries found: {!s}".format(value, subcur.rowcount)
210 present = wid[0] in res[name]
212 assert not present, "Found term for {}/{}: {}".format(row['object'], name, wid[1])
214 assert present, "Missing term for {}/{}: {}".fromat(row['object'], name, wid[1])
215 elif name != 'object':
216 assert db_row.contains(name, value), db_row.assert_msg(name, value)
218 @then("search_name has no entry for (?P<oid>.*)")
219 def check_search_name_has_entry(context, oid):
220 """ Check that there is noentry in the search_name table for the given
221 objects. IDs are in format '<NRW><osm id>[:<class>]'.
223 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
224 NominatimID(oid).row_by_place_id(cur, 'search_name')
226 assert cur.rowcount == 0, \
227 "Found {} entries for ID {}".format(cur.rowcount, oid)
229 @then("location_postcode contains exactly")
230 def check_location_postcode(context):
231 """ Check full contents for location_postcode table. Each row represents a table row
232 and all data must match. Data not present in the expected table, may
233 be arbitry. The rows are identified via 'country' and 'postcode' columns.
234 All rows must be present as excepted and there must not be additional
237 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
238 cur.execute("SELECT *, ST_AsText(geometry) as geomtxt FROM location_postcode")
239 assert cur.rowcount == len(list(context.table)), \
240 "Postcode table has {} rows, expected {}.".foramt(cur.rowcount, len(list(context.table)))
244 key = (row['country_code'], row['postcode'])
245 assert key not in results, "Postcode table has duplicate entry: {}".format(row)
246 results[key] = DBRow((row['country_code'],row['postcode']), row, context)
248 for row in context.table:
249 db_row = results.get((row['country'],row['postcode']))
250 assert db_row is not None, \
251 "Missing row for country '{r['country']}' postcode '{r['postcode']}'.".format(r=row)
253 db_row.assert_row(row, ('country', 'postcode'))
255 @then("word contains(?P<exclude> not)?")
256 def check_word_table(context, exclude):
257 """ Check the contents of the word table. Each row represents a table row
258 and all data must match. Data not present in the expected table, may
259 be arbitry. The rows are identified via all given columns.
261 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
262 for row in context.table:
263 wheres = ' AND '.join(["{} = %s".format(h) for h in row.headings])
264 cur.execute("SELECT * from word WHERE " + wheres, list(row.cells))
266 assert cur.rowcount == 0, "Row still in word table: %s" % '/'.join(values)
268 assert cur.rowcount > 0, "Row not in word table: %s" % '/'.join(values)
270 @then("place_addressline contains")
271 def check_place_addressline(context):
272 """ Check the contents of the place_addressline table. Each row represents
273 a table row and all data must match. Data not present in the expected
274 table, may be arbitry. The rows are identified via the 'object' column,
275 representing the addressee and the 'address' column, representing the
278 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
279 for row in context.table:
280 nid = NominatimID(row['object'])
281 pid = nid.get_place_id(cur)
282 apid = NominatimID(row['address']).get_place_id(cur)
283 cur.execute(""" SELECT * FROM place_addressline
284 WHERE place_id = %s AND address_place_id = %s""",
286 assert cur.rowcount > 0, \
287 "No rows found for place %s and address %s" % (row['object'], row['address'])
290 DBRow(nid, res, context).assert_row(row, ('address', 'object'))
292 @then("place_addressline doesn't contain")
293 def check_place_addressline_exclude(context):
294 """ Check that the place_addressline doesn't contain any entries for the
295 given addressee/address item pairs.
297 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
298 for row in context.table:
299 pid = NominatimID(row['object']).get_place_id(cur)
300 apid = NominatimID(row['address']).get_place_id(cur)
301 cur.execute(""" SELECT * FROM place_addressline
302 WHERE place_id = %s AND address_place_id = %s""",
304 assert cur.rowcount == 0, \
305 "Row found for place %s and address %s" % (row['object'], row['address'])
307 @then("W(?P<oid>\d+) expands to(?P<neg> no)? interpolation")
308 def check_location_property_osmline(context, oid, neg):
309 """ Check that the given way is present in the interpolation table.
311 with context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
312 cur.execute("""SELECT *, ST_AsText(linegeo) as geomtxt
313 FROM location_property_osmline
314 WHERE osm_id = %s AND startnumber IS NOT NULL""",
318 assert cur.rowcount == 0, "Interpolation found for way {}.".format(oid)
321 todo = list(range(len(list(context.table))))
324 row = context.table[i]
325 if (int(row['start']) == res['startnumber']
326 and int(row['end']) == res['endnumber']):
330 assert False, "Unexpected row " + str(res)
332 DBRow(oid, res, context).assert_row(row, ('start', 'end'))