• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 7006148507

27 Nov 2023 02:07PM UTC coverage: 89.647% (+0.1%) from 89.498%
7006148507

push

github

web-flow
Merge pull request #888 from geo-engine/raster_stacks

raster stacking

4032 of 4274 new or added lines in 107 files covered. (94.34%)

19 existing lines in 10 files now uncovered.

113015 of 126066 relevant lines covered (89.65%)

59901.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.14
/services/src/contexts/postgres.rs
1
use super::migrations::{all_migrations, migrate_database, MigrationResult};
2
use super::{ExecutionContextImpl, Session, SimpleApplicationContext};
3
use crate::api::cli::{add_datasets_from_directory, add_providers_from_directory};
4
use crate::contexts::{ApplicationContext, QueryContextImpl, SessionId, SimpleSession};
5
use crate::contexts::{GeoEngineDb, SessionContext};
6
use crate::datasets::upload::{Volume, Volumes};
7
use crate::datasets::DatasetName;
8
use crate::error::{self, Error, Result};
9
use crate::layers::add_from_directory::{
10
    add_layer_collections_from_directory, add_layers_from_directory,
11
};
12
use crate::projects::{ProjectId, STRectangle};
13
use crate::tasks::{SimpleTaskManager, SimpleTaskManagerBackend, SimpleTaskManagerContext};
14
use crate::util::config;
15
use crate::util::config::get_config_element;
16
use async_trait::async_trait;
17
use bb8_postgres::{
18
    bb8::Pool,
19
    bb8::PooledConnection,
20
    tokio_postgres::{error::SqlState, tls::MakeTlsConnect, tls::TlsConnect, Config, Socket},
21
    PostgresConnectionManager,
22
};
23
use geoengine_datatypes::raster::TilingSpecification;
24
use geoengine_operators::engine::ChunkByteSize;
25
use geoengine_operators::util::create_rayon_thread_pool;
26
use log::info;
27
use rayon::ThreadPool;
28
use snafu::ensure;
29
use std::path::PathBuf;
30
use std::sync::Arc;
31

32
// TODO: distinguish user-facing errors from system-facing error messages
33

34
/// A context with references to Postgres backends of the database.
35
#[derive(Clone)]
717✔
36
pub struct PostgresContext<Tls>
37
where
38
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
39
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
40
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
41
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
42
{
43
    default_session_id: SessionId,
44
    thread_pool: Arc<ThreadPool>,
45
    exe_ctx_tiling_spec: TilingSpecification,
46
    query_ctx_chunk_size: ChunkByteSize,
47
    task_manager: Arc<SimpleTaskManagerBackend>,
48
    pool: Pool<PostgresConnectionManager<Tls>>,
49
    volumes: Volumes,
50
}
51

52
enum DatabaseStatus {
53
    Unitialized,
54
    InitializedClearDatabase,
55
    InitializedKeepDatabase,
56
}
57

58
impl<Tls> PostgresContext<Tls>
59
where
60
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
61
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
62
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
63
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
64
{
65
    pub async fn new_with_context_spec(
136✔
66
        config: Config,
136✔
67
        tls: Tls,
136✔
68
        exe_ctx_tiling_spec: TilingSpecification,
136✔
69
        query_ctx_chunk_size: ChunkByteSize,
136✔
70
    ) -> Result<Self> {
136✔
71
        let pg_mgr = PostgresConnectionManager::new(config, tls);
136✔
72

73
        let pool = Pool::builder().build(pg_mgr).await?;
136✔
74
        let created_schema = Self::create_database(pool.get().await?).await?;
3,808✔
75

76
        let session = if created_schema {
136✔
77
            let session = SimpleSession::default();
136✔
78
            Self::create_default_session(pool.get().await?, session.id()).await?;
272✔
79
            session
136✔
80
        } else {
81
            Self::load_default_session(pool.get().await?).await?
×
82
        };
83

84
        Ok(PostgresContext {
136✔
85
            default_session_id: session.id(),
136✔
86
            task_manager: Default::default(),
136✔
87
            thread_pool: create_rayon_thread_pool(0),
136✔
88
            exe_ctx_tiling_spec,
136✔
89
            query_ctx_chunk_size,
136✔
90
            pool,
136✔
91
            volumes: Default::default(),
136✔
92
        })
136✔
93
    }
136✔
94

95
    // TODO: check if the datasets exist already and don't output warnings when skipping them
96
    #[allow(clippy::too_many_arguments)]
97
    pub async fn new_with_data(
×
98
        config: Config,
×
99
        tls: Tls,
×
100
        dataset_defs_path: PathBuf,
×
101
        provider_defs_path: PathBuf,
×
102
        layer_defs_path: PathBuf,
×
103
        layer_collection_defs_path: PathBuf,
×
104
        exe_ctx_tiling_spec: TilingSpecification,
×
105
        query_ctx_chunk_size: ChunkByteSize,
×
106
    ) -> Result<Self> {
×
107
        let pg_mgr = PostgresConnectionManager::new(config, tls);
×
108

109
        let pool = Pool::builder().build(pg_mgr).await?;
×
110
        let created_schema = Self::create_database(pool.get().await?).await?;
×
111

112
        let session = if created_schema {
×
113
            let session = SimpleSession::default();
×
114
            Self::create_default_session(pool.get().await?, session.id()).await?;
×
115
            session
×
116
        } else {
117
            Self::load_default_session(pool.get().await?).await?
×
118
        };
119

120
        let app_ctx = PostgresContext {
×
121
            default_session_id: session.id(),
×
122
            task_manager: Default::default(),
×
123
            thread_pool: create_rayon_thread_pool(0),
×
124
            exe_ctx_tiling_spec,
×
125
            query_ctx_chunk_size,
×
126
            pool,
×
127
            volumes: Default::default(),
×
128
        };
×
129

×
130
        if created_schema {
×
131
            info!("Populating database with initial data...");
×
132

133
            let ctx = app_ctx.session_context(session);
×
134

×
135
            let mut db = ctx.db();
×
136
            add_layers_from_directory(&mut db, layer_defs_path).await;
×
137
            add_layer_collections_from_directory(&mut db, layer_collection_defs_path).await;
×
138

139
            add_datasets_from_directory(&mut db, dataset_defs_path).await;
×
140

141
            add_providers_from_directory(&mut db, provider_defs_path).await;
×
142
        }
×
143

144
        Ok(app_ctx)
×
145
    }
×
146

147
    async fn check_schema_status(
218✔
148
        conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
218✔
149
    ) -> Result<DatabaseStatus> {
218✔
150
        let stmt = match conn
218✔
151
            .prepare("SELECT clear_database_on_start from geoengine;")
218✔
152
            .await
218✔
153
        {
154
            Ok(stmt) => stmt,
×
155
            Err(e) => {
218✔
156
                if let Some(code) = e.code() {
218✔
157
                    if *code == SqlState::UNDEFINED_TABLE {
218✔
158
                        info!("Initializing schema.");
×
159
                        return Ok(DatabaseStatus::Unitialized);
218✔
160
                    }
×
161
                }
×
162
                return Err(error::Error::TokioPostgres { source: e });
×
163
            }
164
        };
165

166
        let row = conn.query_one(&stmt, &[]).await?;
×
167

168
        if row.get(0) {
×
169
            Ok(DatabaseStatus::InitializedClearDatabase)
×
170
        } else {
171
            Ok(DatabaseStatus::InitializedKeepDatabase)
×
172
        }
173
    }
218✔
174

175
    /// Clears the database if the Settings demand and the database properties allows it.
176
    pub(crate) async fn maybe_clear_database(
218✔
177
        conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
218✔
178
    ) -> Result<()> {
218✔
179
        let postgres_config = get_config_element::<crate::util::config::Postgres>()?;
218✔
180
        let database_status = Self::check_schema_status(conn).await?;
218✔
181
        let schema_name = postgres_config.schema;
218✔
182

183
        match database_status {
×
184
            DatabaseStatus::InitializedClearDatabase
×
185
                if postgres_config.clear_database_on_start && schema_name != "pg_temp" =>
×
186
            {
187
                info!("Clearing schema {}.", schema_name);
×
188
                conn.batch_execute(&format!("DROP SCHEMA {schema_name} CASCADE;"))
×
189
                    .await?;
×
190
            }
191
            DatabaseStatus::InitializedKeepDatabase if postgres_config.clear_database_on_start => {
×
192
                return Err(Error::ClearDatabaseOnStartupNotAllowed)
×
193
            }
194
            DatabaseStatus::InitializedClearDatabase
195
            | DatabaseStatus::InitializedKeepDatabase
196
            | DatabaseStatus::Unitialized => (),
218✔
197
        };
198

199
        Ok(())
218✔
200
    }
218✔
201

202
    /// Creates the database schema. Returns true if the schema was created, false if it already existed.
203
    pub(crate) async fn create_database(
136✔
204
        mut conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
136✔
205
    ) -> Result<bool> {
136✔
206
        Self::maybe_clear_database(&conn).await?;
136✔
207

208
        let migration = migrate_database(&mut conn, &all_migrations()).await?;
3,672✔
209

210
        Ok(migration == MigrationResult::CreatedDatabase)
136✔
211
    }
136✔
212

213
    async fn create_default_session(
136✔
214
        conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
136✔
215
        session_id: SessionId,
136✔
216
    ) -> Result<()> {
136✔
217
        let stmt = conn
136✔
218
            .prepare("INSERT INTO sessions (id, project_id, view) VALUES ($1, NULL ,NULL);")
136✔
219
            .await?;
136✔
220

221
        conn.execute(&stmt, &[&session_id]).await?;
136✔
222

223
        Ok(())
136✔
224
    }
136✔
225
    async fn load_default_session(
66✔
226
        conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
66✔
227
    ) -> Result<SimpleSession> {
66✔
228
        let stmt = conn
66✔
229
            .prepare("SELECT id, project_id, view FROM sessions LIMIT 1;")
66✔
230
            .await?;
380✔
231

232
        let row = conn.query_one(&stmt, &[]).await?;
66✔
233

234
        Ok(SimpleSession::new(row.get(0), row.get(1), row.get(2)))
66✔
235
    }
66✔
236
}
237

238
#[async_trait]
239
impl<Tls> SimpleApplicationContext for PostgresContext<Tls>
240
where
241
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
242
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
243
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
244
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
245
{
246
    async fn default_session_id(&self) -> SessionId {
78✔
247
        self.default_session_id
78✔
248
    }
78✔
249

250
    async fn default_session(&self) -> Result<SimpleSession> {
66✔
251
        Self::load_default_session(self.pool.get().await?).await
445✔
252
    }
132✔
253

254
    async fn update_default_session_project(&self, project: ProjectId) -> Result<()> {
1✔
255
        let conn = self.pool.get().await?;
1✔
256

257
        let stmt = conn
1✔
258
            .prepare("UPDATE sessions SET project_id = $1 WHERE id = $2;")
1✔
259
            .await?;
1✔
260

261
        conn.execute(&stmt, &[&project, &self.default_session_id])
1✔
262
            .await?;
1✔
263

264
        Ok(())
1✔
265
    }
2✔
266

267
    async fn update_default_session_view(&self, view: STRectangle) -> Result<()> {
1✔
268
        let conn = self.pool.get().await?;
1✔
269

270
        let stmt = conn
1✔
271
            .prepare("UPDATE sessions SET view = $1 WHERE id = $2;")
1✔
272
            .await?;
1✔
273

274
        conn.execute(&stmt, &[&view, &self.default_session_id])
1✔
275
            .await?;
1✔
276

277
        Ok(())
1✔
278
    }
2✔
279

280
    async fn default_session_context(&self) -> Result<Self::SessionContext> {
273✔
281
        Ok(self.session_context(self.session_by_id(self.default_session_id).await?))
2,662✔
282
    }
546✔
283
}
284

285
#[async_trait]
286
impl<Tls> ApplicationContext for PostgresContext<Tls>
287
where
288
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
289
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
290
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
291
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
292
{
293
    type SessionContext = PostgresSessionContext<Tls>;
294
    type Session = SimpleSession;
295

296
    fn session_context(&self, session: Self::Session) -> Self::SessionContext {
437✔
297
        PostgresSessionContext {
437✔
298
            session,
437✔
299
            context: self.clone(),
437✔
300
        }
437✔
301
    }
437✔
302

303
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
375✔
304
        let mut conn = self.pool.get().await?;
375✔
305

306
        let tx = conn.build_transaction().start().await?;
369✔
307

308
        let stmt = tx
369✔
309
            .prepare(
369✔
310
                "
369✔
311
            SELECT           
369✔
312
                project_id,
369✔
313
                view
369✔
314
            FROM sessions
369✔
315
            WHERE id = $1;",
369✔
316
            )
369✔
317
            .await?;
1,968✔
318

319
        let row = tx
369✔
320
            .query_one(&stmt, &[&session_id])
369✔
321
            .await
343✔
322
            .map_err(|_error| error::Error::InvalidSession)?;
369✔
323

324
        Ok(SimpleSession::new(session_id, row.get(0), row.get(1)))
369✔
325
    }
744✔
326
}
327

328
#[derive(Clone)]
×
329
pub struct PostgresSessionContext<Tls>
330
where
331
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
332
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
333
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
334
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
335
{
336
    session: SimpleSession,
337
    context: PostgresContext<Tls>,
338
}
339

340
#[async_trait]
341
impl<Tls> SessionContext for PostgresSessionContext<Tls>
342
where
343
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
344
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
345
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
346
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
347
{
348
    type Session = SimpleSession;
349
    type GeoEngineDB = PostgresDb<Tls>;
350

351
    type TaskContext = SimpleTaskManagerContext;
352
    type TaskManager = SimpleTaskManager; // this does not persist across restarts
353
    type QueryContext = QueryContextImpl;
354
    type ExecutionContext = ExecutionContextImpl<Self::GeoEngineDB>;
355

356
    fn db(&self) -> Self::GeoEngineDB {
440✔
357
        PostgresDb::new(self.context.pool.clone())
440✔
358
    }
440✔
359

360
    fn tasks(&self) -> Self::TaskManager {
36✔
361
        SimpleTaskManager::new(self.context.task_manager.clone())
36✔
362
    }
36✔
363

364
    fn query_context(&self) -> Result<Self::QueryContext> {
27✔
365
        Ok(QueryContextImpl::new(
27✔
366
            self.context.query_ctx_chunk_size,
27✔
367
            self.context.thread_pool.clone(),
27✔
368
        ))
27✔
369
    }
27✔
370

371
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
46✔
372
        Ok(ExecutionContextImpl::<PostgresDb<Tls>>::new(
46✔
373
            self.db(),
46✔
374
            self.context.thread_pool.clone(),
46✔
375
            self.context.exe_ctx_tiling_spec,
46✔
376
        ))
46✔
377
    }
46✔
378

379
    fn volumes(&self) -> Result<Vec<Volume>> {
×
380
        Ok(self.context.volumes.volumes.clone())
×
381
    }
×
382

383
    fn session(&self) -> &Self::Session {
110✔
384
        &self.session
110✔
385
    }
110✔
386
}
387

388
pub struct PostgresDb<Tls>
389
where
390
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
391
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
392
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
393
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
394
{
395
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
396
}
397

398
impl<Tls> PostgresDb<Tls>
399
where
400
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
401
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
402
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
403
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
404
{
405
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>) -> Self {
442✔
406
        Self { conn_pool }
442✔
407
    }
442✔
408

409
    /// Check whether the namespace of the given dataset is allowed for insertion
410
    pub(crate) fn check_namespace(id: &DatasetName) -> Result<()> {
68✔
411
        // due to a lack of users, etc., we only allow one namespace for now
68✔
412
        if id.namespace.is_none() {
68✔
413
            Ok(())
68✔
414
        } else {
415
            Err(Error::InvalidDatasetIdNamespace)
×
416
        }
417
    }
68✔
418
}
419

420
impl<Tls> GeoEngineDb for PostgresDb<Tls>
421
where
422
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
423
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
424
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
425
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
426
{
427
}
428

429
impl TryFrom<config::Postgres> for Config {
430
    type Error = Error;
431

432
    fn try_from(db_config: config::Postgres) -> Result<Self> {
7✔
433
        ensure!(
7✔
434
            db_config.schema != "public",
7✔
435
            crate::error::InvalidDatabaseSchema
×
436
        );
437

438
        let mut pg_config = Config::new();
7✔
439
        pg_config
7✔
440
            .user(&db_config.user)
7✔
441
            .password(&db_config.password)
7✔
442
            .host(&db_config.host)
7✔
443
            .dbname(&db_config.database)
7✔
444
            .port(db_config.port)
7✔
445
            // fix schema by providing `search_path` option
7✔
446
            .options(&format!("-c search_path={}", db_config.schema));
7✔
447
        Ok(pg_config)
7✔
448
    }
7✔
449
}
450

451
#[cfg(test)]
452
mod tests {
453
    use super::*;
454
    use crate::datasets::external::aruna::ArunaDataProviderDefinition;
455
    use crate::datasets::external::gbif::GbifDataProviderDefinition;
456
    use crate::datasets::external::gfbio_abcd::GfbioAbcdDataProviderDefinition;
457
    use crate::datasets::external::gfbio_collections::GfbioCollectionsDataProviderDefinition;
458
    use crate::datasets::external::netcdfcf::{
459
        EbvPortalDataProviderDefinition, NetCdfCfDataProviderDefinition,
460
    };
461
    use crate::datasets::external::pangaea::PangaeaDataProviderDefinition;
462
    use crate::datasets::listing::{DatasetListOptions, DatasetListing, ProvenanceOutput};
463
    use crate::datasets::listing::{DatasetProvider, Provenance};
464
    use crate::datasets::storage::{DatasetStore, MetaDataDefinition};
465
    use crate::datasets::upload::{FileId, UploadId};
466
    use crate::datasets::upload::{FileUpload, Upload, UploadDb};
467
    use crate::datasets::{AddDataset, DatasetIdAndName};
468
    use crate::ge_context;
469
    use crate::layers::add_from_directory::UNSORTED_COLLECTION_ID;
470
    use crate::layers::external::TypedDataProviderDefinition;
471
    use crate::layers::layer::{
472
        AddLayer, AddLayerCollection, CollectionItem, LayerCollection, LayerCollectionListOptions,
473
        LayerCollectionListing, LayerListing, Property, ProviderLayerCollectionId, ProviderLayerId,
474
    };
475
    use crate::layers::listing::{LayerCollectionId, LayerCollectionProvider};
476
    use crate::layers::storage::{
477
        LayerDb, LayerProviderDb, LayerProviderListing, LayerProviderListingOptions,
478
        INTERNAL_PROVIDER_ID,
479
    };
480
    use crate::projects::{
481
        ColorParam, CreateProject, DerivedColor, DerivedNumber, LayerUpdate, LineSymbology,
482
        LoadVersion, NumberParam, OrderBy, Plot, PlotUpdate, PointSymbology, PolygonSymbology,
483
        ProjectDb, ProjectId, ProjectLayer, ProjectListOptions, ProjectListing, RasterSymbology,
484
        STRectangle, StrokeParam, Symbology, TextSymbology, UpdateProject,
485
    };
486
    use crate::util::postgres::{assert_sql_type, DatabaseConnectionConfig};
487
    use crate::util::tests::register_ndvi_workflow_helper;
488
    use crate::workflows::registry::WorkflowRegistry;
489
    use crate::workflows::workflow::Workflow;
490
    use bb8_postgres::tokio_postgres::NoTls;
491
    use futures::join;
492
    use geoengine_datatypes::collections::VectorDataType;
493
    use geoengine_datatypes::dataset::{DataProviderId, LayerId};
494
    use geoengine_datatypes::operations::image::{Breakpoint, Colorizer, DefaultColors, RgbaColor};
495
    use geoengine_datatypes::primitives::{
496
        BoundingBox2D, ClassificationMeasurement, ColumnSelection, ContinuousMeasurement,
497
        Coordinate2D, DateTimeParseFormat, FeatureDataType, MultiLineString, MultiPoint,
498
        MultiPolygon, NoGeometry, RasterQueryRectangle, SpatialPartition2D, SpatialResolution,
499
        TimeGranularity, TimeInstance, TimeInterval, TimeStep, TypedGeometry, VectorQueryRectangle,
500
    };
501
    use geoengine_datatypes::primitives::{CacheTtlSeconds, Measurement};
502
    use geoengine_datatypes::raster::{
503
        RasterDataType, RasterPropertiesEntryType, RasterPropertiesKey,
504
    };
505
    use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceOption};
506
    use geoengine_datatypes::test_data;
507
    use geoengine_datatypes::util::{NotNanF64, StringPair};
508
    use geoengine_operators::engine::{
509
        MetaData, MetaDataProvider, MultipleRasterOrSingleVectorSource, PlotOperator,
510
        PlotResultDescriptor, RasterBandDescriptor, RasterBandDescriptors, RasterResultDescriptor,
511
        StaticMetaData, TypedOperator, TypedResultDescriptor, VectorColumnInfo, VectorOperator,
512
        VectorResultDescriptor,
513
    };
514
    use geoengine_operators::mock::{
515
        MockDatasetDataSourceLoadingInfo, MockPointSource, MockPointSourceParams,
516
    };
517
    use geoengine_operators::plot::{Statistics, StatisticsParams};
