]> git.openstreetmap.org Git - nominatim.git/blob - lib-sql/tokenizer/icu_tokenizer.sql
Merge pull request #3692 from lonvia/word-lookup-variants
[nominatim.git] / lib-sql / tokenizer / icu_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 PARALLEL SAFE;
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 PARALLEL SAFE;
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 PARALLEL SAFE;
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 PARALLEL SAFE;
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 PARALLEL SAFE;
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 and info->>'street' != '{}';
55 $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
56
57
58 CREATE OR REPLACE FUNCTION token_has_addr_place(info JSONB)
59   RETURNS BOOLEAN
60 AS $$
61   SELECT info->>'place' is not null;
62 $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
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 PARALLEL SAFE;
70
71
72 CREATE OR REPLACE FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[])
73   RETURNS BOOLEAN
74 AS $$
75   SELECT (info->>'place')::INTEGER[] <@ place_tokens
76 $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE;
77
78
79 CREATE OR REPLACE FUNCTION token_addr_place_search_tokens(info JSONB)
80   RETURNS INTEGER[]
81 AS $$
82   SELECT (info->>'place')::INTEGER[]
83 $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE;
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 PARALLEL SAFE;
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)::INTEGER[];
97 $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE;
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)::INTEGER[] <@ tokens;
104 $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE;
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 PARALLEL SAFE;
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 PARALLEL SAFE;
120
121 --------------- private functions ----------------------------------------------
122
123 CREATE OR REPLACE FUNCTION getorcreate_full_word(norm_term TEXT, lookup_terms TEXT[],
124                                                  OUT full_token INT,
125                                                  OUT partial_tokens INT[])
126   AS $$
127 DECLARE
128   partial_terms TEXT[] = '{}'::TEXT[];
129   term TEXT;
130   term_id INTEGER;
131 BEGIN
132   SELECT min(word_id) INTO full_token
133     FROM word WHERE word = norm_term and type = 'W';
134
135   IF full_token IS NULL THEN
136     full_token := nextval('seq_word');
137     INSERT INTO word (word_id, word_token, type, word)
138       SELECT full_token, lookup_term, 'W', norm_term
139         FROM unnest(lookup_terms) as lookup_term;
140   END IF;
141
142   FOR term IN SELECT unnest(string_to_array(unnest(lookup_terms), ' ')) LOOP
143     term := trim(term);
144     IF NOT (ARRAY[term] <@ partial_terms) THEN
145       partial_terms := partial_terms || term;
146     END IF;
147   END LOOP;
148
149   partial_tokens := '{}'::INT[];
150   FOR term IN SELECT unnest(partial_terms) LOOP
151     SELECT min(word_id) INTO term_id
152       FROM word WHERE word_token = term and type = 'w';
153
154     IF term_id IS NULL THEN
155       term_id := nextval('seq_word');
156       INSERT INTO word (word_id, word_token, type)
157         VALUES (term_id, term, 'w');
158     END IF;
159
160     partial_tokens := array_merge(partial_tokens, ARRAY[term_id]);
161   END LOOP;
162 END;
163 $$
164 LANGUAGE plpgsql;
165
166
167 CREATE OR REPLACE FUNCTION getorcreate_full_word(norm_term TEXT,
168                                                  lookup_terms TEXT[],
169                                                  lookup_norm_terms TEXT[],
170                                                  OUT full_token INT,
171                                                  OUT partial_tokens INT[])
172   AS $$
173 DECLARE
174   partial_terms TEXT[] = '{}'::TEXT[];
175   term TEXT;
176   term_id INTEGER;
177 BEGIN
178   SELECT min(word_id) INTO full_token
179     FROM word WHERE word = norm_term and type = 'W';
180
181   IF full_token IS NULL THEN
182     full_token := nextval('seq_word');
183     IF lookup_norm_terms IS NULL THEN
184       INSERT INTO word (word_id, word_token, type, word)
185         SELECT full_token, lookup_term, 'W', norm_term
186           FROM unnest(lookup_terms) as lookup_term;
187     ELSE
188       INSERT INTO word (word_id, word_token, type, word, info)
189         SELECT full_token, t.lookup, 'W', norm_term,
190                CASE WHEN norm_term = t.norm THEN null
191                ELSE json_build_object('lookup', t.norm) END
192           FROM unnest(lookup_terms, lookup_norm_terms) as t(lookup, norm);
193     END IF;
194   END IF;
195
196   FOR term IN SELECT unnest(string_to_array(unnest(lookup_terms), ' ')) LOOP
197     term := trim(term);
198     IF NOT (ARRAY[term] <@ partial_terms) THEN
199       partial_terms := partial_terms || term;
200     END IF;
201   END LOOP;
202
203   partial_tokens := '{}'::INT[];
204   FOR term IN SELECT unnest(partial_terms) LOOP
205     SELECT min(word_id) INTO term_id
206       FROM word WHERE word_token = term and type = 'w';
207
208     IF term_id IS NULL THEN
209       term_id := nextval('seq_word');
210       INSERT INTO word (word_id, word_token, type)
211         VALUES (term_id, term, 'w');
212     END IF;
213
214     partial_tokens := array_merge(partial_tokens, ARRAY[term_id]);
215   END LOOP;
216 END;
217 $$
218 LANGUAGE plpgsql;
219
220
221 CREATE OR REPLACE FUNCTION getorcreate_partial_word(partial TEXT)
222   RETURNS INTEGER
223   AS $$
224 DECLARE
225   token INTEGER;
226 BEGIN
227   SELECT min(word_id) INTO token
228     FROM word WHERE word_token = partial and type = 'w';
229
230   IF token IS NULL THEN
231     token := nextval('seq_word');
232     INSERT INTO word (word_id, word_token, type, info)
233         VALUES (token, partial, 'w', json_build_object('count', 0));
234   END IF;
235
236   RETURN token;
237 END;
238 $$
239 LANGUAGE plpgsql;
240
241
242 CREATE OR REPLACE FUNCTION getorcreate_hnr_id(lookup_term TEXT)
243   RETURNS INTEGER
244   AS $$
245 DECLARE
246   return_id INTEGER;
247 BEGIN
248   SELECT min(word_id) INTO return_id FROM word
249     WHERE word_token = lookup_term and type = 'H';
250
251   IF return_id IS NULL THEN
252     return_id := nextval('seq_word');
253     INSERT INTO word (word_id, word_token, type)
254       VALUES (return_id, lookup_term, 'H');
255   END IF;
256
257   RETURN return_id;
258 END;
259 $$
260 LANGUAGE plpgsql;
261
262
263 CREATE OR REPLACE FUNCTION create_analyzed_hnr_id(norm_term TEXT, lookup_terms TEXT[])
264   RETURNS INTEGER
265   AS $$
266 DECLARE
267   return_id INTEGER;
268 BEGIN
269   SELECT min(word_id) INTO return_id
270     FROM word WHERE word = norm_term and type = 'H';
271
272   IF return_id IS NULL THEN
273     return_id := nextval('seq_word');
274     INSERT INTO word (word_id, word_token, type, word, info)
275       SELECT return_id, lookup_term, 'H', norm_term,
276              json_build_object('lookup', lookup_terms[1])
277         FROM unnest(lookup_terms) as lookup_term;
278   END IF;
279
280   RETURN return_id;
281 END;
282 $$
283 LANGUAGE plpgsql;
284
285 CREATE OR REPLACE FUNCTION create_postcode_word(postcode TEXT, lookup_terms TEXT[])
286   RETURNS BOOLEAN
287   AS $$
288 DECLARE
289   existing INTEGER;
290 BEGIN
291   SELECT count(*) INTO existing
292     FROM word WHERE word = postcode and type = 'P';
293
294   IF existing > 0 THEN
295     RETURN TRUE;
296   END IF;
297
298   -- postcodes don't need word ids
299   INSERT INTO word (word_token, type, word)
300     SELECT lookup_term, 'P', postcode FROM unnest(lookup_terms) as lookup_term;
301
302   RETURN FALSE;
303 END;
304 $$
305 LANGUAGE plpgsql;
306