1 -- SPDX-License-Identifier: GPL-2.0-only
3 -- This file is part of Nominatim. (https://nominatim.org)
5 -- Copyright (C) 2022 by the Nominatim developer community.
6 -- For a full list of authors see the git log.
8 -- Get tokens used for searching the given place.
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)
14 SELECT (info->>'names')::INTEGER[]
15 $$ LANGUAGE SQL IMMUTABLE STRICT;
18 -- Get tokens for matching the place name against others.
20 -- This should usually be restricted to full name tokens.
21 CREATE OR REPLACE FUNCTION token_get_name_match_tokens(info JSONB)
24 SELECT (info->>'names')::INTEGER[]
25 $$ LANGUAGE SQL IMMUTABLE STRICT;
28 -- Return the housenumber tokens applicable for the place.
29 CREATE OR REPLACE FUNCTION token_get_housenumber_search_tokens(info JSONB)
32 SELECT (info->>'hnr_tokens')::INTEGER[]
33 $$ LANGUAGE SQL IMMUTABLE STRICT;
36 -- Return the housenumber in the form that it can be matched during search.
37 CREATE OR REPLACE FUNCTION token_normalized_housenumber(info JSONB)
41 $$ LANGUAGE SQL IMMUTABLE STRICT;
44 CREATE OR REPLACE FUNCTION token_has_addr_street(info JSONB)
47 SELECT info->>'street' is not null;
48 $$ LANGUAGE SQL IMMUTABLE;
51 CREATE OR REPLACE FUNCTION token_has_addr_place(info JSONB)
54 SELECT info->>'place_match' is not null;
55 $$ LANGUAGE SQL IMMUTABLE;
58 CREATE OR REPLACE FUNCTION token_matches_street(info JSONB, street_tokens INTEGER[])
61 SELECT (info->>'street')::INTEGER[] && street_tokens
62 $$ LANGUAGE SQL IMMUTABLE STRICT;
65 CREATE OR REPLACE FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[])
68 SELECT (info->>'place_match')::INTEGER[] && place_tokens
69 $$ LANGUAGE SQL IMMUTABLE STRICT;
72 CREATE OR REPLACE FUNCTION token_addr_place_search_tokens(info JSONB)
75 SELECT (info->>'place_search')::INTEGER[]
76 $$ LANGUAGE SQL IMMUTABLE STRICT;
79 CREATE OR REPLACE FUNCTION token_get_address_keys(info JSONB)
82 SELECT * FROM jsonb_object_keys(info->'addr');
83 $$ LANGUAGE SQL IMMUTABLE STRICT;
86 CREATE OR REPLACE FUNCTION token_get_address_search_tokens(info JSONB, key TEXT)
89 SELECT (info->'addr'->key->>0)::INTEGER[];
90 $$ LANGUAGE SQL IMMUTABLE STRICT;
93 CREATE OR REPLACE FUNCTION token_matches_address(info JSONB, key TEXT, tokens INTEGER[])
96 SELECT (info->'addr'->key->>1)::INTEGER[] && tokens;
97 $$ LANGUAGE SQL IMMUTABLE STRICT;
100 CREATE OR REPLACE FUNCTION token_normalized_postcode(postcode TEXT)
103 SELECT CASE WHEN postcode SIMILAR TO '%(,|;)%' THEN NULL ELSE upper(trim(postcode))END;
104 $$ LANGUAGE SQL IMMUTABLE STRICT;
107 -- Return token info that should be saved permanently in the database.
108 CREATE OR REPLACE FUNCTION token_strip_info(info JSONB)
112 $$ LANGUAGE SQL IMMUTABLE STRICT;
114 --------------- private functions ----------------------------------------------
116 -- Functions for term normalisation and access to the 'word' table.
118 CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text
119 AS '{{ modulepath }}/nominatim.so', 'transliteration'
120 LANGUAGE c IMMUTABLE STRICT;
123 CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text
124 AS '{{ modulepath }}/nominatim.so', 'gettokenstring'
125 LANGUAGE c IMMUTABLE STRICT;
128 CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT
133 o := public.gettokenstring(public.transliteration(name));
134 RETURN trim(substr(o,1,length(o)));
137 LANGUAGE plpgsql IMMUTABLE;
139 -- returns NULL if the word is too common
140 CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT)
145 return_word_id INTEGER;
148 lookup_token := trim(lookup_word);
149 SELECT min(word_id), max(search_name_count) FROM word
150 WHERE word_token = lookup_token and class is null and type is null
151 INTO return_word_id, count;
152 IF return_word_id IS NULL THEN
153 return_word_id := nextval('seq_word');
154 INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0);
156 IF count > {{ max_word_freq }} THEN
157 return_word_id := NULL;
160 RETURN return_word_id;
166 -- Create housenumber tokens from an OSM addr:housenumber.
167 -- The housnumber is split at comma and semicolon as necessary.
168 -- The function returns the normalized form of the housenumber suitable
170 CREATE OR REPLACE FUNCTION create_housenumbers(housenumbers TEXT[],
175 SELECT array_to_string(array_agg(trans), ';'), array_agg(tid)::TEXT
176 INTO normtext, tokens
177 FROM (SELECT lookup_word as trans, getorcreate_housenumber_id(lookup_word) as tid
178 FROM (SELECT make_standard_name(h) as lookup_word
179 FROM unnest(housenumbers) h) x) y;
181 $$ LANGUAGE plpgsql STABLE STRICT;
184 CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
189 return_word_id INTEGER;
191 lookup_token := ' ' || trim(lookup_word);
192 SELECT min(word_id) FROM word
193 WHERE word_token = lookup_token and class='place' and type='house'
195 IF return_word_id IS NULL THEN
196 return_word_id := nextval('seq_word');
197 INSERT INTO word VALUES (return_word_id, lookup_token, null,
198 'place', 'house', null, 0);
200 RETURN return_word_id;
206 CREATE OR REPLACE FUNCTION create_postcode_id(postcode TEXT)
212 return_word_id INTEGER;
214 lookup_token := ' ' || make_standard_name(postcode);
216 SELECT word_id FROM word
217 WHERE word_token = lookup_token and word = postcode
218 and class='place' and type='postcode'
223 INSERT INTO word VALUES (nextval('seq_word'), lookup_token, postcode,
224 'place', 'postcode', null, 0);
231 CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT, src_word TEXT)
236 nospace_lookup_token TEXT;
237 return_word_id INTEGER;
239 lookup_token := ' '||trim(lookup_word);
240 SELECT min(word_id) FROM word
241 WHERE word_token = lookup_token and class is null and type is null
243 IF return_word_id IS NULL THEN
244 return_word_id := nextval('seq_word');
245 INSERT INTO word VALUES (return_word_id, lookup_token, src_word,
246 null, null, null, 0);
248 RETURN return_word_id;
254 -- Normalize a string and lookup its word ids (partial words).
255 CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
261 return_word_id INTEGER[];
265 words := string_to_array(make_standard_name(lookup_word), ' ');
266 IF array_upper(words, 1) IS NOT NULL THEN
267 FOR j IN 1..array_upper(words, 1) LOOP
268 IF (words[j] != '') THEN
269 SELECT array_agg(word_id) INTO word_ids
271 WHERE word_token = words[j] and class is null and type is null;
273 IF word_ids IS NULL THEN
274 id := nextval('seq_word');
275 INSERT INTO word VALUES (id, words[j], null, null, null, null, 0);
276 return_word_id := return_word_id || id;
278 return_word_id := array_merge(return_word_id, word_ids);
284 RETURN return_word_id;
290 -- Normalize a string and look up its name ids (full words).
291 CREATE OR REPLACE FUNCTION word_ids_from_name(lookup_word TEXT)
296 return_word_ids INTEGER[];
298 lookup_token := ' '|| make_standard_name(lookup_word);
299 SELECT array_agg(word_id) FROM word
300 WHERE word_token = lookup_token and class is null and type is null
301 INTO return_word_ids;
302 RETURN return_word_ids;
305 LANGUAGE plpgsql STABLE STRICT;
308 CREATE OR REPLACE FUNCTION make_keywords(src HSTORE)
319 result := '{}'::INTEGER[];
321 FOR value IN SELECT unnest(regexp_split_to_array(svals(src), E'[,;]')) LOOP
323 s := make_standard_name(value);
324 w := getorcreate_name_id(s, value);
326 IF not(ARRAY[w] <@ result) THEN
327 result := result || w;
330 -- partial single-word terms
331 words := string_to_array(s, ' ');
332 IF array_upper(words, 1) IS NOT NULL THEN
333 FOR j IN 1..array_upper(words, 1) LOOP
334 IF (words[j] != '') THEN
335 w = getorcreate_word_id(words[j]);
336 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
337 result := result || w;
343 -- consider parts before an opening braket a full word as well
344 words := regexp_split_to_array(value, E'[(]');
345 IF array_upper(words, 1) > 1 THEN
346 s := make_standard_name(words[1]);
348 w := getorcreate_name_id(s, words[1]);
349 IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
350 result := result || w;
355 s := regexp_replace(value, '市$', '');
357 s := make_standard_name(s);
359 w := getorcreate_name_id(s, value);
360 IF NOT (ARRAY[w] <@ result) THEN
361 result := result || w;
374 CREATE OR REPLACE FUNCTION precompute_words(src TEXT)
384 s := make_standard_name(src);
385 w := getorcreate_name_id(s, src);
387 w := getorcreate_word_id(s);
389 words := string_to_array(s, ' ');
390 IF array_upper(words, 1) IS NOT NULL THEN
391 FOR j IN 1..array_upper(words, 1) LOOP
392 IF (words[j] != '') THEN
393 w := getorcreate_word_id(words[j]);
398 words := regexp_split_to_array(src, E'[,;()]');
399 IF array_upper(words, 1) != 1 THEN
400 FOR j IN 1..array_upper(words, 1) LOOP
401 s := make_standard_name(words[j]);
403 w := getorcreate_word_id(s);
408 s := regexp_replace(src, '市$', '');
410 s := make_standard_name(s);
412 w := getorcreate_name_id(s, src);