518
    use geoengine_operators::source::{
519
        CsvHeader, FileNotFoundHandling, FormatSpecifics, GdalDatasetGeoTransform,
520
        GdalDatasetParameters, GdalLoadingInfo, GdalLoadingInfoTemporalSlice, GdalMetaDataList,
521
        GdalMetaDataRegular, GdalMetaDataStatic, GdalMetadataMapping, GdalMetadataNetCdfCf,
522
        GdalRetryOptions, GdalSourceTimePlaceholder, OgrSourceColumnSpec, OgrSourceDataset,
523
        OgrSourceDatasetTimeType, OgrSourceDurationSpec, OgrSourceErrorSpec, OgrSourceTimeFormat,
524
        TimeReference, UnixTimeStampType,
525
    };
526
    use geoengine_operators::util::input::MultiRasterOrVectorOperator::Raster;
527
    use ordered_float::NotNan;
528
    use serde_json::json;
529
    use std::marker::PhantomData;
530
    use std::str::FromStr;
531
    use tokio_postgres::config::Host;
532

533
    #[ge_context::test]
2✔
534
    async fn test(app_ctx: PostgresContext<NoTls>) {
1✔
535
        let session = app_ctx.default_session().await.unwrap();
18✔
536

1✔
537
        create_projects(&app_ctx, &session).await;
74✔
538

539
        let projects = list_projects(&app_ctx, &session).await;
14✔
540

541
        let project_id = projects[0].id;
1✔
542

1✔
543
        update_projects(&app_ctx, &session, project_id).await;
164✔
544

545
        delete_project(&app_ctx, &session, project_id).await;
7✔
546
    }
1✔
547

548
    async fn delete_project(
1✔
549
        app_ctx: &PostgresContext<NoTls>,
1✔
550
        session: &SimpleSession,
1✔
551
        project_id: ProjectId,
1✔
552
    ) {
1✔
553
        let db = app_ctx.session_context(session.clone()).db();
1✔
554

1✔
555
        db.delete_project(project_id).await.unwrap();
3✔
556

1✔
557
        assert!(db.load_project(project_id).await.is_err());
4✔
558
    }
1✔
559

560
    #[allow(clippy::too_many_lines)]
561
    async fn update_projects(
1✔
562
        app_ctx: &PostgresContext<NoTls>,
1✔
563
        session: &SimpleSession,
1✔
564
        project_id: ProjectId,
1✔
565
    ) {
1✔
566
        let db = app_ctx.session_context(session.clone()).db();
1✔
567

568
        let project = db
1✔
569
            .load_project_version(project_id, LoadVersion::Latest)
1✔
570
            .await
39✔
571
            .unwrap();
1✔
572

573
        let layer_workflow_id = db
1✔
574
            .register_workflow(Workflow {
1✔
575
                operator: TypedOperator::Vector(
1✔
576
                    MockPointSource {
1✔
577
                        params: MockPointSourceParams {
1✔
578
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
579
                        },
1✔
580
                    }
1✔
581
                    .boxed(),
1✔
582
                ),
1✔
583
            })
1✔
584
            .await
3✔
585
            .unwrap();
1✔
586

1✔
587
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
3✔
588

589
        let plot_workflow_id = db
1✔
590
            .register_workflow(Workflow {
1✔
591
                operator: Statistics {
1✔
592
                    params: StatisticsParams {
1✔
593
                        column_names: vec![],
1✔
594
                    },
1✔
595
                    sources: MultipleRasterOrSingleVectorSource {
1✔
596
                        source: Raster(vec![]),
1✔
597
                    },
1✔
598
                }
1✔
599
                .boxed()
1✔
600
                .into(),
1✔
601
            })
1✔
602
            .await
3✔
603
            .unwrap();
1✔
604

1✔
605
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
3✔
606

607
        // add a plot
608
        let update = UpdateProject {
1✔
609
            id: project.id,
1✔
610
            name: Some("Test9 Updated".into()),
1✔
611
            description: None,
1✔
612
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
1✔
613
                workflow: layer_workflow_id,
1✔
614
                name: "TestLayer".into(),
1✔
615
                symbology: PointSymbology::default().into(),
1✔
616
                visibility: Default::default(),
1✔
617
            })]),
1✔
618
            plots: Some(vec![PlotUpdate::UpdateOrInsert(Plot {
1✔
619
                workflow: plot_workflow_id,
1✔
620
                name: "Test Plot".into(),
1✔
621
            })]),
1✔
622
            bounds: None,
1✔
623
            time_step: None,
1✔
624
        };
1✔
625
        db.update_project(update).await.unwrap();
67✔
626

627
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
628
        assert_eq!(versions.len(), 2);
1✔
629

630
        // add second plot
631
        let update = UpdateProject {
1✔
632
            id: project.id,
1✔
633
            name: Some("Test9 Updated".into()),
1✔
634
            description: None,
1✔
635
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
1✔
636
                workflow: layer_workflow_id,
1✔
637
                name: "TestLayer".into(),
1✔
638
                symbology: PointSymbology::default().into(),
1✔
639
                visibility: Default::default(),
1✔
640
            })]),
1✔
641
            plots: Some(vec![
1✔
642
                PlotUpdate::UpdateOrInsert(Plot {
1✔
643
                    workflow: plot_workflow_id,
1✔
644
                    name: "Test Plot".into(),
1✔
645
                }),
1✔
646
                PlotUpdate::UpdateOrInsert(Plot {
1✔
647
                    workflow: plot_workflow_id,
1✔
648
                    name: "Test Plot".into(),
1✔
649
                }),
1✔
650
            ]),
1✔
651
            bounds: None,
1✔
652
            time_step: None,
1✔
653
        };
1✔
654
        db.update_project(update).await.unwrap();
21✔
655

656
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
657
        assert_eq!(versions.len(), 3);
1✔
658

659
        // delete plots
660
        let update = UpdateProject {
1✔
661
            id: project.id,
1✔
662
            name: None,
1✔
663
            description: None,
1✔
664
            layers: None,
1✔
665
            plots: Some(vec![]),
1✔
666
            bounds: None,
1✔
667
            time_step: None,
1✔
668
        };
1✔
669
        db.update_project(update).await.unwrap();
16✔
670

671
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
672
        assert_eq!(versions.len(), 4);
1✔
673
    }
1✔
674

675
    async fn list_projects(
1✔
676
        app_ctx: &PostgresContext<NoTls>,
1✔
677
        session: &SimpleSession,
1✔
678
    ) -> Vec<ProjectListing> {
1✔
679
        let options = ProjectListOptions {
1✔
680
            order: OrderBy::NameDesc,
1✔
681
            offset: 0,
1✔
682
            limit: 2,
1✔
683
        };
1✔
684

1✔
685
        let db = app_ctx.session_context(session.clone()).db();
1✔
686

687
        let projects = db.list_projects(options).await.unwrap();
14✔
688

1✔
689
        assert_eq!(projects.len(), 2);
1✔
690
        assert_eq!(projects[0].name, "Test9");
1✔
691
        assert_eq!(projects[1].name, "Test8");
1✔
692
        projects
1✔
693
    }
1✔
694

695
    async fn create_projects(app_ctx: &PostgresContext<NoTls>, session: &SimpleSession) {
1✔
696
        let db = app_ctx.session_context(session.clone()).db();
1✔
697

698
        for i in 0..10 {
11✔
699
            let create = CreateProject {
10✔
700
                name: format!("Test{i}"),
10✔
701
                description: format!("Test{}", 10 - i),
10✔
702
                bounds: STRectangle::new(
10✔
703
                    SpatialReferenceOption::Unreferenced,
10✔
704
                    0.,
10✔
705
                    0.,
10✔
706
                    1.,
10✔
707
                    1.,
10✔
708
                    0,
10✔
709
                    1,
10✔
710
                )
10✔
711
                .unwrap(),
10✔
712
                time_step: None,
10✔
713
            };
10✔
714
            db.create_project(create).await.unwrap();
74✔
715
        }
716
    }
1✔
717

718
    #[ge_context::test]
2✔
719
    async fn it_persists_workflows(app_ctx: PostgresContext<NoTls>) {
1✔
720
        let workflow = Workflow {
1✔
721
            operator: TypedOperator::Vector(
1✔
722
                MockPointSource {
1✔
723
                    params: MockPointSourceParams {
1✔
724
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
725
                    },
1✔
726
                }
1✔
727
                .boxed(),
1✔
728
            ),
1✔
729
        };
1✔
730

731
        let session = app_ctx.default_session().await.unwrap();
18✔
732
        let ctx = app_ctx.session_context(session);
1✔
733

1✔
734
        let db = ctx.db();
1✔
735
        let id = db.register_workflow(workflow).await.unwrap();
3✔
736

1✔
737
        drop(ctx);
1✔
738

739
        let workflow = db.load_workflow(&id).await.unwrap();
3✔
740

1✔
741
        let json = serde_json::to_string(&workflow).unwrap();
1✔
742
        assert_eq!(
1✔
743
            json,
1✔
744
            r#"{"type":"Vector","operator":{"type":"MockPointSource","params":{"points":[{"x":1.0,"y":2.0},{"x":1.0,"y":2.0},{"x":1.0,"y":2.0}]}}}"#
1✔
745
        );
1✔
746
    }
1✔
747

748
    #[allow(clippy::too_many_lines)]
749
    #[ge_context::test]
2✔
750
    async fn it_persists_datasets(app_ctx: PostgresContext<NoTls>) {
1✔
751
        let loading_info = OgrSourceDataset {
1✔
752
            file_name: PathBuf::from("test.csv"),
1✔
753
            layer_name: "test.csv".to_owned(),
1✔
754
            data_type: Some(VectorDataType::MultiPoint),
1✔
755
            time: OgrSourceDatasetTimeType::Start {
1✔
756
                start_field: "start".to_owned(),
1✔
757
                start_format: OgrSourceTimeFormat::Auto,
1✔
758
                duration: OgrSourceDurationSpec::Zero,
1✔
759
            },
1✔
760
            default_geometry: None,
1✔
761
            columns: Some(OgrSourceColumnSpec {
1✔
762
                format_specifics: Some(FormatSpecifics::Csv {
1✔
763
                    header: CsvHeader::Auto,
1✔
764
                }),
1✔
765
                x: "x".to_owned(),
1✔
766
                y: None,
1✔
767
                int: vec![],
1✔
768
                float: vec![],
1✔
769
                text: vec![],
1✔
770
                bool: vec![],
1✔
771
                datetime: vec![],
1✔
772
                rename: None,
1✔
773
            }),
1✔
774
            force_ogr_time_filter: false,
1✔
775
            force_ogr_spatial_filter: false,
1✔
776
            on_error: OgrSourceErrorSpec::Ignore,
1✔
777
            sql_query: None,
1✔
778
            attribute_query: None,
1✔
779
            cache_ttl: CacheTtlSeconds::default(),
1✔
780
        };
1✔
781

1✔
782
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
783
            OgrSourceDataset,
1✔
784
            VectorResultDescriptor,
1✔
785
            VectorQueryRectangle,
1✔
786
        > {
1✔
787
            loading_info: loading_info.clone(),
1✔
788
            result_descriptor: VectorResultDescriptor {
1✔
789
                data_type: VectorDataType::MultiPoint,
1✔
790
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
791
                columns: [(
1✔
792
                    "foo".to_owned(),
1✔
793
                    VectorColumnInfo {
1✔
794
                        data_type: FeatureDataType::Float,
1✔
795
                        measurement: Measurement::Unitless,
1✔
796
                    },
1✔
797
                )]
1✔
798
                .into_iter()
1✔
799
                .collect(),
1✔
800
                time: None,
1✔
801
                bbox: None,
1✔
802
            },
1✔
803
            phantom: Default::default(),
1✔
804
        });
1✔
805

806
        let session = app_ctx.default_session().await.unwrap();
18✔
807

1✔
808
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
809

1✔
810
        let db = app_ctx.session_context(session.clone()).db();
1✔
811
        let wrap = db.wrap_meta_data(meta_data);
1✔
812
        let DatasetIdAndName {
813
            id: dataset_id,
1✔
814
            name: dataset_name,
1✔
815
        } = db
1✔
816
            .add_dataset(
1✔
817
                AddDataset {
1✔
818
                    name: Some(dataset_name.clone()),
1✔
819
                    display_name: "Ogr Test".to_owned(),
1✔
820
                    description: "desc".to_owned(),
1✔
821
                    source_operator: "OgrSource".to_owned(),
1✔
822
                    symbology: None,
1✔
823
                    provenance: Some(vec![Provenance {
1✔
824
                        citation: "citation".to_owned(),
1✔
825
                        license: "license".to_owned(),
1✔
826
                        uri: "uri".to_owned(),
1✔
827
                    }]),
1✔
828
                },
1✔
829
                wrap,
1✔
830
            )
1✔
831
            .await
156✔
832
            .unwrap();
1✔
833

834
        let datasets = db
1✔
835
            .list_datasets(DatasetListOptions {
1✔
836
                filter: None,
1✔
837
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
838
                offset: 0,
1✔
839
                limit: 10,
1✔
840
            })
1✔
841
            .await
3✔
842
            .unwrap();
1✔
843

1✔
844
        assert_eq!(datasets.len(), 1);
1✔
845

846
        assert_eq!(
1✔
847
            datasets[0],
1✔
848
            DatasetListing {
1✔
849
                id: dataset_id,
1✔
850
                name: dataset_name,
1✔
851
                display_name: "Ogr Test".to_owned(),
1✔
852
                description: "desc".to_owned(),
1✔
853
                source_operator: "OgrSource".to_owned(),
1✔
854
                symbology: None,
1✔
855
                tags: vec![],
1✔
856
                result_descriptor: TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
857
                    data_type: VectorDataType::MultiPoint,
1✔
858
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
859
                    columns: [(
1✔
860
                        "foo".to_owned(),
1✔
861
                        VectorColumnInfo {
1✔
862
                            data_type: FeatureDataType::Float,
1✔
863
                            measurement: Measurement::Unitless
1✔
864
                        }
1✔
865
                    )]
1✔
866
                    .into_iter()
1✔
867
                    .collect(),
1✔
868
                    time: None,
1✔
869
                    bbox: None,
1✔
870
                })
1✔
871
            },
1✔
872
        );
1✔
873

874
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
4✔
875

1✔
876
        assert_eq!(
1✔
877
            provenance,
1✔
878
            ProvenanceOutput {
1✔
879
                data: dataset_id.into(),
1✔
880
                provenance: Some(vec![Provenance {
1✔
881
                    citation: "citation".to_owned(),
1✔
882
                    license: "license".to_owned(),
1✔
883
                    uri: "uri".to_owned(),
1✔
884
                }])
1✔
885
            }
1✔
886
        );
1✔
887

888
        let meta_data: Box<dyn MetaData<OgrSourceDataset, _, _>> =
1✔
889
            db.meta_data(&dataset_id.into()).await.unwrap();
3✔
890

1✔
891
        assert_eq!(
1✔
892
            meta_data
1✔
893
                .loading_info(VectorQueryRectangle {
1✔
894
                    spatial_bounds: BoundingBox2D::new_unchecked(
1✔
895
                        (-180., -90.).into(),
1✔
896
                        (180., 90.).into()
1✔
897
                    ),
1✔
898
                    time_interval: TimeInterval::default(),
1✔
899
                    spatial_resolution: SpatialResolution::zero_point_one(),
1✔
900
                    attributes: ColumnSelection::all()
1✔
901
                })
1✔
902
                .await
×
903
                .unwrap(),
1✔
904
            loading_info
905
        );
906
    }
1✔
907

908
    #[ge_context::test]
2✔
909
    async fn it_persists_uploads(app_ctx: PostgresContext<NoTls>) {
1✔
910
        let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
911
        let input = Upload {
1✔
912
            id,
1✔
913
            files: vec![FileUpload {
1✔
914
                id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
915
                name: "test.csv".to_owned(),
1✔
916
                byte_size: 1337,
1✔
917
            }],
1✔
918
        };
1✔
919

920
        let session = app_ctx.default_session().await.unwrap();
18✔
921

1✔
922
        let db = app_ctx.session_context(session.clone()).db();
1✔
923

1✔
924
        db.create_upload(input.clone()).await.unwrap();
6✔
925

926
        let upload = db.load_upload(id).await.unwrap();
3✔
927

1✔
928
        assert_eq!(upload, input);
1✔
929
    }
1✔
930

931
    #[allow(clippy::too_many_lines)]
932
    #[ge_context::test]
2✔
933
    async fn it_persists_layer_providers(app_ctx: PostgresContext<NoTls>) {
1✔
934
        let db = app_ctx.default_session_context().await.unwrap().db();
19✔
935

1✔
936
        let provider = NetCdfCfDataProviderDefinition {
1✔
937
            name: "netcdfcf".to_string(),
1✔
938
            path: test_data!("netcdf4d/").into(),
1✔
939
            overviews: test_data!("netcdf4d/overviews/").into(),
1✔
940
            cache_ttl: CacheTtlSeconds::new(0),
1✔
941
        };
1✔
942

943
        let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
29✔
944

945
        let providers = db
1✔
946
            .list_layer_providers(LayerProviderListingOptions {
1✔
947
                offset: 0,
1✔
948
                limit: 10,
1✔
949
            })
1✔
950
            .await
3✔
951
            .unwrap();
1✔
952

1✔
953
        assert_eq!(providers.len(), 1);
1✔
954

955
        assert_eq!(
1✔
956
            providers[0],
1✔
957
            LayerProviderListing {
1✔
958
                id: provider_id,
1✔
959
                name: "netcdfcf".to_owned(),
1✔
960
                description: "NetCdfCfProviderDefinition".to_owned(),
1✔
961
            }
1✔
962
        );
1✔
963

964
        let provider = db.load_layer_provider(provider_id).await.unwrap();
3✔
965

966
        let datasets = provider
1✔
967
            .load_layer_collection(
968
                &provider.get_root_layer_collection_id().await.unwrap(),
1✔
969
                LayerCollectionListOptions {
1✔
970
                    offset: 0,
1✔
971
                    limit: 10,
1✔
972
                },
1✔
973
            )
974
            .await
4✔
975
            .unwrap();
1✔
976

1✔
977
        assert_eq!(datasets.items.len(), 3);
1✔
978
    }
1✔
979

980
    #[allow(clippy::too_many_lines)]
981
    #[ge_context::test]
