diff --git a/martin/src/pg/query_tables.rs b/martin/src/pg/query_tables.rs index 1efa2ce2c..c8a609952 100644 --- a/martin/src/pg/query_tables.rs +++ b/martin/src/pg/query_tables.rs @@ -103,6 +103,7 @@ fn escape_with_alias(mapping: &HashMap, field: &str) -> String { } } +#[allow(clippy::too_many_lines)] /// Generate a query to fetch tiles from a table. /// The function is async because it may need to query the database for the table bounds (could be very slow). pub async fn table_to_query( @@ -112,9 +113,6 @@ pub async fn table_to_query( bounds_type: BoundsCalcType, max_feature_count: Option, ) -> PgResult<(String, PgSqlInfo, TableInfo)> { - let schema = escape_identifier(&info.schema); - let table = escape_identifier(&info.table); - let geometry_column = escape_identifier(&info.geometry_column); let srid = info.srid; if info.bounds.is_none() { @@ -122,7 +120,7 @@ pub async fn table_to_query( BoundsCalcType::Skip => {} BoundsCalcType::Calc => { debug!("Computing {} table bounds for {id}", info.format_id()); - info.bounds = calc_bounds(&pool, &schema, &table, &geometry_column, srid).await?; + info.bounds = calc_bounds(&pool, &info, srid, false).await?; } BoundsCalcType::Quick => { debug!( @@ -130,9 +128,13 @@ pub async fn table_to_query( info.format_id(), DEFAULT_BOUNDS_TIMEOUT.as_secs() ); - let bounds = calc_bounds(&pool, &schema, &table, &geometry_column, srid); - pin_mut!(bounds); - if let Ok(bounds) = timeout(DEFAULT_BOUNDS_TIMEOUT, &mut bounds).await { + let bounds = { + let bounds = calc_bounds(&pool, &info, srid, true); + pin_mut!(bounds); + timeout(DEFAULT_BOUNDS_TIMEOUT, &mut bounds).await + }; + + if let Ok(bounds) = bounds { info.bounds = bounds?; } else { warn!( @@ -189,6 +191,9 @@ pub async fn table_to_query( let limit_clause = max_feature_count.map_or(String::new(), |v| format!("LIMIT {v}")); let layer_id = escape_literal(info.layer_id.as_ref().unwrap_or(&id)); let clip_geom = info.clip_geom.unwrap_or(DEFAULT_CLIP_GEOM); + let schema = escape_identifier(&info.schema); + let table = escape_identifier(&info.table); + let geometry_column = escape_identifier(&info.geometry_column); let query = format!( r" SELECT @@ -218,15 +223,30 @@ FROM ( /// Compute the bounds of a table. This could be slow if the table is large or has no geo index. async fn calc_bounds( pool: &PgPool, - schema: &str, - table: &str, - geometry_column: &str, + info: &TableInfo, srid: i32, + mut is_quick: bool, ) -> PgResult> { - Ok(pool.get() - .await? - .query_one(&format!( - r" + let schema = escape_identifier(&info.schema); + let table = escape_identifier(&info.table); + + let cn = pool.get().await?; + loop { + let query = if is_quick { + // This method is faster but less accurate, and can fail in a number of cases (returns NULL) + cn.query_one( + "SELECT ST_Transform(ST_SetSRID(ST_EstimatedExtent($1, $2, $3)::geometry, $4), 4326) as bounds", + &[ + &&schema[1..schema.len() - 1], + &&table[1..table.len() - 1], + &info.geometry_column, + &srid, + ], + ).await + } else { + let geometry_column = escape_identifier(&info.geometry_column); + cn.query_one( + &format!(r" WITH real_bounds AS (SELECT ST_SetSRID(ST_Extent({geometry_column}::geometry), {srid}) AS rb FROM {schema}.{table}) SELECT ST_Transform( CASE @@ -236,10 +256,27 @@ SELECT ST_Transform( END, 4326 ) AS bounds -FROM {schema}.{table}; - "), &[]) - .await - .map_err(|e| PostgresError(e, "querying table bounds"))? - .get::<_, Option>("bounds") - .and_then(|p| polygon_to_bbox(&p))) +FROM {schema}.{table};"), + &[] + ).await + }; + + if let Some(bounds) = query + .map_err(|e| PostgresError(e, "querying table bounds"))? + .get::<_, Option>("bounds") + { + return Ok(polygon_to_bbox(&bounds)); + } + if is_quick { + // ST_EstimatedExtent failed probably because there is no index or statistics or if it's a view + // This can only happen once if we are in quick mode + is_quick = false; + warn!( + "ST_EstimatedExtent on {schema}.{table}.{} failed, trying slower method to compute bounds", + info.geometry_column + ); + } else { + return Ok(None); + } + } } diff --git a/tests/expected/configured/save_config.yaml b/tests/expected/configured/save_config.yaml index 348874368..3b82e9b84 100644 --- a/tests/expected/configured/save_config.yaml +++ b/tests/expected/configured/save_config.yaml @@ -23,10 +23,10 @@ postgres: geometry_column: Geom id_column: giD bounds: - - -170.94984639004662 - - -84.20025580733805 - - 167.70892858284475 - - 74.23573284753762 + - -170.94985961914062 + - -84.20025634765625 + - 167.7089385986328 + - 74.23573303222656 geometry_type: POINT properties: taBLe: text @@ -37,10 +37,10 @@ postgres: geometry_column: geom id_column: feat_id bounds: - - -166.87107126230424 - - -53.44747249115674 - - 168.14061220360549 - - 84.22411861475385 + - -166.87107849121094 + - -53.44747543334961 + - 168.140625 + - 84.22412109375 extent: 9000 buffer: 3 clip_geom: false @@ -54,10 +54,10 @@ postgres: geometry_column: geom id_column: big_feat_id bounds: - - -174.89475564568033 - - -77.2579745396886 - - 174.72753224514435 - - 73.80785950599903 + - -174.89476013183594 + - -77.25798034667969 + - 174.7275390625 + - 73.807861328125 extent: 9000 buffer: 3 clip_geom: false diff --git a/tests/expected/configured/tbl_comment_cfg.json b/tests/expected/configured/tbl_comment_cfg.json index 702e0d310..e5e02a63d 100644 --- a/tests/expected/configured/tbl_comment_cfg.json +++ b/tests/expected/configured/tbl_comment_cfg.json @@ -1,9 +1,9 @@ { "bounds": [ - -170.94984639004662, - -84.20025580733805, - 167.70892858284475, - 74.23573284753762 + -170.94985961914062, + -84.20025634765625, + 167.7089385986328, + 74.23573303222656 ], "description": "a description from comment on table", "name": "MixPoints",