11-- Latest dataset id should return the feed_id, not the UID of the dataset
2- DO
3- '
4- DECLARE
5- BEGIN
6- IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = ' ' translationtype' ' ) THEN
7- CREATE TYPE TranslationType AS ENUM (' ' country' ' , ' ' subdivision_name' ' , ' ' municipality' ' );
8- END IF;
9- END;
10- ' LANGUAGE PLPGSQL;
11-
12- CREATE TABLE IF NOT EXISTS Translation (
13- type TranslationType NOT NULL ,
14- language_code VARCHAR (3 ) NOT NULL , -- ISO 639-2
15- key VARCHAR (255 ) NOT NULL ,
16- value VARCHAR (255 ) NOT NULL ,
17- PRIMARY KEY (type, language_code, key)
18- );
192
20- -- Dropping the materialized view if it exists as we cannot update it
21- DROP MATERIALIZED VIEW IF EXISTS FeedSearch;
3+ DROP MATERIALIZED VIEW IF EXISTS feedsearch;
4+
5+ ALTER TABLE feed
6+ ALTER COLUMN note TYPE TEXT ;
227
8+ -- Recreate the FeedSearch materialized view
239CREATE MATERIALIZED VIEW FeedSearch AS
2410SELECT
2511 -- feed
@@ -37,12 +23,29 @@ SELECT
3723 Feed .api_key_parameter_name ,
3824 Feed .license_url ,
3925 Feed .provider ,
26+ Feed .operational_status ,
27+ -- official status
28+ Feed .official AS official,
29+ -- created_at
30+ Feed .created_at AS created_at,
4031 -- latest_dataset
4132 Latest_dataset .stable_id AS latest_dataset_id,
4233 Latest_dataset .hosted_url AS latest_dataset_hosted_url,
4334 Latest_dataset .downloaded_at AS latest_dataset_downloaded_at,
4435 Latest_dataset .bounding_box AS latest_dataset_bounding_box,
4536 Latest_dataset .hash AS latest_dataset_hash,
37+ Latest_dataset .agency_timezone AS latest_dataset_agency_timezone,
38+ Latest_dataset .service_date_range_start AS latest_dataset_service_date_range_start,
39+ Latest_dataset .service_date_range_end AS latest_dataset_service_date_range_end,
40+ -- Latest dataset features
41+ LatestDatasetFeatures AS latest_dataset_features,
42+ -- Latest dataset validation totals
43+ COALESCE(LatestDatasetValidationReportJoin .total_error , 0 ) as latest_total_error,
44+ COALESCE(LatestDatasetValidationReportJoin .total_warning , 0 ) as latest_total_warning,
45+ COALESCE(LatestDatasetValidationReportJoin .total_info , 0 ) as latest_total_info,
46+ COALESCE(LatestDatasetValidationReportJoin .unique_error_count , 0 ) as latest_unique_error_count,
47+ COALESCE(LatestDatasetValidationReportJoin .unique_warning_count , 0 ) as latest_unique_warning_count,
48+ COALESCE(LatestDatasetValidationReportJoin .unique_info_count , 0 ) as latest_unique_info_count,
4649 -- external_ids
4750 ExternalIdJoin .external_ids ,
4851 -- redirect_ids
@@ -53,10 +56,11 @@ SELECT
5356 EntityTypeFeedJoin .entities ,
5457 -- locations
5558 FeedLocationJoin .locations ,
56- -- translations
57- FeedCountryTranslationJoin .translations AS country_translations,
58- FeedSubdivisionNameTranslationJoin .translations AS subdivision_name_translations,
59- FeedMunicipalityTranslationJoin .translations AS municipality_translations,
59+ -- osm locations grouped
60+ OsmLocationJoin .osm_locations ,
61+ -- gbfs versions
62+ COALESCE(GbfsVersionsJoin .versions , ' []' ::jsonb) AS versions,
63+
6064 -- full-text searchable document
6165 setweight(to_tsvector(' english' , coalesce(unaccent(Feed .feed_name ), ' ' )), ' C' ) ||
6266 setweight(to_tsvector(' english' , coalesce(unaccent(Feed .provider ), ' ' )), ' C' ) ||
@@ -70,40 +74,80 @@ SELECT
7074 )
7175 FROM json_array_elements(FeedLocationJoin .locations ) AS location
7276 )), ' ' )), ' A' ) ||
73- setweight(to_tsvector(' english' , coalesce(unaccent((
74- SELECT string_agg(
75- coalesce(translation- >> ' value' , ' ' ),
76- ' '
77- )
78- FROM json_array_elements(FeedCountryTranslationJoin .translations ) AS translation
79- )), ' ' )), ' A' ) ||
80- setweight(to_tsvector(' english' , coalesce(unaccent((
81- SELECT string_agg(
82- coalesce(translation- >> ' value' , ' ' ),
83- ' '
84- )
85- FROM json_array_elements(FeedSubdivisionNameTranslationJoin .translations ) AS translation
86- )), ' ' )), ' A' ) ||
87- setweight(to_tsvector(' english' , coalesce(unaccent((
88- SELECT string_agg(
89- coalesce(translation- >> ' value' , ' ' ),
90- ' '
91- )
92- FROM json_array_elements(FeedMunicipalityTranslationJoin .translations ) AS translation
93- )), ' ' )), ' A' ) AS document
77+ setweight(to_tsvector(' english' , coalesce(unaccent(OsmLocationNamesJoin .osm_location_names ), ' ' )), ' A' )
78+ AS document
9479FROM Feed
80+
81+ -- Latest dataset
82+ LEFT JOIN gtfsfeed gtf ON gtf .id = Feed .id AND Feed .data_type = ' gtfs'
83+ LEFT JOIN gtfsdataset Latest_dataset ON Latest_dataset .id = gtf .latest_dataset_id
84+
85+ -- Latest dataset features
86+ LEFT JOIN (
87+ SELECT
88+ GtfsDataset .id AS FeatureGtfsDatasetId,
89+ array_agg(DISTINCT FeatureValidationReport .feature ) AS LatestDatasetFeatures
90+ FROM GtfsDataset
91+ JOIN ValidationReportGtfsDataset
92+ ON ValidationReportGtfsDataset .dataset_id = GtfsDataset .id
93+ JOIN (
94+ -- Pick latest ValidationReport per dataset based on validated_at
95+ SELECT DISTINCT ON (ValidationReportGtfsDataset .dataset_id )
96+ ValidationReportGtfsDataset .dataset_id ,
97+ ValidationReport .id AS latest_validation_report_id
98+ FROM ValidationReportGtfsDataset
99+ JOIN ValidationReport
100+ ON ValidationReport .id = ValidationReportGtfsDataset .validation_report_id
101+ ORDER BY
102+ ValidationReportGtfsDataset .dataset_id ,
103+ ValidationReport .validated_at DESC
104+ ) AS LatestReports
105+ ON LatestReports .latest_validation_report_id = ValidationReportGtfsDataset .validation_report_id
106+ JOIN FeatureValidationReport
107+ ON FeatureValidationReport .validation_id = ValidationReportGtfsDataset .validation_report_id
108+ GROUP BY FeatureGtfsDatasetId
109+ ) AS LatestDatasetFeaturesJoin ON Latest_dataset .id = FeatureGtfsDatasetId
110+
111+ -- Latest dataset validation report
95112LEFT JOIN (
96- SELECT *
97- FROM gtfsdataset
98- WHERE latest = true
99- ) AS Latest_dataset ON Latest_dataset .feed_id = Feed .id AND Feed .data_type = ' gtfs'
113+ SELECT
114+ GtfsDataset .id AS ValidationReportGtfsDatasetId,
115+ ValidationReport .total_error ,
116+ ValidationReport .total_warning ,
117+ ValidationReport .total_info ,
118+ ValidationReport .unique_error_count ,
119+ ValidationReport .unique_warning_count ,
120+ ValidationReport .unique_info_count
121+ FROM GtfsDataset
122+ JOIN ValidationReportGtfsDataset
123+ ON ValidationReportGtfsDataset .dataset_id = GtfsDataset .id
124+ JOIN (
125+ -- Pick latest ValidationReport per dataset based on validated_at
126+ SELECT DISTINCT ON (ValidationReportGtfsDataset .dataset_id )
127+ ValidationReportGtfsDataset .dataset_id ,
128+ ValidationReport .id AS latest_validation_report_id
129+ FROM ValidationReportGtfsDataset
130+ JOIN ValidationReport
131+ ON ValidationReport .id = ValidationReportGtfsDataset .validation_report_id
132+ ORDER BY
133+ ValidationReportGtfsDataset .dataset_id ,
134+ ValidationReport .validated_at DESC
135+ ) AS LatestReports
136+ ON LatestReports .latest_validation_report_id = ValidationReportGtfsDataset .validation_report_id
137+ JOIN ValidationReport
138+ ON ValidationReport .id = ValidationReportGtfsDataset .validation_report_id
139+ ) AS LatestDatasetValidationReportJoin ON Latest_dataset .id = ValidationReportGtfsDatasetId
140+
141+ -- External ids
100142LEFT JOIN (
101143 SELECT
102144 feed_id,
103145 json_agg(json_build_object(' external_id' , associated_id, ' source' , source)) AS external_ids
104146 FROM externalid
105147 GROUP BY feed_id
106148) AS ExternalIdJoin ON ExternalIdJoin .feed_id = Feed .id
149+
150+ -- feed reference ids
107151LEFT JOIN (
108152 SELECT
109153 gtfs_rt_feed_id,
@@ -112,13 +156,17 @@ LEFT JOIN (
112156 LEFT JOIN Feed AS FeedReferenceJoinInnerQuery ON FeedReferenceJoinInnerQuery .id = FeedReference .gtfs_feed_id
113157 GROUP BY gtfs_rt_feed_id
114158) AS FeedReferenceJoin ON FeedReferenceJoin .gtfs_rt_feed_id = Feed .id AND Feed .data_type = ' gtfs_rt'
159+
160+ -- Redirect ids
115161LEFT JOIN (
116162 SELECT
117163 target_id,
118164 json_agg(json_build_object(' target_id' , target_id, ' comment' , redirect_comment)) AS redirect_ids
119165 FROM RedirectingId
120166 GROUP BY target_id
121167) AS RedirectingIdJoin ON RedirectingIdJoin .target_id = Feed .id
168+
169+ -- Feed locations
122170LEFT JOIN (
123171 SELECT
124172 LocationFeed .feed_id ,
@@ -128,50 +176,70 @@ LEFT JOIN (
128176 LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
129177 GROUP BY LocationFeed .feed_id
130178) AS FeedLocationJoin ON FeedLocationJoin .feed_id = Feed .id
179+
180+ -- Entity types
131181LEFT JOIN (
132182 SELECT
133- LocationFeed .feed_id ,
134- json_agg(json_build_object(' value' , Translation .value , ' key' , Translation .key )) AS translations
135- FROM Location
136- LEFT JOIN Translation ON Location .country = Translation .key
137- LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
138- WHERE Translation .language_code = ' en'
139- AND Translation .type = ' country'
140- AND Location .country IS NOT NULL
141- GROUP BY LocationFeed .feed_id
142- ) AS FeedCountryTranslationJoin ON FeedCountryTranslationJoin .feed_id = Feed .id
183+ feed_id,
184+ array_agg(entity_name) AS entities
185+ FROM EntityTypeFeed
186+ GROUP BY feed_id
187+ ) AS EntityTypeFeedJoin ON EntityTypeFeedJoin .feed_id = Feed .id AND Feed .data_type = ' gtfs_rt'
188+
189+ -- OSM locations
143190LEFT JOIN (
191+ WITH locations_per_group AS (
192+ SELECT
193+ fog .feed_id ,
194+ olg .group_name ,
195+ jsonb_agg(
196+ DISTINCT jsonb_build_object(
197+ ' admin_level' , gp .admin_level ,
198+ ' name' , gp .name
199+ )
200+ ) AS locations
201+ FROM FeedOsmLocationGroup fog
202+ JOIN OsmLocationGroup olg ON olg .group_id = fog .group_id
203+ JOIN OsmLocationGroupGeopolygon olgg ON olgg .group_id = olg .group_id
204+ JOIN Geopolygon gp ON gp .osm_id = olgg .osm_id
205+ GROUP BY fog .feed_id , olg .group_name
206+ )
144207 SELECT
145- LocationFeed .feed_id ,
146- json_agg(json_build_object(' value' , Translation .value , ' key' , Translation .key )) AS translations
147- FROM Location
148- LEFT JOIN Translation ON Location .subdivision_name = Translation .key
149- LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
150- WHERE Translation .language_code = ' en'
151- AND Translation .type = ' subdivision_name'
152- AND Location .subdivision_name IS NOT NULL
153- GROUP BY LocationFeed .feed_id
154- ) AS FeedSubdivisionNameTranslationJoin ON FeedSubdivisionNameTranslationJoin .feed_id = Feed .id
208+ feed_id,
209+ jsonb_agg(
210+ jsonb_build_object(
211+ ' group_name' , group_name,
212+ ' locations' , locations
213+ )
214+ )::json AS osm_locations
215+ FROM locations_per_group
216+ GROUP BY feed_id
217+ ) AS OsmLocationJoin ON OsmLocationJoin .feed_id = Feed .id
218+
219+ -- OSM location names
155220LEFT JOIN (
156221 SELECT
157- LocationFeed .feed_id ,
158- json_agg(json_build_object(' value' , Translation .value , ' key' , Translation .key )) AS translations
159- FROM Location
160- LEFT JOIN Translation ON Location .municipality = Translation .key
161- LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
162- WHERE Translation .language_code = ' en'
163- AND Translation .type = ' municipality'
164- AND Location .municipality IS NOT NULL
165- GROUP BY LocationFeed .feed_id
166- ) AS FeedMunicipalityTranslationJoin ON FeedMunicipalityTranslationJoin .feed_id = Feed .id
222+ fog .feed_id ,
223+ string_agg(DISTINCT gp .name , ' ' ) AS osm_location_names
224+ FROM FeedOsmLocationGroup fog
225+ JOIN OsmLocationGroup olg ON olg .group_id = fog .group_id
226+ JOIN OsmLocationGroupGeopolygon olgg ON olgg .group_id = olg .group_id
227+ JOIN Geopolygon gp ON gp .osm_id = olgg .osm_id
228+ WHERE gp .name IS NOT NULL
229+ GROUP BY fog .feed_id
230+ ) AS OsmLocationNamesJoin ON OsmLocationNamesJoin .feed_id = Feed .id
231+
232+ -- GBFS versions
167233LEFT JOIN (
168234 SELECT
169- feed_id,
170- array_agg(entity_name) AS entities
171- FROM EntityTypeFeed
172- GROUP BY feed_id
173- ) AS EntityTypeFeedJoin ON EntityTypeFeedJoin .feed_id = Feed .id AND Feed .data_type = ' gtfs_rt'
174- ;
235+ Feed .id AS feed_id,
236+ to_jsonb(array_agg(DISTINCT GbfsVersion .version ORDER BY GbfsVersion .version )) AS versions
237+ FROM Feed
238+ JOIN GbfsFeed ON GbfsFeed .id = Feed .id
239+ JOIN GbfsVersion ON GbfsVersion .feed_id = GbfsFeed .id
240+ WHERE Feed .data_type = ' gbfs'
241+ GROUP BY Feed .id
242+ ) AS GbfsVersionsJoin ON GbfsVersionsJoin .feed_id = Feed .id ;
175243
176244
177245-- This index allows concurrent refresh on the materialized view avoiding table locks
@@ -182,3 +250,19 @@ CREATE INDEX feedsearch_document_idx ON FeedSearch USING GIN(document);
182250CREATE INDEX feedsearch_feed_stable_id ON FeedSearch(feed_stable_id);
183251CREATE INDEX feedsearch_data_type ON FeedSearch(data_type);
184252CREATE INDEX feedsearch_status ON FeedSearch(status);
253+
254+
255+ -- Update search
256+ REFRESH MATERIALIZED VIEW CONCURRENTLY FeedSearch;
257+
258+ -- create new related links table
259+ CREATE TABLE IF NOT EXISTS FeedRelatedLink (
260+ feed_id varchar (255 ) NOT NULL REFERENCES Feed(id) ON DELETE CASCADE ,
261+ description TEXT NOT NULL ,
262+ code TEXT NOT NULL ,
263+ url TEXT NOT NULL ,
264+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ,
265+ PRIMARY KEY (feed_id, code)
266+ );
267+
268+
0 commit comments