2✔
982
    async fn it_loads_all_meta_data_types(app_ctx: PostgresContext<NoTls>) {
1✔
983
        let session = app_ctx.default_session().await.unwrap();
18✔
984

1✔
985
        let db = app_ctx.session_context(session.clone()).db();
1✔
986

1✔
987
        let vector_descriptor = VectorResultDescriptor {
1✔
988
            data_type: VectorDataType::Data,
1✔
989
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
990
            columns: Default::default(),
1✔
991
            time: None,
1✔
992
            bbox: None,
1✔
993
        };
1✔
994

1✔
995
        let raster_descriptor = RasterResultDescriptor {
1✔
996
            data_type: RasterDataType::U8,
1✔
997
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
998
            time: None,
1✔
999
            bbox: None,
1✔
1000
            resolution: None,
1✔
1001
            bands: RasterBandDescriptors::new_single_band(),
1✔
1002
        };
1✔
1003

1✔
1004
        let vector_ds = AddDataset {
1✔
1005
            name: None,
1✔
1006
            display_name: "OgrDataset".to_string(),
1✔
1007
            description: "My Ogr dataset".to_string(),
1✔
1008
            source_operator: "OgrSource".to_string(),
1✔
1009
            symbology: None,
1✔
1010
            provenance: None,
1✔
1011
        };
1✔
1012

1✔
1013
        let raster_ds = AddDataset {
1✔
1014
            name: None,
1✔
1015
            display_name: "GdalDataset".to_string(),
1✔
1016
            description: "My Gdal dataset".to_string(),
1✔
1017
            source_operator: "GdalSource".to_string(),
1✔
1018
            symbology: None,
1✔
1019
            provenance: None,
1✔
1020
        };
1✔
1021

1✔
1022
        let gdal_params = GdalDatasetParameters {
1✔
1023
            file_path: Default::default(),
1✔
1024
            rasterband_channel: 0,
1✔
1025
            geo_transform: GdalDatasetGeoTransform {
1✔
1026
                origin_coordinate: Default::default(),
1✔
1027
                x_pixel_size: 0.0,
1✔
1028
                y_pixel_size: 0.0,
1✔
1029
            },
1✔
1030
            width: 0,
1✔
1031
            height: 0,
1✔
1032
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1033
            no_data_value: None,
1✔
1034
            properties_mapping: None,
1✔
1035
            gdal_open_options: None,
1✔
1036
            gdal_config_options: None,
1✔
1037
            allow_alphaband_as_mask: false,
1✔
1038
            retry: None,
1✔
1039
        };
1✔
1040

1✔
1041
        let meta = StaticMetaData {
1✔
1042
            loading_info: OgrSourceDataset {
1✔
1043
                file_name: Default::default(),
1✔
1044
                layer_name: String::new(),
1✔
1045
                data_type: None,
1✔
1046
                time: Default::default(),
1✔
1047
                default_geometry: None,
1✔
1048
                columns: None,
1✔
1049
                force_ogr_time_filter: false,
1✔
1050
                force_ogr_spatial_filter: false,
1✔
1051
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1052
                sql_query: None,
1✔
1053
                attribute_query: None,
1✔
1054
                cache_ttl: CacheTtlSeconds::default(),
1✔
1055
            },
1✔
1056
            result_descriptor: vector_descriptor.clone(),
1✔
1057
            phantom: Default::default(),
1✔
1058
        };
1✔
1059

1✔
1060
        let meta = db.wrap_meta_data(MetaDataDefinition::OgrMetaData(meta));
1✔
1061

1062
        let id = db.add_dataset(vector_ds, meta).await.unwrap().id;
158✔
1063

1064
        let meta: geoengine_operators::util::Result<
1✔
1065
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1066
        > = db.meta_data(&id.into()).await;
3✔
1067

1068
        assert!(meta.is_ok());
1✔
1069

1070
        let meta = GdalMetaDataRegular {
1✔
1071
            result_descriptor: raster_descriptor.clone(),
1✔
1072
            params: gdal_params.clone(),
1✔
1073
            time_placeholders: Default::default(),
1✔
1074
            data_time: Default::default(),
1✔
1075
            step: TimeStep {
1✔
1076
                granularity: TimeGranularity::Millis,
1✔
1077
                step: 0,
1✔
1078
            },
1✔
1079
            cache_ttl: CacheTtlSeconds::default(),
1✔
1080
        };
1✔
1081

1✔
1082
        let meta = db.wrap_meta_data(MetaDataDefinition::GdalMetaDataRegular(meta));
1✔
1083

1084
        let id = db.add_dataset(raster_ds.clone(), meta).await.unwrap().id;
3✔
1085

1086
        let meta: geoengine_operators::util::Result<
1✔
1087
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1088
        > = db.meta_data(&id.into()).await;
3✔
1089

1090
        assert!(meta.is_ok());
1✔
1091

1092
        let meta = GdalMetaDataStatic {
1✔
1093
            time: None,
1✔
1094
            params: gdal_params.clone(),
1✔
1095
            result_descriptor: raster_descriptor.clone(),
1✔
1096
            cache_ttl: CacheTtlSeconds::default(),
1✔
1097
        };
1✔
1098

1✔
1099
        let meta = db.wrap_meta_data(MetaDataDefinition::GdalStatic(meta));
1✔
1100

1101
        let id = db.add_dataset(raster_ds.clone(), meta).await.unwrap().id;
3✔
1102

1103
        let meta: geoengine_operators::util::Result<
1✔
1104
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1105
        > = db.meta_data(&id.into()).await;
3✔
1106

1107
        assert!(meta.is_ok());
1✔
1108

1109
        let meta = GdalMetaDataList {
1✔
1110
            result_descriptor: raster_descriptor.clone(),
1✔
1111
            params: vec![],
1✔
1112
        };
1✔
1113

1✔
1114
        let meta = db.wrap_meta_data(MetaDataDefinition::GdalMetaDataList(meta));
1✔
1115

1116
        let id = db.add_dataset(raster_ds.clone(), meta).await.unwrap().id;
3✔
1117

1118
        let meta: geoengine_operators::util::Result<
1✔
1119
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1120
        > = db.meta_data(&id.into()).await;
3✔
1121

1122
        assert!(meta.is_ok());
1✔
1123

1124
        let meta = GdalMetadataNetCdfCf {
1✔
1125
            result_descriptor: raster_descriptor.clone(),
1✔
1126
            params: gdal_params.clone(),
1✔
1127
            start: TimeInstance::MIN,
1✔
1128
            end: TimeInstance::MAX,
1✔
1129
            step: TimeStep {
1✔
1130
                granularity: TimeGranularity::Millis,
1✔
1131
                step: 0,
1✔
1132
            },
1✔
1133
            band_offset: 0,
1✔
1134
            cache_ttl: CacheTtlSeconds::default(),
1✔
1135
        };
1✔
1136

1✔
1137
        let meta = db.wrap_meta_data(MetaDataDefinition::GdalMetadataNetCdfCf(meta));
1✔
1138

1139
        let id = db.add_dataset(raster_ds.clone(), meta).await.unwrap().id;
3✔
1140

1141
        let meta: geoengine_operators::util::Result<
1✔
1142
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1143
        > = db.meta_data(&id.into()).await;
3✔
1144

1145
        assert!(meta.is_ok());
1✔
1146
    }
1✔
1147

1148
    #[allow(clippy::too_many_lines)]
1149
    #[ge_context::test]
2✔
1150
    async fn it_collects_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1151
        let session = app_ctx.default_session().await.unwrap();
18✔
1152

1✔
1153
        let layer_db = app_ctx.session_context(session).db();
1✔
1154

1✔
1155
        let workflow = Workflow {
1✔
1156
            operator: TypedOperator::Vector(
1✔
1157
                MockPointSource {
1✔
1158
                    params: MockPointSourceParams {
1✔
1159
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1160
                    },
1✔
1161
                }
1✔
1162
                .boxed(),
1✔
1163
            ),
1✔
1164
        };
1✔
1165

1166
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1167

1168
        let layer1 = layer_db
1✔
1169
            .add_layer(
1✔
1170
                AddLayer {
1✔
1171
                    name: "Layer1".to_string(),
1✔
1172
                    description: "Layer 1".to_string(),
1✔
1173
                    symbology: None,
1✔
1174
                    workflow: workflow.clone(),
1✔
1175
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1176
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1177
                },
1✔
1178
                &root_collection_id,
1✔
1179
            )
1✔
1180
            .await
43✔
1181
            .unwrap();
1✔
1182

1✔
1183
        assert_eq!(
1✔
1184
            layer_db.load_layer(&layer1).await.unwrap(),
3✔
1185
            crate::layers::layer::Layer {
1✔
1186
                id: ProviderLayerId {
1✔
1187
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1188
                    layer_id: layer1.clone(),
1✔
1189
                },
1✔
1190
                name: "Layer1".to_string(),
1✔
1191
                description: "Layer 1".to_string(),
1✔
1192
                symbology: None,
1✔
1193
                workflow: workflow.clone(),
1✔
1194
                metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1195
                properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1196
            }
1✔
1197
        );
1198

1199
        let collection1_id = layer_db
1✔
1200
            .add_layer_collection(
1✔
1201
                AddLayerCollection {
1✔
1202
                    name: "Collection1".to_string(),
1✔
1203
                    description: "Collection 1".to_string(),
1✔
1204
                    properties: Default::default(),
1✔
1205
                },
1✔
1206
                &root_collection_id,
1✔
1207
            )
1✔
1208
            .await
7✔
1209
            .unwrap();
1✔
1210

1211
        let layer2 = layer_db
1✔
1212
            .add_layer(
1✔
1213
                AddLayer {
1✔
1214
                    name: "Layer2".to_string(),
1✔
1215
                    description: "Layer 2".to_string(),
1✔
1216
                    symbology: None,
1✔
1217
                    workflow: workflow.clone(),
1✔
1218
                    metadata: Default::default(),
1✔
1219
                    properties: Default::default(),
1✔
1220
                },
1✔
1221
                &collection1_id,
1✔
1222
            )
1✔
1223
            .await
9✔
1224
            .unwrap();
1✔
1225

1226
        let collection2_id = layer_db
1✔
1227
            .add_layer_collection(
1✔
1228
                AddLayerCollection {
1✔
1229
                    name: "Collection2".to_string(),
1✔
1230
                    description: "Collection 2".to_string(),
1✔
1231
                    properties: Default::default(),
1✔
1232
                },
1✔
1233
                &collection1_id,
1✔
1234
            )
1✔
1235
            .await
7✔
1236
            .unwrap();
1✔
1237

1✔
1238
        layer_db
1✔
1239
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1240
            .await
3✔
1241
            .unwrap();
1✔
1242

1243
        let root_collection = layer_db
1✔
1244
            .load_layer_collection(
1✔
1245
                &root_collection_id,
1✔
1246
                LayerCollectionListOptions {
1✔
1247
                    offset: 0,
1✔
1248
                    limit: 20,
1✔
1249
                },
1✔
1250
            )
1✔
1251
            .await
5✔
1252
            .unwrap();
1✔
1253

1✔
1254
        assert_eq!(
1✔
1255
            root_collection,
1✔
1256
            LayerCollection {
1✔
1257
                id: ProviderLayerCollectionId {
1✔
1258
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1259
                    collection_id: root_collection_id,
1✔
1260
                },
1✔
1261
                name: "Layers".to_string(),
1✔
1262
                description: "All available Geo Engine layers".to_string(),
1✔
1263
                items: vec![
1✔
1264
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1265
                        id: ProviderLayerCollectionId {
1✔
1266
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1267
                            collection_id: collection1_id.clone(),
1✔
1268
                        },
1✔
1269
                        name: "Collection1".to_string(),
1✔
1270
                        description: "Collection 1".to_string(),
1✔
1271
                        properties: Default::default(),
1✔
1272
                    }),
1✔
1273
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1274
                        id: ProviderLayerCollectionId {
1✔
1275
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1276
                            collection_id: LayerCollectionId(UNSORTED_COLLECTION_ID.to_string()),
1✔
1277
                        },
1✔
1278
                        name: "Unsorted".to_string(),
1✔
1279
                        description: "Unsorted Layers".to_string(),
1✔
1280
                        properties: Default::default(),
1✔
1281
                    }),
1✔
1282
                    CollectionItem::Layer(LayerListing {
1✔
1283
                        id: ProviderLayerId {
1✔
1284
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1285
                            layer_id: layer1,
1✔
1286
                        },
1✔
1287
                        name: "Layer1".to_string(),
1✔
1288
                        description: "Layer 1".to_string(),
1✔
1289
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1290
                    })
1✔
1291
                ],
1✔
1292
                entry_label: None,
1✔
1293
                properties: vec![],
1✔
1294
            }
1✔
1295
        );
1✔
1296

1297
        let collection1 = layer_db
1✔
1298
            .load_layer_collection(
1✔
1299
                &collection1_id,
1✔
1300
                LayerCollectionListOptions {
1✔
1301
                    offset: 0,
1✔
1302
                    limit: 20,
1✔
1303
                },
1✔
1304
            )
1✔
1305
            .await
5✔
1306
            .unwrap();
1✔
1307

1✔
1308
        assert_eq!(
1✔
1309
            collection1,
1✔
1310
            LayerCollection {
1✔
1311
                id: ProviderLayerCollectionId {
1✔
1312
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1313
                    collection_id: collection1_id,
1✔
1314
                },
1✔
1315
                name: "Collection1".to_string(),
1✔
1316
                description: "Collection 1".to_string(),
1✔
1317
                items: vec![
1✔
1318
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1319
                        id: ProviderLayerCollectionId {
1✔
1320
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1321
                            collection_id: collection2_id,
1✔
1322
                        },
1✔
1323
                        name: "Collection2".to_string(),
1✔
1324
                        description: "Collection 2".to_string(),
1✔
1325
                        properties: Default::default(),
1✔
1326
                    }),
1✔
1327
                    CollectionItem::Layer(LayerListing {
1✔
1328
                        id: ProviderLayerId {
1✔
1329
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1330
                            layer_id: layer2,
1✔
1331
                        },
1✔
1332
                        name: "Layer2".to_string(),
1✔
1333
                        description: "Layer 2".to_string(),
1✔
1334
                        properties: vec![],
1✔
1335
                    })
1✔
1336
                ],
1✔
1337
                entry_label: None,
1✔
1338
                properties: vec![],
1✔
1339
            }
1✔
1340
        );
1✔
1341
    }
1✔
1342

1343
    #[allow(clippy::too_many_lines)]
1344
    #[ge_context::test]
2✔
1345
    async fn it_removes_layer_collections(app_ctx: PostgresContext<NoTls>) {
1✔
1346
        let session = app_ctx.default_session().await.unwrap();
18✔
1347

1✔
1348
        let layer_db = app_ctx.session_context(session).db();
1✔
1349

1✔
1350
        let layer = AddLayer {
1✔
1351
            name: "layer".to_string(),
1✔
1352
            description: "description".to_string(),
1✔
1353
            workflow: Workflow {
1✔
1354
                operator: TypedOperator::Vector(
1✔
1355
                    MockPointSource {
1✔
1356
                        params: MockPointSourceParams {
1✔
1357
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1358
                        },
1✔
1359
                    }
1✔
1360
                    .boxed(),
1✔
1361
                ),
1✔
1362
            },
1✔
1363
            symbology: None,
1✔
1364
            metadata: Default::default(),
1✔
1365
            properties: Default::default(),
1✔
1366
        };
1✔
1367

1368
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1369

1✔
1370
        let collection = AddLayerCollection {
1✔
1371
            name: "top collection".to_string(),
1✔
1372
            description: "description".to_string(),
1✔
1373
            properties: Default::default(),
1✔
1374
        };
1✔
1375

1376
        let top_c_id = layer_db
1✔
1377
            .add_layer_collection(collection, root_collection)
1✔
1378
            .await
10✔
1379
            .unwrap();
1✔
1380

1381
        let l_id = layer_db.add_layer(layer, &top_c_id).await.unwrap();
41✔
1382

1✔
1383
        let collection = AddLayerCollection {
1✔
1384
            name: "empty collection".to_string(),
1✔
1385
            description: "description".to_string(),
1✔
1386
            properties: Default::default(),
1✔
1387
        };
1✔
1388

1389
        let empty_c_id = layer_db
1✔
1390
            .add_layer_collection(collection, &top_c_id)
1✔
1391
            .await
7✔
1392
            .unwrap();
1✔
1393

1394
        let items = layer_db
1✔
1395
            .load_layer_collection(
1✔
1396
                &top_c_id,
1✔
1397
                LayerCollectionListOptions {
1✔
1398
                    offset: 0,
1✔
1399
                    limit: 20,
1✔
1400
                },
1✔
1401
            )
1✔
1402
            .await
5✔
1403
            .unwrap();
1✔
1404

1✔
1405
        assert_eq!(
1✔
1406
            items,
1✔
1407
            LayerCollection {
1✔
1408
                id: ProviderLayerCollectionId {
1✔
1409
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1410
                    collection_id: top_c_id.clone(),
1✔
1411
                },
1✔
1412
                name: "top collection".to_string(),
1✔
1413
                description: "description".to_string(),
1✔
1414
                items: vec![
1✔
1415
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1416
                        id: ProviderLayerCollectionId {
1✔
1417
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1418
                            collection_id: empty_c_id.clone(),
1✔
1419
                        },
1✔
1420
                        name: "empty collection".to_string(),
1✔
1421
                        description: "description".to_string(),
1✔
1422
                        properties: Default::default(),
1✔
1423
                    }),
1✔
1424
                    CollectionItem::Layer(LayerListing {
1✔
1425
                        id: ProviderLayerId {
1✔
1426
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1427
                            layer_id: l_id.clone(),
1✔
1428
                        },
1✔
1429
                        name: "layer".to_string(),
1✔
1430
                        description: "description".to_string(),
1✔
1431
                        properties: vec![],
1✔
1432
                    })
1✔
1433
                ],
1✔
1434
                entry_label: None,
1✔
1435
                properties: vec![],
1✔
1436
            }
1✔
1437
        );
1✔
1438

1439
        // remove empty collection
1440
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
9✔
1441

1442
        let items = layer_db
1✔
1443
            .load_layer_collection(
1✔
1444
                &top_c_id,
1✔
1445
                LayerCollectionListOptions {
1✔
1446
                    offset: 0,
1✔
1447
                    limit: 20,
1✔
1448
                },
1✔
1449
            )
1✔
1450
            .await
5✔
1451
            .unwrap();
1✔
1452

1✔
1453
        assert_eq!(
1✔
1454
            items,
1✔
1455
            LayerCollection {
1✔
1456
                id: ProviderLayerCollectionId {
1✔
1457
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1458
                    collection_id: top_c_id.clone(),
1✔
1459
                },
1✔
1460
                name: "top collection".to_string(),
1✔
1461
                description: "description".to_string(),
1✔
1462
                items: vec![CollectionItem::Layer(LayerListing {
1✔
1463
                    id: ProviderLayerId {
1✔
1464
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1465
                        layer_id: l_id.clone(),
1✔
1466
                    },
1✔
1467
                    name: "layer".to_string(),
1✔
1468
                    description: "description".to_string(),
1✔
1469
                    properties: vec![],
1✔
1470
                })],
1✔
1471
                entry_label: None,
1✔
1472
                properties: vec![],
1✔
1473
            }
1✔
1474
        );
1✔
1475

1476
        // remove top (not root) collection
1477
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
9✔
1478

1✔
1479
        layer_db
1✔
1480
            .load_layer_collection(
1✔
1481
                &top_c_id,
1✔
1482
                LayerCollectionListOptions {
1✔
1483
                    offset: 0,
1✔
1484
                    limit: 20,
1✔
1485
                },
1✔
1486
            )
1✔
1487
            .await
3✔
1488
            .unwrap_err();
1✔
1489

1✔
1490
        // should be deleted automatically
1✔
1491
        layer_db.load_layer(&l_id).await.unwrap_err();
3✔
1492

1✔
1493
        // it is not allowed to remove the root collection
1✔
1494
        layer_db
1✔
1495
            .remove_layer_collection(root_collection)
1✔
1496
            .await
2✔
1497
            .unwrap_err();
1✔
1498
        layer_db
1✔
1499
            .load_layer_collection(
1✔
1500
                root_collection,
1✔
1501
                LayerCollectionListOptions {
1✔
1502
                    offset: 0,
1✔
1503
                    limit: 20,
1✔
1504
                },
1✔
1505
            )
1✔
1506
            .await
5✔
1507
            .unwrap();
1✔
1508
    }
1✔
1509

1510
    #[ge_context::test]
2✔
1511
    #[allow(clippy::too_many_lines)]
1512
    async fn it_removes_collections_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
1513
        let session = app_ctx.default_session().await.unwrap();
18✔
1514

1✔
1515
        let db = app_ctx.session_context(session).db();
1✔
1516

1517
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
1518

1519
        let mid_collection_id = db
1✔
1520
            .add_layer_collection(
1✔
1521
                AddLayerCollection {
1✔
1522
                    name: "mid collection".to_string(),
1✔
1523
                    description: "description".to_string(),
1✔
1524
                    properties: Default::default(),
1✔
1525
                },
1✔
1526
                root_collection_id,
1✔
1527
            )
1✔
1528
            .await
10✔
1529
            .unwrap();
1✔
1530

1531
        let bottom_collection_id = db
1✔
1532
            .add_layer_collection(
1✔
1533
                AddLayerCollection {
1✔
1534
                    name: "bottom collection".to_string(),
1✔
1535
                    description: "description".to_string(),
1✔
1536
                    properties: Default::default(),
1✔
1537
                },
1✔
1538
                &mid_collection_id,
1✔
1539
            )
1✔
1540
            .await
7✔
1541
            .unwrap();
1✔
1542

1543
        let layer_id = db
1✔
1544
            .add_layer(
1✔
1545
                AddLayer {
1✔
1546
                    name: "layer".to_string(),
1✔
1547
                    description: "description".to_string(),
1✔
1548
                    workflow: Workflow {
1✔
1549
                        operator: TypedOperator::Vector(
1✔
1550
                            MockPointSource {
1✔
1551
                                params: MockPointSourceParams {
1✔
1552
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1553
                                },
1✔
1554
                            }
1✔
1555
                            .boxed(),
1✔
1556
                        ),
1✔
1557
                    },
1✔
1558
                    symbology: None,
1✔
1559
                    metadata: Default::default(),
1✔
1560
                    properties: Default::default(),
1✔
1561
                },
1✔
1562
                &mid_collection_id,
1✔
1563
            )
1✔
1564
            .await
40✔
1565
            .unwrap();
1✔
1566

1✔
1567
        // removing the mid collection…
1✔
1568
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
1569
            .await
11✔
1570
            .unwrap();
1✔
1571

1✔
1572
        // …should remove itself
1✔
1573
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
1574
            .await
3✔
1575
            .unwrap_err();
1✔
1576

1✔
1577
        // …should remove the bottom collection
1✔
1578
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
1579
            .await
3✔
1580
            .unwrap_err();
1✔
1581

1✔
1582
        // … and should remove the layer of the bottom collection
1✔
1583
        db.load_layer(&layer_id).await.unwrap_err();
3✔
1584

1✔
1585
        // the root collection is still there
1✔
1586
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
1587
            .await
5✔
1588
            .unwrap();
1✔
1589
    }
1✔
1590

1591
    #[ge_context::test]
2✔
1592
    #[allow(clippy::too_many_lines)]
1593
    async fn it_removes_layers_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
1594
        let session = app_ctx.default_session().await.unwrap();
18✔
1595

1✔
1596
        let db = app_ctx.session_context(session).db();
1✔
1597

1598
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
1599

1600
        let another_collection = db
1✔
1601
            .add_layer_collection(
1✔
1602
                AddLayerCollection {
1✔
1603
                    name: "top collection".to_string(),
1✔
1604
                    description: "description".to_string(),
1✔
1605
                    properties: Default::default(),
1✔
1606
                },
1✔
1607
                root_collection,
1✔
1608
            )
1✔
1609
            .await
10✔
1610
            .unwrap();
1✔
1611

1612
        let layer_in_one_collection = db
