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