Fix handling for null-returning PG queries (#521)

Handle cases when a query returns a NULL or a table with no rows, or a
single row with a null value in it.

This fully fixes #519 in the main branch
This commit is contained in:
Yuri Astrakhan 2022-12-15 07:12:55 -05:00 committed by GitHub
parent 3f7f35ecdc
commit 9efa364eb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 11 deletions

View File

@ -73,19 +73,22 @@ impl Source for PgSource {
let json = query_to_json(url_query);
debug!("SQL: {query} [{xyz}, {json:?}]");
let params: &[&(dyn ToSql + Sync)] = &[&xyz.z, &xyz.x, &xyz.y, &json];
conn.query_one(&prep_query, params).await
conn.query_opt(&prep_query, params).await
} else {
debug!("SQL: {query} [{xyz}]");
conn.query_one(&prep_query, &[&xyz.z, &xyz.x, &xyz.y]).await
conn.query_opt(&prep_query, &[&xyz.z, &xyz.x, &xyz.y]).await
};
let tile = tile.map(|row| row.get(0)).map_err(|e| {
let tile = tile
.map(|row| row.map_or(Default::default(), |r| r.get::<_, Option<Tile>>(0)))
.map_err(|e| {
if self.info.has_query_params {
GetTileWithQueryError(e, self.id.to_string(), *xyz, url_query.clone())
} else {
GetTileError(e, self.id.to_string(), *xyz)
}
})?;
})?
.unwrap_or_default();
Ok(tile)
}

View File

@ -134,10 +134,10 @@ pub enum PgError {
String,
),
#[error(r#"Unable to get tile {1}/{2:#}: {0}"#)]
#[error(r#"Unable to get tile {2:#} from {1}: {0}"#)]
GetTileError(#[source] bb8_postgres::tokio_postgres::Error, String, Xyz),
#[error(r#"Unable to get tile {1}/{2:#} with {:?} params: {0}"#, query_to_json(.3))]
#[error(r#"Unable to get tile {2:#} with {:?} params from {1}: {0}"#, query_to_json(.3))]
GetTileWithQueryError(
#[source] bb8_postgres::tokio_postgres::Error,
String,

View File

@ -0,0 +1,7 @@
DROP FUNCTION IF EXISTS public.function_null;
CREATE OR REPLACE FUNCTION public.function_null(z integer, x integer, y integer) RETURNS bytea AS $$
BEGIN
RETURN null;
END
$$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;

View File

@ -0,0 +1,6 @@
DROP FUNCTION IF EXISTS public.function_null_row;
CREATE OR REPLACE FUNCTION public.function_null_row(z integer, x integer, y integer)
RETURNS TABLE(mvt bytea, key text) AS $$
SELECT NULL::bytea, NULL::text
$$ LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE;

View File

@ -0,0 +1,6 @@
DROP FUNCTION IF EXISTS public.function_null_row2;
CREATE OR REPLACE FUNCTION public.function_null_row2(z integer, x integer, y integer)
RETURNS TABLE(mvt bytea, key text) AS $$
SELECT NULL::bytea, NULL::text WHERE FALSE;
$$ LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE;

View File

@ -316,6 +316,23 @@ async fn get_composite_source_tile_minmax_zoom_ok() {
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn null_functions() {
let app = create_app!(mock_empty_config());
let req = test_get("/function_null/0/0/0");
let response = call_service(&app, req).await;
assert_eq!(response.status(), StatusCode::NO_CONTENT);
let req = test_get("/function_null_row/0/0/0");
let response = call_service(&app, req).await;
assert_eq!(response.status(), StatusCode::NO_CONTENT);
let req = test_get("/function_null_row2/0/0/0");
let response = call_service(&app, req).await;
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
#[actix_rt::test]
async fn get_function_source_ok() {
let app = create_app!(mock_empty_config());