1✔
1613
            .add_layer(
1✔
1614
                AddLayer {
1✔
1615
                    name: "layer 1".to_string(),
1✔
1616
                    description: "description".to_string(),
1✔
1617
                    workflow: Workflow {
1✔
1618
                        operator: TypedOperator::Vector(
1✔
1619
                            MockPointSource {
1✔
1620
                                params: MockPointSourceParams {
1✔
1621
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1622
                                },
1✔
1623
                            }
1✔
1624
                            .boxed(),
1✔
1625
                        ),
1✔
1626
                    },
1✔
1627
                    symbology: None,
1✔
1628
                    metadata: Default::default(),
1✔
1629
                    properties: Default::default(),
1✔
1630
                },
1✔
1631
                &another_collection,
1✔
1632
            )
1✔
1633
            .await
40✔
1634
            .unwrap();
1✔
1635

1636
        let layer_in_two_collections = db
1✔
1637
            .add_layer(
1✔
1638
                AddLayer {
1✔
1639
                    name: "layer 2".to_string(),
1✔
1640
                    description: "description".to_string(),
1✔
1641
                    workflow: Workflow {
1✔
1642
                        operator: TypedOperator::Vector(
1✔
1643
                            MockPointSource {
1✔
1644
                                params: MockPointSourceParams {
1✔
1645
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1646
                                },
1✔
1647
                            }
1✔
1648
                            .boxed(),
1✔
1649
                        ),
1✔
1650
                    },
1✔
1651
                    symbology: None,
1✔
1652
                    metadata: Default::default(),
1✔
1653
                    properties: Default::default(),
1✔
1654
                },
1✔
1655
                &another_collection,
1✔
1656
            )
1✔
1657
            .await
9✔
1658
            .unwrap();
1✔
1659

1✔
1660
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
1661
            .await
3✔
1662
            .unwrap();
1✔
1663

1✔
1664
        // remove first layer --> should be deleted entirely
1✔
1665

1✔
1666
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
1667
            .await
7✔
1668
            .unwrap();
1✔
1669

1670
        let number_of_layer_in_collection = db
1✔
1671
            .load_layer_collection(
1✔
1672
                &another_collection,
1✔
1673
                LayerCollectionListOptions {
1✔
1674
                    offset: 0,
1✔
1675
                    limit: 20,
1✔
1676
                },
1✔
1677
            )
1✔
1678
            .await
5✔
1679
            .unwrap()
1✔
1680
            .items
1✔
1681
            .len();
1✔
1682
        assert_eq!(
1✔
1683
            number_of_layer_in_collection,
1✔
1684
            1 /* only the other collection should be here */
1✔
1685
        );
1✔
1686

1687
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
3✔
1688

1✔
1689
        // remove second layer --> should only be gone in collection
1✔
1690

1✔
1691
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
1692
            .await
7✔
1693
            .unwrap();
1✔
1694

1695
        let number_of_layer_in_collection = db
1✔
1696
            .load_layer_collection(
1✔
1697
                &another_collection,
1✔
1698
                LayerCollectionListOptions {
1✔
1699
                    offset: 0,
1✔
1700
                    limit: 20,
1✔
1701
                },
1✔
1702
            )
1✔
1703
            .await
5✔
1704
            .unwrap()
1✔
1705
            .items
1✔
1706
            .len();
1✔
1707
        assert_eq!(
1✔
1708
            number_of_layer_in_collection,
1✔
1709
            0 /* both layers were deleted */
1✔
1710
        );
1✔
1711

1712
        db.load_layer(&layer_in_two_collections).await.unwrap();
3✔
1713
    }
1✔
1714

1715
    #[ge_context::test]
2✔
1716
    #[allow(clippy::too_many_lines)]
1717
    async fn it_deletes_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
1718
        let loading_info = OgrSourceDataset {
1✔
1719
            file_name: PathBuf::from("test.csv"),
1✔
1720
            layer_name: "test.csv".to_owned(),
1✔
1721
            data_type: Some(VectorDataType::MultiPoint),
1✔
1722
            time: OgrSourceDatasetTimeType::Start {
1✔
1723
                start_field: "start".to_owned(),
1✔
1724
                start_format: OgrSourceTimeFormat::Auto,
1✔
1725
                duration: OgrSourceDurationSpec::Zero,
1✔
1726
            },
1✔
1727
            default_geometry: None,
1✔
1728
            columns: Some(OgrSourceColumnSpec {
1✔
1729
                format_specifics: Some(FormatSpecifics::Csv {
1✔
1730
                    header: CsvHeader::Auto,
1✔
1731
                }),
1✔
1732
                x: "x".to_owned(),
1✔
1733
                y: None,
1✔
1734
                int: vec![],
1✔
1735
                float: vec![],
1✔
1736
                text: vec![],
1✔
1737
                bool: vec![],
1✔
1738
                datetime: vec![],
1✔
1739
                rename: None,
1✔
1740
            }),
1✔
1741
            force_ogr_time_filter: false,
1✔
1742
            force_ogr_spatial_filter: false,
1✔
1743
            on_error: OgrSourceErrorSpec::Ignore,
1✔
1744
            sql_query: None,
1✔
1745
            attribute_query: None,
1✔
1746
            cache_ttl: CacheTtlSeconds::default(),
1✔
1747
        };
1✔
1748

1✔
1749
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
1750
            OgrSourceDataset,
1✔
1751
            VectorResultDescriptor,
1✔
1752
            VectorQueryRectangle,
1✔
1753
        > {
1✔
1754
            loading_info: loading_info.clone(),
1✔
1755
            result_descriptor: VectorResultDescriptor {
1✔
1756
                data_type: VectorDataType::MultiPoint,
1✔
1757
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1758
                columns: [(
1✔
1759
                    "foo".to_owned(),
1✔
1760
                    VectorColumnInfo {
1✔
1761
                        data_type: FeatureDataType::Float,
1✔
1762
                        measurement: Measurement::Unitless,
1✔
1763
                    },
1✔
1764
                )]
1✔
1765
                .into_iter()
1✔
1766
                .collect(),
1✔
1767
                time: None,
1✔
1768
                bbox: None,
1✔
1769
            },
1✔
1770
            phantom: Default::default(),
1✔
1771
        });
1✔
1772

1773
        let session = app_ctx.default_session().await.unwrap();
18✔
1774

1✔
1775
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
1776

1✔
1777
        let db = app_ctx.session_context(session.clone()).db();
1✔
1778
        let wrap = db.wrap_meta_data(meta_data);
1✔
1779
        let dataset_id = db
1✔
1780
            .add_dataset(
1✔
1781
                AddDataset {
1✔
1782
                    name: Some(dataset_name),
1✔
1783
                    display_name: "Ogr Test".to_owned(),
1✔
1784
                    description: "desc".to_owned(),
1✔
1785
                    source_operator: "OgrSource".to_owned(),
1✔
1786
                    symbology: None,
1✔
1787
                    provenance: Some(vec![Provenance {
1✔
1788
                        citation: "citation".to_owned(),
1✔
1789
                        license: "license".to_owned(),
1✔
1790
                        uri: "uri".to_owned(),
1✔
1791
                    }]),
1✔
1792
                },
1✔
1793
                wrap,
1✔
1794
            )
1✔
1795
            .await
157✔
1796
            .unwrap()
1✔
1797
            .id;
1✔
1798

1✔
1799
        assert!(db.load_dataset(&dataset_id).await.is_ok());
3✔
1800

1801
        db.delete_dataset(dataset_id).await.unwrap();
3✔
1802

1✔
1803
        assert!(db.load_dataset(&dataset_id).await.is_err());
3✔
1804
    }
1✔
1805

1806
    #[ge_context::test]
2✔
1807
    #[allow(clippy::too_many_lines)]
1808
    async fn it_deletes_admin_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
1809
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
1810

1✔
1811
        let loading_info = OgrSourceDataset {
1✔
1812
            file_name: PathBuf::from("test.csv"),
1✔
1813
            layer_name: "test.csv".to_owned(),
1✔
1814
            data_type: Some(VectorDataType::MultiPoint),
1✔
1815
            time: OgrSourceDatasetTimeType::Start {
1✔
1816
                start_field: "start".to_owned(),
1✔
1817
                start_format: OgrSourceTimeFormat::Auto,
1✔
1818
                duration: OgrSourceDurationSpec::Zero,
1✔
1819
            },
1✔
1820
            default_geometry: None,
1✔
1821
            columns: Some(OgrSourceColumnSpec {
1✔
1822
                format_specifics: Some(FormatSpecifics::Csv {
1✔
1823
                    header: CsvHeader::Auto,
1✔
1824
                }),
1✔
1825
                x: "x".to_owned(),
1✔
1826
                y: None,
1✔
1827
                int: vec![],
1✔
1828
                float: vec![],
1✔
1829
                text: vec![],
1✔
1830
                bool: vec![],
1✔
1831
                datetime: vec![],
1✔
1832
                rename: None,
1✔
1833
            }),
1✔
1834
            force_ogr_time_filter: false,
1✔
1835
            force_ogr_spatial_filter: false,
1✔
1836
            on_error: OgrSourceErrorSpec::Ignore,
1✔
1837
            sql_query: None,
1✔
1838
            attribute_query: None,
1✔
1839
            cache_ttl: CacheTtlSeconds::default(),
1✔
1840
        };
1✔
1841

1✔
1842
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
1843
            OgrSourceDataset,
1✔
1844
            VectorResultDescriptor,
1✔
1845
            VectorQueryRectangle,
1✔
1846
        > {
1✔
1847
            loading_info: loading_info.clone(),
1✔
1848
            result_descriptor: VectorResultDescriptor {
1✔
1849
                data_type: VectorDataType::MultiPoint,
1✔
1850
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1851
                columns: [(
1✔
1852
                    "foo".to_owned(),
1✔
1853
                    VectorColumnInfo {
1✔
1854
                        data_type: FeatureDataType::Float,
1✔
1855
                        measurement: Measurement::Unitless,
1✔
1856
                    },
1✔
1857
                )]
1✔
1858
                .into_iter()
1✔
1859
                .collect(),
1✔
1860
                time: None,
1✔
1861
                bbox: None,
1✔
1862
            },
1✔
1863
            phantom: Default::default(),
1✔
1864
        });
1✔
1865

1866
        let session = app_ctx.default_session().await.unwrap();
18✔
1867

1✔
1868
        let db = app_ctx.session_context(session).db();
1✔
1869
        let wrap = db.wrap_meta_data(meta_data);
1✔
1870
        let dataset_id = db
1✔
1871
            .add_dataset(
1✔
1872
                AddDataset {
1✔
1873
                    name: Some(dataset_name),
1✔
1874
                    display_name: "Ogr Test".to_owned(),
1✔
1875
                    description: "desc".to_owned(),
1✔
1876
                    source_operator: "OgrSource".to_owned(),
1✔
1877
                    symbology: None,
1✔
1878
                    provenance: Some(vec![Provenance {
1✔
1879
                        citation: "citation".to_owned(),
1✔
1880
                        license: "license".to_owned(),
1✔
1881
                        uri: "uri".to_owned(),
1✔
1882
                    }]),
1✔
1883
                },
1✔
1884
                wrap,
1✔
1885
            )
1✔
1886
            .await
156✔
1887
            .unwrap()
1✔
1888
            .id;
1✔
1889

1✔
1890
        assert!(db.load_dataset(&dataset_id).await.is_ok());
3✔
1891

1892
        db.delete_dataset(dataset_id).await.unwrap();
3✔
1893

1✔
1894
        assert!(db.load_dataset(&dataset_id).await.is_err());
3✔
1895
    }
1✔
1896

1897
    #[ge_context::test]
2✔
1898
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: PostgresContext<NoTls>) {
1✔
1899
        let session = app_ctx.default_session().await.unwrap();
18✔
1900
        let db = app_ctx.session_context(session).db();
1✔
1901

1902
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
1903

1904
        let top_collection_id = db
1✔
1905
            .add_layer_collection(
1✔
1906
                AddLayerCollection {
1✔
1907
                    name: "top collection".to_string(),
1✔
1908
                    description: "description".to_string(),
1✔
1909
                    properties: Default::default(),
1✔
1910
                },
1✔
1911
                root_collection_id,
1✔
1912
            )
1✔
1913
            .await
10✔
1914
            .unwrap();
1✔
1915

1✔
1916
        let faux_layer = LayerId("faux".to_string());
1✔
1917

1✔
1918
        // this should fail
1✔
1919
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
1920
            .await
×
1921
            .unwrap_err();
1✔
1922

1923
        let root_collection_layers = db
1✔
1924
            .load_layer_collection(
1✔
1925
                &top_collection_id,
1✔
1926
                LayerCollectionListOptions {
1✔
1927
                    offset: 0,
1✔
1928
                    limit: 20,
1✔
1929
                },
1✔
1930
            )
1✔
1931
            .await
5✔
1932
            .unwrap();
1✔
1933

1✔
1934
        assert_eq!(
1✔
1935
            root_collection_layers,
1✔
1936
            LayerCollection {
1✔
1937
                id: ProviderLayerCollectionId {
1✔
1938
                    provider_id: DataProviderId(
1✔
1939
                        "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
1940
                    ),
1✔
1941
                    collection_id: top_collection_id.clone(),
1✔
1942
                },
1✔
1943
                name: "top collection".to_string(),
1✔
1944
                description: "description".to_string(),
1✔
1945
                items: vec![],
1✔
1946
                entry_label: None,
1✔
1947
                properties: vec![],
1✔
1948
            }
1✔
1949
        );
1✔
1950
    }
1✔
1951

1952
    #[allow(clippy::too_many_lines)]
1953
    #[ge_context::test]
2✔
1954
    async fn it_updates_project_layer_symbology(app_ctx: PostgresContext<NoTls>) {
1✔
1955
        let session = app_ctx.default_session().await.unwrap();
18✔
1956

1957
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
167✔
1958

1959
        let db = app_ctx.session_context(session.clone()).db();
1✔
1960

1✔
1961
        let create_project: CreateProject = serde_json::from_value(json!({
1✔
1962
            "name": "Default",
1✔
1963
            "description": "Default project",
1✔
1964
            "bounds": {
1✔
1965
                "boundingBox": {
1✔
1966
                    "lowerLeftCoordinate": {
1✔
1967
                        "x": -180,
1✔
1968
                        "y": -90
1✔
1969
                    },
1✔
1970
                    "upperRightCoordinate": {
1✔
1971
                        "x": 180,
1✔
1972
                        "y": 90
1✔
1973
                    }
1✔
1974
                },
1✔
1975
                "spatialReference": "EPSG:4326",
1✔
1976
                "timeInterval": {
1✔
1977
                    "start": 1_396_353_600_000i64,
1✔
1978
                    "end": 1_396_353_600_000i64
1✔
1979
                }
1✔
1980
            },
1✔
1981
            "timeStep": {
1✔
1982
                "step": 1,
1✔
1983
                "granularity": "months"
1✔
1984
            }
1✔
1985
        }))
1✔
1986
        .unwrap();
1✔
1987

1988
        let project_id = db.create_project(create_project).await.unwrap();
7✔
1989

1✔
1990
        let update: UpdateProject = serde_json::from_value(json!({
1✔
1991
            "id": project_id.to_string(),
1✔
1992
            "layers": [{
1✔
1993
                "name": "NDVI",
1✔
1994
                "workflow": workflow_id.to_string(),
1✔
1995
                "visibility": {
1✔
1996
                    "data": true,
1✔
1997
                    "legend": false
1✔
1998
                },
1✔
1999
                "symbology": {
1✔
2000
                    "type": "raster",
1✔
2001
                    "opacity": 1,
1✔
2002
                    "colorizer": {
1✔
2003
                        "type": "linearGradient",
1✔
2004
                        "breakpoints": [{
1✔
2005
                            "value": 1,
1✔
2006
                            "color": [0, 0, 0, 255]
1✔
2007
                        }, {
1✔
2008
                            "value": 255,
1✔
2009
                            "color": [255, 255, 255, 255]
1✔
2010
                        }],
1✔
2011
                        "noDataColor": [0, 0, 0, 0],
1✔
2012
                        "overColor": [255, 255, 255, 127],
1✔
2013
                        "underColor": [255, 255, 255, 127]
1✔
2014
                    }
1✔
2015
                }
1✔
2016
            }]
1✔
2017
        }))
1✔
2018
        .unwrap();
1✔
2019

1✔
2020
        db.update_project(update).await.unwrap();
67✔
2021

1✔
2022
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2023
            "id": project_id.to_string(),
1✔
2024
            "layers": [{
1✔
2025
                "name": "NDVI",
1✔
2026
                "workflow": workflow_id.to_string(),
1✔
2027
                "visibility": {
1✔
2028
                    "data": true,
1✔
2029
                    "legend": false
1✔
2030
                },
1✔
2031
                "symbology": {
1✔
2032
                    "type": "raster",
1✔
2033
                    "opacity": 1,
1✔
2034
                    "colorizer": {
1✔
2035
                        "type": "linearGradient",
1✔
2036
                        "breakpoints": [{
1✔
2037
                            "value": 1,
1✔
2038
                            "color": [0, 0, 4, 255]
1✔
2039
                        }, {
1✔
2040
                            "value": 17.866_666_666_666_667,
1✔
2041
                            "color": [11, 9, 36, 255]
1✔
2042
                        }, {
1✔
2043
                            "value": 34.733_333_333_333_334,
1✔
2044
                            "color": [32, 17, 75, 255]
1✔
2045
                        }, {
1✔
2046
                            "value": 51.6,
1✔
2047
                            "color": [59, 15, 112, 255]
1✔
2048
                        }, {
1✔
2049
                            "value": 68.466_666_666_666_67,
1✔
2050
                            "color": [87, 21, 126, 255]
1✔
2051
                        }, {
1✔
2052
                            "value": 85.333_333_333_333_33,
1✔
2053
                            "color": [114, 31, 129, 255]
1✔
2054
                        }, {
1✔
2055
                            "value": 102.199_999_999_999_99,
1✔
2056
                            "color": [140, 41, 129, 255]
1✔
2057
                        }, {
1✔
2058
                            "value": 119.066_666_666_666_65,
1✔
2059
                            "color": [168, 50, 125, 255]
1✔
2060
                        }, {
1✔
2061
                            "value": 135.933_333_333_333_34,
1✔
2062
                            "color": [196, 60, 117, 255]
1✔
2063
                        }, {
1✔
2064
                            "value": 152.799_999_999_999_98,
1✔
2065
                            "color": [222, 73, 104, 255]
1✔
2066
                        }, {
1✔
2067
                            "value": 169.666_666_666_666_66,
1✔
2068
                            "color": [241, 96, 93, 255]
1✔
2069
                        }, {
1✔
2070
                            "value": 186.533_333_333_333_33,
1✔
2071
                            "color": [250, 127, 94, 255]
1✔
2072
                        }, {
1✔
2073
                            "value": 203.399_999_999_999_98,
1✔
2074
                            "color": [254, 159, 109, 255]
1✔
2075
                        }, {
1✔
2076
                            "value": 220.266_666_666_666_65,
1✔
2077
                            "color": [254, 191, 132, 255]
1✔
2078
                        }, {
1✔
2079
                            "value": 237.133_333_333_333_3,
1✔
2080
                            "color": [253, 222, 160, 255]
1✔
2081
                        }, {
1✔
2082
                            "value": 254,
1✔
2083
                            "color": [252, 253, 191, 255]
1✔
2084
                        }],
1✔
2085
                        "noDataColor": [0, 0, 0, 0],
1✔
2086
                        "overColor": [255, 255, 255, 127],
1✔
2087
                        "underColor": [255, 255, 255, 127]
1✔
2088
                    }
1✔
2089
                }
1✔
2090
            }]
1✔
2091
        }))
1✔
2092
        .unwrap();
1✔
2093

1✔
2094
        db.update_project(update).await.unwrap();
16✔
2095

1✔
2096
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2097
            "id": project_id.to_string(),
1✔
2098
            "layers": [{
1✔
2099
                "name": "NDVI",
1✔
2100
                "workflow": workflow_id.to_string(),
1✔
2101
                "visibility": {
1✔
2102
                    "data": true,
1✔
2103
                    "legend": false
1✔
2104
                },
1✔
2105
                "symbology": {
1✔
2106
                    "type": "raster",
1✔
2107
                    "opacity": 1,
1✔
2108
                    "colorizer": {
1✔
2109
                        "type": "linearGradient",
1✔
2110
                        "breakpoints": [{
1✔
2111
                            "value": 1,
1✔
2112
                            "color": [0, 0, 4, 255]
1✔
2113
                        }, {
1✔
2114
                            "value": 17.866_666_666_666_667,
1✔
2115
                            "color": [11, 9, 36, 255]
1✔
2116
                        }, {
1✔
2117
                            "value": 34.733_333_333_333_334,
1✔
2118
                            "color": [32, 17, 75, 255]
1✔
2119
                        }, {
1✔
2120
                            "value": 51.6,
1✔
2121
                            "color": [59, 15, 112, 255]
1✔
2122
                        }, {
1✔
2123
                            "value": 68.466_666_666_666_67,
1✔
2124
                            "color": [87, 21, 126, 255]
1✔
2125
                        }, {
1✔
2126
                            "value": 85.333_333_333_333_33,
1✔
2127
                            "color": [114, 31, 129, 255]
1✔
2128
                        }, {
1✔
2129
                            "value": 102.199_999_999_999_99,
1✔
2130
                            "color": [140, 41, 129, 255]
1✔
2131
                        }, {
1✔
2132
                            "value": 119.066_666_666_666_65,
1✔
2133
                            "color": [168, 50, 125, 255]
1✔
2134
                        }, {
1✔
2135
                            "value": 135.933_333_333_333_34,
1✔
2136
                            "color": [196, 60, 117, 255]
1✔
2137
                        }, {
1✔
2138
                            "value": 152.799_999_999_999_98,
1✔
2139
                            "color": [222, 73, 104, 255]
1✔
2140
                        }, {
1✔
2141
                            "value": 169.666_666_666_666_66,
1✔
2142
                            "color": [241, 96, 93, 255]
1✔
2143
                        }, {
1✔
2144
                            "value": 186.533_333_333_333_33,
1✔
2145
                            "color": [250, 127, 94, 255]
1✔
2146
                        }, {
1✔
2147
                            "value": 203.399_999_999_999_98,
1✔
2148
                            "color": [254, 159, 109, 255]
1✔
2149
                        }, {
1✔
2150
                            "value": 220.266_666_666_666_65,
1✔
2151
                            "color": [254, 191, 132, 255]
1✔
2152
                        }, {
1✔
2153
                            "value": 237.133_333_333_333_3,
1✔
2154
                            "color": [253, 222, 160, 255]
1✔
2155
                        }, {
1✔
2156
                            "value": 254,
1✔
2157
                            "color": [252, 253, 191, 255]
1✔
2158
                        }],
1✔
2159
                        "noDataColor": [0, 0, 0, 0],
1✔
2160
                        "overColor": [255, 255, 255, 127],
1✔
2161
                        "underColor": [255, 255, 255, 127]
1✔
2162
                    }
1✔
2163
                }
1✔
2164
            }]
