Skip to content

Commit

Permalink
backend, frontend: enum constraints and indexings of fields
Browse files Browse the repository at this point in the history
  • Loading branch information
ElysaSrc committed Jun 23, 2024
1 parent 717577c commit e18067b
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 27 deletions.
71 changes: 66 additions & 5 deletions backend/migrations/20240322224230_create_entities_cache.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
-- Create the materialized view with a deterministic ID
CREATE MATERIALIZED VIEW entities_caches AS
-- Get the indexed fields for each family
WITH families_indexed_fields AS (
SELECT
f.id AS family_id,
(
SELECT jsonb_object_agg(field->>'key', field->>'field_type')
FROM jsonb_array_elements(f.entity_form->'fields') AS field
WHERE
(field->>'indexed')::boolean IS TRUE
AND
(field->>'field_type')::text IN ('EnumSingleOption', 'EnumMultiOption')
) AS indexed_enums,
(
SELECT jsonb_object_agg(field->>'key', field->>'field_type')
FROM jsonb_array_elements(f.entity_form->'fields') AS field
WHERE
(field->>'indexed')::boolean IS TRUE
AND
(field->>'field_type')::text IN ('SingleLineText', 'MultiLineText')
) AS indexed_strings
FROM families f
),
-- For each location of each parent, get a row with the parent and its location flattened
WITH transitive_locations AS (
transitive_locations AS (
SELECT
ee.child_id,
e.id AS parent_id,
Expand All @@ -27,7 +49,30 @@ direct_locations AS (
e.hidden,
location.value as location,
location.ordinality AS location_index,
array_remove(array_agg(DISTINCT et.tag_id), NULL) AS tags_ids
array_remove(array_agg(DISTINCT et.tag_id), NULL) AS tags_ids,
COALESCE(
jsonb_object_agg(
key,
CASE
WHEN jsonb_typeof(transformed_fields.value) = 'array' THEN transformed_fields.value
ELSE
CASE
WHEN transformed_fields.value IS NULL THEN '[]'::jsonb
ELSE jsonb_build_array(transformed_fields.value)
END
END
) FILTER (WHERE key IS NOT NULL),
'{}'::jsonb
)AS enums,
(
SELECT string_agg(value::text, ' ')
FROM jsonb_each_text(e.data)
WHERE key IN (
SELECT jsonb_object_keys(f.indexed_strings)
FROM families_indexed_fields f
WHERE f.family_id = c.family_id
)
) AS indexed_string_values
FROM entities e
JOIN categories c ON e.category_id = c.id
LEFT JOIN entity_tags et ON e.id = et.entity_id
Expand All @@ -38,6 +83,17 @@ direct_locations AS (
SELECT value, ordinality
FROM jsonb_array_elements(e.locations) WITH ORDINALITY AS location(value, ordinality)
) AS location ON true
LEFT JOIN LATERAL (
SELECT
key,
value
FROM jsonb_each(e.data)
WHERE key IN (
SELECT jsonb_object_keys(f.indexed_enums)
FROM families_indexed_fields f
WHERE f.family_id = c.family_id
)
) AS transformed_fields ON true
WHERE e.moderated
GROUP BY e.id, c.family_id, e.display_name, e.category_id, location.value, location.ordinality
)
Expand All @@ -57,7 +113,8 @@ SELECT
NULL AS parent_id,
NULL AS parent_display_name,
dl.hidden,
to_tsvector(dl.display_name) AS full_text_search_ts
to_tsvector(dl.display_name || ' ' || COALESCE(dl.indexed_string_values, '')) AS full_text_search_ts,
dl.enums
FROM direct_locations dl

UNION
Expand All @@ -78,7 +135,8 @@ SELECT
tl.parent_id,
tl.parent_display_name,
dl.hidden,
to_tsvector(dl.display_name) AS full_text_search_ts
to_tsvector(dl.display_name || ' ' || COALESCE(dl.indexed_string_values, '')) AS full_text_search_ts,
dl.enums
FROM transitive_locations tl
JOIN direct_locations dl ON tl.child_id = dl.entity_id;

Expand All @@ -88,9 +146,12 @@ CREATE UNIQUE INDEX entities_caches_id_idx ON entities_caches(id);
-- Create indexes on the materialized view
CREATE INDEX entities_caches_entity_id_idx ON entities_caches(entity_id);
CREATE INDEX entities_caches_category_id_idx ON entities_caches(category_id);
CREATE INDEX entities_caches_family_id_idx ON entities_caches(family_id);
CREATE INDEX entities_caches_hidden_idx ON entities_caches (hidden);
CREATE INDEX entities_caches_enums_idx ON entities_caches USING GIN (enums);
CREATE INDEX entities_caches_gps_location_idx ON entities_caches USING GIST((ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)));
CREATE INDEX entities_caches_web_mercator_location_idx ON entities_caches USING GIST(web_mercator_location);
CREATE INDEX entities_caches_full_text_search_idx ON entities_caches USING GIN(full_text_search_ts);
CREATE INDEX entities_caches_full_text_search_idx ON entities_caches USING GIST(full_text_search_ts);

-- Function to refresh the materialized view
CREATE OR REPLACE FUNCTION refresh_entities_caches() RETURNS void AS $$
Expand Down
49 changes: 46 additions & 3 deletions backend/migrations/20240322224255_requests_for_entities_cache.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ CREATE OR REPLACE FUNCTION fetch_entities_within_view(

active_categories_ids UUID[],
required_tags_ids UUID[],
excluded_tags_ids UUID[]
excluded_tags_ids UUID[],

enum_constraints JSONB
) RETURNS TABLE (
id UUID,
entity_id UUID,
Expand Down Expand Up @@ -65,6 +67,18 @@ BEGIN
AND (ec.category_id = ANY(active_categories_ids))
AND (array_length(required_tags_ids, 1) = 0 OR required_tags_ids <@ ec.tags_ids)
AND NOT (ec.tags_ids && excluded_tags_ids)
-- Enum constraints
AND (
enum_constraints IS NULL OR
enum_constraints = '{}'::jsonb OR
(
SELECT bool_and(
ec.enums->key ?| array(SELECT jsonb_array_elements_text(value))
)
FROM jsonb_each(enum_constraints) AS constraints(key, value)
WHERE key IS NOT NULL AND ec.enums ? key
)
)
),
parent_entities AS (
SELECT
Expand Down Expand Up @@ -143,7 +157,9 @@ CREATE OR REPLACE FUNCTION search_entities(
required_tags_ids UUID[],
excluded_tags_ids UUID[],

require_locations BOOL
require_locations BOOL,

enum_constraints JSONB
) RETURNS TABLE (
id UUID,
entity_id UUID,
Expand Down Expand Up @@ -185,6 +201,18 @@ BEGIN
AND NOT (ec.tags_ids && excluded_tags_ids)
-- If we do not require locations, we only return entities with locations
AND (NOT require_locations OR ec.web_mercator_location IS NOT NULL)
-- Enum constraints
AND (
enum_constraints IS NULL OR
enum_constraints = '{}'::jsonb OR
(
SELECT bool_and(
ec.enums->key ?| array(SELECT jsonb_array_elements_text(value))
)
FROM jsonb_each(enum_constraints) AS constraints(key, value)
WHERE key IS NOT NULL AND ec.enums ? key
)
)
ORDER BY
ts_rank(full_text_search_ts, plainto_tsquery(search_query)) DESC,
(ec.web_mercator_location IS NOT NULL) DESC -- prioritize entities with locations
Expand Down Expand Up @@ -229,7 +257,9 @@ CREATE OR REPLACE FUNCTION search_entities_admin(

active_categories_ids UUID[],
required_tags_ids UUID[],
excluded_tags_ids UUID[]
excluded_tags_ids UUID[],

enum_constraints JSONB
) RETURNS TABLE (
id UUID,
entity_id UUID,
Expand All @@ -256,8 +286,21 @@ BEGIN
)
AND ec.family_id = input_family_id
AND (ec.category_id = ANY(active_categories_ids))
-- Categories and tags constraints
AND (array_length(required_tags_ids, 1) = 0 OR required_tags_ids <@ ec.tags_ids)
AND NOT (ec.tags_ids && excluded_tags_ids)
-- Enum constraints
AND (
enum_constraints IS NULL OR
enum_constraints = '{}'::jsonb OR
(
SELECT bool_and(
ec.enums->key ?| array(SELECT jsonb_array_elements_text(value))
)
FROM jsonb_each(enum_constraints) AS constraints(key, value)
WHERE key IS NOT NULL AND ec.enums ? key
)
)
ORDER BY
ts_rank(full_text_search_ts, plainto_tsquery(search_query)) DESC
),
Expand Down
2 changes: 2 additions & 0 deletions backend/src/api/admin/entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct AdminSearchRequest {
pub active_categories_ids: Vec<Uuid>,
pub required_tags_ids: Vec<Uuid>,
pub excluded_tags_ids: Vec<Uuid>,
pub enums_constraints: Value,
}

#[derive(FromRow, Deserialize, Serialize, ToSchema, Debug)]
Expand Down Expand Up @@ -84,6 +85,7 @@ pub async fn admin_entities_search(
active_categories_ids: search_req.active_categories_ids,
required_tags_ids: search_req.required_tags_ids,
excluded_tags_ids: search_req.excluded_tags_ids,
enums_constraints: search_req.enums_constraints,
},
&mut conn,
)
Expand Down
5 changes: 5 additions & 0 deletions backend/src/api/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use axum::{
Json,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Display;
use utoipa::ToSchema;
use uuid::Uuid;
Expand All @@ -38,6 +39,7 @@ pub struct ViewRequest {
active_categories: Vec<Uuid>,
active_required_tags: Vec<Uuid>,
active_hidden_tags: Vec<Uuid>,
enums_constraints: Value,
}

impl Display for ViewRequest {
Expand Down Expand Up @@ -125,6 +127,7 @@ pub async fn viewer_view_request(
active_categories: request.active_categories,
active_required_tags: request.active_required_tags,
active_hidden_tags: request.active_hidden_tags,
enums_constraints: request.enums_constraints,
};

Ok(AppJson(
Expand All @@ -142,6 +145,7 @@ pub struct SearchRequest {
active_required_tags: Vec<Uuid>,
active_hidden_tags: Vec<Uuid>,
require_locations: bool,
enums_constraints: Value,
}

impl Display for SearchRequest {
Expand Down Expand Up @@ -189,6 +193,7 @@ async fn viewer_search_request(
active_required_tags: request.active_required_tags,
active_hidden_tags: request.active_hidden_tags,
require_locations: request.require_locations,
enums_constraints: request.enums_constraints,
};

Ok(AppJson(
Expand Down
2 changes: 1 addition & 1 deletion backend/src/api/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ async fn bootstrap(

let signed_token = app_state.generate_token(MapUserTokenClaims {
iat: Utc::now().timestamp() as usize,
exp: (Utc::now() + TimeDelta::try_minutes(1).expect("valid duration")).timestamp() as usize,
exp: (Utc::now() + TimeDelta::try_hours(1).expect("valid duration")).timestamp() as usize,
perms: perms.clone(),
});
tracing::trace!("Generated access token");
Expand Down
Loading

0 comments on commit e18067b

Please sign in to comment.