]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/tokenizer/legacy_tokenizer.sql
use information from tokenizer to determine street vs. place address
[nominatim.git] / lib-sql / tokenizer / legacy_tokenizer.sql
1 -- SPDX-License-Identifier: GPL-2.0-only
2 --
3 -- This file is part of Nominatim. (https://nominatim.org)
4 --
5 -- Copyright (C) 2022 by the Nominatim developer community.
6 -- For a full list of authors see the git log.
7
8 -- Get tokens used for searching the given place.
9 --
10 -- These are the tokens that will be saved in the search_name table.
11 CREATE OR REPLACE FUNCTION token_get_name_search_tokens(info JSONB)
12   RETURNS INTEGER[]
13 AS $$
14   SELECT (info->>'names')::INTEGER[]
15 $$ LANGUAGE SQL IMMUTABLE STRICT;
16
17
18 -- Get tokens for matching the place name against others.
19 --
20 -- This should usually be restricted to full name tokens.
21 CREATE OR REPLACE FUNCTION token_get_name_match_tokens(info JSONB)
22   RETURNS INTEGER[]
23 AS $$
24   SELECT (info->>'names')::INTEGER[]
25 $$ LANGUAGE SQL IMMUTABLE STRICT;
26
27
28 -- Return the housenumber tokens applicable for the place.
29 CREATE OR REPLACE FUNCTION token_get_housenumber_search_tokens(info JSONB)
30   RETURNS INTEGER[]
31 AS $$
32   SELECT (info->>'hnr_tokens')::INTEGER[]
33 $$ LANGUAGE SQL IMMUTABLE STRICT;
34
35
36 -- Return the housenumber in the form that it can be matched during search.
37 CREATE OR REPLACE FUNCTION token_normalized_housenumber(info JSONB)
38   RETURNS TEXT
39 AS $$
40   SELECT info->>'hnr';
41 $$ LANGUAGE SQL IMMUTABLE STRICT;
42
43
44 CREATE OR REPLACE FUNCTION token_is_street_address(info JSONB)
45   RETURNS BOOLEAN
46 AS $$
47   SELECT info->>'street' is not null or info->>'place' is null;
48 $$ LANGUAGE SQL IMMUTABLE;
49
50
51 CREATE OR REPLACE FUNCTION token_has_addr_street(info JSONB)
52   RETURNS BOOLEAN
53 AS $$
54   SELECT info->>'street' is not null;
55 $$ LANGUAGE SQL IMMUTABLE;
56
57
58 CREATE OR REPLACE FUNCTION token_has_addr_place(info JSONB)
59   RETURNS BOOLEAN
60 AS $$
61   SELECT info->>'place_match' is not null;
62 $$ LANGUAGE SQL IMMUTABLE;
63
64
65 CREATE OR REPLACE FUNCTION token_matches_street(info JSONB, street_tokens INTEGER[])
66   RETURNS BOOLEAN
67 AS $$
68   SELECT (info->>'street')::INTEGER[] && street_tokens
69 $$ LANGUAGE SQL IMMUTABLE STRICT;
70
71
72 CREATE OR REPLACE FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[])
73   RETURNS BOOLEAN
74 AS $$
75   SELECT (info->>'place_match')::INTEGER[] && place_tokens
76 $$ LANGUAGE SQL IMMUTABLE STRICT;
77
78
79 CREATE OR REPLACE FUNCTION token_addr_place_search_tokens(info JSONB)
80   RETURNS INTEGER[]
81 AS $$
82   SELECT (info->>'place_search')::INTEGER[]
83 $$ LANGUAGE SQL IMMUTABLE STRICT;
84
85
86 CREATE OR REPLACE FUNCTION token_get_address_keys(info JSONB)
87   RETURNS SETOF TEXT
88 AS $$
89   SELECT * FROM jsonb_object_keys(info->'addr');
90 $$ LANGUAGE SQL IMMUTABLE STRICT;
91
92
93 CREATE OR REPLACE FUNCTION token_get_address_search_tokens(info JSONB, key TEXT)
94   RETURNS INTEGER[]
95 AS $$
96   SELECT (info->'addr'->key->>0)::INTEGER[];
97 $$ LANGUAGE SQL IMMUTABLE STRICT;
98
99
100 CREATE OR REPLACE FUNCTION token_matches_address(info JSONB, key TEXT, tokens INTEGER[])
101   RETURNS BOOLEAN
102 AS $$
103   SELECT (info->'addr'->key->>1)::INTEGER[] && tokens;
104 $$ LANGUAGE SQL IMMUTABLE STRICT;
105
106
107 CREATE OR REPLACE FUNCTION token_get_postcode(info JSONB)
108   RETURNS TEXT
109 AS $$
110   SELECT info->>'postcode';
111 $$ LANGUAGE SQL IMMUTABLE STRICT;
112
113
114 -- Return token info that should be saved permanently in the database.
115 CREATE OR REPLACE FUNCTION token_strip_info(info JSONB)
116   RETURNS JSONB
117 AS $$
118   SELECT NULL::JSONB;
119 $$ LANGUAGE SQL IMMUTABLE STRICT;
120
121 --------------- private functions ----------------------------------------------
122
123 -- Functions for term normalisation and access to the 'word' table.
124
125 CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text
126   AS '{{ modulepath }}/nominatim.so', 'transliteration'
127 LANGUAGE c IMMUTABLE STRICT;
128
129
130 CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text
131   AS '{{ modulepath }}/nominatim.so', 'gettokenstring'
132 LANGUAGE c IMMUTABLE STRICT;
133
134
135 CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT
136   AS $$
137 DECLARE
138   o TEXT;
139 BEGIN
140   o := public.gettokenstring(public.transliteration(name));
141   RETURN trim(substr(o,1,length(o)));
142 END;
143 $$
144 LANGUAGE plpgsql IMMUTABLE;
145
146 -- returns NULL if the word is too common
147 CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT) 
148   RETURNS INTEGER
149   AS $$
150 DECLARE
151   lookup_token TEXT;
152   return_word_id INTEGER;
153   count INTEGER;
154 BEGIN
155   lookup_token := trim(lookup_word);
156   SELECT min(word_id), max(search_name_count) FROM word
157     WHERE word_token = lookup_token and class is null and type is null
158     INTO return_word_id, count;
159   IF return_word_id IS NULL THEN
160     return_word_id := nextval('seq_word');
161     INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0);
162   ELSE
163     IF count > {{ max_word_freq }} THEN
164       return_word_id := NULL;
165     END IF;
166   END IF;
167   RETURN return_word_id;
168 END;
169 $$
170 LANGUAGE plpgsql;
171
172
173 -- Create housenumber tokens from an OSM addr:housenumber.
174 -- The housnumber is split at comma and semicolon as necessary.
175 -- The function returns the normalized form of the housenumber suitable
176 -- for comparison.
177 CREATE OR REPLACE FUNCTION create_housenumbers(housenumbers TEXT[],
178                                                OUT tokens TEXT,
179                                                OUT normtext TEXT)
180   AS $$
181 BEGIN
182   SELECT array_to_string(array_agg(trans), ';'), array_agg(tid)::TEXT
183     INTO normtext, tokens
184     FROM (SELECT lookup_word as trans, getorcreate_housenumber_id(lookup_word) as tid
185           FROM (SELECT make_standard_name(h) as lookup_word
186                 FROM unnest(housenumbers) h) x) y;
187 END;
188 $$ LANGUAGE plpgsql STABLE STRICT;
189
190
191 CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
192   RETURNS INTEGER
193   AS $$
194 DECLARE
195   lookup_token TEXT;
196   return_word_id INTEGER;
197 BEGIN
198   lookup_token := ' ' || trim(lookup_word);
199   SELECT min(word_id) FROM word
200     WHERE word_token = lookup_token and class='place' and type='house'
201     INTO return_word_id;
202   IF return_word_id IS NULL THEN
203     return_word_id := nextval('seq_word');
204     INSERT INTO word VALUES (return_word_id, lookup_token, null,
205                              'place', 'house', null, 0);
206   END IF;
207   RETURN return_word_id;
208 END;
209 $$
210 LANGUAGE plpgsql;
211
212
213 CREATE OR REPLACE FUNCTION create_postcode_id(postcode TEXT)
214   RETURNS BOOLEAN
215   AS $$
216 DECLARE
217   r RECORD;
218   lookup_token TEXT;
219   return_word_id INTEGER;
220 BEGIN
221   lookup_token := ' ' || make_standard_name(postcode);
222   FOR r IN
223     SELECT word_id FROM word
224     WHERE word_token = lookup_token and word = postcode
225           and class='place' and type='postcode'
226   LOOP
227     RETURN false;
228   END LOOP;
229
230   INSERT INTO word VALUES (nextval('seq_word'), lookup_token, postcode,
231                            'place', 'postcode', null, 0);
232   RETURN true;
233 END;
234 $$
235 LANGUAGE plpgsql;
236
237
238 CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT, src_word TEXT)
239   RETURNS INTEGER
240   AS $$
241 DECLARE
242   lookup_token TEXT;
243   nospace_lookup_token TEXT;
244   return_word_id INTEGER;
245 BEGIN
246   lookup_token := ' '||trim(lookup_word);
247   SELECT min(word_id) FROM word
248   WHERE word_token = lookup_token and class is null and type is null
249   INTO return_word_id;
250   IF return_word_id IS NULL THEN
251     return_word_id := nextval('seq_word');
252     INSERT INTO word VALUES (return_word_id, lookup_token, src_word,
253                              null, null, null, 0);
254   END IF;
255   RETURN return_word_id;
256 END;
257 $$
258 LANGUAGE plpgsql;
259
260
261 -- Normalize a string and lookup its word ids (partial words).
262 CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
263   RETURNS INTEGER[]
264   AS $$
265 DECLARE
266   words TEXT[];
267   id INTEGER;
268   return_word_id INTEGER[];
269   word_ids INTEGER[];
270   j INTEGER;
271 BEGIN
272   words := string_to_array(make_standard_name(lookup_word), ' ');
273   IF array_upper(words, 1) IS NOT NULL THEN
274     FOR j IN 1..array_upper(words, 1) LOOP
275       IF (words[j] != '') THEN
276         SELECT array_agg(word_id) INTO word_ids
277           FROM word
278          WHERE word_token = words[j] and class is null and type is null;
279
280         IF word_ids IS NULL THEN
281           id := nextval('seq_word');
282           INSERT INTO word VALUES (id, words[j], null, null, null, null, 0);
283           return_word_id := return_word_id || id;
284         ELSE
285           return_word_id := array_merge(return_word_id, word_ids);
286         END IF;
287       END IF;
288     END LOOP;
289   END IF;
290
291   RETURN return_word_id;
292 END;
293 $$
294 LANGUAGE plpgsql;
295
296
297 -- Normalize a string and look up its name ids (full words).
298 CREATE OR REPLACE FUNCTION word_ids_from_name(lookup_word TEXT)
299   RETURNS INTEGER[]
300   AS $$
301 DECLARE
302   lookup_token TEXT;
303   return_word_ids INTEGER[];
304 BEGIN
305   lookup_token := ' '|| make_standard_name(lookup_word);
306   SELECT array_agg(word_id) FROM word
307     WHERE word_token = lookup_token and class is null and type is null
308     INTO return_word_ids;
309   RETURN return_word_ids;
310 END;
311 $$
312 LANGUAGE plpgsql STABLE STRICT;
313
314
315 CREATE OR REPLACE FUNCTION make_keywords(src HSTORE)
316   RETURNS INTEGER[]
317   AS $$
318 DECLARE
319   result INTEGER[];
320   s TEXT;
321   w INTEGER;
322   words TEXT[];
323   value TEXT;
324   j INTEGER;
325 BEGIN
326   result := '{}'::INTEGER[];
327
328   FOR value IN SELECT unnest(regexp_split_to_array(svals(src), E'[,;]')) LOOP
329     -- full name
330     s := make_standard_name(value);
331     w := getorcreate_name_id(s, value);
332
333     IF not(ARRAY[w] <@ result) THEN
334       result := result || w;
335     END IF;
336
337     -- partial single-word terms
338     words := string_to_array(s, ' ');
339     IF array_upper(words, 1) IS NOT NULL THEN
340       FOR j IN 1..array_upper(words, 1) LOOP
341         IF (words[j] != '') THEN
342           w = getorcreate_word_id(words[j]);
343           IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
344             result := result || w;
345           END IF;
346         END IF;
347       END LOOP;
348     END IF;
349
350     -- consider parts before an opening braket a full word as well
351     words := regexp_split_to_array(value, E'[(]');
352     IF array_upper(words, 1) > 1 THEN
353       s := make_standard_name(words[1]);
354       IF s != '' THEN
355         w := getorcreate_name_id(s, words[1]);
356         IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
357           result := result || w;
358         END IF;
359       END IF;
360     END IF;
361
362     s := regexp_replace(value, '市$', '');
363     IF s != value THEN
364       s := make_standard_name(s);
365       IF s != '' THEN
366         w := getorcreate_name_id(s, value);
367         IF NOT (ARRAY[w] <@ result) THEN
368           result := result || w;
369         END IF;
370       END IF;
371     END IF;
372
373   END LOOP;
374
375   RETURN result;
376 END;
377 $$
378 LANGUAGE plpgsql;
379
380
381 CREATE OR REPLACE FUNCTION precompute_words(src TEXT)
382   RETURNS INTEGER
383   AS $$
384 DECLARE
385   s TEXT;
386   w INTEGER;
387   words TEXT[];
388   i INTEGER;
389   j INTEGER;
390 BEGIN
391   s := make_standard_name(src);
392   w := getorcreate_name_id(s, src);
393
394   w := getorcreate_word_id(s);
395
396   words := string_to_array(s, ' ');
397   IF array_upper(words, 1) IS NOT NULL THEN
398     FOR j IN 1..array_upper(words, 1) LOOP
399       IF (words[j] != '') THEN
400         w := getorcreate_word_id(words[j]);
401       END IF;
402     END LOOP;
403   END IF;
404
405   words := regexp_split_to_array(src, E'[,;()]');
406   IF array_upper(words, 1) != 1 THEN
407     FOR j IN 1..array_upper(words, 1) LOOP
408       s := make_standard_name(words[j]);
409       IF s != '' THEN
410         w := getorcreate_word_id(s);
411       END IF;
412     END LOOP;
413   END IF;
414
415   s := regexp_replace(src, '市$', '');
416   IF s != src THEN
417     s := make_standard_name(s);
418     IF s != '' THEN
419       w := getorcreate_name_id(s, src);
420     END IF;
421   END IF;
422
423   RETURN 1;
424 END;
425 $$
426 LANGUAGE plpgsql;