1✔
2165
        }))
1✔
2166
        .unwrap();
1✔
2167

1✔
2168
        db.update_project(update).await.unwrap();
17✔
2169

1✔
2170
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2171
            "id": project_id.to_string(),
1✔
2172
            "layers": [{
1✔
2173
                "name": "NDVI",
1✔
2174
                "workflow": workflow_id.to_string(),
1✔
2175
                "visibility": {
1✔
2176
                    "data": true,
1✔
2177
                    "legend": false
1✔
2178
                },
1✔
2179
                "symbology": {
1✔
2180
                    "type": "raster",
1✔
2181
                    "opacity": 1,
1✔
2182
                    "colorizer": {
1✔
2183
                        "type": "linearGradient",
1✔
2184
                        "breakpoints": [{
1✔
2185
                            "value": 1,
1✔
2186
                            "color": [0, 0, 4, 255]
1✔
2187
                        }, {
1✔
2188
                            "value": 17.933_333_333_333_334,
1✔
2189
                            "color": [11, 9, 36, 255]
1✔
2190
                        }, {
1✔
2191
                            "value": 34.866_666_666_666_67,
1✔
2192
                            "color": [32, 17, 75, 255]
1✔
2193
                        }, {
1✔
2194
                            "value": 51.800_000_000_000_004,
1✔
2195
                            "color": [59, 15, 112, 255]
1✔
2196
                        }, {
1✔
2197
                            "value": 68.733_333_333_333_33,
1✔
2198
                            "color": [87, 21, 126, 255]
1✔
2199
                        }, {
1✔
2200
                            "value": 85.666_666_666_666_66,
1✔
2201
                            "color": [114, 31, 129, 255]
1✔
2202
                        }, {
1✔
2203
                            "value": 102.6,
1✔
2204
                            "color": [140, 41, 129, 255]
1✔
2205
                        }, {
1✔
2206
                            "value": 119.533_333_333_333_32,
1✔
2207
                            "color": [168, 50, 125, 255]
1✔
2208
                        }, {
1✔
2209
                            "value": 136.466_666_666_666_67,
1✔
2210
                            "color": [196, 60, 117, 255]
1✔
2211
                        }, {
1✔
2212
                            "value": 153.4,
1✔
2213
                            "color": [222, 73, 104, 255]
1✔
2214
                        }, {
1✔
2215
                            "value": 170.333_333_333_333_31,
1✔
2216
                            "color": [241, 96, 93, 255]
1✔
2217
                        }, {
1✔
2218
                            "value": 187.266_666_666_666_65,
1✔
2219
                            "color": [250, 127, 94, 255]
1✔
2220
                        }, {
1✔
2221
                            "value": 204.2,
1✔
2222
                            "color": [254, 159, 109, 255]
1✔
2223
                        }, {
1✔
2224
                            "value": 221.133_333_333_333_33,
1✔
2225
                            "color": [254, 191, 132, 255]
1✔
2226
                        }, {
1✔
2227
                            "value": 238.066_666_666_666_63,
1✔
2228
                            "color": [253, 222, 160, 255]
1✔
2229
                        }, {
1✔
2230
                            "value": 255,
1✔
2231
                            "color": [252, 253, 191, 255]
1✔
2232
                        }],
1✔
2233
                        "noDataColor": [0, 0, 0, 0],
1✔
2234
                        "overColor": [255, 255, 255, 127],
1✔
2235
                        "underColor": [255, 255, 255, 127]
1✔
2236
                    }
1✔
2237
                }
1✔
2238
            }]
1✔
2239
        }))
1✔
2240
        .unwrap();
1✔
2241

1✔
2242
        let update = update;
1✔
2243

2244
        // run two updates concurrently
2245
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
2246

2247
        assert!(r0.is_ok());
1✔
2248
        assert!(r1.is_ok());
1✔
2249
    }
1✔
2250

2251
    #[ge_context::test]
2✔
2252
    #[allow(clippy::too_many_lines)]
2253
    async fn it_resolves_dataset_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
2254
        let session = app_ctx.default_session().await.unwrap();
18✔
2255
        let db = app_ctx.session_context(session.clone()).db();
1✔
2256

1✔
2257
        let loading_info = OgrSourceDataset {
1✔
2258
            file_name: PathBuf::from("test.csv"),
1✔
2259
            layer_name: "test.csv".to_owned(),
1✔
2260
            data_type: Some(VectorDataType::MultiPoint),
1✔
2261
            time: OgrSourceDatasetTimeType::Start {
1✔
2262
                start_field: "start".to_owned(),
1✔
2263
                start_format: OgrSourceTimeFormat::Auto,
1✔
2264
                duration: OgrSourceDurationSpec::Zero,
1✔
2265
            },
1✔
2266
            default_geometry: None,
1✔
2267
            columns: Some(OgrSourceColumnSpec {
1✔
2268
                format_specifics: Some(FormatSpecifics::Csv {
1✔
2269
                    header: CsvHeader::Auto,
1✔
2270
                }),
1✔
2271
                x: "x".to_owned(),
1✔
2272
                y: None,
1✔
2273
                int: vec![],
1✔
2274
                float: vec![],
1✔
2275
                text: vec![],
1✔
2276
                bool: vec![],
1✔
2277
                datetime: vec![],
1✔
2278
                rename: None,
1✔
2279
            }),
1✔
2280
            force_ogr_time_filter: false,
1✔
2281
            force_ogr_spatial_filter: false,
1✔
2282
            on_error: OgrSourceErrorSpec::Ignore,
1✔
2283
            sql_query: None,
1✔
2284
            attribute_query: None,
1✔
2285
            cache_ttl: CacheTtlSeconds::default(),
1✔
2286
        };
1✔
2287

1✔
2288
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
2289
            OgrSourceDataset,
1✔
2290
            VectorResultDescriptor,
1✔
2291
            VectorQueryRectangle,
1✔
2292
        > {
1✔
2293
            loading_info: loading_info.clone(),
1✔
2294
            result_descriptor: VectorResultDescriptor {
1✔
2295
                data_type: VectorDataType::MultiPoint,
1✔
2296
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
2297
                columns: [(
1✔
2298
                    "foo".to_owned(),
1✔
2299
                    VectorColumnInfo {
1✔
2300
                        data_type: FeatureDataType::Float,
1✔
2301
                        measurement: Measurement::Unitless,
1✔
2302
                    },
1✔
2303
                )]
1✔
2304
                .into_iter()
1✔
2305
                .collect(),
1✔
2306
                time: None,
1✔
2307
                bbox: None,
1✔
2308
            },
1✔
2309
            phantom: Default::default(),
1✔
2310
        });
1✔
2311

2312
        let DatasetIdAndName {
2313
            id: dataset_id1,
1✔
2314
            name: dataset_name1,
1✔
2315
        } = db
1✔
2316
            .add_dataset(
1✔
2317
                AddDataset {
1✔
2318
                    name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
2319
                    display_name: "Ogr Test".to_owned(),
1✔
2320
                    description: "desc".to_owned(),
1✔
2321
                    source_operator: "OgrSource".to_owned(),
1✔
2322
                    symbology: None,
1✔
2323
                    provenance: Some(vec![Provenance {
1✔
2324
                        citation: "citation".to_owned(),
1✔
2325
                        license: "license".to_owned(),
1✔
2326
                        uri: "uri".to_owned(),
1✔
2327
                    }]),
1✔
2328
                },
1✔
2329
                db.wrap_meta_data(meta_data.clone()),
1✔
2330
            )
1✔
2331
            .await
157✔
2332
            .unwrap();
1✔
2333

1✔
2334
        assert_eq!(
1✔
2335
            db.resolve_dataset_name_to_id(&dataset_name1).await.unwrap(),
3✔
2336
            dataset_id1
2337
        );
2338
    }
1✔
2339

2340
    #[ge_context::test]
2✔
2341
    #[allow(clippy::too_many_lines)]
2342
    async fn test_postgres_type_serialization(app_ctx: PostgresContext<NoTls>) {
1✔
2343
        let pool = app_ctx.pool.get().await.unwrap();
1✔
2344

1✔
2345
        assert_sql_type(&pool, "RgbaColor", [RgbaColor::new(0, 1, 2, 3)]).await;
4✔
2346

2347
        assert_sql_type(
1✔
2348
            &pool,
1✔
2349
            "double precision",
1✔
2350
            [NotNanF64::from(NotNan::<f64>::new(1.0).unwrap())],
1✔
2351
        )
1✔
2352
        .await;
2✔
2353

2354
        assert_sql_type(
1✔
2355
            &pool,
1✔
2356
            "Breakpoint",
1✔
2357
            [Breakpoint {
1✔
2358
                value: NotNan::<f64>::new(1.0).unwrap(),
1✔
2359
                color: RgbaColor::new(0, 0, 0, 0),
1✔
2360
            }],
1✔
2361
        )
1✔
2362
        .await;
5✔
2363

2364
        assert_sql_type(
1✔
2365
            &pool,
1✔
2366
            "DefaultColors",
1✔
2367
            [
1✔
2368
                DefaultColors::DefaultColor {
1✔
2369
                    default_color: RgbaColor::new(0, 10, 20, 30),
1✔
2370
                },
1✔
2371
                DefaultColors::OverUnder {
1✔
2372
                    over_color: RgbaColor::new(1, 2, 3, 4),
1✔
2373
                    under_color: RgbaColor::new(5, 6, 7, 8),
1✔
2374
                },
1✔
2375
            ],
1✔
2376
        )
1✔
2377
        .await;
6✔
2378

2379
        assert_sql_type(
1✔
2380
            &pool,
1✔
2381
            "Colorizer",
1✔
2382
            [
1✔
2383
                Colorizer::LinearGradient {
1✔
2384
                    breakpoints: vec![
1✔
2385
                        Breakpoint {
1✔
2386
                            value: NotNan::<f64>::new(-10.0).unwrap(),
1✔
2387
                            color: RgbaColor::new(0, 0, 0, 0),
1✔
2388
                        },
1✔
2389
                        Breakpoint {
1✔
2390
                            value: NotNan::<f64>::new(2.0).unwrap(),
1✔
2391
                            color: RgbaColor::new(255, 0, 0, 255),
1✔
2392
                        },
1✔
2393
                    ],
1✔
2394
                    no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
2395
                    default_colors: DefaultColors::OverUnder {
1✔
2396
                        over_color: RgbaColor::new(1, 2, 3, 4),
1✔
2397
                        under_color: RgbaColor::new(5, 6, 7, 8),
1✔
2398
                    },
1✔
2399
                },
1✔
2400
                Colorizer::LogarithmicGradient {
1✔
2401
                    breakpoints: vec![
1✔
2402
                        Breakpoint {
1✔
2403
                            value: NotNan::<f64>::new(1.0).unwrap(),
1✔
2404
                            color: RgbaColor::new(0, 0, 0, 0),
1✔
2405
                        },
1✔
2406
                        Breakpoint {
1✔
2407
                            value: NotNan::<f64>::new(2.0).unwrap(),
1✔
2408
                            color: RgbaColor::new(255, 0, 0, 255),
1✔
2409
                        },
1✔
2410
                    ],
1✔
2411
                    no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
2412
                    default_colors: DefaultColors::OverUnder {
1✔
2413
                        over_color: RgbaColor::new(1, 2, 3, 4),
1✔
2414
                        under_color: RgbaColor::new(5, 6, 7, 8),
1✔
2415
                    },
1✔
2416
                },
1✔
2417
                Colorizer::palette(
1✔
2418
                    [
1✔
2419
                        (NotNan::<f64>::new(1.0).unwrap(), RgbaColor::new(0, 0, 0, 0)),
1✔
2420
                        (
1✔
2421
                            NotNan::<f64>::new(2.0).unwrap(),
1✔
2422
                            RgbaColor::new(255, 0, 0, 255),
1✔
2423
                        ),
1✔
2424
                        (
1✔
2425
                            NotNan::<f64>::new(3.0).unwrap(),
1✔
2426
                            RgbaColor::new(0, 10, 20, 30),
1✔
2427
                        ),
1✔
2428
                    ]
1✔
2429
                    .into(),
1✔
2430
                    RgbaColor::new(1, 2, 3, 4),
1✔
2431
                    RgbaColor::new(5, 6, 7, 8),
1✔
2432
                )
1✔
2433
                .unwrap(),
1✔
2434
                Colorizer::Rgba,
1✔
2435
            ],
1✔
2436
        )
1✔
2437
        .await;
14✔
2438

2439
        assert_sql_type(
1✔
2440
            &pool,
1✔
2441
            "ColorParam",
1✔
2442
            [
1✔
2443
                ColorParam::Static {
1✔
2444
                    color: RgbaColor::new(0, 10, 20, 30),
1✔
2445
                },
1✔
2446
                ColorParam::Derived(DerivedColor {
1✔
2447
                    attribute: "foobar".to_string(),
1✔
2448
                    colorizer: Colorizer::Rgba,
1✔
2449
                }),
1✔
2450
            ],
1✔
2451
        )
1✔
2452
        .await;
6✔
2453

2454
        assert_sql_type(
1✔
2455
            &pool,
1✔
2456
            "NumberParam",
1✔
2457
            [
1✔
2458
                NumberParam::Static { value: 42 },
1✔
2459
                NumberParam::Derived(DerivedNumber {
1✔
2460
                    attribute: "foobar".to_string(),
1✔
2461
                    factor: 1.0,
1✔
2462
                    default_value: 42.,
1✔
2463
                }),
1✔
2464
            ],
1✔
2465
        )
1✔
2466
        .await;
6✔
2467

2468
        assert_sql_type(
1✔
2469
            &pool,
1✔
2470
            "StrokeParam",
1✔
2471
            [StrokeParam {
1✔
2472
                width: NumberParam::Static { value: 42 },
1✔
2473
                color: ColorParam::Static {
1✔
2474
                    color: RgbaColor::new(0, 10, 20, 30),
1✔
2475
                },
1✔
2476
            }],
1✔
2477
        )
1✔
2478
        .await;
4✔
2479

2480
        assert_sql_type(
1✔
2481
            &pool,
1✔
2482
            "TextSymbology",
1✔
2483
            [TextSymbology {
1✔
2484
                attribute: "attribute".to_string(),
1✔
2485
                fill_color: ColorParam::Static {
1✔
2486
                    color: RgbaColor::new(0, 10, 20, 30),
1✔
2487
                },
1✔
2488
                stroke: StrokeParam {
1✔
2489
                    width: NumberParam::Static { value: 42 },
1✔
2490
                    color: ColorParam::Static {
1✔
2491
                        color: RgbaColor::new(0, 10, 20, 30),
1✔
2492
                    },
1✔
2493
                },
1✔
2494
            }],
1✔
2495
        )
1✔
2496
        .await;
4✔
2497

2498
        assert_sql_type(
1✔
2499
            &pool,
1✔
2500
            "Symbology",
1✔
2501
            [
1✔
2502
                Symbology::Point(PointSymbology {
1✔
2503
                    fill_color: ColorParam::Static {
1✔
2504
                        color: RgbaColor::new(0, 10, 20, 30),
1✔
2505
                    },
1✔
2506
                    stroke: StrokeParam {
1✔
2507
                        width: NumberParam::Static { value: 42 },
1✔
2508
                        color: ColorParam::Static {
1✔
2509
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
2510
                        },
1✔
2511
                    },
1✔
2512
                    radius: NumberParam::Static { value: 42 },
1✔
2513
                    text: Some(TextSymbology {
1✔
2514
                        attribute: "attribute".to_string(),
1✔
2515
                        fill_color: ColorParam::Static {
1✔
2516
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
2517
                        },
1✔
2518
                        stroke: StrokeParam {
1✔
2519
                            width: NumberParam::Static { value: 42 },
1✔
2520
                            color: ColorParam::Static {
1✔
2521
                                color: RgbaColor::new(0, 10, 20, 30),
1✔
2522
                            },
1✔
2523
                        },
1✔
2524
                    }),
1✔
2525
                }),
1✔
2526
                Symbology::Line(LineSymbology {
1✔
2527
                    stroke: StrokeParam {
1✔
2528
                        width: NumberParam::Static { value: 42 },
1✔
2529
                        color: ColorParam::Static {
1✔
2530
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
2531
                        },
1✔
2532
                    },
1✔
2533
                    text: Some(TextSymbology {
1✔
2534
                        attribute: "attribute".to_string(),
1✔
2535
                        fill_color: ColorParam::Static {
1✔
2536
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
2537
                        },
1✔
2538
                        stroke: StrokeParam {
1✔
2539
                            width: NumberParam::Static { value: 42 },
1✔
2540
                            color: ColorParam::Static {
1✔
2541
                                color: RgbaColor::new(0, 10, 20, 30),
1✔
2542
                            },
1✔
2543
                        },
1✔
2544
                    }),
1✔
2545
                    auto_simplified: true,
1✔
2546
                }),
1✔
2547
                Symbology::Polygon(PolygonSymbology {
1✔
2548
                    fill_color: ColorParam::Static {
1✔
2549
                        color: RgbaColor::new(0, 10, 20, 30),
1✔
2550
                    },
1✔
2551
                    stroke: StrokeParam {
1✔
2552
                        width: NumberParam::Static { value: 42 },
1✔
2553
                        color: ColorParam::Static {
1✔
2554
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
2555
                        },
1✔
2556
                    },
1✔
2557
                    text: Some(TextSymbology {
1✔
2558
                        attribute: "attribute".to_string(),
1✔
2559
                        fill_color: ColorParam::Static {
1✔
2560
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
2561
                        },
1✔
2562
                        stroke: StrokeParam {
1✔
2563
                            width: NumberParam::Static { value: 42 },
1✔
2564
                            color: ColorParam::Static {
1✔
2565
                                color: RgbaColor::new(0, 10, 20, 30),
1✔
2566
                            },
1✔
2567
                        },
1✔
2568
                    }),
1✔
2569
                    auto_simplified: true,
1✔
2570
                }),
1✔
2571
                Symbology::Raster(RasterSymbology {
1✔
2572
                    opacity: 1.0,
1✔
2573
                    colorizer: Colorizer::LinearGradient {
1✔
2574
                        breakpoints: vec![
1✔
2575
                            Breakpoint {
1✔
2576
                                value: NotNan::<f64>::new(-10.0).unwrap(),
1✔
2577
                                color: RgbaColor::new(0, 0, 0, 0),
1✔
2578
                            },
1✔
2579
                            Breakpoint {
1✔
2580
                                value: NotNan::<f64>::new(2.0).unwrap(),
1✔
2581
                                color: RgbaColor::new(255, 0, 0, 255),
1✔
2582
                            },
1✔
2583
                        ],
1✔
2584
                        no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
2585
                        default_colors: DefaultColors::OverUnder {
1✔
2586
                            over_color: RgbaColor::new(1, 2, 3, 4),
1✔
2587
                            under_color: RgbaColor::new(5, 6, 7, 8),
1✔
2588
                        },
1✔
2589
                    },
1✔
2590
                }),
1✔
2591
            ],
1✔
2592
        )
1✔
2593
        .await;
18✔
2594

2595
        assert_sql_type(
1✔
2596
            &pool,
1✔
2597
            "RasterDataType",
1✔
2598
            [
1✔
2599
                RasterDataType::U8,
1✔
2600
                RasterDataType::U16,
1✔
2601
                RasterDataType::U32,
1✔
2602
                RasterDataType::U64,
1✔
2603
                RasterDataType::I8,
1✔
2604
                RasterDataType::I16,
1✔
2605
                RasterDataType::I32,
1✔
2606
                RasterDataType::I64,
1✔
2607
                RasterDataType::F32,
1✔
2608
                RasterDataType::F64,
1✔
2609
            ],
1✔
2610
        )
1✔
2611
        .await;
22✔
2612

2613
        assert_sql_type(
1✔
2614
            &pool,
1✔
2615
            "Measurement",
1✔
2616
            [
1✔
2617
                Measurement::Unitless,
1✔
2618
                Measurement::Continuous(ContinuousMeasurement {
1✔
2619
                    measurement: "Temperature".to_string(),
1✔
2620
                    unit: Some("°C".to_string()),
1✔
2621
                }),
1✔
2622
                Measurement::Classification(ClassificationMeasurement {
1✔
2623
                    measurement: "Color".to_string(),
1✔
2624
                    classes: [(1, "Grayscale".to_string()), (2, "Colorful".to_string())].into(),
1✔
2625
                }),
1✔
2626
            ],
1✔
2627
        )
1✔
2628
        .await;
15✔
2629

2630
        assert_sql_type(&pool, "Coordinate2D", [Coordinate2D::new(0.0f64, 1.)]).await;
4✔
2631

2632
        assert_sql_type(
1✔
2633
            &pool,
1✔
2634
            "SpatialPartition2D",
1✔
2635
            [
1✔
2636
                SpatialPartition2D::new(Coordinate2D::new(0.0f64, 1.), Coordinate2D::new(2., 0.5))
1✔
2637
                    .unwrap(),
1✔
2638
            ],
1✔
2639
        )
1✔
2640
        .await;
4✔
2641

2642
        assert_sql_type(
1✔
2643
            &pool,
1✔
2644
            "BoundingBox2D",
1✔
2645
            [
1✔
2646
                BoundingBox2D::new(Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0))
1✔
2647
                    .unwrap(),
1✔
2648
            ],
1✔
2649
        )
