diff --git a/docs/src/config-file.md b/docs/src/config-file.md index 788ef3e66..c1e8567e5 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -46,9 +46,11 @@ web_ui: disable # Database configuration. This can also be a list of PG configs. postgres: - # Database connection string. You can use env vars too, for example: - # $DATABASE_URL - # ${DATABASE_URL:-postgresql://postgres@localhost/db} + # Database connection string. + # + # You can use environment variables too, for example: + # connection_string: $DATABASE_URL + # connection_string: ${DATABASE_URL:-postgresql://postgres@localhost/db} connection_string: 'postgresql://postgres@localhost:5432/db' # Same as PGSSLCERT for psql @@ -71,11 +73,11 @@ postgres: # It is sensible to set this limit if you have user generated/untrusted geodata, e.g. a lot of data points at [Null Island](https://en.wikipedia.org/wiki/Null_Island). max_feature_count: null # either a positive integer, or null=unlimited (default) - # Control the automatic generation of bounds for spatial tables [default: quick] + # Specify how bounds should be computed for the spatial PG tables [default: quick] # 'calc' - compute table geometry bounds on startup. # 'quick' - same as 'calc', but the calculation will be aborted if it takes more than 5 seconds. # 'skip' - do not compute table geometry bounds on startup. - auto_bounds: skip + auto_bounds: quick # Enable automatic discovery of tables and functions. # You may set this to `false` to disable. diff --git a/martin/src/args/pg.rs b/martin/src/args/pg.rs index b948eaf90..cd47d6ec6 100644 --- a/martin/src/args/pg.rs +++ b/martin/src/args/pg.rs @@ -43,7 +43,13 @@ pub struct PgArgs { pub default_srid: Option, #[arg(help = format!("Maximum Postgres connections pool size [DEFAULT: {POOL_SIZE_DEFAULT}]"), short, long)] pub pool_size: Option, - /// Limit the number of features in a tile from a PG table source. + /// Limit the number of geo features per tile. + /// + /// If the source table has more features than set here, they will not be included in the tile and the result will look "cut off"/incomplete. + /// This feature allows to put a maximum latency bound on tiles with extreme amount of detail at the cost of not returning all data. + /// It is sensible to set this limit if you have user generated/untrusted geodata, e.g. a lot of data points at [Null Island](https://en.wikipedia.org/wiki/Null_Island). + /// + /// Can be either a positive integer or unlimited if omitted. #[arg(short, long)] pub max_feature_count: Option, } diff --git a/martin/src/pg/builder.rs b/martin/src/pg/builder.rs index a505f2263..3a97dbf13 100644 --- a/martin/src/pg/builder.rs +++ b/martin/src/pg/builder.rs @@ -28,12 +28,22 @@ pub type SqlTableInfoMapMapMap = InfoMap>>; #[derive(Debug)] pub struct PgBuilder { pool: PgPool, + /// If a spatial table has SRID 0, then this SRID will be used as a fallback default_srid: Option, + /// Specify how bounds should be computed for the spatial PG tables auto_bounds: BoundsCalcType, + /// Limit the number of geo features per tile. + /// + /// If the source table has more features than set here, they will not be included in the tile and the result will look "cut off"/incomplete. + /// This feature allows to put a maximum latency bound on tiles with extreme amount of detail at the cost of not returning all data. + /// It is sensible to set this limit if you have user generated/untrusted geodata, e.g. a lot of data points at [Null Island](https://en.wikipedia.org/wiki/Null_Island). + /// + /// Can be either a positive integer or unlimited if omitted. max_feature_count: Option, auto_functions: Option, auto_tables: Option, id_resolver: IdResolver, + /// Associative arrays of table sources tables: TableInfoSources, functions: FuncInfoSources, } @@ -78,6 +88,7 @@ macro_rules! get_auto_schemas { } impl PgBuilder { + /// Creates a new Builder from the [`PgConfig`] and a way to deterministically convert duplicate to unique names pub async fn new(config: &PgConfig, id_resolver: IdResolver) -> PgResult { let pool = PgPool::new(config).await?; @@ -100,6 +111,7 @@ impl PgBuilder { self.auto_bounds } + /// ID under which this [`PgBuilder`] is identified externally pub fn get_id(&self) -> &str { self.pool.get_id() } diff --git a/martin/src/pg/config.rs b/martin/src/pg/config.rs index b513e7a20..b3773f6cb 100644 --- a/martin/src/pg/config.rs +++ b/martin/src/pg/config.rs @@ -39,16 +39,32 @@ pub struct PgSslCerts { #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct PgConfig { + /// Database connection string pub connection_string: Option, #[serde(flatten)] pub ssl_certificates: PgSslCerts, + /// If a spatial table has SRID 0, then this SRID will be used as a fallback pub default_srid: Option, + /// Specify how bounds should be computed for the spatial PG tables pub auto_bounds: Option, + /// Limit the number of geo features per tile. + /// + /// If the source table has more features than set here, they will not be included in the tile and the result will look "cut off"/incomplete. + /// This feature allows to put a maximum latency bound on tiles with extreme amount of detail at the cost of not returning all data. + /// It is sensible to set this limit if you have user generated/untrusted geodata, e.g. a lot of data points at [Null Island](https://en.wikipedia.org/wiki/Null_Island). + /// + /// Can be either a positive integer or unlimited if omitted. pub max_feature_count: Option, + /// Maximum Postgres connections pool size [DEFAULT: 20] pub pool_size: Option, + /// Enable/disable/configure automatic discovery of tables and functions. + /// + /// You may set this to `OptBoolObj::Bool(false)` to disable. #[serde(default, skip_serializing_if = "OptBoolObj::is_none")] pub auto_publish: OptBoolObj, + /// Associative arrays of table sources pub tables: Option, + /// Associative arrays of function sources pub functions: Option, } diff --git a/martin/src/pg/config_table.rs b/martin/src/pg/config_table.rs index a4188ea7e..771fa99fc 100644 --- a/martin/src/pg/config_table.rs +++ b/martin/src/pg/config_table.rs @@ -168,6 +168,8 @@ impl TableInfo { } /// Determine the SRID value to use for a table, or None if unknown, assuming self is a table info from the database + /// + /// Tries to use `default_srid` if a spatial table has SRID 0. #[must_use] pub fn calc_srid(&self, new_id: &str, cfg_srid: i32, default_srid: Option) -> Option { match (self.srid, cfg_srid, default_srid) { diff --git a/martin/src/pg/pool.rs b/martin/src/pg/pool.rs index b371dd435..6c91f9b2a 100644 --- a/martin/src/pg/pool.rs +++ b/martin/src/pg/pool.rs @@ -45,8 +45,12 @@ impl PgPool { .max_size(config.pool_size.unwrap_or(POOL_SIZE_DEFAULT)) .build() .map_err(|e| PostgresPoolBuildError(e, id.clone()))?; - - let conn = get_conn(&pool, &id).await?; + let mut res = Self { + id: id.clone(), + pool, + supports_tile_margin: false, + }; + let conn = res.get().await?; let pg_ver = get_postgres_version(&conn).await?; if pg_ver < MINIMUM_POSTGRES_VERSION { return Err(PostgresqlTooOld(pg_ver, MINIMUM_POSTGRES_VERSION)); @@ -59,14 +63,13 @@ impl PgPool { // In the warning cases below, we could technically run. // This is not ideal for reasons explained in the warnings - if pg_ver < RECOMMENDED_POSTGRES_VERSION { warn!( "PostgreSQL {pg_ver} is older than the recommended minimum {RECOMMENDED_POSTGRES_VERSION}." ); } - let supports_tile_margin = postgis_ver >= ST_TILE_ENVELOPE_POSTGIS_VERSION; - if !supports_tile_margin { + res.supports_tile_margin = postgis_ver >= ST_TILE_ENVELOPE_POSTGIS_VERSION; + if !res.supports_tile_margin { warn!( "PostGIS {postgis_ver} is older than {ST_TILE_ENVELOPE_POSTGIS_VERSION}. Margin parameter in ST_TileEnvelope is not supported, so tiles may be cut off at the edges." ); @@ -76,14 +79,8 @@ impl PgPool { "PostGIS {postgis_ver} is older than the recommended minimum {MISSING_GEOM_FIXED_POSTGIS_VERSION}. In the used version, some geometry may be hidden on some zoom levels. If You encounter this bug, please consider updating your postgis installation. For further details please refer to https://github.com/maplibre/martin/issues/1651#issuecomment-2628674788" ); } - info!("Connected to PostgreSQL {pg_ver} / PostGIS {postgis_ver} for source {id}"); - - Ok(Self { - id, - pool, - supports_tile_margin, - }) + Ok(res) } fn parse_config(config: &PgConfig) -> PgResult<(String, Manager)> { @@ -122,13 +119,22 @@ impl PgPool { Ok((id, mgr)) } + /// Retrieves an [`Object`] from this [`PgPool`] or waits for one to become available. + /// + /// # Errors + /// + /// See [`PostgresPoolConnError`] for details. pub async fn get(&self) -> PgResult { - get_conn(&self.pool, self.id.as_str()).await + self.pool + .get() + .await + .map_err(|e| PostgresPoolConnError(e, self.id.clone())) } + /// ID under which this [`PgPool`] is identified externally #[must_use] pub fn get_id(&self) -> &str { - self.id.as_str() + &self.id } /// Indicates if `ST_TileEnvelope` supports the margin parameter. @@ -141,12 +147,6 @@ impl PgPool { } } -async fn get_conn(pool: &Pool, id: &str) -> PgResult { - pool.get() - .await - .map_err(|e| PostgresPoolConnError(e, id.to_string())) -} - /// Get [PostgreSQL version](https://www.postgresql.org/support/versioning/). /// `PostgreSQL` only has a Major.Minor versioning, so we use 0 the patch version async fn get_postgres_version(conn: &Object) -> PgResult { diff --git a/martin/src/source.rs b/martin/src/source.rs index 12ae36a95..af2d33c5e 100644 --- a/martin/src/source.rs +++ b/martin/src/source.rs @@ -103,10 +103,15 @@ impl TileSources { #[async_trait] pub trait Source: Send + Debug { + /// ID under which this [`Source`] is identified if accessed externally fn get_id(&self) -> &str; + /// TileJSON of this [`Source`] + /// + /// Will be communicated verbatim to the outside to give rendering engines information about the source's contents such as zoom levels, center points, ... fn get_tilejson(&self) -> &TileJSON; + /// Information for serving the source such as which Mime-type to apply or how compression should work fn get_tile_info(&self) -> TileInfo; fn clone_source(&self) -> TileInfoSource;