1✔
2650
        .await;
4✔
2651

2652
        assert_sql_type(
1✔
2653
            &pool,
1✔
2654
            "SpatialResolution",
1✔
2655
            [SpatialResolution { x: 1.2, y: 2.3 }],
1✔
2656
        )
1✔
2657
        .await;
4✔
2658

2659
        assert_sql_type(
1✔
2660
            &pool,
1✔
2661
            "VectorDataType",
1✔
2662
            [
1✔
2663
                VectorDataType::Data,
1✔
2664
                VectorDataType::MultiPoint,
1✔
2665
                VectorDataType::MultiLineString,
1✔
2666
                VectorDataType::MultiPolygon,
1✔
2667
            ],
1✔
2668
        )
1✔
2669
        .await;
10✔
2670

2671
        assert_sql_type(
1✔
2672
            &pool,
1✔
2673
            "FeatureDataType",
1✔
2674
            [
1✔
2675
                FeatureDataType::Category,
1✔
2676
                FeatureDataType::Int,
1✔
2677
                FeatureDataType::Float,
1✔
2678
                FeatureDataType::Text,
1✔
2679
                FeatureDataType::Bool,
1✔
2680
                FeatureDataType::DateTime,
1✔
2681
            ],
1✔
2682
        )
1✔
2683
        .await;
14✔
2684

2685
        assert_sql_type(&pool, "TimeInterval", [TimeInterval::default()]).await;
4✔
2686

2687
        assert_sql_type(
1✔
2688
            &pool,
1✔
2689
            "SpatialReference",
1✔
2690
            [
1✔
2691
                SpatialReferenceOption::Unreferenced,
1✔
2692
                SpatialReferenceOption::SpatialReference(SpatialReference::epsg_4326()),
1✔
2693
            ],
1✔
2694
        )
1✔
2695
        .await;
8✔
2696

2697
        assert_sql_type(
1✔
2698
            &pool,
1✔
2699
            "PlotResultDescriptor",
1✔
2700
            [PlotResultDescriptor {
1✔
2701
                spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
2702
                time: None,
1✔
2703
                bbox: None,
1✔
2704
            }],
1✔
2705
        )
1✔
2706
        .await;
4✔
2707

2708
        assert_sql_type(
1✔
2709
            &pool,
1✔
2710
            "VectorResultDescriptor",
1✔
2711
            [VectorResultDescriptor {
1✔
2712
                data_type: VectorDataType::MultiPoint,
1✔
2713
                spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2714
                    SpatialReference::epsg_4326(),
1✔
2715
                ),
1✔
2716
                columns: [(
1✔
2717
                    "foo".to_string(),
1✔
2718
                    VectorColumnInfo {
1✔
2719
                        data_type: FeatureDataType::Int,
1✔
2720
                        measurement: Measurement::Unitless,
1✔
2721
                    },
1✔
2722
                )]
1✔
2723
                .into(),
1✔
2724
                time: Some(TimeInterval::default()),
1✔
2725
                bbox: Some(
1✔
2726
                    BoundingBox2D::new(Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0))
1✔
2727
                        .unwrap(),
1✔
2728
                ),
1✔
2729
            }],
1✔
2730
        )
1✔
2731
        .await;
7✔
2732

2733
        assert_sql_type(
1✔
2734
            &pool,
1✔
2735
            "RasterResultDescriptor",
1✔
2736
            [RasterResultDescriptor {
1✔
2737
                data_type: RasterDataType::U8,
1✔
2738
                spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2739
                    SpatialReference::epsg_4326(),
1✔
2740
                ),
1✔
2741
                time: Some(TimeInterval::default()),
1✔
2742
                bbox: Some(
1✔
2743
                    SpatialPartition2D::new(
1✔
2744
                        Coordinate2D::new(0.0f64, 1.),
1✔
2745
                        Coordinate2D::new(2., 0.5),
1✔
2746
                    )
1✔
2747
                    .unwrap(),
1✔
2748
                ),
1✔
2749
                resolution: Some(SpatialResolution { x: 1.2, y: 2.3 }),
1✔
2750
                bands: RasterBandDescriptors::new_single_band(),
1✔
2751
            }],
1✔
2752
        )
1✔
2753
        .await;
7✔
2754

2755
        assert_sql_type(
1✔
2756
            &pool,
1✔
2757
            "ResultDescriptor",
1✔
2758
            [
1✔
2759
                TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
2760
                    data_type: VectorDataType::MultiPoint,
1✔
2761
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2762
                        SpatialReference::epsg_4326(),
1✔
2763
                    ),
1✔
2764
                    columns: [(
1✔
2765
                        "foo".to_string(),
1✔
2766
                        VectorColumnInfo {
1✔
2767
                            data_type: FeatureDataType::Int,
1✔
2768
                            measurement: Measurement::Unitless,
1✔
2769
                        },
1✔
2770
                    )]
1✔
2771
                    .into(),
1✔
2772
                    time: Some(TimeInterval::default()),
1✔
2773
                    bbox: Some(
1✔
2774
                        BoundingBox2D::new(
1✔
2775
                            Coordinate2D::new(0.0f64, 0.5),
1✔
2776
                            Coordinate2D::new(2., 1.0),
1✔
2777
                        )
1✔
2778
                        .unwrap(),
1✔
2779
                    ),
1✔
2780
                }),
1✔
2781
                TypedResultDescriptor::Raster(RasterResultDescriptor {
1✔
2782
                    data_type: RasterDataType::U8,
1✔
2783
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2784
                        SpatialReference::epsg_4326(),
1✔
2785
                    ),
1✔
2786
                    time: Some(TimeInterval::default()),
1✔
2787
                    bbox: Some(
1✔
2788
                        SpatialPartition2D::new(
1✔
2789
                            Coordinate2D::new(0.0f64, 1.),
1✔
2790
                            Coordinate2D::new(2., 0.5),
1✔
2791
                        )
1✔
2792
                        .unwrap(),
1✔
2793
                    ),
1✔
2794
                    resolution: Some(SpatialResolution { x: 1.2, y: 2.3 }),
1✔
2795
                    bands: RasterBandDescriptors::new_single_band(),
1✔
2796
                }),
1✔
2797
                TypedResultDescriptor::Plot(PlotResultDescriptor {
1✔
2798
                    spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
2799
                    time: None,
1✔
2800
                    bbox: None,
1✔
2801
                }),
1✔
2802
            ],
1✔
2803
        )
1✔
2804
        .await;
8✔
2805

2806
        assert_sql_type(
1✔
2807
            &pool,
1✔
2808
            "MockDatasetDataSourceLoadingInfo",
1✔
2809
            [MockDatasetDataSourceLoadingInfo {
1✔
2810
                points: vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
2811
            }],
1✔
2812
        )
1✔
2813
        .await;
5✔
2814

2815
        assert_sql_type(
1✔
2816
            &pool,
1✔
2817
            "OgrSourceTimeFormat",
1✔
2818
            [
1✔
2819
                OgrSourceTimeFormat::Auto,
1✔
2820
                OgrSourceTimeFormat::Custom {
1✔
2821
                    custom_format: geoengine_datatypes::primitives::DateTimeParseFormat::custom(
1✔
2822
                        "%Y-%m-%dT%H:%M:%S%.3fZ".to_string(),
1✔
2823
                    ),
1✔
2824
                },
1✔
2825
                OgrSourceTimeFormat::UnixTimeStamp {
1✔
2826
                    timestamp_type: UnixTimeStampType::EpochSeconds,
1✔
2827
                    fmt: geoengine_datatypes::primitives::DateTimeParseFormat::unix(),
1✔
2828
                },
1✔
2829
            ],
1✔
2830
        )
1✔
2831
        .await;
16✔
2832

2833
        assert_sql_type(
1✔
2834
            &pool,
1✔
2835
            "OgrSourceDurationSpec",
1✔
2836
            [
1✔
2837
                OgrSourceDurationSpec::Infinite,
1✔
2838
                OgrSourceDurationSpec::Zero,
1✔
2839
                OgrSourceDurationSpec::Value(TimeStep {
1✔
2840
                    granularity: TimeGranularity::Millis,
1✔
2841
                    step: 1000,
1✔
2842
                }),
1✔
2843
            ],
1✔
2844
        )
1✔
2845
        .await;
12✔
2846

2847
        assert_sql_type(
1✔
2848
            &pool,
1✔
2849
            "OgrSourceDatasetTimeType",
1✔
2850
            [
1✔
2851
                OgrSourceDatasetTimeType::None,
1✔
2852
                OgrSourceDatasetTimeType::Start {
1✔
2853
                    start_field: "start".to_string(),
1✔
2854
                    start_format: OgrSourceTimeFormat::Auto,
1✔
2855
                    duration: OgrSourceDurationSpec::Zero,
1✔
2856
                },
1✔
2857
                OgrSourceDatasetTimeType::StartEnd {
1✔
2858
                    start_field: "start".to_string(),
1✔
2859
                    start_format: OgrSourceTimeFormat::Auto,
1✔
2860
                    end_field: "end".to_string(),
1✔
2861
                    end_format: OgrSourceTimeFormat::Auto,
1✔
2862
                },
1✔
2863
                OgrSourceDatasetTimeType::StartDuration {
1✔
2864
                    start_field: "start".to_string(),
1✔
2865
                    start_format: OgrSourceTimeFormat::Auto,
1✔
2866
                    duration_field: "duration".to_string(),
1✔
2867
                },
1✔
2868
            ],
1✔
2869
        )
1✔
2870
        .await;
16✔
2871

2872
        assert_sql_type(
1✔
2873
            &pool,
1✔
2874
            "FormatSpecifics",
1✔
2875
            [FormatSpecifics::Csv {
1✔
2876
                header: CsvHeader::Yes,
1✔
2877
            }],
1✔
2878
        )
1✔
2879
        .await;
8✔
2880

2881
        assert_sql_type(
1✔
2882
            &pool,
1✔
2883
            "OgrSourceColumnSpec",
1✔
2884
            [OgrSourceColumnSpec {
1✔
2885
                format_specifics: Some(FormatSpecifics::Csv {
1✔
2886
                    header: CsvHeader::Auto,
1✔
2887
                }),
1✔
2888
                x: "x".to_string(),
1✔
2889
                y: Some("y".to_string()),
1✔
2890
                int: vec!["int".to_string()],
1✔
2891
                float: vec!["float".to_string()],
1✔
2892
                text: vec!["text".to_string()],
1✔
2893
                bool: vec!["bool".to_string()],
1✔
2894
                datetime: vec!["datetime".to_string()],
1✔
2895
                rename: Some(
1✔
2896
                    [
1✔
2897
                        ("xx".to_string(), "xx_renamed".to_string()),
1✔
2898
                        ("yx".to_string(), "yy_renamed".to_string()),
1✔
2899
                    ]
1✔
2900
                    .into(),
1✔
2901
                ),
1✔
2902
            }],
1✔
2903
        )
1✔
2904
        .await;
7✔
2905

2906
        assert_sql_type(
1✔
2907
            &pool,
1✔
2908
            "point[]",
1✔
2909
            [MultiPoint::new(vec![
1✔
2910
                Coordinate2D::new(0.0f64, 0.5),
1✔
2911
                Coordinate2D::new(2., 1.0),
1✔
2912
            ])
1✔
2913
            .unwrap()],
1✔
2914
        )
1✔
2915
        .await;
2✔
2916

2917
        assert_sql_type(
1✔
2918
            &pool,
1✔
2919
            "path[]",
1✔
2920
            [MultiLineString::new(vec![
1✔
2921
                vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
2922
                vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
2923
            ])
1✔
2924
            .unwrap()],
1✔
2925
        )
1✔
2926
        .await;
2✔
2927

2928
        assert_sql_type(
1✔
2929
            &pool,
1✔
2930
            "\"Polygon\"[]",
1✔
2931
            [MultiPolygon::new(vec![
1✔
2932
                vec![
1✔
2933
                    vec![
1✔
2934
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2935
                        Coordinate2D::new(2., 1.0),
1✔
2936
                        Coordinate2D::new(2., 1.0),
1✔
2937
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2938
                    ],
1✔
2939
                    vec![
1✔
2940
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2941
                        Coordinate2D::new(2., 1.0),
1✔
2942
                        Coordinate2D::new(2., 1.0),
1✔
2943
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2944
                    ],
1✔
2945
                ],
1✔
2946
                vec![
1✔
2947
                    vec![
1✔
2948
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2949
                        Coordinate2D::new(2., 1.0),
1✔
2950
                        Coordinate2D::new(2., 1.0),
1✔
2951
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2952
                    ],
1✔
2953
                    vec![
1✔
2954
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2955
                        Coordinate2D::new(2., 1.0),
1✔
2956
                        Coordinate2D::new(2., 1.0),
1✔
2957
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2958
                    ],
1✔
2959
                ],
1✔
2960
            ])
1✔
2961
            .unwrap()],
1✔
2962
        )
1✔
2963
        .await;
4✔
2964

2965
        assert_sql_type(
1✔
2966
            &pool,
1✔
2967
            "TypedGeometry",
1✔
2968
            [
1✔
2969
                TypedGeometry::Data(NoGeometry),
1✔
2970
                TypedGeometry::MultiPoint(
1✔
2971
                    MultiPoint::new(vec![
1✔
2972
                        Coordinate2D::new(0.0f64, 0.5),
1✔
2973
                        Coordinate2D::new(2., 1.0),
1✔
2974
                    ])
1✔
2975
                    .unwrap(),
1✔
2976
                ),
1✔
2977
                TypedGeometry::MultiLineString(
1✔
2978
                    MultiLineString::new(vec![
1✔
2979
                        vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
2980
                        vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
2981
                    ])
1✔
2982
                    .unwrap(),
1✔
2983
                ),
1✔
2984
                TypedGeometry::MultiPolygon(
1✔
2985
                    MultiPolygon::new(vec![
1✔
2986
                        vec![
1✔
2987
                            vec![
1✔
2988
                                Coordinate2D::new(0.0f64, 0.5),
1✔
2989
                                Coordinate2D::new(2., 1.0),
1✔
2990
                                Coordinate2D::new(2., 1.0),
1✔
2991
                                Coordinate2D::new(0.0f64, 0.5),
1✔
2992
                            ],
1✔
2993
                            vec![
1✔
2994
                                Coordinate2D::new(0.0f64, 0.5),
1✔
2995
                                Coordinate2D::new(2., 1.0),
1✔
2996
                                Coordinate2D::new(2., 1.0),
1✔
2997
                                Coordinate2D::new(0.0f64, 0.5),
1✔
2998
                            ],
1✔
2999
                        ],
1✔
3000
                        vec![
1✔
3001
                            vec![
1✔
3002
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3003
                                Coordinate2D::new(2., 1.0),
1✔
3004
                                Coordinate2D::new(2., 1.0),
1✔
3005
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3006
                            ],
1✔
3007
                            vec![
1✔
3008
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3009
                                Coordinate2D::new(2., 1.0),
1✔
3010
                                Coordinate2D::new(2., 1.0),
1✔
3011
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3012
                            ],
1✔
3013
                        ],
1✔
3014
                    ])
1✔
3015
                    .unwrap(),
1✔
3016
                ),
1✔
3017
            ],
1✔
3018
        )
1✔
3019
        .await;
11✔
3020

3021
        assert_sql_type(&pool, "int", [CacheTtlSeconds::new(100)]).await;
2✔
3022

3023
        assert_sql_type(
1✔
3024
            &pool,
1✔
3025
            "OgrSourceDataset",
1✔
3026
            [OgrSourceDataset {
1✔
3027
                file_name: "test".into(),
1✔
3028
                layer_name: "test".to_string(),
1✔
3029
                data_type: Some(VectorDataType::MultiPoint),
1✔
3030
                time: OgrSourceDatasetTimeType::Start {
1✔
3031
                    start_field: "start".to_string(),
1✔
3032
                    start_format: OgrSourceTimeFormat::Auto,
1✔
3033
                    duration: OgrSourceDurationSpec::Zero,
1✔
3034
                },
1✔
3035
                default_geometry: Some(TypedGeometry::MultiPoint(
1✔
3036
                    MultiPoint::new(vec![
1✔
3037
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3038
                        Coordinate2D::new(2., 1.0),
1✔
3039
                    ])
1✔
3040
                    .unwrap(),
1✔
3041
                )),
1✔
3042
                columns: Some(OgrSourceColumnSpec {
1✔
3043
                    format_specifics: Some(FormatSpecifics::Csv {
1✔
3044
                        header: CsvHeader::Auto,
1✔
3045
                    }),
1✔
3046
                    x: "x".to_string(),
1✔
3047
                    y: Some("y".to_string()),
1✔
3048
                    int: vec!["int".to_string()],
1✔
3049
                    float: vec!["float".to_string()],
1✔
3050
                    text: vec!["text".to_string()],
1✔
3051
                    bool: vec!["bool".to_string()],
1✔
3052
                    datetime: vec!["datetime".to_string()],
1✔
3053
                    rename: Some(
1✔
3054
                        [
1✔
3055
                            ("xx".to_string(), "xx_renamed".to_string()),
1✔
3056
                            ("yx".to_string(), "yy_renamed".to_string()),
1✔
3057
                        ]
1✔
3058
                        .into(),
1✔
3059
                    ),
1✔
3060
                }),
1✔
3061
                force_ogr_time_filter: false,
1✔
3062
                force_ogr_spatial_filter: true,
1✔
3063
                on_error: OgrSourceErrorSpec::Abort,
1✔
3064
                sql_query: None,
1✔
3065
                attribute_query: Some("foo = 'bar'".to_string()),
1✔
3066
                cache_ttl: CacheTtlSeconds::new(5),
1✔
3067
            }],
1✔
3068
        )
1✔
3069
        .await;
6✔
3070

3071
        assert_sql_type(
1✔
3072
            &pool,
1✔
3073
            "MockMetaData",
1✔
3074
            [StaticMetaData::<
1✔
3075
                MockDatasetDataSourceLoadingInfo,
1✔
3076
                VectorResultDescriptor,
1✔
3077
                VectorQueryRectangle,
1✔
3078
            > {
1✔
3079
                loading_info: MockDatasetDataSourceLoadingInfo {
1✔
3080
                    points: vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3081
                },
1✔
3082
                result_descriptor: VectorResultDescriptor {
1✔
3083
                    data_type: VectorDataType::MultiPoint,
1✔
3084
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3085
                        SpatialReference::epsg_4326(),
1✔
3086
                    ),
1✔
3087
                    columns: [(
1✔
3088
                        "foo".to_string(),
1✔
3089
                        VectorColumnInfo {
1✔
3090
                            data_type: FeatureDataType::Int,
1✔
3091
                            measurement: Measurement::Unitless,
1✔
3092
                        },
1✔
3093
                    )]
1✔
3094
                    .into(),
1✔
3095
                    time: Some(TimeInterval::default()),
1✔
3096
                    bbox: Some(
1✔
3097
                        BoundingBox2D::new(
1✔
3098
                            Coordinate2D::new(0.0f64, 0.5),
1✔
3099
                            Coordinate2D::new(2., 1.0),
1✔
3100
                        )
1✔
3101
                        .unwrap(),
1✔
3102
                    ),
1✔
3103
                },
1✔
3104
                phantom: PhantomData,
1✔
3105
            }],
1✔
3106
        )
1✔
3107
        .await;
4✔
3108

3109
        assert_sql_type(
1✔
3110
            &pool,
1✔
3111
            "OgrMetaData",
1✔
3112
            [
1✔
3113
                StaticMetaData::<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle> {
1✔
3114
                    loading_info: OgrSourceDataset {
1✔
3115
                        file_name: "test".into(),
1✔
3116
                        layer_name: "test".to_string(),
1✔
3117
                        data_type: Some(VectorDataType::MultiPoint),
1✔
3118
                        time: OgrSourceDatasetTimeType::Start {
1✔
3119
                            start_field: "start".to_string(),
1✔
3120
                            start_format: OgrSourceTimeFormat::Auto,
1✔
3121
                            duration: OgrSourceDurationSpec::Zero,
1✔
3122
                        },
1✔
3123
                        default_geometry: Some(TypedGeometry::MultiPoint(
1✔
3124
                            MultiPoint::new(vec![
1✔
3125
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3126
                                Coordinate2D::new(2., 1.0),
1✔
3127
                            ])
1✔
3128
                            .unwrap(),
1✔
3129
                        )),
1✔
3130
                        columns: Some(OgrSourceColumnSpec {
1✔
3131
                            format_specifics: Some(FormatSpecifics::Csv {
1✔
3132
                                header: CsvHeader::Auto,
1✔
3133
                            }),
1✔
3134
                            x: "x".to_string(),
1✔
3135
                            y: Some("y".to_string()),
1✔
3136
                            int: vec!["int".to_string()],
1✔
3137
                            float: vec!["float".to_string()],
1✔
3138
                            text: vec!["text".to_string()],
1✔
3139
                            bool: vec!["bool".to_string()],
1✔
3140
                            datetime: vec!["datetime".to_string()],
1✔
3141
                            rename: Some(
1✔
3142
                                [
1✔
3143
                                    ("xx".to_string(), "xx_renamed".to_string()),
1✔
3144
                                    ("yx".to_string(), "yy_renamed".to_string()),
1✔
3145
                                ]
1✔
3146
                                .into(),
1✔
3147
                            ),
1✔
3148
                        }),
1✔
3149
                        force_ogr_time_filter: false,
1✔
3150
                        force_ogr_spatial_filter: true,
1✔
3151
                        on_error: OgrSourceErrorSpec::Abort,
1✔
3152
                        sql_query: None,
1✔
3153
                        attribute_query: Some("foo = 'bar'".to_string()),
1✔
3154
                        cache_ttl: CacheTtlSeconds::new(5),
1✔
3155
                    },
1✔
3156
                    result_descriptor: VectorResultDescriptor {
1✔
3157
                        data_type: VectorDataType::MultiPoint,
1✔
3158
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3159
                            SpatialReference::epsg_4326(),
1✔
3160
                        ),
1✔
3161
                        columns: [(
1✔
3162
                            "foo".to_string(),
1✔
3163
                            VectorColumnInfo {
1✔
3164
                                data_type: FeatureDataType::Int,
1✔
3165
                                measurement: Measurement::Unitless,
1✔
3166
                            },
1✔
3167
                        )]
1✔
3168
                        .into(),
1✔
3169
                        time: Some(TimeInterval::default()),
1✔
3170
                        bbox: Some(
1✔
3171
                            BoundingBox2D::new(
1✔
3172
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3173
                                Coordinate2D::new(2., 1.0),
1✔
3174
                            )
1✔
3175
                            .unwrap(),
1✔
3176
                        ),
1✔
3177
                    },
1✔
3178
                    phantom: PhantomData,
1✔
3179
                },
1✔
3180
            ],
1✔
3181
        )
1✔
3182
        .await;
4✔
3183

3184
        assert_sql_type(
1✔
3185
            &pool,
1✔
3186
            "GdalDatasetGeoTransform",
1✔
3187
            [GdalDatasetGeoTransform {
1✔
3188
                origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3189
                x_pixel_size: 1.0,
1✔
3190
                y_pixel_size: 2.0,
1✔
3191
            }],
1✔
3192
        )
1✔
3193
        .await;
4✔
3194

3195
        assert_sql_type(
1✔
3196
            &pool,
1✔
3197
            "FileNotFoundHandling",
1✔
3198
            [FileNotFoundHandling::NoData, FileNotFoundHandling::Error],
1✔
3199
        )
1✔
3200
        .await;
6✔
3201

3202
        assert_sql_type(
1✔
3203
            &pool,
1✔
3204
            "GdalMetadataMapping",
1✔
3205
            [GdalMetadataMapping {
1✔
3206
                source_key: RasterPropertiesKey {
1✔
3207
                    domain: None,
1✔
3208
                    key: "foo".to_string(),
1✔
3209
                },
1✔
3210
                target_key: RasterPropertiesKey {
1✔
3211
                    domain: Some("bar".to_string()),
1✔
3212
                    key: "foo".to_string(),
1✔
3213
                },
1✔
3214
                target_type: RasterPropertiesEntryType::String,
1✔
3215
            }],
1✔
3216
        )
1✔
3217
        .await;
8✔
3218

3219
        assert_sql_type(
1✔
3220
            &pool,
1✔
3221
            "StringPair",
1✔
3222
            [StringPair::from(("foo".to_string(), "bar".to_string()))],
1✔
3223
        )
1✔
3224
        .await;
3✔
3225

3226
        assert_sql_type(
1✔
3227
            &pool,
1✔
3228
            "GdalDatasetParameters",
1✔
3229
            [GdalDatasetParameters {
1✔
3230
                file_path: "text".into(),
1✔
3231
                rasterband_channel: 1,
1✔
3232
                geo_transform: GdalDatasetGeoTransform {
1✔
3233
                    origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3234
                    x_pixel_size: 1.0,
1✔
3235
                    y_pixel_size: 2.0,
1✔
3236
                },
1✔
3237
                width: 42,
1✔
3238
                height: 23,
1✔
3239
                file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3240
                no_data_value: Some(42.0),
1✔
3241
                properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3242
                    source_key: RasterPropertiesKey {
1✔
3243
                        domain: None,
1✔
3244
                        key: "foo".to_string(),
1✔
3245
                    },
1✔
3246
                    target_key: RasterPropertiesKey {
1✔
3247
                        domain: Some("bar".to_string()),
1✔
3248
                        key: "foo".to_string(),
1✔
3249
                    },
1✔
3250
                    target_type: RasterPropertiesEntryType::String,
1✔
3251
                }]),
1✔
3252
                gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3253
                gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3254
                allow_alphaband_as_mask: false,
1✔
3255
                retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3256
            }],
1✔
3257
        )
1✔
3258
        .await;
8✔
3259

3260
        assert_sql_type(
1✔
3261
            &pool,
1✔
3262
            "GdalMetaDataRegular",
1✔
3263
            [GdalMetaDataRegular {
1✔
3264
                result_descriptor: RasterResultDescriptor {
1✔
3265
                    data_type: RasterDataType::U8,
1✔
3266
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3267
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3268
                    bbox: Some(
1✔
3269
                        SpatialPartition2D::new(
1✔
3270
                            Coordinate2D::new(0.0f64, 1.),
1✔
3271
                            Coordinate2D::new(2., 0.5),
1✔
3272
                        )
1✔
3273
                        .unwrap(),
1✔
3274
                    ),
1✔
3275
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
3276
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3277
                        "band".into(),
1✔
3278
                        Measurement::Continuous(ContinuousMeasurement {
1✔
3279
                            measurement: "Temperature".to_string(),
1✔
3280
                            unit: Some("°C".to_string()),
1✔
3281
                        }),
1✔
3282
                    )])
1✔
3283
                    .unwrap(),
1✔
3284
                },
1✔
3285
                params: GdalDatasetParameters {
1✔
3286
                    file_path: "text".into(),
1✔
3287
                    rasterband_channel: 1,
1✔
3288
                    geo_transform: GdalDatasetGeoTransform {
1✔
3289
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3290
                        x_pixel_size: 1.0,
1✔
3291
                        y_pixel_size: 2.0,
1✔
3292
                    },
1✔
3293
                    width: 42,
1✔
3294
                    height: 23,
1✔
3295
                    file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3296
                    no_data_value: Some(42.0),
1✔
3297
                    properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3298
                        source_key: RasterPropertiesKey {
1✔
3299
                            domain: None,
1✔
3300
                            key: "foo".to_string(),
1✔
3301
                        },
1✔
3302
                        target_key: RasterPropertiesKey {
1✔
3303
                            domain: Some("bar".to_string()),
1✔
3304
                            key: "foo".to_string(),
1✔
3305
                        },
1✔
3306
                        target_type: RasterPropertiesEntryType::String,
1✔
3307
                    }]),
1✔
3308
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3309
                    gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3310
                    allow_alphaband_as_mask: false,
1✔
3311
                    retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3312
                },
1✔
3313
                time_placeholders: [(
1✔
3314
                    "foo".to_string(),
1✔
3315
                    GdalSourceTimePlaceholder {
1✔
3316
                        format: geoengine_datatypes::primitives::DateTimeParseFormat::unix(),
1✔
3317
                        reference: TimeReference::Start,
1✔
3318
                    },
1✔
3319
                )]
1✔
3320
                .into(),
1✔
3321
                data_time: TimeInterval::new_unchecked(0, 1),
1✔
3322
                step: TimeStep {
1✔
3323
                    granularity: TimeGranularity::Millis,
1✔
3324
                    step: 1,
1✔
3325
                },
1✔
3326
                cache_ttl: CacheTtlSeconds::max(),
1✔
3327
            }],
1✔
3328
        )
1✔
3329
        .await;
11✔
3330

3331
        assert_sql_type(
1✔
3332
            &pool,
1✔
3333
            "GdalMetaDataStatic",
1✔
3334
            [GdalMetaDataStatic {
1✔
3335
                time: Some(TimeInterval::new_unchecked(0, 1)),
1✔
3336
                result_descriptor: RasterResultDescriptor {
1✔
3337
                    data_type: RasterDataType::U8,
1✔
3338
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3339
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3340
                    bbox: Some(
1✔
3341
                        SpatialPartition2D::new(
1✔
3342
                            Coordinate2D::new(0.0f64, 1.),
1✔
3343
                            Coordinate2D::new(2., 0.5),
1✔
3344
                        )
1✔
3345
                        .unwrap(),
1✔
3346
                    ),
1✔
3347
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
3348
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3349
                        "band".into(),
1✔
3350
                        Measurement::Continuous(ContinuousMeasurement {
1✔
3351
                            measurement: "Temperature".to_string(),
1✔
3352
                            unit: Some("°C".to_string()),
1✔
3353
                        }),
1✔
3354
                    )])
1✔
3355
                    .unwrap(),
1✔
3356
                },
1✔
3357
                params: GdalDatasetParameters {
1✔
3358
                    file_path: "text".into(),
1✔
3359
                    rasterband_channel: 1,
1✔
3360
                    geo_transform: GdalDatasetGeoTransform {
1✔
3361
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3362
                        x_pixel_size: 1.0,
1✔
3363
                        y_pixel_size: 2.0,
1✔
3364
                    },
1✔
3365
                    width: 42,
1✔
3366
                    height: 23,
1✔
3367
                    file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3368
                    no_data_value: Some(42.0),
1✔
3369
                    properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3370
                        source_key: RasterPropertiesKey {
1✔
3371
                            domain: None,
1✔
3372
                            key: "foo".to_string(),
1✔
3373
                        },
1✔
3374
                        target_key: RasterPropertiesKey {
1✔
3375
                            domain: Some("bar".to_string()),
1✔
3376
                            key: "foo".to_string(),
1✔
3377
                        },
1✔
3378
                        target_type: RasterPropertiesEntryType::String,
1✔
3379
                    }]),
1✔
3380
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3381
                    gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3382
                    allow_alphaband_as_mask: false,
1✔
3383
                    retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3384
                },
1✔
3385
                cache_ttl: CacheTtlSeconds::max(),
1✔
3386
            }],
1✔
3387
        )
1✔
3388
        .await;
4✔
3389

3390
        assert_sql_type(
1✔
3391
            &pool,
1✔
3392
            "GdalMetadataNetCdfCf",
1✔
3393
            [GdalMetadataNetCdfCf {
1✔
3394
                result_descriptor: RasterResultDescriptor {
1✔
3395
                    data_type: RasterDataType::U8,
1✔
3396
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3397
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3398
                    bbox: Some(
1✔
3399
                        SpatialPartition2D::new(
1✔
3400
                            Coordinate2D::new(0.0f64, 1.),
1✔
3401
                            Coordinate2D::new(2., 0.5),
1✔
3402
                        )
1✔
3403
                        .unwrap(),
1✔
3404
                    ),
1✔
3405
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
3406
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3407
                        "band".into(),
1✔
3408
                        Measurement::Continuous(ContinuousMeasurement {
1✔
3409
                            measurement: "Temperature".to_string(),
1✔
3410
                            unit: Some("°C".to_string()),
1✔
3411
                        }),
1✔
3412
                    )])
1✔
3413
                    .unwrap(),
1✔
3414
                },
1✔
3415
                params: GdalDatasetParameters {
1✔
3416
                    file_path: "text".into(),
1✔
3417
                    rasterband_channel: 1,
1✔
3418
                    geo_transform: GdalDatasetGeoTransform {
1✔
3419
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3420
                        x_pixel_size: 1.0,
1✔
3421
                        y_pixel_size: 2.0,
1✔
3422
                    },
1✔
3423
                    width: 42,
1✔
3424
                    height: 23,
1✔
3425
                    file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3426
                    no_data_value: Some(42.0),
1✔
3427
                    properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3428
                        source_key: RasterPropertiesKey {
1✔
3429
                            domain: None,
1✔
3430
                            key: "foo".to_string(),
1✔
3431
                        },
1✔
3432
                        target_key: RasterPropertiesKey {
1✔
3433
                            domain: Some("bar".to_string()),
1✔
3434
                            key: "foo".to_string(),
1✔
3435
                        },
1✔
3436
                        target_type: RasterPropertiesEntryType::String,
1✔
3437
                    }]),
1✔
3438
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3439
                    gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3440
                    allow_alphaband_as_mask: false,
1✔
3441
                    retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3442
                },
1✔
3443
                start: TimeInstance::from_millis(0).unwrap(),
1✔
3444
                end: TimeInstance::from_millis(1000).unwrap(),
1✔
3445
                cache_ttl: CacheTtlSeconds::max(),
1✔
3446
                step: TimeStep {
1✔
3447
                    granularity: TimeGranularity::Millis,
1✔
3448
                    step: 1,
1✔
3449
                },
1✔
3450
                band_offset: 3,
1✔
3451
            }],
1✔
3452
        )
1✔
3453
        .await;
4✔
3454

3455
        assert_sql_type(
1✔
3456
            &pool,
1✔
3457
            "GdalMetaDataList",
1✔
3458
            [GdalMetaDataList {
1✔
3459
                result_descriptor: RasterResultDescriptor {
1✔
3460
                    data_type: RasterDataType::U8,
1✔
3461
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3462
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3463
                    bbox: Some(
1✔
3464
                        SpatialPartition2D::new(
1✔
3465
                            Coordinate2D::new(0.0f64, 1.),
1✔
3466
                            Coordinate2D::new(2., 0.5),
1✔
3467
                        )
1✔
3468
                        .unwrap(),
1✔
3469
                    ),
1✔
3470
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
3471
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3472
                        "band".into(),
1✔
3473
                        Measurement::Continuous(ContinuousMeasurement {
1✔
3474
                            measurement: "Temperature".to_string(),
1✔
3475
                            unit: Some("°C".to_string()),
1✔
3476
                        }),
1✔
3477
                    )])
1✔
3478
                    .unwrap(),
1✔
3479
                },
1✔
3480
                params: vec![GdalLoadingInfoTemporalSlice {
1✔
3481
                    time: TimeInterval::new_unchecked(0, 1),
1✔
3482
                    params: Some(GdalDatasetParameters {
1✔
3483
                        file_path: "text".into(),
1✔
3484
                        rasterband_channel: 1,
1✔
3485
                        geo_transform: GdalDatasetGeoTransform {
1✔
3486
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3487
                            x_pixel_size: 1.0,
1✔
3488
                            y_pixel_size: 2.0,
1✔
3489
                        },
1✔
3490
                        width: 42,
1✔
3491
                        height: 23,
1✔
3492
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3493
                        no_data_value: Some(42.0),
1✔
3494
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3495
                            source_key: RasterPropertiesKey {
1✔
3496
                                domain: None,
1✔
3497
                                key: "foo".to_string(),
1✔
3498
                            },
1✔
3499
                            target_key: RasterPropertiesKey {
1✔
3500
                                domain: Some("bar".to_string()),
1✔
3501
                                key: "foo".to_string(),
1✔
3502
                            },
1✔
3503
                            target_type: RasterPropertiesEntryType::String,
1✔
3504
                        }]),
1✔
3505
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3506
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3507
                        allow_alphaband_as_mask: false,
1✔
3508
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3509
                    }),
1✔
3510
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3511
                }],
1✔
3512
            }],
1✔
3513
        )
1✔
3514
        .await;
7✔
3515

3516
        assert_sql_type(
1✔
3517
            &pool,
1✔
3518
            "MetaDataDefinition",
1✔
3519
            [
1✔
3520
                MetaDataDefinition::MockMetaData(StaticMetaData::<
1✔
3521
                    MockDatasetDataSourceLoadingInfo,
1✔
3522
                    VectorResultDescriptor,
1✔
3523
                    VectorQueryRectangle,
1✔
3524
                > {
1✔
3525
                    loading_info: MockDatasetDataSourceLoadingInfo {
1✔
3526
                        points: vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3527
                    },
1✔
3528
                    result_descriptor: VectorResultDescriptor {
1✔
3529
                        data_type: VectorDataType::MultiPoint,
1✔
3530
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3531
                            SpatialReference::epsg_4326(),
1✔
3532
                        ),
1✔
3533
                        columns: [(
1✔
3534
                            "foo".to_string(),
1✔
3535
                            VectorColumnInfo {
1✔
3536
                                data_type: FeatureDataType::Int,
1✔
3537
                                measurement: Measurement::Unitless,
1✔
3538
                            },
1✔
3539
                        )]
1✔
3540
                        .into(),
1✔
3541
                        time: Some(TimeInterval::default()),
1✔
3542
                        bbox: Some(
1✔
3543
                            BoundingBox2D::new(
1✔
3544
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3545
                                Coordinate2D::new(2., 1.0),
1✔
3546
                            )
1✔
3547
                            .unwrap(),
1✔
3548
                        ),
1✔
3549
                    },
1✔
3550
                    phantom: PhantomData,
1✔
3551
                }),
1✔
3552
                MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3553
                    OgrSourceDataset,
1✔
3554
                    VectorResultDescriptor,
1✔
3555
                    VectorQueryRectangle,
1✔
3556
                > {
1✔
3557
                    loading_info: OgrSourceDataset {
1✔
3558
                        file_name: "test".into(),
1✔
3559
                        layer_name: "test".to_string(),
1✔
3560
                        data_type: Some(VectorDataType::MultiPoint),
1✔
3561
                        time: OgrSourceDatasetTimeType::Start {
1✔
3562
                            start_field: "start".to_string(),
1✔
3563
                            start_format: OgrSourceTimeFormat::Auto,
1✔
3564
                            duration: OgrSourceDurationSpec::Zero,
1✔
3565
                        },
1✔
3566
                        default_geometry: Some(TypedGeometry::MultiPoint(
1✔
3567
                            MultiPoint::new(vec![
1✔
3568
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3569
                                Coordinate2D::new(2., 1.0),
1✔
3570
                            ])
1✔
3571
                            .unwrap(),
1✔
3572
                        )),
1✔
3573
                        columns: Some(OgrSourceColumnSpec {
1✔
3574
                            format_specifics: Some(FormatSpecifics::Csv {
1✔
3575
                                header: CsvHeader::Auto,
1✔
3576
                            }),
1✔
3577
                            x: "x".to_string(),
1✔
3578
                            y: Some("y".to_string()),
1✔
3579
                            int: vec!["int".to_string()],
1✔
3580
                            float: vec!["float".to_string()],
1✔
3581
                            text: vec!["text".to_string()],
1✔
3582
                            bool: vec!["bool".to_string()],
1✔
3583
                            datetime: vec!["datetime".to_string()],
1✔
3584
                            rename: Some(
1✔
3585
                                [
1✔
3586
                                    ("xx".to_string(), "xx_renamed".to_string()),
1✔
3587
                                    ("yx".to_string(), "yy_renamed".to_string()),
1✔
3588
                                ]
1✔
3589
                                .into(),
1✔
3590
                            ),
1✔
3591
                        }),
1✔
3592
                        force_ogr_time_filter: false,
1✔
3593
                        force_ogr_spatial_filter: true,
1✔
3594
                        on_error: OgrSourceErrorSpec::Abort,
1✔
3595
                        sql_query: None,
1✔
3596
                        attribute_query: Some("foo = 'bar'".to_string()),
1✔
3597
                        cache_ttl: CacheTtlSeconds::new(5),
1✔
3598
                    },
1✔
3599
                    result_descriptor: VectorResultDescriptor {
1✔
3600
                        data_type: VectorDataType::MultiPoint,
1✔
3601
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3602
                            SpatialReference::epsg_4326(),
1✔
3603
                        ),
1✔
3604
                        columns: [(
1✔
3605
                            "foo".to_string(),
1✔
3606
                            VectorColumnInfo {
1✔
3607
                                data_type: FeatureDataType::Int,
1✔
3608
                                measurement: Measurement::Unitless,
1✔
3609
                            },
1✔
3610
                        )]
1✔
3611
                        .into(),
1✔
3612
                        time: Some(TimeInterval::default()),
1✔
3613
                        bbox: Some(
1✔
3614
                            BoundingBox2D::new(
1✔
3615
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3616
                                Coordinate2D::new(2., 1.0),
1✔
3617
                            )
1✔
3618
                            .unwrap(),
1✔
3619
                        ),
1✔
3620
                    },
1✔
3621
                    phantom: PhantomData,
1✔
3622
                }),
1✔
3623
                MetaDataDefinition::GdalMetaDataRegular(GdalMetaDataRegular {
1✔
3624
                    result_descriptor: RasterResultDescriptor {
1✔
3625
                        data_type: RasterDataType::U8,
1✔
3626
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3627
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3628
                        bbox: Some(
1✔
3629
                            SpatialPartition2D::new(
1✔
3630
                                Coordinate2D::new(0.0f64, 1.),
1✔
3631
                                Coordinate2D::new(2., 0.5),
1✔
3632
                            )
1✔
3633
                            .unwrap(),
1✔
3634
                        ),
1✔
3635
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3636
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3637
                            "band".into(),
1✔
3638
                            Measurement::Continuous(ContinuousMeasurement {
1✔
3639
                                measurement: "Temperature".to_string(),
1✔
3640
                                unit: Some("°C".to_string()),
1✔
3641
                            }),
1✔
3642
                        )])
1✔
3643
                        .unwrap(),
1✔
3644
                    },
1✔
3645
                    params: GdalDatasetParameters {
1✔
3646
                        file_path: "text".into(),
1✔
3647
                        rasterband_channel: 1,
1✔
3648
                        geo_transform: GdalDatasetGeoTransform {
1✔
3649
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3650
                            x_pixel_size: 1.0,
1✔
3651
                            y_pixel_size: 2.0,
1✔
3652
                        },
1✔
3653
                        width: 42,
1✔
3654
                        height: 23,
1✔
3655
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3656
                        no_data_value: Some(42.0),
1✔
3657
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3658
                            source_key: RasterPropertiesKey {
1✔
3659
                                domain: None,
1✔
3660
                                key: "foo".to_string(),
1✔
3661
                            },
1✔
3662
                            target_key: RasterPropertiesKey {
1✔
3663
                                domain: Some("bar".to_string()),
1✔
3664
                                key: "foo".to_string(),
1✔
3665
                            },
1✔
3666
                            target_type: RasterPropertiesEntryType::String,
1✔
3667
                        }]),
1✔
3668
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3669
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3670
                        allow_alphaband_as_mask: false,
1✔
3671
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3672
                    },
1✔
3673
                    time_placeholders: [(
1✔
3674
                        "foo".to_string(),
1✔
3675
                        GdalSourceTimePlaceholder {
1✔
3676
                            format: DateTimeParseFormat::unix(),
1✔
3677
                            reference: TimeReference::Start,
1✔
3678
                        },
1✔
3679
                    )]
1✔
3680
                    .into(),
1✔
3681
                    data_time: TimeInterval::new_unchecked(0, 1),
1✔
3682
                    step: TimeStep {
1✔
3683
                        granularity: TimeGranularity::Millis,
1✔
3684
                        step: 1,
1✔
3685
                    },
1✔
3686
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3687
                }),
1✔
3688
                MetaDataDefinition::GdalStatic(GdalMetaDataStatic {
1✔
3689
                    time: Some(TimeInterval::new_unchecked(0, 1)),
1✔
3690
                    result_descriptor: RasterResultDescriptor {
1✔
3691
                        data_type: RasterDataType::U8,
1✔
3692
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3693
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3694
                        bbox: Some(
1✔
3695
                            SpatialPartition2D::new(
1✔
3696
                                Coordinate2D::new(0.0f64, 1.),
1✔
3697
                                Coordinate2D::new(2., 0.5),
1✔
3698
                            )
1✔
3699
                            .unwrap(),
1✔
3700
                        ),
1✔
3701
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3702
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3703
                            "band".into(),
1✔
3704
                            Measurement::Continuous(ContinuousMeasurement {
1✔
3705
                                measurement: "Temperature".to_string(),
1✔
3706
                                unit: Some("°C".to_string()),
1✔
3707
                            }),
1✔
3708
                        )])
1✔
3709
                        .unwrap(),
1✔
3710
                    },
1✔
3711
                    params: GdalDatasetParameters {
1✔
3712
                        file_path: "text".into(),
1✔
3713
                        rasterband_channel: 1,
1✔
3714
                        geo_transform: GdalDatasetGeoTransform {
1✔
3715
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3716
                            x_pixel_size: 1.0,
1✔
3717
                            y_pixel_size: 2.0,
1✔
3718
                        },
1✔
3719
                        width: 42,
1✔
3720
                        height: 23,
1✔
3721
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3722
                        no_data_value: Some(42.0),
1✔
3723
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3724
                            source_key: RasterPropertiesKey {
1✔
3725
                                domain: None,
1✔
3726
                                key: "foo".to_string(),
1✔
3727
                            },
1✔
3728
                            target_key: RasterPropertiesKey {
1✔
3729
                                domain: Some("bar".to_string()),
1✔
3730
                                key: "foo".to_string(),
1✔
3731
                            },
1✔
3732
                            target_type: RasterPropertiesEntryType::String,
1✔
3733
                        }]),
1✔
3734
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3735
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3736
                        allow_alphaband_as_mask: false,
1✔
3737
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3738
                    },
1✔
3739
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3740
                }),
1✔
3741
                MetaDataDefinition::GdalMetadataNetCdfCf(GdalMetadataNetCdfCf {
1✔
3742
                    result_descriptor: RasterResultDescriptor {
1✔
3743
                        data_type: RasterDataType::U8,
1✔
3744
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3745
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3746
                        bbox: Some(
1✔
3747
                            SpatialPartition2D::new(
1✔
3748
                                Coordinate2D::new(0.0f64, 1.),
1✔
3749
                                Coordinate2D::new(2., 0.5),
1✔
3750
                            )
1✔
3751
                            .unwrap(),
1✔
3752
                        ),
1✔
3753
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3754
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3755
                            "band".into(),
1✔
3756
                            Measurement::Continuous(ContinuousMeasurement {
1✔
3757
                                measurement: "Temperature".to_string(),
1✔
3758
                                unit: Some("°C".to_string()),
1✔
3759
                            }),
1✔
3760
                        )])
1✔
3761
                        .unwrap(),
1✔
3762
                    },
1✔
3763
                    params: GdalDatasetParameters {
1✔
3764
                        file_path: "text".into(),
1✔
3765
                        rasterband_channel: 1,
1✔
3766
                        geo_transform: GdalDatasetGeoTransform {
1✔
3767
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3768
                            x_pixel_size: 1.0,
1✔
3769
                            y_pixel_size: 2.0,
1✔
3770
                        },
1✔
3771
                        width: 42,
1✔
3772
                        height: 23,
1✔
3773
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3774
                        no_data_value: Some(42.0),
1✔
3775
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3776
                            source_key: RasterPropertiesKey {
1✔
3777
                                domain: None,
1✔
3778
                                key: "foo".to_string(),
1✔
3779
                            },
1✔
3780
                            target_key: RasterPropertiesKey {
1✔
3781
                                domain: Some("bar".to_string()),
1✔
3782
                                key: "foo".to_string(),
1✔
3783
                            },
1✔
3784
                            target_type: RasterPropertiesEntryType::String,
1✔
3785
                        }]),
1✔
3786
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3787
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3788
                        allow_alphaband_as_mask: false,
1✔
3789
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3790
                    },
1✔
3791
                    start: TimeInstance::from_millis(0).unwrap(),
1✔
3792
                    end: TimeInstance::from_millis(1000).unwrap(),
1✔
3793
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3794
                    step: TimeStep {
1✔
3795
                        granularity: TimeGranularity::Millis,
1✔
3796
                        step: 1,
1✔
3797
                    },
1✔
3798
                    band_offset: 3,
1✔
3799
                }),
1✔
3800
                MetaDataDefinition::GdalMetaDataList(GdalMetaDataList {
1✔
3801
                    result_descriptor: RasterResultDescriptor {
1✔
3802
                        data_type: RasterDataType::U8,
1✔
3803
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3804
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3805
                        bbox: Some(
1✔
3806
                            SpatialPartition2D::new(
1✔
3807
                                Coordinate2D::new(0.0f64, 1.),
1✔
3808
                                Coordinate2D::new(2., 0.5),
1✔
3809
                            )
1✔
3810
                            .unwrap(),
1✔
3811
                        ),
1✔
3812
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3813
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3814
                            "band".into(),
1✔
3815
                            Measurement::Continuous(ContinuousMeasurement {
1✔
3816
                                measurement: "Temperature".to_string(),
1✔
3817
                                unit: Some("°C".to_string()),
1✔
3818
                            }),
1✔
3819
                        )])
1✔
3820
                        .unwrap(),
1✔
3821
                    },
1✔
3822
                    params: vec![GdalLoadingInfoTemporalSlice {
1✔
3823
                        time: TimeInterval::new_unchecked(0, 1),
1✔
3824
                        params: Some(GdalDatasetParameters {
1✔
3825
                            file_path: "text".into(),
1✔
3826
                            rasterband_channel: 1,
1✔
3827
                            geo_transform: GdalDatasetGeoTransform {
1✔
3828
                                origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3829
                                x_pixel_size: 1.0,
1✔
3830
                                y_pixel_size: 2.0,
1✔
3831
                            },
1✔
3832
                            width: 42,
1✔
3833
                            height: 23,
1✔
3834
                            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3835
                            no_data_value: Some(42.0),
1✔
3836
                            properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3837
                                source_key: RasterPropertiesKey {
1✔
3838
                                    domain: None,
1✔
3839
                                    key: "foo".to_string(),
1✔
3840
                                },
1✔
3841
                                target_key: RasterPropertiesKey {
1✔
3842
                                    domain: Some("bar".to_string()),
1✔
3843
                                    key: "foo".to_string(),
1✔
3844
                                },
1✔
3845
                                target_type: RasterPropertiesEntryType::String,
1✔
3846
                            }]),
1✔
3847
                            gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3848
                            gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3849
                            allow_alphaband_as_mask: false,
1✔
3850
                            retry: None,
1✔
3851
                        }),
1✔
3852
                        cache_ttl: CacheTtlSeconds::max(),
1✔
3853
                    }],
1✔
3854
                }),
1✔
3855
            ],
1✔
3856
        )
1✔
3857
        .await;
14✔
3858

3859
        test_data_provider_definition_types(&pool).await;
31✔
3860
    }
1✔
3861

3862
    #[test]
1✔
3863
    fn test_postgres_config_translation() {
1✔
3864
        let host = "localhost";
1✔
3865
        let port = 8095;
1✔
3866
        let ge_default = "geoengine";
1✔
3867
        let schema = "geoengine";
1✔
3868

1✔
3869
        let db_config = config::Postgres {
1✔
3870
            host: host.to_string(),
1✔
3871
            port,
1✔
3872
            database: ge_default.to_string(),
1✔
3873
            schema: schema.to_string(),
1✔
3874
            user: ge_default.to_string(),
1✔
3875
            password: ge_default.to_string(),
1✔
3876
            clear_database_on_start: false,
1✔
3877
        };
1✔
3878

1✔
3879
        let pg_config = Config::try_from(db_config).unwrap();
1✔
3880

1✔
3881
        assert_eq!(ge_default, pg_config.get_user().unwrap());
1✔
3882
        assert_eq!(
1✔
3883
            <str as AsRef<[u8]>>::as_ref(ge_default).to_vec(),
1✔
3884
            pg_config.get_password().unwrap()
1✔
3885
        );
1✔
3886
        assert_eq!(ge_default, pg_config.get_dbname().unwrap());
1✔
3887
        assert_eq!(
1✔
3888
            &format!("-c search_path={schema}"),
1✔
3889
            pg_config.get_options().unwrap()
1✔
3890
        );
1✔
3891
        assert_eq!(vec![Host::Tcp(host.to_string())], pg_config.get_hosts());
1✔
3892
        assert_eq!(vec![port], pg_config.get_ports());
1✔
3893
    }
1✔
3894

3895
    #[allow(clippy::too_many_lines)]
3896
    async fn test_data_provider_definition_types(
1✔
3897
        pool: &PooledConnection<'_, PostgresConnectionManager<tokio_postgres::NoTls>>,
1✔
3898
    ) {
1✔
3899
        assert_sql_type(
1✔
3900
            pool,
1✔
3901
            "ArunaDataProviderDefinition",
1✔
3902
            [ArunaDataProviderDefinition {
1✔
3903
                id: DataProviderId::from_str("86a7f7ce-1bab-4ce9-a32b-172c0f958ee0").unwrap(),
1✔
3904
                name: "NFDI".to_string(),
1✔
3905
                api_url: "http://test".to_string(),
1✔
3906
                project_id: "project".to_string(),
1✔
3907
                api_token: "api_token".to_string(),
1✔
3908
                filter_label: "filter".to_string(),
1✔
3909
                cache_ttl: CacheTtlSeconds::new(0),
1✔
3910
            }],
1✔
3911
        )
1✔
3912
        .await;
4✔
3913

3914
        assert_sql_type(
1✔
3915
            pool,
1✔
3916
            "GbifDataProviderDefinition",
1✔
3917
            [GbifDataProviderDefinition {
1✔
3918
                name: "GBIF".to_string(),
1✔
3919
                db_config: DatabaseConnectionConfig {
1✔
3920
                    host: "testhost".to_string(),
1✔
3921
                    port: 1234,
1✔
3922
                    database: "testdb".to_string(),
1✔
3923
                    schema: "testschema".to_string(),
1✔
3924
                    user: "testuser".to_string(),
1✔
3925
                    password: "testpass".to_string(),
1✔
3926
                },
1✔
3927
                cache_ttl: CacheTtlSeconds::new(0),
1✔
3928
            }],
1✔
3929
        )
1✔
3930
        .await;
6✔
3931

3932
        assert_sql_type(
1✔
3933
            pool,
1✔
3934
            "GfbioAbcdDataProviderDefinition",
1✔
3935
            [GfbioAbcdDataProviderDefinition {
1✔
3936
                name: "GFbio".to_string(),
1✔
3937
                db_config: DatabaseConnectionConfig {
1✔
3938
                    host: "testhost".to_string(),
1✔
3939
                    port: 1234,
1✔
3940
                    database: "testdb".to_string(),
1✔
3941
                    schema: "testschema".to_string(),
1✔
3942
                    user: "testuser".to_string(),
1✔
3943
                    password: "testpass".to_string(),
1✔
3944
                },
1✔
3945
                cache_ttl: CacheTtlSeconds::new(0),
1✔
3946
            }],
1✔
3947
        )
1✔
3948
        .await;
4✔
3949

3950
        assert_sql_type(
1✔
3951
            pool,
1✔
3952
            "GfbioCollectionsDataProviderDefinition",
1✔
3953
            [GfbioCollectionsDataProviderDefinition {
1✔
3954
                name: "GFbio".to_string(),
1✔
3955
                collection_api_url: "http://testhost".try_into().unwrap(),
1✔
3956
                collection_api_auth_token: "token".to_string(),
1✔
3957
                abcd_db_config: DatabaseConnectionConfig {
1✔
3958
                    host: "testhost".to_string(),
1✔
3959
                    port: 1234,
1✔
3960
                    database: "testdb".to_string(),
1✔
3961
                    schema: "testschema".to_string(),
1✔
3962
                    user: "testuser".to_string(),
1✔
3963
                    password: "testpass".to_string(),
1✔
3964
                },
1✔
3965
                pangaea_url: "http://panaea".try_into().unwrap(),
1✔
3966
                cache_ttl: CacheTtlSeconds::new(0),
1✔
3967
            }],
1✔
3968
        )
1✔
3969
        .await;
4✔
3970

3971
        assert_sql_type(pool, "\"PropertyType\"[]", [Vec::<Property>::new()]).await;
5✔
3972

3973
        assert_sql_type(
1✔
3974
            pool,
1✔
3975
            "EbvPortalDataProviderDefinition",
1✔
3976
            [EbvPortalDataProviderDefinition {
1✔
3977
                name: "ebv".to_string(),
1✔
3978
                path: "a_path".into(),
1✔
3979
                base_url: "http://base".try_into().unwrap(),
1✔
3980
                overviews: "another_path".into(),
1✔
3981
                cache_ttl: CacheTtlSeconds::new(0),
1✔
3982
            }],
1✔
3983
        )
1✔
3984
        .await;
1✔
3985

3986
        assert_sql_type(
1✔
3987
            pool,
1✔
3988
            "NetCdfCfDataProviderDefinition",
1✔
3989
            [NetCdfCfDataProviderDefinition {
1✔
3990
                name: "netcdfcf".to_string(),
1✔
3991
                path: "a_path".into(),
1✔
3992
                overviews: "another_path".into(),
1✔
3993
                cache_ttl: CacheTtlSeconds::new(0),
1✔
3994
            }],
1✔
3995
        )
1✔
3996
        .await;
4✔
3997

3998
        assert_sql_type(
1✔
3999
            pool,
1✔
4000
            "PangaeaDataProviderDefinition",
1✔
4001
            [PangaeaDataProviderDefinition {
1✔
4002
                name: "pangaea".to_string(),
1✔
4003
                base_url: "http://base".try_into().unwrap(),
1✔
4004
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4005
            }],
1✔
4006
        )
1✔
4007
        .await;
3✔
4008

4009
        assert_sql_type(
1✔
4010
            pool,
1✔
4011
            "DataProviderDefinition",
1✔
4012
            [
1✔
4013
                TypedDataProviderDefinition::ArunaDataProviderDefinition(
1✔
4014
                    ArunaDataProviderDefinition {
1✔
4015
                        id: DataProviderId::from_str("86a7f7ce-1bab-4ce9-a32b-172c0f958ee0")
1✔
4016
                            .unwrap(),
1✔
4017
                        name: "NFDI".to_string(),
1✔
4018
                        api_url: "http://test".to_string(),
1✔
4019
                        project_id: "project".to_string(),
1✔
4020
                        api_token: "api_token".to_string(),
1✔
4021
                        filter_label: "filter".to_string(),
1✔
4022
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4023
                    },
1✔
4024
                ),
1✔
4025
                TypedDataProviderDefinition::GbifDataProviderDefinition(
1✔
4026
                    GbifDataProviderDefinition {
1✔
4027
                        name: "GBIF".to_string(),
1✔
4028
                        db_config: DatabaseConnectionConfig {
1✔
4029
                            host: "testhost".to_string(),
1✔
4030
                            port: 1234,
1✔
4031
                            database: "testdb".to_string(),
1✔
4032
                            schema: "testschema".to_string(),
1✔
4033
                            user: "testuser".to_string(),
1✔
4034
                            password: "testpass".to_string(),
1✔
4035
                        },
1✔
4036
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4037
                    },
1✔
4038
                ),
1✔
4039
                TypedDataProviderDefinition::GfbioAbcdDataProviderDefinition(
1✔
4040
                    GfbioAbcdDataProviderDefinition {
1✔
4041
                        name: "GFbio".to_string(),
1✔
4042
                        db_config: DatabaseConnectionConfig {
1✔
4043
                            host: "testhost".to_string(),
1✔
4044
                            port: 1234,
1✔
4045
                            database: "testdb".to_string(),
1✔
4046
                            schema: "testschema".to_string(),
1✔
4047
                            user: "testuser".to_string(),
1✔
4048
                            password: "testpass".to_string(),
1✔
4049
                        },
1✔
4050
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4051
                    },
1✔
4052
                ),
1✔
4053
                TypedDataProviderDefinition::GfbioCollectionsDataProviderDefinition(
1✔
4054
                    GfbioCollectionsDataProviderDefinition {
1✔
4055
                        name: "GFbio".to_string(),
1✔
4056
                        collection_api_url: "http://testhost".try_into().unwrap(),
1✔
4057
                        collection_api_auth_token: "token".to_string(),
1✔
4058
                        abcd_db_config: DatabaseConnectionConfig {
1✔
4059
                            host: "testhost".to_string(),
1✔
4060
                            port: 1234,
1✔
4061
                            database: "testdb".to_string(),
1✔
4062
                            schema: "testschema".to_string(),
1✔
4063
                            user: "testuser".to_string(),
1✔
4064
                            password: "testpass".to_string(),
1✔
4065
                        },
1✔
4066
                        pangaea_url: "http://panaea".try_into().unwrap(),
1✔
4067
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4068
                    },
1✔
4069
                ),
1✔
4070
                TypedDataProviderDefinition::EbvPortalDataProviderDefinition(
1✔
4071
                    EbvPortalDataProviderDefinition {
1✔
4072
                        name: "ebv".to_string(),
1✔
4073
                        path: "a_path".into(),
1✔
4074
                        base_url: "http://base".try_into().unwrap(),
1✔
4075
                        overviews: "another_path".into(),
1✔
4076
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4077
                    },
1✔
4078
                ),
1✔
4079
                TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(
1✔
4080
                    NetCdfCfDataProviderDefinition {
1✔
4081
                        name: "netcdfcf".to_string(),
1✔
4082
                        path: "a_path".into(),
1✔
4083
                        overviews: "another_path".into(),
1✔
4084
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4085
                    },
1✔
4086
                ),
1✔
4087
                TypedDataProviderDefinition::PangaeaDataProviderDefinition(
1✔
4088
                    PangaeaDataProviderDefinition {
1✔
4089
                        name: "pangaea".to_string(),
1✔
4090
                        base_url: "http://base".try_into().unwrap(),
1✔
4091
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4092
                    },
1✔
4093
                ),
1✔
4094
            ],
1✔
4095
        )
1✔
UNCOV
4096
        .await;
×
4097
    }
1✔
4098
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc