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

geo-engine / geoengine / 12417203919

19 Dec 2024 04:55PM UTC coverage: 90.351% (-0.2%) from 90.512%
12417203919

Pull #998

github

web-flow
Merge c7e5c8ae4 into 34e12969f
Pull Request #998: quota logging wip

834 of 1211 new or added lines in 66 files covered. (68.87%)

220 existing lines in 21 files now uncovered.

133830 of 148123 relevant lines covered (90.35%)

54352.72 hits per line

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

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

34
// TODO: distinguish user-facing errors from system-facing error messages
35

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

54
enum DatabaseStatus {
55
    Unitialized,
56
    InitializedClearDatabase,
57
    InitializedKeepDatabase,
58
}
59

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

76
        let pool = Pool::builder().build(pg_mgr).await?;
229✔
77
        let created_schema = Self::create_database(pool.get().await?).await?;
2,976✔
78

79
        let session = if created_schema {
229✔
80
            let session = SimpleSession::default();
229✔
81
            Self::create_default_session(pool.get().await?, session.id()).await?;
458✔
82
            session
229✔
83
        } else {
84
            Self::load_default_session(pool.get().await?).await?
×
85
        };
86

87
        Ok(PostgresContext {
229✔
88
            default_session_id: session.id(),
229✔
89
            task_manager: Default::default(),
229✔
90
            thread_pool: create_rayon_thread_pool(0),
229✔
91
            exe_ctx_tiling_spec,
229✔
92
            query_ctx_chunk_size,
229✔
93
            pool,
229✔
94
            volumes,
229✔
95
        })
229✔
96
    }
229✔
97

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

112
        let pool = Pool::builder().build(pg_mgr).await?;
×
113
        let created_schema = Self::create_database(pool.get().await?).await?;
×
114

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

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

×
133
        if created_schema {
×
134
            info!("Populating database with initial data...");
×
135

136
            let ctx = app_ctx.session_context(session);
×
137

×
138
            let mut db = ctx.db();
×
139
            add_layers_from_directory(&mut db, layer_defs_path).await;
×
140
            add_layer_collections_from_directory(&mut db, layer_collection_defs_path).await;
×
141

142
            add_datasets_from_directory(&mut db, dataset_defs_path).await;
×
143

144
            add_providers_from_directory(&mut db, provider_defs_path).await;
×
145
        }
×
146

147
        Ok(app_ctx)
×
148
    }
×
149

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

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

171
        if row.get(0) {
×
172
            Ok(DatabaseStatus::InitializedClearDatabase)
×
173
        } else {
174
            Ok(DatabaseStatus::InitializedKeepDatabase)
×
175
        }
176
    }
324✔
177

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

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

202
        Ok(())
324✔
203
    }
324✔
204

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

211
        let migration = initialize_database(
229✔
212
            &mut conn,
229✔
213
            Box::new(CurrentSchemaMigration),
229✔
214
            &all_migrations(),
229✔
215
        )
229✔
216
        .await?;
2,747✔
217

218
        Ok(migration == MigrationResult::CreatedDatabase)
229✔
219
    }
229✔
220

221
    async fn create_default_session(
229✔
222
        conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
229✔
223
        session_id: SessionId,
229✔
224
    ) -> Result<()> {
229✔
225
        let stmt = conn
229✔
226
            .prepare("INSERT INTO sessions (id, project_id, view) VALUES ($1, NULL ,NULL);")
229✔
227
            .await?;
229✔
228

229
        conn.execute(&stmt, &[&session_id]).await?;
229✔
230

231
        Ok(())
229✔
232
    }
229✔
233
    async fn load_default_session(
71✔
234
        conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
71✔
235
    ) -> Result<SimpleSession> {
71✔
236
        let stmt = conn
71✔
237
            .prepare("SELECT id, project_id, view FROM sessions LIMIT 1;")
71✔
238
            .await?;
422✔
239

240
        let row = conn.query_one(&stmt, &[]).await?;
72✔
241

242
        Ok(SimpleSession::new(row.get(0), row.get(1), row.get(2)))
71✔
243
    }
71✔
244
}
245

246
#[async_trait]
247
impl<Tls> SimpleApplicationContext for PostgresContext<Tls>
248
where
249
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
250
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
251
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
252
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
253
{
254
    async fn default_session_id(&self) -> SessionId {
89✔
255
        self.default_session_id
89✔
256
    }
178✔
257

258
    async fn default_session(&self) -> Result<SimpleSession> {
71✔
259
        Self::load_default_session(self.pool.get().await?).await
494✔
260
    }
142✔
261

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

265
        let stmt = conn
1✔
266
            .prepare("UPDATE sessions SET project_id = $1 WHERE id = $2;")
1✔
UNCOV
267
            .await?;
×
268

269
        conn.execute(&stmt, &[&project, &self.default_session_id])
1✔
270
            .await?;
1✔
271

272
        Ok(())
1✔
273
    }
2✔
274

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

278
        let stmt = conn
1✔
279
            .prepare("UPDATE sessions SET view = $1 WHERE id = $2;")
1✔
280
            .await?;
1✔
281

282
        conn.execute(&stmt, &[&view, &self.default_session_id])
1✔
283
            .await?;
1✔
284

285
        Ok(())
1✔
286
    }
2✔
287

288
    async fn default_session_context(&self) -> Result<Self::SessionContext> {
364✔
289
        Ok(self.session_context(self.session_by_id(self.default_session_id).await?))
4,278✔
290
    }
728✔
291
}
292

293
#[async_trait]
294
impl<Tls> ApplicationContext for PostgresContext<Tls>
295
where
296
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
297
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
298
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
299
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
300
{
301
    type SessionContext = PostgresSessionContext<Tls>;
302
    type Session = SimpleSession;
303

304
    fn session_context(&self, session: Self::Session) -> Self::SessionContext {
556✔
305
        PostgresSessionContext {
556✔
306
            session,
556✔
307
            context: self.clone(),
556✔
308
        }
556✔
309
    }
556✔
310

311
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
489✔
312
        let mut conn = self.pool.get().await?;
489✔
313

314
        let tx = conn.build_transaction().start().await?;
484✔
315

316
        let stmt = tx
484✔
317
            .prepare(
484✔
318
                "
484✔
319
            SELECT           
484✔
320
                project_id,
484✔
321
                view
484✔
322
            FROM sessions
484✔
323
            WHERE id = $1;",
484✔
324
            )
484✔
325
            .await?;
3,390✔
326

327
        let row = tx
484✔
328
            .query_one(&stmt, &[&session_id])
484✔
329
            .await
446✔
330
            .map_err(|_error| error::Error::InvalidSession)?;
484✔
331

332
        Ok(SimpleSession::new(session_id, row.get(0), row.get(1)))
484✔
333
    }
973✔
334
}
335

336
#[derive(Clone)]
337
pub struct PostgresSessionContext<Tls>
338
where
339
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
340
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
341
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
342
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
343
{
344
    session: SimpleSession,
345
    context: PostgresContext<Tls>,
346
}
347

348
#[async_trait]
349
impl<Tls> SessionContext for PostgresSessionContext<Tls>
350
where
351
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
352
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
353
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
354
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
355
{
356
    type Session = SimpleSession;
357
    type GeoEngineDB = PostgresDb<Tls>;
358

359
    type TaskContext = SimpleTaskManagerContext;
360
    type TaskManager = SimpleTaskManager; // this does not persist across restarts
361
    type QueryContext = QueryContextImpl;
362
    type ExecutionContext = ExecutionContextImpl<Self::GeoEngineDB>;
363

364
    fn db(&self) -> Self::GeoEngineDB {
575✔
365
        PostgresDb::new(self.context.pool.clone())
575✔
366
    }
575✔
367

368
    fn tasks(&self) -> Self::TaskManager {
40✔
369
        SimpleTaskManager::new(self.context.task_manager.clone())
40✔
370
    }
40✔
371

372
    fn query_context(&self, _workflow: Uuid, _computation: Uuid) -> Result<Self::QueryContext> {
29✔
373
        Ok(QueryContextImpl::new(
29✔
374
            self.context.query_ctx_chunk_size,
29✔
375
            self.context.thread_pool.clone(),
29✔
376
        ))
29✔
377
    }
29✔
378

379
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
54✔
380
        Ok(ExecutionContextImpl::<PostgresDb<Tls>>::new(
54✔
381
            self.db(),
54✔
382
            self.context.thread_pool.clone(),
54✔
383
            self.context.exe_ctx_tiling_spec,
54✔
384
        ))
54✔
385
    }
54✔
386

387
    fn volumes(&self) -> Result<Vec<Volume>> {
1✔
388
        Ok(self
1✔
389
            .context
1✔
390
            .volumes
1✔
391
            .volumes
1✔
392
            .iter()
1✔
393
            .map(|v| Volume {
1✔
394
                name: v.name.0.clone(),
1✔
395
                path: Some(v.path.to_string_lossy().to_string()),
1✔
396
            })
1✔
397
            .collect())
1✔
398
    }
1✔
399

400
    fn session(&self) -> &Self::Session {
116✔
401
        &self.session
116✔
402
    }
116✔
403
}
404

405
#[derive(Debug)]
406
pub struct PostgresDb<Tls>
407
where
408
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
409
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
410
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
411
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
412
{
413
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
414
}
415

416
impl<Tls> PostgresDb<Tls>
417
where
418
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
419
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
420
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
421
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
422
{
423
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>) -> Self {
576✔
424
        Self { conn_pool }
576✔
425
    }
576✔
426

427
    /// Check whether the namespace of the given dataset is allowed for insertion
428
    pub(crate) fn check_namespace(id: &DatasetName) -> Result<()> {
83✔
429
        // due to a lack of users, etc., we only allow one namespace for now
83✔
430
        if id.namespace.is_none() {
83✔
431
            Ok(())
83✔
432
        } else {
433
            Err(Error::InvalidDatasetIdNamespace)
×
434
        }
435
    }
83✔
436
}
437

438
impl<Tls> GeoEngineDb for PostgresDb<Tls>
439
where
440
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
441
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
442
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
443
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
444
{
445
}
446

447
impl TryFrom<config::Postgres> for Config {
448
    type Error = Error;
449

450
    fn try_from(db_config: config::Postgres) -> Result<Self> {
26✔
451
        ensure!(
26✔
452
            db_config.schema != "public",
26✔
453
            crate::error::InvalidDatabaseSchema
×
454
        );
455

456
        let mut pg_config = Config::new();
26✔
457
        pg_config
26✔
458
            .user(&db_config.user)
26✔
459
            .password(&db_config.password)
26✔
460
            .host(&db_config.host)
26✔
461
            .dbname(&db_config.database)
26✔
462
            .port(db_config.port)
26✔
463
            // fix schema by providing `search_path` option
26✔
464
            .options(format!("-c search_path={}", db_config.schema));
26✔
465
        Ok(pg_config)
26✔
466
    }
26✔
467
}
468

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

558
    #[ge_context::test]
2✔
559
    async fn test(app_ctx: PostgresContext<NoTls>) {
1✔
560
        let session = app_ctx.default_session().await.unwrap();
18✔
561

1✔
562
        create_projects(&app_ctx, &session).await;
74✔
563

564
        let projects = list_projects(&app_ctx, &session).await;
13✔
565

566
        let project_id = projects[0].id;
1✔
567

1✔
568
        update_projects(&app_ctx, &session, project_id).await;
173✔
569

570
        delete_project(&app_ctx, &session, project_id).await;
7✔
571
    }
1✔
572

573
    async fn delete_project(
1✔
574
        app_ctx: &PostgresContext<NoTls>,
1✔
575
        session: &SimpleSession,
1✔
576
        project_id: ProjectId,
1✔
577
    ) {
1✔
578
        let db = app_ctx.session_context(session.clone()).db();
1✔
579

1✔
580
        db.delete_project(project_id).await.unwrap();
3✔
581

1✔
582
        assert!(db.load_project(project_id).await.is_err());
4✔
583
    }
1✔
584

585
    #[allow(clippy::too_many_lines)]
586
    async fn update_projects(
1✔
587
        app_ctx: &PostgresContext<NoTls>,
1✔
588
        session: &SimpleSession,
1✔
589
        project_id: ProjectId,
1✔
590
    ) {
1✔
591
        let db = app_ctx.session_context(session.clone()).db();
1✔
592

593
        let project = db
1✔
594
            .load_project_version(project_id, LoadVersion::Latest)
1✔
595
            .await
41✔
596
            .unwrap();
1✔
597

598
        let layer_workflow_id = db
1✔
599
            .register_workflow(Workflow {
1✔
600
                operator: TypedOperator::Vector(
1✔
601
                    MockPointSource {
1✔
602
                        params: MockPointSourceParams {
1✔
603
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
604
                        },
1✔
605
                    }
1✔
606
                    .boxed(),
1✔
607
                ),
1✔
608
            })
1✔
609
            .await
5✔
610
            .unwrap();
1✔
611

1✔
612
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
3✔
613

614
        let plot_workflow_id = db
1✔
615
            .register_workflow(Workflow {
1✔
616
                operator: Statistics {
1✔
617
                    params: StatisticsParams {
1✔
618
                        column_names: vec![],
1✔
619
                        percentiles: vec![],
1✔
620
                    },
1✔
621
                    sources: MultipleRasterOrSingleVectorSource {
1✔
622
                        source: Raster(vec![]),
1✔
623
                    },
1✔
624
                }
1✔
625
                .boxed()
1✔
626
                .into(),
1✔
627
            })
1✔
628
            .await
5✔
629
            .unwrap();
1✔
630

1✔
631
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
3✔
632

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

653
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
654
        assert_eq!(versions.len(), 2);
1✔
655

656
        // add second plot
657
        let update = UpdateProject {
1✔
658
            id: project.id,
1✔
659
            name: Some("Test9 Updated".into()),
1✔
660
            description: None,
1✔
661
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
1✔
662
                workflow: layer_workflow_id,
1✔
663
                name: "TestLayer".into(),
1✔
664
                symbology: PointSymbology::default().into(),
1✔
665
                visibility: Default::default(),
1✔
666
            })]),
1✔
667
            plots: Some(vec![
1✔
668
                PlotUpdate::UpdateOrInsert(Plot {
1✔
669
                    workflow: plot_workflow_id,
1✔
670
                    name: "Test Plot".into(),
1✔
671
                }),
1✔
672
                PlotUpdate::UpdateOrInsert(Plot {
1✔
673
                    workflow: plot_workflow_id,
1✔
674
                    name: "Test Plot".into(),
1✔
675
                }),
1✔
676
            ]),
1✔
677
            bounds: None,
1✔
678
            time_step: None,
1✔
679
        };
1✔
680
        db.update_project(update).await.unwrap();
21✔
681

682
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
683
        assert_eq!(versions.len(), 3);
1✔
684

685
        // delete plots
686
        let update = UpdateProject {
1✔
687
            id: project.id,
1✔
688
            name: None,
1✔
689
            description: None,
1✔
690
            layers: None,
1✔
691
            plots: Some(vec![]),
1✔
692
            bounds: None,
1✔
693
            time_step: None,
1✔
694
        };
1✔
695
        db.update_project(update).await.unwrap();
16✔
696

697
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
698
        assert_eq!(versions.len(), 4);
1✔
699
    }
1✔
700

701
    async fn list_projects(
1✔
702
        app_ctx: &PostgresContext<NoTls>,
1✔
703
        session: &SimpleSession,
1✔
704
    ) -> Vec<ProjectListing> {
1✔
705
        let options = ProjectListOptions {
1✔
706
            order: OrderBy::NameDesc,
1✔
707
            offset: 0,
1✔
708
            limit: 2,
1✔
709
        };
1✔
710

1✔
711
        let db = app_ctx.session_context(session.clone()).db();
1✔
712

713
        let projects = db.list_projects(options).await.unwrap();
13✔
714

1✔
715
        assert_eq!(projects.len(), 2);
1✔
716
        assert_eq!(projects[0].name, "Test9");
1✔
717
        assert_eq!(projects[1].name, "Test8");
1✔
718
        projects
1✔
719
    }
1✔
720

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

724
        for i in 0..10 {
11✔
725
            let create = CreateProject {
10✔
726
                name: format!("Test{i}"),
10✔
727
                description: format!("Test{}", 10 - i),
10✔
728
                bounds: STRectangle::new(
10✔
729
                    SpatialReferenceOption::Unreferenced,
10✔
730
                    0.,
10✔
731
                    0.,
10✔
732
                    1.,
10✔
733
                    1.,
10✔
734
                    0,
10✔
735
                    1,
10✔
736
                )
10✔
737
                .unwrap(),
10✔
738
                time_step: None,
10✔
739
            };
10✔
740
            db.create_project(create).await.unwrap();
74✔
741
        }
742
    }
1✔
743

744
    #[ge_context::test]
2✔
745
    async fn it_persists_workflows(app_ctx: PostgresContext<NoTls>) {
1✔
746
        let workflow = Workflow {
1✔
747
            operator: TypedOperator::Vector(
1✔
748
                MockPointSource {
1✔
749
                    params: MockPointSourceParams {
1✔
750
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
751
                    },
1✔
752
                }
1✔
753
                .boxed(),
1✔
754
            ),
1✔
755
        };
1✔
756

757
        let session = app_ctx.default_session().await.unwrap();
18✔
758
        let ctx = app_ctx.session_context(session);
1✔
759

1✔
760
        let db = ctx.db();
1✔
761
        let id = db.register_workflow(workflow).await.unwrap();
5✔
762

1✔
763
        drop(ctx);
1✔
764

765
        let workflow = db.load_workflow(&id).await.unwrap();
3✔
766

1✔
767
        let json = serde_json::to_string(&workflow).unwrap();
1✔
768
        assert_eq!(
1✔
769
            json,
1✔
770
            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✔
771
        );
1✔
772
    }
1✔
773

774
    #[allow(clippy::too_many_lines)]
775
    #[ge_context::test]
2✔
776
    async fn it_persists_datasets(app_ctx: PostgresContext<NoTls>) {
1✔
777
        let loading_info = OgrSourceDataset {
1✔
778
            file_name: PathBuf::from("test.csv"),
1✔
779
            layer_name: "test.csv".to_owned(),
1✔
780
            data_type: Some(VectorDataType::MultiPoint),
1✔
781
            time: OgrSourceDatasetTimeType::Start {
1✔
782
                start_field: "start".to_owned(),
1✔
783
                start_format: OgrSourceTimeFormat::Auto,
1✔
784
                duration: OgrSourceDurationSpec::Zero,
1✔
785
            },
1✔
786
            default_geometry: None,
1✔
787
            columns: Some(OgrSourceColumnSpec {
1✔
788
                format_specifics: Some(FormatSpecifics::Csv {
1✔
789
                    header: CsvHeader::Auto,
1✔
790
                }),
1✔
791
                x: "x".to_owned(),
1✔
792
                y: None,
1✔
793
                int: vec![],
1✔
794
                float: vec![],
1✔
795
                text: vec![],
1✔
796
                bool: vec![],
1✔
797
                datetime: vec![],
1✔
798
                rename: None,
1✔
799
            }),
1✔
800
            force_ogr_time_filter: false,
1✔
801
            force_ogr_spatial_filter: false,
1✔
802
            on_error: OgrSourceErrorSpec::Ignore,
1✔
803
            sql_query: None,
1✔
804
            attribute_query: None,
1✔
805
            cache_ttl: CacheTtlSeconds::default(),
1✔
806
        };
1✔
807

1✔
808
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
809
            OgrSourceDataset,
1✔
810
            VectorResultDescriptor,
1✔
811
            VectorQueryRectangle,
1✔
812
        > {
1✔
813
            loading_info: loading_info.clone(),
1✔
814
            result_descriptor: VectorResultDescriptor {
1✔
815
                data_type: VectorDataType::MultiPoint,
1✔
816
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
817
                columns: [(
1✔
818
                    "foo".to_owned(),
1✔
819
                    VectorColumnInfo {
1✔
820
                        data_type: FeatureDataType::Float,
1✔
821
                        measurement: Measurement::Unitless,
1✔
822
                    },
1✔
823
                )]
1✔
824
                .into_iter()
1✔
825
                .collect(),
1✔
826
                time: None,
1✔
827
                bbox: None,
1✔
828
            },
1✔
829
            phantom: Default::default(),
1✔
830
        });
1✔
831

832
        let session = app_ctx.default_session().await.unwrap();
18✔
833

1✔
834
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
835

1✔
836
        let db = app_ctx.session_context(session.clone()).db();
1✔
837
        let DatasetIdAndName {
838
            id: dataset_id,
1✔
839
            name: dataset_name,
1✔
840
        } = db
1✔
841
            .add_dataset(
1✔
842
                AddDataset {
1✔
843
                    name: Some(dataset_name.clone()),
1✔
844
                    display_name: "Ogr Test".to_owned(),
1✔
845
                    description: "desc".to_owned(),
1✔
846
                    source_operator: "OgrSource".to_owned(),
1✔
847
                    symbology: None,
1✔
848
                    provenance: Some(vec![Provenance {
1✔
849
                        citation: "citation".to_owned(),
1✔
850
                        license: "license".to_owned(),
1✔
851
                        uri: "uri".to_owned(),
1✔
852
                    }]),
1✔
853
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
854
                },
1✔
855
                meta_data,
1✔
856
            )
1✔
857
            .await
161✔
858
            .unwrap();
1✔
859

860
        let datasets = db
1✔
861
            .list_datasets(DatasetListOptions {
1✔
862
                filter: None,
1✔
863
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
864
                offset: 0,
1✔
865
                limit: 10,
1✔
866
                tags: None,
1✔
867
            })
1✔
868
            .await
4✔
869
            .unwrap();
1✔
870

1✔
871
        assert_eq!(datasets.len(), 1);
1✔
872

873
        assert_eq!(
1✔
874
            datasets[0],
1✔
875
            DatasetListing {
1✔
876
                id: dataset_id,
1✔
877
                name: dataset_name,
1✔
878
                display_name: "Ogr Test".to_owned(),
1✔
879
                description: "desc".to_owned(),
1✔
880
                source_operator: "OgrSource".to_owned(),
1✔
881
                symbology: None,
1✔
882
                tags: vec!["upload".to_owned(), "test".to_owned()],
1✔
883
                result_descriptor: TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
884
                    data_type: VectorDataType::MultiPoint,
1✔
885
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
886
                    columns: [(
1✔
887
                        "foo".to_owned(),
1✔
888
                        VectorColumnInfo {
1✔
889
                            data_type: FeatureDataType::Float,
1✔
890
                            measurement: Measurement::Unitless
1✔
891
                        }
1✔
892
                    )]
1✔
893
                    .into_iter()
1✔
894
                    .collect(),
1✔
895
                    time: None,
1✔
896
                    bbox: None,
1✔
897
                })
1✔
898
            },
1✔
899
        );
1✔
900

901
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
3✔
902

1✔
903
        assert_eq!(
1✔
904
            provenance,
1✔
905
            ProvenanceOutput {
1✔
906
                data: dataset_id.into(),
1✔
907
                provenance: Some(vec![Provenance {
1✔
908
                    citation: "citation".to_owned(),
1✔
909
                    license: "license".to_owned(),
1✔
910
                    uri: "uri".to_owned(),
1✔
911
                }])
1✔
912
            }
1✔
913
        );
1✔
914

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

918
        assert_eq!(
1✔
919
            meta_data
1✔
920
                .loading_info(VectorQueryRectangle {
1✔
921
                    spatial_bounds: BoundingBox2D::new_unchecked(
1✔
922
                        (-180., -90.).into(),
1✔
923
                        (180., 90.).into()
1✔
924
                    ),
1✔
925
                    time_interval: TimeInterval::default(),
1✔
926
                    spatial_resolution: SpatialResolution::zero_point_one(),
1✔
927
                    attributes: ColumnSelection::all()
1✔
928
                })
1✔
929
                .await
×
930
                .unwrap(),
1✔
931
            loading_info
932
        );
933
    }
1✔
934

935
    #[ge_context::test]
2✔
936
    async fn it_persists_uploads(app_ctx: PostgresContext<NoTls>) {
1✔
937
        let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
938
        let input = Upload {
1✔
939
            id,
1✔
940
            files: vec![FileUpload {
1✔
941
                id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
942
                name: "test.csv".to_owned(),
1✔
943
                byte_size: 1337,
1✔
944
            }],
1✔
945
        };
1✔
946

947
        let session = app_ctx.default_session().await.unwrap();
18✔
948

1✔
949
        let db = app_ctx.session_context(session.clone()).db();
1✔
950

1✔
951
        db.create_upload(input.clone()).await.unwrap();
6✔
952

953
        let upload = db.load_upload(id).await.unwrap();
3✔
954

1✔
955
        assert_eq!(upload, input);
1✔
956
    }
1✔
957

958
    #[allow(clippy::too_many_lines)]
959
    #[ge_context::test]
2✔
960
    async fn it_persists_layer_providers(app_ctx: PostgresContext<NoTls>) {
1✔
961
        let db = app_ctx.default_session_context().await.unwrap().db();
19✔
962

1✔
963
        let provider = NetCdfCfDataProviderDefinition {
1✔
964
            name: "netcdfcf".to_string(),
1✔
965
            description: "NetCdfCfProviderDefinition".to_string(),
1✔
966
            priority: Some(21),
1✔
967
            data: test_data!("netcdf4d/").into(),
1✔
968
            overviews: test_data!("netcdf4d/overviews/").into(),
1✔
969
            cache_ttl: CacheTtlSeconds::new(0),
1✔
970
        };
1✔
971

972
        let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
33✔
973

974
        let providers = db
1✔
975
            .list_layer_providers(LayerProviderListingOptions {
1✔
976
                offset: 0,
1✔
977
                limit: 10,
1✔
978
            })
1✔
979
            .await
3✔
980
            .unwrap();
1✔
981

1✔
982
        assert_eq!(providers.len(), 1);
1✔
983

984
        assert_eq!(
1✔
985
            providers[0],
1✔
986
            LayerProviderListing {
1✔
987
                id: provider_id,
1✔
988
                name: "netcdfcf".to_owned(),
1✔
989
                priority: 21,
1✔
990
            }
1✔
991
        );
1✔
992

993
        let provider = db.load_layer_provider(provider_id).await.unwrap();
3✔
994

995
        let datasets = provider
1✔
996
            .load_layer_collection(
1✔
997
                &provider.get_root_layer_collection_id().await.unwrap(),
1✔
998
                LayerCollectionListOptions {
1✔
999
                    offset: 0,
1✔
1000
                    limit: 10,
1✔
1001
                },
1✔
1002
            )
1003
            .await
5✔
1004
            .unwrap();
1✔
1005

1✔
1006
        assert_eq!(datasets.items.len(), 5, "{:?}", datasets.items);
1✔
1007
    }
1✔
1008

1009
    #[allow(clippy::too_many_lines)]
1010
    #[ge_context::test]
2✔
1011
    async fn it_loads_all_meta_data_types(app_ctx: PostgresContext<NoTls>) {
1✔
1012
        let session = app_ctx.default_session().await.unwrap();
18✔
1013

1✔
1014
        let db = app_ctx.session_context(session.clone()).db();
1✔
1015

1✔
1016
        let vector_descriptor = VectorResultDescriptor {
1✔
1017
            data_type: VectorDataType::Data,
1✔
1018
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1019
            columns: Default::default(),
1✔
1020
            time: None,
1✔
1021
            bbox: None,
1✔
1022
        };
1✔
1023

1✔
1024
        let raster_descriptor = RasterResultDescriptor {
1✔
1025
            data_type: RasterDataType::U8,
1✔
1026
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1027
            time: None,
1✔
1028
            bbox: None,
1✔
1029
            resolution: None,
1✔
1030
            bands: RasterBandDescriptors::new_single_band(),
1✔
1031
        };
1✔
1032

1✔
1033
        let vector_ds = AddDataset {
1✔
1034
            name: None,
1✔
1035
            display_name: "OgrDataset".to_string(),
1✔
1036
            description: "My Ogr dataset".to_string(),
1✔
1037
            source_operator: "OgrSource".to_string(),
1✔
1038
            symbology: None,
1✔
1039
            provenance: None,
1✔
1040
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1041
        };
1✔
1042

1✔
1043
        let raster_ds = AddDataset {
1✔
1044
            name: None,
1✔
1045
            display_name: "GdalDataset".to_string(),
1✔
1046
            description: "My Gdal dataset".to_string(),
1✔
1047
            source_operator: "GdalSource".to_string(),
1✔
1048
            symbology: None,
1✔
1049
            provenance: None,
1✔
1050
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1051
        };
1✔
1052

1✔
1053
        let gdal_params = GdalDatasetParameters {
1✔
1054
            file_path: Default::default(),
1✔
1055
            rasterband_channel: 0,
1✔
1056
            geo_transform: GdalDatasetGeoTransform {
1✔
1057
                origin_coordinate: Default::default(),
1✔
1058
                x_pixel_size: 0.0,
1✔
1059
                y_pixel_size: 0.0,
1✔
1060
            },
1✔
1061
            width: 0,
1✔
1062
            height: 0,
1✔
1063
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1064
            no_data_value: None,
1✔
1065
            properties_mapping: None,
1✔
1066
            gdal_open_options: None,
1✔
1067
            gdal_config_options: None,
1✔
1068
            allow_alphaband_as_mask: false,
1✔
1069
            retry: None,
1✔
1070
        };
1✔
1071

1✔
1072
        let meta = StaticMetaData {
1✔
1073
            loading_info: OgrSourceDataset {
1✔
1074
                file_name: Default::default(),
1✔
1075
                layer_name: String::new(),
1✔
1076
                data_type: None,
1✔
1077
                time: Default::default(),
1✔
1078
                default_geometry: None,
1✔
1079
                columns: None,
1✔
1080
                force_ogr_time_filter: false,
1✔
1081
                force_ogr_spatial_filter: false,
1✔
1082
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1083
                sql_query: None,
1✔
1084
                attribute_query: None,
1✔
1085
                cache_ttl: CacheTtlSeconds::default(),
1✔
1086
            },
1✔
1087
            result_descriptor: vector_descriptor.clone(),
1✔
1088
            phantom: Default::default(),
1✔
1089
        };
1✔
1090

1091
        let id = db.add_dataset(vector_ds, meta.into()).await.unwrap().id;
160✔
1092

1093
        let meta: geoengine_operators::util::Result<
1✔
1094
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1095
        > = db.meta_data(&id.into()).await;
3✔
1096

1097
        assert!(meta.is_ok());
1✔
1098

1099
        let meta = GdalMetaDataRegular {
1✔
1100
            result_descriptor: raster_descriptor.clone(),
1✔
1101
            params: gdal_params.clone(),
1✔
1102
            time_placeholders: Default::default(),
1✔
1103
            data_time: Default::default(),
1✔
1104
            step: TimeStep {
1✔
1105
                granularity: TimeGranularity::Millis,
1✔
1106
                step: 0,
1✔
1107
            },
1✔
1108
            cache_ttl: CacheTtlSeconds::default(),
1✔
1109
        };
1✔
1110

1111
        let id = db
1✔
1112
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1113
            .await
5✔
1114
            .unwrap()
1✔
1115
            .id;
1116

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

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

1123
        let meta = GdalMetaDataStatic {
1✔
1124
            time: None,
1✔
1125
            params: gdal_params.clone(),
1✔
1126
            result_descriptor: raster_descriptor.clone(),
1✔
1127
            cache_ttl: CacheTtlSeconds::default(),
1✔
1128
        };
1✔
1129

1130
        let id = db
1✔
1131
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1132
            .await
5✔
1133
            .unwrap()
1✔
1134
            .id;
1135

1136
        let meta: geoengine_operators::util::Result<
1✔
1137
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1138
        > = db.meta_data(&id.into()).await;
3✔
1139

1140
        assert!(meta.is_ok());
1✔
1141

1142
        let meta = GdalMetaDataList {
1✔
1143
            result_descriptor: raster_descriptor.clone(),
1✔
1144
            params: vec![],
1✔
1145
        };
1✔
1146

1147
        let id = db
1✔
1148
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1149
            .await
5✔
1150
            .unwrap()
1✔
1151
            .id;
1152

1153
        let meta: geoengine_operators::util::Result<
1✔
1154
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1155
        > = db.meta_data(&id.into()).await;
3✔
1156

1157
        assert!(meta.is_ok());
1✔
1158

1159
        let meta = GdalMetadataNetCdfCf {
1✔
1160
            result_descriptor: raster_descriptor.clone(),
1✔
1161
            params: gdal_params.clone(),
1✔
1162
            start: TimeInstance::MIN,
1✔
1163
            end: TimeInstance::MAX,
1✔
1164
            step: TimeStep {
1✔
1165
                granularity: TimeGranularity::Millis,
1✔
1166
                step: 0,
1✔
1167
            },
1✔
1168
            band_offset: 0,
1✔
1169
            cache_ttl: CacheTtlSeconds::default(),
1✔
1170
        };
1✔
1171

1172
        let id = db
1✔
1173
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1174
            .await
5✔
1175
            .unwrap()
1✔
1176
            .id;
1177

1178
        let meta: geoengine_operators::util::Result<
1✔
1179
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1180
        > = db.meta_data(&id.into()).await;
3✔
1181

1182
        assert!(meta.is_ok());
1✔
1183
    }
1✔
1184

1185
    #[allow(clippy::too_many_lines)]
1186
    #[ge_context::test]
2✔
1187
    async fn it_collects_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1188
        let session = app_ctx.default_session().await.unwrap();
18✔
1189

1✔
1190
        let layer_db = app_ctx.session_context(session).db();
1✔
1191

1✔
1192
        let workflow = Workflow {
1✔
1193
            operator: TypedOperator::Vector(
1✔
1194
                MockPointSource {
1✔
1195
                    params: MockPointSourceParams {
1✔
1196
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1197
                    },
1✔
1198
                }
1✔
1199
                .boxed(),
1✔
1200
            ),
1✔
1201
        };
1✔
1202

1203
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1204

1205
        let layer1 = layer_db
1✔
1206
            .add_layer(
1✔
1207
                AddLayer {
1✔
1208
                    name: "Layer1".to_string(),
1✔
1209
                    description: "Layer 1".to_string(),
1✔
1210
                    symbology: None,
1✔
1211
                    workflow: workflow.clone(),
1✔
1212
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1213
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1214
                },
1✔
1215
                &root_collection_id,
1✔
1216
            )
1✔
1217
            .await
45✔
1218
            .unwrap();
1✔
1219

1220
        assert_eq!(
1✔
1221
            layer_db.load_layer(&layer1).await.unwrap(),
3✔
1222
            crate::layers::layer::Layer {
1✔
1223
                id: ProviderLayerId {
1✔
1224
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1225
                    layer_id: layer1.clone(),
1✔
1226
                },
1✔
1227
                name: "Layer1".to_string(),
1✔
1228
                description: "Layer 1".to_string(),
1✔
1229
                symbology: None,
1✔
1230
                workflow: workflow.clone(),
1✔
1231
                metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1232
                properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1233
            }
1✔
1234
        );
1235

1236
        let collection1_id = layer_db
1✔
1237
            .add_layer_collection(
1✔
1238
                AddLayerCollection {
1✔
1239
                    name: "Collection1".to_string(),
1✔
1240
                    description: "Collection 1".to_string(),
1✔
1241
                    properties: Default::default(),
1✔
1242
                },
1✔
1243
                &root_collection_id,
1✔
1244
            )
1✔
1245
            .await
7✔
1246
            .unwrap();
1✔
1247

1248
        let layer2 = layer_db
1✔
1249
            .add_layer(
1✔
1250
                AddLayer {
1✔
1251
                    name: "Layer2".to_string(),
1✔
1252
                    description: "Layer 2".to_string(),
1✔
1253
                    symbology: None,
1✔
1254
                    workflow: workflow.clone(),
1✔
1255
                    metadata: Default::default(),
1✔
1256
                    properties: Default::default(),
1✔
1257
                },
1✔
1258
                &collection1_id,
1✔
1259
            )
1✔
1260
            .await
9✔
1261
            .unwrap();
1✔
1262

1263
        let collection2_id = layer_db
1✔
1264
            .add_layer_collection(
1✔
1265
                AddLayerCollection {
1✔
1266
                    name: "Collection2".to_string(),
1✔
1267
                    description: "Collection 2".to_string(),
1✔
1268
                    properties: Default::default(),
1✔
1269
                },
1✔
1270
                &collection1_id,
1✔
1271
            )
1✔
1272
            .await
7✔
1273
            .unwrap();
1✔
1274

1✔
1275
        layer_db
1✔
1276
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1277
            .await
3✔
1278
            .unwrap();
1✔
1279

1280
        let root_collection = layer_db
1✔
1281
            .load_layer_collection(
1✔
1282
                &root_collection_id,
1✔
1283
                LayerCollectionListOptions {
1✔
1284
                    offset: 0,
1✔
1285
                    limit: 20,
1✔
1286
                },
1✔
1287
            )
1✔
1288
            .await
5✔
1289
            .unwrap();
1✔
1290

1✔
1291
        assert_eq!(
1✔
1292
            root_collection,
1✔
1293
            LayerCollection {
1✔
1294
                id: ProviderLayerCollectionId {
1✔
1295
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1296
                    collection_id: root_collection_id,
1✔
1297
                },
1✔
1298
                name: "Layers".to_string(),
1✔
1299
                description: "All available Geo Engine layers".to_string(),
1✔
1300
                items: vec![
1✔
1301
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1302
                        id: ProviderLayerCollectionId {
1✔
1303
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1304
                            collection_id: collection1_id.clone(),
1✔
1305
                        },
1✔
1306
                        name: "Collection1".to_string(),
1✔
1307
                        description: "Collection 1".to_string(),
1✔
1308
                        properties: Default::default(),
1✔
1309
                    }),
1✔
1310
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1311
                        id: ProviderLayerCollectionId {
1✔
1312
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1313
                            collection_id: LayerCollectionId(UNSORTED_COLLECTION_ID.to_string()),
1✔
1314
                        },
1✔
1315
                        name: "Unsorted".to_string(),
1✔
1316
                        description: "Unsorted Layers".to_string(),
1✔
1317
                        properties: Default::default(),
1✔
1318
                    }),
1✔
1319
                    CollectionItem::Layer(LayerListing {
1✔
1320
                        id: ProviderLayerId {
1✔
1321
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1322
                            layer_id: layer1,
1✔
1323
                        },
1✔
1324
                        name: "Layer1".to_string(),
1✔
1325
                        description: "Layer 1".to_string(),
1✔
1326
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1327
                    })
1✔
1328
                ],
1✔
1329
                entry_label: None,
1✔
1330
                properties: vec![],
1✔
1331
            }
1✔
1332
        );
1✔
1333

1334
        let collection1 = layer_db
1✔
1335
            .load_layer_collection(
1✔
1336
                &collection1_id,
1✔
1337
                LayerCollectionListOptions {
1✔
1338
                    offset: 0,
1✔
1339
                    limit: 20,
1✔
1340
                },
1✔
1341
            )
1✔
1342
            .await
5✔
1343
            .unwrap();
1✔
1344

1✔
1345
        assert_eq!(
1✔
1346
            collection1,
1✔
1347
            LayerCollection {
1✔
1348
                id: ProviderLayerCollectionId {
1✔
1349
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1350
                    collection_id: collection1_id,
1✔
1351
                },
1✔
1352
                name: "Collection1".to_string(),
1✔
1353
                description: "Collection 1".to_string(),
1✔
1354
                items: vec![
1✔
1355
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1356
                        id: ProviderLayerCollectionId {
1✔
1357
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1358
                            collection_id: collection2_id,
1✔
1359
                        },
1✔
1360
                        name: "Collection2".to_string(),
1✔
1361
                        description: "Collection 2".to_string(),
1✔
1362
                        properties: Default::default(),
1✔
1363
                    }),
1✔
1364
                    CollectionItem::Layer(LayerListing {
1✔
1365
                        id: ProviderLayerId {
1✔
1366
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1367
                            layer_id: layer2,
1✔
1368
                        },
1✔
1369
                        name: "Layer2".to_string(),
1✔
1370
                        description: "Layer 2".to_string(),
1✔
1371
                        properties: vec![],
1✔
1372
                    })
1✔
1373
                ],
1✔
1374
                entry_label: None,
1✔
1375
                properties: vec![],
1✔
1376
            }
1✔
1377
        );
1✔
1378
    }
1✔
1379

1380
    #[allow(clippy::too_many_lines)]
1381
    #[ge_context::test]
2✔
1382
    async fn it_searches_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1383
        let session = app_ctx.default_session().await.unwrap();
18✔
1384

1✔
1385
        let layer_db = app_ctx.session_context(session).db();
1✔
1386

1✔
1387
        let workflow = Workflow {
1✔
1388
            operator: TypedOperator::Vector(
1✔
1389
                MockPointSource {
1✔
1390
                    params: MockPointSourceParams {
1✔
1391
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1392
                    },
1✔
1393
                }
1✔
1394
                .boxed(),
1✔
1395
            ),
1✔
1396
        };
1✔
1397

1398
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1399

1400
        let layer1 = layer_db
1✔
1401
            .add_layer(
1✔
1402
                AddLayer {
1✔
1403
                    name: "Layer1".to_string(),
1✔
1404
                    description: "Layer 1".to_string(),
1✔
1405
                    symbology: None,
1✔
1406
                    workflow: workflow.clone(),
1✔
1407
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1408
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1409
                },
1✔
1410
                &root_collection_id,
1✔
1411
            )
1✔
1412
            .await
45✔
1413
            .unwrap();
1✔
1414

1415
        let collection1_id = layer_db
1✔
1416
            .add_layer_collection(
1✔
1417
                AddLayerCollection {
1✔
1418
                    name: "Collection1".to_string(),
1✔
1419
                    description: "Collection 1".to_string(),
1✔
1420
                    properties: Default::default(),
1✔
1421
                },
1✔
1422
                &root_collection_id,
1✔
1423
            )
1✔
1424
            .await
7✔
1425
            .unwrap();
1✔
1426

1427
        let layer2 = layer_db
1✔
1428
            .add_layer(
1✔
1429
                AddLayer {
1✔
1430
                    name: "Layer2".to_string(),
1✔
1431
                    description: "Layer 2".to_string(),
1✔
1432
                    symbology: None,
1✔
1433
                    workflow: workflow.clone(),
1✔
1434
                    metadata: Default::default(),
1✔
1435
                    properties: Default::default(),
1✔
1436
                },
1✔
1437
                &collection1_id,
1✔
1438
            )
1✔
1439
            .await
9✔
1440
            .unwrap();
1✔
1441

1442
        let collection2_id = layer_db
1✔
1443
            .add_layer_collection(
1✔
1444
                AddLayerCollection {
1✔
1445
                    name: "Collection2".to_string(),
1✔
1446
                    description: "Collection 2".to_string(),
1✔
1447
                    properties: Default::default(),
1✔
1448
                },
1✔
1449
                &collection1_id,
1✔
1450
            )
1✔
1451
            .await
7✔
1452
            .unwrap();
1✔
1453

1454
        let root_collection_all = layer_db
1✔
1455
            .search(
1✔
1456
                &root_collection_id,
1✔
1457
                SearchParameters {
1✔
1458
                    search_type: SearchType::Fulltext,
1✔
1459
                    search_string: String::new(),
1✔
1460
                    limit: 10,
1✔
1461
                    offset: 0,
1✔
1462
                },
1✔
1463
            )
1✔
1464
            .await
5✔
1465
            .unwrap();
1✔
1466

1✔
1467
        assert_eq!(
1✔
1468
            root_collection_all,
1✔
1469
            LayerCollection {
1✔
1470
                id: ProviderLayerCollectionId {
1✔
1471
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1472
                    collection_id: root_collection_id.clone(),
1✔
1473
                },
1✔
1474
                name: "Layers".to_string(),
1✔
1475
                description: "All available Geo Engine layers".to_string(),
1✔
1476
                items: vec![
1✔
1477
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1478
                        id: ProviderLayerCollectionId {
1✔
1479
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1480
                            collection_id: collection1_id.clone(),
1✔
1481
                        },
1✔
1482
                        name: "Collection1".to_string(),
1✔
1483
                        description: "Collection 1".to_string(),
1✔
1484
                        properties: Default::default(),
1✔
1485
                    }),
1✔
1486
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1487
                        id: ProviderLayerCollectionId {
1✔
1488
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1489
                            collection_id: collection2_id.clone(),
1✔
1490
                        },
1✔
1491
                        name: "Collection2".to_string(),
1✔
1492
                        description: "Collection 2".to_string(),
1✔
1493
                        properties: Default::default(),
1✔
1494
                    }),
1✔
1495
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1496
                        id: ProviderLayerCollectionId {
1✔
1497
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1498
                            collection_id: LayerCollectionId(
1✔
1499
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
1500
                            ),
1✔
1501
                        },
1✔
1502
                        name: "Unsorted".to_string(),
1✔
1503
                        description: "Unsorted Layers".to_string(),
1✔
1504
                        properties: Default::default(),
1✔
1505
                    }),
1✔
1506
                    CollectionItem::Layer(LayerListing {
1✔
1507
                        id: ProviderLayerId {
1✔
1508
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1509
                            layer_id: layer1.clone(),
1✔
1510
                        },
1✔
1511
                        name: "Layer1".to_string(),
1✔
1512
                        description: "Layer 1".to_string(),
1✔
1513
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1514
                    }),
1✔
1515
                    CollectionItem::Layer(LayerListing {
1✔
1516
                        id: ProviderLayerId {
1✔
1517
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1518
                            layer_id: layer2.clone(),
1✔
1519
                        },
1✔
1520
                        name: "Layer2".to_string(),
1✔
1521
                        description: "Layer 2".to_string(),
1✔
1522
                        properties: vec![],
1✔
1523
                    }),
1✔
1524
                ],
1✔
1525
                entry_label: None,
1✔
1526
                properties: vec![],
1✔
1527
            }
1✔
1528
        );
1✔
1529

1530
        let root_collection_filtered = layer_db
1✔
1531
            .search(
1✔
1532
                &root_collection_id,
1✔
1533
                SearchParameters {
1✔
1534
                    search_type: SearchType::Fulltext,
1✔
1535
                    search_string: "lection".to_string(),
1✔
1536
                    limit: 10,
1✔
1537
                    offset: 0,
1✔
1538
                },
1✔
1539
            )
1✔
1540
            .await
5✔
1541
            .unwrap();
1✔
1542

1✔
1543
        assert_eq!(
1✔
1544
            root_collection_filtered,
1✔
1545
            LayerCollection {
1✔
1546
                id: ProviderLayerCollectionId {
1✔
1547
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1548
                    collection_id: root_collection_id.clone(),
1✔
1549
                },
1✔
1550
                name: "Layers".to_string(),
1✔
1551
                description: "All available Geo Engine layers".to_string(),
1✔
1552
                items: vec![
1✔
1553
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1554
                        id: ProviderLayerCollectionId {
1✔
1555
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1556
                            collection_id: collection1_id.clone(),
1✔
1557
                        },
1✔
1558
                        name: "Collection1".to_string(),
1✔
1559
                        description: "Collection 1".to_string(),
1✔
1560
                        properties: Default::default(),
1✔
1561
                    }),
1✔
1562
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1563
                        id: ProviderLayerCollectionId {
1✔
1564
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1565
                            collection_id: collection2_id.clone(),
1✔
1566
                        },
1✔
1567
                        name: "Collection2".to_string(),
1✔
1568
                        description: "Collection 2".to_string(),
1✔
1569
                        properties: Default::default(),
1✔
1570
                    }),
1✔
1571
                ],
1✔
1572
                entry_label: None,
1✔
1573
                properties: vec![],
1✔
1574
            }
1✔
1575
        );
1✔
1576

1577
        let collection1_all = layer_db
1✔
1578
            .search(
1✔
1579
                &collection1_id,
1✔
1580
                SearchParameters {
1✔
1581
                    search_type: SearchType::Fulltext,
1✔
1582
                    search_string: String::new(),
1✔
1583
                    limit: 10,
1✔
1584
                    offset: 0,
1✔
1585
                },
1✔
1586
            )
1✔
1587
            .await
5✔
1588
            .unwrap();
1✔
1589

1✔
1590
        assert_eq!(
1✔
1591
            collection1_all,
1✔
1592
            LayerCollection {
1✔
1593
                id: ProviderLayerCollectionId {
1✔
1594
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1595
                    collection_id: collection1_id.clone(),
1✔
1596
                },
1✔
1597
                name: "Collection1".to_string(),
1✔
1598
                description: "Collection 1".to_string(),
1✔
1599
                items: vec![
1✔
1600
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1601
                        id: ProviderLayerCollectionId {
1✔
1602
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1603
                            collection_id: collection2_id.clone(),
1✔
1604
                        },
1✔
1605
                        name: "Collection2".to_string(),
1✔
1606
                        description: "Collection 2".to_string(),
1✔
1607
                        properties: Default::default(),
1✔
1608
                    }),
1✔
1609
                    CollectionItem::Layer(LayerListing {
1✔
1610
                        id: ProviderLayerId {
1✔
1611
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1612
                            layer_id: layer2.clone(),
1✔
1613
                        },
1✔
1614
                        name: "Layer2".to_string(),
1✔
1615
                        description: "Layer 2".to_string(),
1✔
1616
                        properties: vec![],
1✔
1617
                    }),
1✔
1618
                ],
1✔
1619
                entry_label: None,
1✔
1620
                properties: vec![],
1✔
1621
            }
1✔
1622
        );
1✔
1623

1624
        let collection1_filtered_fulltext = layer_db
1✔
1625
            .search(
1✔
1626
                &collection1_id,
1✔
1627
                SearchParameters {
1✔
1628
                    search_type: SearchType::Fulltext,
1✔
1629
                    search_string: "ay".to_string(),
1✔
1630
                    limit: 10,
1✔
1631
                    offset: 0,
1✔
1632
                },
1✔
1633
            )
1✔
1634
            .await
5✔
1635
            .unwrap();
1✔
1636

1✔
1637
        assert_eq!(
1✔
1638
            collection1_filtered_fulltext,
1✔
1639
            LayerCollection {
1✔
1640
                id: ProviderLayerCollectionId {
1✔
1641
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1642
                    collection_id: collection1_id.clone(),
1✔
1643
                },
1✔
1644
                name: "Collection1".to_string(),
1✔
1645
                description: "Collection 1".to_string(),
1✔
1646
                items: vec![CollectionItem::Layer(LayerListing {
1✔
1647
                    id: ProviderLayerId {
1✔
1648
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1649
                        layer_id: layer2.clone(),
1✔
1650
                    },
1✔
1651
                    name: "Layer2".to_string(),
1✔
1652
                    description: "Layer 2".to_string(),
1✔
1653
                    properties: vec![],
1✔
1654
                }),],
1✔
1655
                entry_label: None,
1✔
1656
                properties: vec![],
1✔
1657
            }
1✔
1658
        );
1✔
1659

1660
        let collection1_filtered_prefix = layer_db
1✔
1661
            .search(
1✔
1662
                &collection1_id,
1✔
1663
                SearchParameters {
1✔
1664
                    search_type: SearchType::Prefix,
1✔
1665
                    search_string: "ay".to_string(),
1✔
1666
                    limit: 10,
1✔
1667
                    offset: 0,
1✔
1668
                },
1✔
1669
            )
1✔
1670
            .await
5✔
1671
            .unwrap();
1✔
1672

1✔
1673
        assert_eq!(
1✔
1674
            collection1_filtered_prefix,
1✔
1675
            LayerCollection {
1✔
1676
                id: ProviderLayerCollectionId {
1✔
1677
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1678
                    collection_id: collection1_id.clone(),
1✔
1679
                },
1✔
1680
                name: "Collection1".to_string(),
1✔
1681
                description: "Collection 1".to_string(),
1✔
1682
                items: vec![],
1✔
1683
                entry_label: None,
1✔
1684
                properties: vec![],
1✔
1685
            }
1✔
1686
        );
1✔
1687

1688
        let collection1_filtered_prefix2 = layer_db
1✔
1689
            .search(
1✔
1690
                &collection1_id,
1✔
1691
                SearchParameters {
1✔
1692
                    search_type: SearchType::Prefix,
1✔
1693
                    search_string: "Lay".to_string(),
1✔
1694
                    limit: 10,
1✔
1695
                    offset: 0,
1✔
1696
                },
1✔
1697
            )
1✔
1698
            .await
5✔
1699
            .unwrap();
1✔
1700

1✔
1701
        assert_eq!(
1✔
1702
            collection1_filtered_prefix2,
1✔
1703
            LayerCollection {
1✔
1704
                id: ProviderLayerCollectionId {
1✔
1705
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1706
                    collection_id: collection1_id.clone(),
1✔
1707
                },
1✔
1708
                name: "Collection1".to_string(),
1✔
1709
                description: "Collection 1".to_string(),
1✔
1710
                items: vec![CollectionItem::Layer(LayerListing {
1✔
1711
                    id: ProviderLayerId {
1✔
1712
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1713
                        layer_id: layer2.clone(),
1✔
1714
                    },
1✔
1715
                    name: "Layer2".to_string(),
1✔
1716
                    description: "Layer 2".to_string(),
1✔
1717
                    properties: vec![],
1✔
1718
                }),],
1✔
1719
                entry_label: None,
1✔
1720
                properties: vec![],
1✔
1721
            }
1✔
1722
        );
1✔
1723
    }
1✔
1724

1725
    #[allow(clippy::too_many_lines)]
1726
    #[ge_context::test]
2✔
1727
    async fn it_autocompletes_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1728
        let session = app_ctx.default_session().await.unwrap();
18✔
1729

1✔
1730
        let layer_db = app_ctx.session_context(session).db();
1✔
1731

1✔
1732
        let workflow = Workflow {
1✔
1733
            operator: TypedOperator::Vector(
1✔
1734
                MockPointSource {
1✔
1735
                    params: MockPointSourceParams {
1✔
1736
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1737
                    },
1✔
1738
                }
1✔
1739
                .boxed(),
1✔
1740
            ),
1✔
1741
        };
1✔
1742

1743
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1744

1745
        let _layer1 = layer_db
1✔
1746
            .add_layer(
1✔
1747
                AddLayer {
1✔
1748
                    name: "Layer1".to_string(),
1✔
1749
                    description: "Layer 1".to_string(),
1✔
1750
                    symbology: None,
1✔
1751
                    workflow: workflow.clone(),
1✔
1752
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1753
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1754
                },
1✔
1755
                &root_collection_id,
1✔
1756
            )
1✔
1757
            .await
45✔
1758
            .unwrap();
1✔
1759

1760
        let collection1_id = layer_db
1✔
1761
            .add_layer_collection(
1✔
1762
                AddLayerCollection {
1✔
1763
                    name: "Collection1".to_string(),
1✔
1764
                    description: "Collection 1".to_string(),
1✔
1765
                    properties: Default::default(),
1✔
1766
                },
1✔
1767
                &root_collection_id,
1✔
1768
            )
1✔
1769
            .await
7✔
1770
            .unwrap();
1✔
1771

1772
        let _layer2 = layer_db
1✔
1773
            .add_layer(
1✔
1774
                AddLayer {
1✔
1775
                    name: "Layer2".to_string(),
1✔
1776
                    description: "Layer 2".to_string(),
1✔
1777
                    symbology: None,
1✔
1778
                    workflow: workflow.clone(),
1✔
1779
                    metadata: Default::default(),
1✔
1780
                    properties: Default::default(),
1✔
1781
                },
1✔
1782
                &collection1_id,
1✔
1783
            )
1✔
1784
            .await
9✔
1785
            .unwrap();
1✔
1786

1787
        let _collection2_id = layer_db
1✔
1788
            .add_layer_collection(
1✔
1789
                AddLayerCollection {
1✔
1790
                    name: "Collection2".to_string(),
1✔
1791
                    description: "Collection 2".to_string(),
1✔
1792
                    properties: Default::default(),
1✔
1793
                },
1✔
1794
                &collection1_id,
1✔
1795
            )
1✔
1796
            .await
7✔
1797
            .unwrap();
1✔
1798

1799
        let root_collection_all = layer_db
1✔
1800
            .autocomplete_search(
1✔
1801
                &root_collection_id,
1✔
1802
                SearchParameters {
1✔
1803
                    search_type: SearchType::Fulltext,
1✔
1804
                    search_string: String::new(),
1✔
1805
                    limit: 10,
1✔
1806
                    offset: 0,
1✔
1807
                },
1✔
1808
            )
1✔
1809
            .await
3✔
1810
            .unwrap();
1✔
1811

1✔
1812
        assert_eq!(
1✔
1813
            root_collection_all,
1✔
1814
            vec![
1✔
1815
                "Collection1".to_string(),
1✔
1816
                "Collection2".to_string(),
1✔
1817
                "Layer1".to_string(),
1✔
1818
                "Layer2".to_string(),
1✔
1819
                "Unsorted".to_string(),
1✔
1820
            ]
1✔
1821
        );
1✔
1822

1823
        let root_collection_filtered = layer_db
1✔
1824
            .autocomplete_search(
1✔
1825
                &root_collection_id,
1✔
1826
                SearchParameters {
1✔
1827
                    search_type: SearchType::Fulltext,
1✔
1828
                    search_string: "lection".to_string(),
1✔
1829
                    limit: 10,
1✔
1830
                    offset: 0,
1✔
1831
                },
1✔
1832
            )
1✔
1833
            .await
3✔
1834
            .unwrap();
1✔
1835

1✔
1836
        assert_eq!(
1✔
1837
            root_collection_filtered,
1✔
1838
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
1839
        );
1✔
1840

1841
        let collection1_all = layer_db
1✔
1842
            .autocomplete_search(
1✔
1843
                &collection1_id,
1✔
1844
                SearchParameters {
1✔
1845
                    search_type: SearchType::Fulltext,
1✔
1846
                    search_string: String::new(),
1✔
1847
                    limit: 10,
1✔
1848
                    offset: 0,
1✔
1849
                },
1✔
1850
            )
1✔
1851
            .await
3✔
1852
            .unwrap();
1✔
1853

1✔
1854
        assert_eq!(
1✔
1855
            collection1_all,
1✔
1856
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
1857
        );
1✔
1858

1859
        let collection1_filtered_fulltext = layer_db
1✔
1860
            .autocomplete_search(
1✔
1861
                &collection1_id,
1✔
1862
                SearchParameters {
1✔
1863
                    search_type: SearchType::Fulltext,
1✔
1864
                    search_string: "ay".to_string(),
1✔
1865
                    limit: 10,
1✔
1866
                    offset: 0,
1✔
1867
                },
1✔
1868
            )
1✔
1869
            .await
3✔
1870
            .unwrap();
1✔
1871

1✔
1872
        assert_eq!(collection1_filtered_fulltext, vec!["Layer2".to_string(),]);
1✔
1873

1874
        let collection1_filtered_prefix = layer_db
1✔
1875
            .autocomplete_search(
1✔
1876
                &collection1_id,
1✔
1877
                SearchParameters {
1✔
1878
                    search_type: SearchType::Prefix,
1✔
1879
                    search_string: "ay".to_string(),
1✔
1880
                    limit: 10,
1✔
1881
                    offset: 0,
1✔
1882
                },
1✔
1883
            )
1✔
1884
            .await
3✔
1885
            .unwrap();
1✔
1886

1✔
1887
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
1888

1889
        let collection1_filtered_prefix2 = layer_db
1✔
1890
            .autocomplete_search(
1✔
1891
                &collection1_id,
1✔
1892
                SearchParameters {
1✔
1893
                    search_type: SearchType::Prefix,
1✔
1894
                    search_string: "Lay".to_string(),
1✔
1895
                    limit: 10,
1✔
1896
                    offset: 0,
1✔
1897
                },
1✔
1898
            )
1✔
1899
            .await
3✔
1900
            .unwrap();
1✔
1901

1✔
1902
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
1903
    }
1✔
1904

1905
    #[allow(clippy::too_many_lines)]
1906
    #[ge_context::test]
2✔
1907
    async fn it_reports_search_capabilities(app_ctx: PostgresContext<NoTls>) {
1✔
1908
        let session = app_ctx.default_session().await.unwrap();
18✔
1909

1✔
1910
        let layer_db = app_ctx.session_context(session).db();
1✔
1911

1✔
1912
        let capabilities = layer_db.capabilities().search;
1✔
1913

1914
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1915

1✔
1916
        if capabilities.search_types.fulltext {
1✔
1917
            assert!(layer_db
1✔
1918
                .search(
1✔
1919
                    &root_collection_id,
1✔
1920
                    SearchParameters {
1✔
1921
                        search_type: SearchType::Fulltext,
1✔
1922
                        search_string: String::new(),
1✔
1923
                        limit: 10,
1✔
1924
                        offset: 0,
1✔
1925
                    },
1✔
1926
                )
1✔
1927
                .await
8✔
1928
                .is_ok());
1✔
1929

1930
            if capabilities.autocomplete {
1✔
1931
                assert!(layer_db
1✔
1932
                    .autocomplete_search(
1✔
1933
                        &root_collection_id,
1✔
1934
                        SearchParameters {
1✔
1935
                            search_type: SearchType::Fulltext,
1✔
1936
                            search_string: String::new(),
1✔
1937
                            limit: 10,
1✔
1938
                            offset: 0,
1✔
1939
                        },
1✔
1940
                    )
1✔
1941
                    .await
3✔
1942
                    .is_ok());
1✔
1943
            } else {
1944
                assert!(layer_db
×
1945
                    .autocomplete_search(
×
1946
                        &root_collection_id,
×
1947
                        SearchParameters {
×
1948
                            search_type: SearchType::Fulltext,
×
1949
                            search_string: String::new(),
×
1950
                            limit: 10,
×
1951
                            offset: 0,
×
1952
                        },
×
1953
                    )
×
1954
                    .await
×
1955
                    .is_err());
×
1956
            }
1957
        }
×
1958
        if capabilities.search_types.prefix {
1✔
1959
            assert!(layer_db
1✔
1960
                .search(
1✔
1961
                    &root_collection_id,
1✔
1962
                    SearchParameters {
1✔
1963
                        search_type: SearchType::Prefix,
1✔
1964
                        search_string: String::new(),
1✔
1965
                        limit: 10,
1✔
1966
                        offset: 0,
1✔
1967
                    },
1✔
1968
                )
1✔
1969
                .await
5✔
1970
                .is_ok());
1✔
1971

1972
            if capabilities.autocomplete {
1✔
1973
                assert!(layer_db
1✔
1974
                    .autocomplete_search(
1✔
1975
                        &root_collection_id,
1✔
1976
                        SearchParameters {
1✔
1977
                            search_type: SearchType::Prefix,
1✔
1978
                            search_string: String::new(),
1✔
1979
                            limit: 10,
1✔
1980
                            offset: 0,
1✔
1981
                        },
1✔
1982
                    )
1✔
1983
                    .await
3✔
1984
                    .is_ok());
1✔
1985
            } else {
1986
                assert!(layer_db
×
1987
                    .autocomplete_search(
×
1988
                        &root_collection_id,
×
1989
                        SearchParameters {
×
1990
                            search_type: SearchType::Prefix,
×
1991
                            search_string: String::new(),
×
1992
                            limit: 10,
×
1993
                            offset: 0,
×
1994
                        },
×
1995
                    )
×
1996
                    .await
×
1997
                    .is_err());
×
1998
            }
1999
        }
×
2000
    }
1✔
2001

2002
    #[allow(clippy::too_many_lines)]
2003
    #[ge_context::test]
2✔
2004
    async fn it_removes_layer_collections(app_ctx: PostgresContext<NoTls>) {
1✔
2005
        let session = app_ctx.default_session().await.unwrap();
18✔
2006

1✔
2007
        let layer_db = app_ctx.session_context(session).db();
1✔
2008

1✔
2009
        let layer = AddLayer {
1✔
2010
            name: "layer".to_string(),
1✔
2011
            description: "description".to_string(),
1✔
2012
            workflow: Workflow {
1✔
2013
                operator: TypedOperator::Vector(
1✔
2014
                    MockPointSource {
1✔
2015
                        params: MockPointSourceParams {
1✔
2016
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2017
                        },
1✔
2018
                    }
1✔
2019
                    .boxed(),
1✔
2020
                ),
1✔
2021
            },
1✔
2022
            symbology: None,
1✔
2023
            metadata: Default::default(),
1✔
2024
            properties: Default::default(),
1✔
2025
        };
1✔
2026

2027
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2028

1✔
2029
        let collection = AddLayerCollection {
1✔
2030
            name: "top collection".to_string(),
1✔
2031
            description: "description".to_string(),
1✔
2032
            properties: Default::default(),
1✔
2033
        };
1✔
2034

2035
        let top_c_id = layer_db
1✔
2036
            .add_layer_collection(collection, root_collection)
1✔
2037
            .await
10✔
2038
            .unwrap();
1✔
2039

2040
        let l_id = layer_db.add_layer(layer, &top_c_id).await.unwrap();
42✔
2041

1✔
2042
        let collection = AddLayerCollection {
1✔
2043
            name: "empty collection".to_string(),
1✔
2044
            description: "description".to_string(),
1✔
2045
            properties: Default::default(),
1✔
2046
        };
1✔
2047

2048
        let empty_c_id = layer_db
1✔
2049
            .add_layer_collection(collection, &top_c_id)
1✔
2050
            .await
7✔
2051
            .unwrap();
1✔
2052

2053
        let items = layer_db
1✔
2054
            .load_layer_collection(
1✔
2055
                &top_c_id,
1✔
2056
                LayerCollectionListOptions {
1✔
2057
                    offset: 0,
1✔
2058
                    limit: 20,
1✔
2059
                },
1✔
2060
            )
1✔
2061
            .await
5✔
2062
            .unwrap();
1✔
2063

1✔
2064
        assert_eq!(
1✔
2065
            items,
1✔
2066
            LayerCollection {
1✔
2067
                id: ProviderLayerCollectionId {
1✔
2068
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2069
                    collection_id: top_c_id.clone(),
1✔
2070
                },
1✔
2071
                name: "top collection".to_string(),
1✔
2072
                description: "description".to_string(),
1✔
2073
                items: vec![
1✔
2074
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2075
                        id: ProviderLayerCollectionId {
1✔
2076
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2077
                            collection_id: empty_c_id.clone(),
1✔
2078
                        },
1✔
2079
                        name: "empty collection".to_string(),
1✔
2080
                        description: "description".to_string(),
1✔
2081
                        properties: Default::default(),
1✔
2082
                    }),
1✔
2083
                    CollectionItem::Layer(LayerListing {
1✔
2084
                        id: ProviderLayerId {
1✔
2085
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2086
                            layer_id: l_id.clone(),
1✔
2087
                        },
1✔
2088
                        name: "layer".to_string(),
1✔
2089
                        description: "description".to_string(),
1✔
2090
                        properties: vec![],
1✔
2091
                    })
1✔
2092
                ],
1✔
2093
                entry_label: None,
1✔
2094
                properties: vec![],
1✔
2095
            }
1✔
2096
        );
1✔
2097

2098
        // remove empty collection
2099
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
9✔
2100

2101
        let items = layer_db
1✔
2102
            .load_layer_collection(
1✔
2103
                &top_c_id,
1✔
2104
                LayerCollectionListOptions {
1✔
2105
                    offset: 0,
1✔
2106
                    limit: 20,
1✔
2107
                },
1✔
2108
            )
1✔
2109
            .await
5✔
2110
            .unwrap();
1✔
2111

1✔
2112
        assert_eq!(
1✔
2113
            items,
1✔
2114
            LayerCollection {
1✔
2115
                id: ProviderLayerCollectionId {
1✔
2116
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2117
                    collection_id: top_c_id.clone(),
1✔
2118
                },
1✔
2119
                name: "top collection".to_string(),
1✔
2120
                description: "description".to_string(),
1✔
2121
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2122
                    id: ProviderLayerId {
1✔
2123
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2124
                        layer_id: l_id.clone(),
1✔
2125
                    },
1✔
2126
                    name: "layer".to_string(),
1✔
2127
                    description: "description".to_string(),
1✔
2128
                    properties: vec![],
1✔
2129
                })],
1✔
2130
                entry_label: None,
1✔
2131
                properties: vec![],
1✔
2132
            }
1✔
2133
        );
1✔
2134

2135
        // remove top (not root) collection
2136
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
9✔
2137

1✔
2138
        layer_db
1✔
2139
            .load_layer_collection(
1✔
2140
                &top_c_id,
1✔
2141
                LayerCollectionListOptions {
1✔
2142
                    offset: 0,
1✔
2143
                    limit: 20,
1✔
2144
                },
1✔
2145
            )
1✔
2146
            .await
3✔
2147
            .unwrap_err();
1✔
2148

1✔
2149
        // should be deleted automatically
1✔
2150
        layer_db.load_layer(&l_id).await.unwrap_err();
3✔
2151

1✔
2152
        // it is not allowed to remove the root collection
1✔
2153
        layer_db
1✔
2154
            .remove_layer_collection(root_collection)
1✔
2155
            .await
1✔
2156
            .unwrap_err();
1✔
2157
        layer_db
1✔
2158
            .load_layer_collection(
1✔
2159
                root_collection,
1✔
2160
                LayerCollectionListOptions {
1✔
2161
                    offset: 0,
1✔
2162
                    limit: 20,
1✔
2163
                },
1✔
2164
            )
1✔
2165
            .await
5✔
2166
            .unwrap();
1✔
2167
    }
1✔
2168

2169
    #[ge_context::test]
2✔
2170
    #[allow(clippy::too_many_lines)]
2171
    async fn it_removes_collections_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
2172
        let session = app_ctx.default_session().await.unwrap();
18✔
2173

1✔
2174
        let db = app_ctx.session_context(session).db();
1✔
2175

2176
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
2177

2178
        let mid_collection_id = db
1✔
2179
            .add_layer_collection(
1✔
2180
                AddLayerCollection {
1✔
2181
                    name: "mid collection".to_string(),
1✔
2182
                    description: "description".to_string(),
1✔
2183
                    properties: Default::default(),
1✔
2184
                },
1✔
2185
                root_collection_id,
1✔
2186
            )
1✔
2187
            .await
10✔
2188
            .unwrap();
1✔
2189

2190
        let bottom_collection_id = db
1✔
2191
            .add_layer_collection(
1✔
2192
                AddLayerCollection {
1✔
2193
                    name: "bottom collection".to_string(),
1✔
2194
                    description: "description".to_string(),
1✔
2195
                    properties: Default::default(),
1✔
2196
                },
1✔
2197
                &mid_collection_id,
1✔
2198
            )
1✔
2199
            .await
7✔
2200
            .unwrap();
1✔
2201

2202
        let layer_id = db
1✔
2203
            .add_layer(
1✔
2204
                AddLayer {
1✔
2205
                    name: "layer".to_string(),
1✔
2206
                    description: "description".to_string(),
1✔
2207
                    workflow: Workflow {
1✔
2208
                        operator: TypedOperator::Vector(
1✔
2209
                            MockPointSource {
1✔
2210
                                params: MockPointSourceParams {
1✔
2211
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2212
                                },
1✔
2213
                            }
1✔
2214
                            .boxed(),
1✔
2215
                        ),
1✔
2216
                    },
1✔
2217
                    symbology: None,
1✔
2218
                    metadata: Default::default(),
1✔
2219
                    properties: Default::default(),
1✔
2220
                },
1✔
2221
                &mid_collection_id,
1✔
2222
            )
1✔
2223
            .await
42✔
2224
            .unwrap();
1✔
2225

1✔
2226
        // removing the mid collection…
1✔
2227
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
2228
            .await
11✔
2229
            .unwrap();
1✔
2230

1✔
2231
        // …should remove itself
1✔
2232
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
2233
            .await
3✔
2234
            .unwrap_err();
1✔
2235

1✔
2236
        // …should remove the bottom collection
1✔
2237
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
2238
            .await
3✔
2239
            .unwrap_err();
1✔
2240

1✔
2241
        // … and should remove the layer of the bottom collection
1✔
2242
        db.load_layer(&layer_id).await.unwrap_err();
3✔
2243

1✔
2244
        // the root collection is still there
1✔
2245
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
2246
            .await
5✔
2247
            .unwrap();
1✔
2248
    }
1✔
2249

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

1✔
2255
        let db = app_ctx.session_context(session).db();
1✔
2256

2257
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
2258

2259
        let another_collection = db
1✔
2260
            .add_layer_collection(
1✔
2261
                AddLayerCollection {
1✔
2262
                    name: "top collection".to_string(),
1✔
2263
                    description: "description".to_string(),
1✔
2264
                    properties: Default::default(),
1✔
2265
                },
1✔
2266
                root_collection,
1✔
2267
            )
1✔
2268
            .await
10✔
2269
            .unwrap();
1✔
2270

2271
        let layer_in_one_collection = db
1✔
2272
            .add_layer(
1✔
2273
                AddLayer {
1✔
2274
                    name: "layer 1".to_string(),
1✔
2275
                    description: "description".to_string(),
1✔
2276
                    workflow: Workflow {
1✔
2277
                        operator: TypedOperator::Vector(
1✔
2278
                            MockPointSource {
1✔
2279
                                params: MockPointSourceParams {
1✔
2280
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2281
                                },
1✔
2282
                            }
1✔
2283
                            .boxed(),
1✔
2284
                        ),
1✔
2285
                    },
1✔
2286
                    symbology: None,
1✔
2287
                    metadata: Default::default(),
1✔
2288
                    properties: Default::default(),
1✔
2289
                },
1✔
2290
                &another_collection,
1✔
2291
            )
1✔
2292
            .await
42✔
2293
            .unwrap();
1✔
2294

2295
        let layer_in_two_collections = db
1✔
2296
            .add_layer(
1✔
2297
                AddLayer {
1✔
2298
                    name: "layer 2".to_string(),
1✔
2299
                    description: "description".to_string(),
1✔
2300
                    workflow: Workflow {
1✔
2301
                        operator: TypedOperator::Vector(
1✔
2302
                            MockPointSource {
1✔
2303
                                params: MockPointSourceParams {
1✔
2304
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2305
                                },
1✔
2306
                            }
1✔
2307
                            .boxed(),
1✔
2308
                        ),
1✔
2309
                    },
1✔
2310
                    symbology: None,
1✔
2311
                    metadata: Default::default(),
1✔
2312
                    properties: Default::default(),
1✔
2313
                },
1✔
2314
                &another_collection,
1✔
2315
            )
1✔
2316
            .await
9✔
2317
            .unwrap();
1✔
2318

1✔
2319
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
2320
            .await
3✔
2321
            .unwrap();
1✔
2322

1✔
2323
        // remove first layer --> should be deleted entirely
1✔
2324

1✔
2325
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
2326
            .await
7✔
2327
            .unwrap();
1✔
2328

2329
        let number_of_layer_in_collection = db
1✔
2330
            .load_layer_collection(
1✔
2331
                &another_collection,
1✔
2332
                LayerCollectionListOptions {
1✔
2333
                    offset: 0,
1✔
2334
                    limit: 20,
1✔
2335
                },
1✔
2336
            )
1✔
2337
            .await
5✔
2338
            .unwrap()
1✔
2339
            .items
1✔
2340
            .len();
1✔
2341
        assert_eq!(
1✔
2342
            number_of_layer_in_collection,
1✔
2343
            1 /* only the other collection should be here */
1✔
2344
        );
1✔
2345

2346
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
3✔
2347

1✔
2348
        // remove second layer --> should only be gone in collection
1✔
2349

1✔
2350
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
2351
            .await
7✔
2352
            .unwrap();
1✔
2353

2354
        let number_of_layer_in_collection = db
1✔
2355
            .load_layer_collection(
1✔
2356
                &another_collection,
1✔
2357
                LayerCollectionListOptions {
1✔
2358
                    offset: 0,
1✔
2359
                    limit: 20,
1✔
2360
                },
1✔
2361
            )
1✔
2362
            .await
5✔
2363
            .unwrap()
1✔
2364
            .items
1✔
2365
            .len();
1✔
2366
        assert_eq!(
1✔
2367
            number_of_layer_in_collection,
1✔
2368
            0 /* both layers were deleted */
1✔
2369
        );
1✔
2370

2371
        db.load_layer(&layer_in_two_collections).await.unwrap();
3✔
2372
    }
1✔
2373

2374
    #[ge_context::test]
2✔
2375
    #[allow(clippy::too_many_lines)]
2376
    async fn it_deletes_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
2377
        let loading_info = OgrSourceDataset {
1✔
2378
            file_name: PathBuf::from("test.csv"),
1✔
2379
            layer_name: "test.csv".to_owned(),
1✔
2380
            data_type: Some(VectorDataType::MultiPoint),
1✔
2381
            time: OgrSourceDatasetTimeType::Start {
1✔
2382
                start_field: "start".to_owned(),
1✔
2383
                start_format: OgrSourceTimeFormat::Auto,
1✔
2384
                duration: OgrSourceDurationSpec::Zero,
1✔
2385
            },
1✔
2386
            default_geometry: None,
1✔
2387
            columns: Some(OgrSourceColumnSpec {
1✔
2388
                format_specifics: Some(FormatSpecifics::Csv {
1✔
2389
                    header: CsvHeader::Auto,
1✔
2390
                }),
1✔
2391
                x: "x".to_owned(),
1✔
2392
                y: None,
1✔
2393
                int: vec![],
1✔
2394
                float: vec![],
1✔
2395
                text: vec![],
1✔
2396
                bool: vec![],
1✔
2397
                datetime: vec![],
1✔
2398
                rename: None,
1✔
2399
            }),
1✔
2400
            force_ogr_time_filter: false,
1✔
2401
            force_ogr_spatial_filter: false,
1✔
2402
            on_error: OgrSourceErrorSpec::Ignore,
1✔
2403
            sql_query: None,
1✔
2404
            attribute_query: None,
1✔
2405
            cache_ttl: CacheTtlSeconds::default(),
1✔
2406
        };
1✔
2407

1✔
2408
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
2409
            OgrSourceDataset,
1✔
2410
            VectorResultDescriptor,
1✔
2411
            VectorQueryRectangle,
1✔
2412
        > {
1✔
2413
            loading_info: loading_info.clone(),
1✔
2414
            result_descriptor: VectorResultDescriptor {
1✔
2415
                data_type: VectorDataType::MultiPoint,
1✔
2416
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
2417
                columns: [(
1✔
2418
                    "foo".to_owned(),
1✔
2419
                    VectorColumnInfo {
1✔
2420
                        data_type: FeatureDataType::Float,
1✔
2421
                        measurement: Measurement::Unitless,
1✔
2422
                    },
1✔
2423
                )]
1✔
2424
                .into_iter()
1✔
2425
                .collect(),
1✔
2426
                time: None,
1✔
2427
                bbox: None,
1✔
2428
            },
1✔
2429
            phantom: Default::default(),
1✔
2430
        });
1✔
2431

2432
        let session = app_ctx.default_session().await.unwrap();
18✔
2433

1✔
2434
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
2435

1✔
2436
        let db = app_ctx.session_context(session.clone()).db();
1✔
2437
        let dataset_id = db
1✔
2438
            .add_dataset(
1✔
2439
                AddDataset {
1✔
2440
                    name: Some(dataset_name),
1✔
2441
                    display_name: "Ogr Test".to_owned(),
1✔
2442
                    description: "desc".to_owned(),
1✔
2443
                    source_operator: "OgrSource".to_owned(),
1✔
2444
                    symbology: None,
1✔
2445
                    provenance: Some(vec![Provenance {
1✔
2446
                        citation: "citation".to_owned(),
1✔
2447
                        license: "license".to_owned(),
1✔
2448
                        uri: "uri".to_owned(),
1✔
2449
                    }]),
1✔
2450
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
2451
                },
1✔
2452
                meta_data,
1✔
2453
            )
1✔
2454
            .await
161✔
2455
            .unwrap()
1✔
2456
            .id;
1✔
2457

1✔
2458
        assert!(db.load_dataset(&dataset_id).await.is_ok());
4✔
2459

2460
        db.delete_dataset(dataset_id).await.unwrap();
3✔
2461

1✔
2462
        assert!(db.load_dataset(&dataset_id).await.is_err());
3✔
2463
    }
1✔
2464

2465
    #[ge_context::test]
2✔
2466
    #[allow(clippy::too_many_lines)]
2467
    async fn it_deletes_admin_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
2468
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
2469

1✔
2470
        let loading_info = OgrSourceDataset {
1✔
2471
            file_name: PathBuf::from("test.csv"),
1✔
2472
            layer_name: "test.csv".to_owned(),
1✔
2473
            data_type: Some(VectorDataType::MultiPoint),
1✔
2474
            time: OgrSourceDatasetTimeType::Start {
1✔
2475
                start_field: "start".to_owned(),
1✔
2476
                start_format: OgrSourceTimeFormat::Auto,
1✔
2477
                duration: OgrSourceDurationSpec::Zero,
1✔
2478
            },
1✔
2479
            default_geometry: None,
1✔
2480
            columns: Some(OgrSourceColumnSpec {
1✔
2481
                format_specifics: Some(FormatSpecifics::Csv {
1✔
2482
                    header: CsvHeader::Auto,
1✔
2483
                }),
1✔
2484
                x: "x".to_owned(),
1✔
2485
                y: None,
1✔
2486
                int: vec![],
1✔
2487
                float: vec![],
1✔
2488
                text: vec![],
1✔
2489
                bool: vec![],
1✔
2490
                datetime: vec![],
1✔
2491
                rename: None,
1✔
2492
            }),
1✔
2493
            force_ogr_time_filter: false,
1✔
2494
            force_ogr_spatial_filter: false,
1✔
2495
            on_error: OgrSourceErrorSpec::Ignore,
1✔
2496
            sql_query: None,
1✔
2497
            attribute_query: None,
1✔
2498
            cache_ttl: CacheTtlSeconds::default(),
1✔
2499
        };
1✔
2500

1✔
2501
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
2502
            OgrSourceDataset,
1✔
2503
            VectorResultDescriptor,
1✔
2504
            VectorQueryRectangle,
1✔
2505
        > {
1✔
2506
            loading_info: loading_info.clone(),
1✔
2507
            result_descriptor: VectorResultDescriptor {
1✔
2508
                data_type: VectorDataType::MultiPoint,
1✔
2509
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
2510
                columns: [(
1✔
2511
                    "foo".to_owned(),
1✔
2512
                    VectorColumnInfo {
1✔
2513
                        data_type: FeatureDataType::Float,
1✔
2514
                        measurement: Measurement::Unitless,
1✔
2515
                    },
1✔
2516
                )]
1✔
2517
                .into_iter()
1✔
2518
                .collect(),
1✔
2519
                time: None,
1✔
2520
                bbox: None,
1✔
2521
            },
1✔
2522
            phantom: Default::default(),
1✔
2523
        });
1✔
2524

2525
        let session = app_ctx.default_session().await.unwrap();
18✔
2526

1✔
2527
        let db = app_ctx.session_context(session).db();
1✔
2528
        let dataset_id = db
1✔
2529
            .add_dataset(
1✔
2530
                AddDataset {
1✔
2531
                    name: Some(dataset_name),
1✔
2532
                    display_name: "Ogr Test".to_owned(),
1✔
2533
                    description: "desc".to_owned(),
1✔
2534
                    source_operator: "OgrSource".to_owned(),
1✔
2535
                    symbology: None,
1✔
2536
                    provenance: Some(vec![Provenance {
1✔
2537
                        citation: "citation".to_owned(),
1✔
2538
                        license: "license".to_owned(),
1✔
2539
                        uri: "uri".to_owned(),
1✔
2540
                    }]),
1✔
2541
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
2542
                },
1✔
2543
                meta_data,
1✔
2544
            )
1✔
2545
            .await
160✔
2546
            .unwrap()
1✔
2547
            .id;
1✔
2548

1✔
2549
        assert!(db.load_dataset(&dataset_id).await.is_ok());
3✔
2550

2551
        db.delete_dataset(dataset_id).await.unwrap();
3✔
2552

1✔
2553
        assert!(db.load_dataset(&dataset_id).await.is_err());
3✔
2554
    }
1✔
2555

2556
    #[ge_context::test]
2✔
2557
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: PostgresContext<NoTls>) {
1✔
2558
        let session = app_ctx.default_session().await.unwrap();
18✔
2559
        let db = app_ctx.session_context(session).db();
1✔
2560

2561
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
2562

2563
        let top_collection_id = db
1✔
2564
            .add_layer_collection(
1✔
2565
                AddLayerCollection {
1✔
2566
                    name: "top collection".to_string(),
1✔
2567
                    description: "description".to_string(),
1✔
2568
                    properties: Default::default(),
1✔
2569
                },
1✔
2570
                root_collection_id,
1✔
2571
            )
1✔
2572
            .await
10✔
2573
            .unwrap();
1✔
2574

1✔
2575
        let faux_layer = LayerId("faux".to_string());
1✔
2576

1✔
2577
        // this should fail
1✔
2578
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
2579
            .await
×
2580
            .unwrap_err();
1✔
2581

2582
        let root_collection_layers = db
1✔
2583
            .load_layer_collection(
1✔
2584
                &top_collection_id,
1✔
2585
                LayerCollectionListOptions {
1✔
2586
                    offset: 0,
1✔
2587
                    limit: 20,
1✔
2588
                },
1✔
2589
            )
1✔
2590
            .await
5✔
2591
            .unwrap();
1✔
2592

1✔
2593
        assert_eq!(
1✔
2594
            root_collection_layers,
1✔
2595
            LayerCollection {
1✔
2596
                id: ProviderLayerCollectionId {
1✔
2597
                    provider_id: DataProviderId(
1✔
2598
                        "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
2599
                    ),
1✔
2600
                    collection_id: top_collection_id.clone(),
1✔
2601
                },
1✔
2602
                name: "top collection".to_string(),
1✔
2603
                description: "description".to_string(),
1✔
2604
                items: vec![],
1✔
2605
                entry_label: None,
1✔
2606
                properties: vec![],
1✔
2607
            }
1✔
2608
        );
1✔
2609
    }
1✔
2610

2611
    #[allow(clippy::too_many_lines)]
2612
    #[ge_context::test]
2✔
2613
    async fn it_updates_project_layer_symbology(app_ctx: PostgresContext<NoTls>) {
1✔
2614
        let session = app_ctx.default_session().await.unwrap();
18✔
2615

2616
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
174✔
2617

2618
        let db = app_ctx.session_context(session.clone()).db();
1✔
2619

1✔
2620
        let create_project: CreateProject = serde_json::from_value(json!({
1✔
2621
            "name": "Default",
1✔
2622
            "description": "Default project",
1✔
2623
            "bounds": {
1✔
2624
                "boundingBox": {
1✔
2625
                    "lowerLeftCoordinate": {
1✔
2626
                        "x": -180,
1✔
2627
                        "y": -90
1✔
2628
                    },
1✔
2629
                    "upperRightCoordinate": {
1✔
2630
                        "x": 180,
1✔
2631
                        "y": 90
1✔
2632
                    }
1✔
2633
                },
1✔
2634
                "spatialReference": "EPSG:4326",
1✔
2635
                "timeInterval": {
1✔
2636
                    "start": 1_396_353_600_000i64,
1✔
2637
                    "end": 1_396_353_600_000i64
1✔
2638
                }
1✔
2639
            },
1✔
2640
            "timeStep": {
1✔
2641
                "step": 1,
1✔
2642
                "granularity": "months"
1✔
2643
            }
1✔
2644
        }))
1✔
2645
        .unwrap();
1✔
2646

2647
        let project_id = db.create_project(create_project).await.unwrap();
7✔
2648

1✔
2649
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2650
            "id": project_id.to_string(),
1✔
2651
            "layers": [{
1✔
2652
                "name": "NDVI",
1✔
2653
                "workflow": workflow_id.to_string(),
1✔
2654
                "visibility": {
1✔
2655
                    "data": true,
1✔
2656
                    "legend": false
1✔
2657
                },
1✔
2658
                "symbology": {
1✔
2659
                    "type": "raster",
1✔
2660
                    "opacity": 1,
1✔
2661
                    "rasterColorizer": {
1✔
2662
                        "type": "singleBand",
1✔
2663
                        "band": 0,
1✔
2664
                        "bandColorizer": {
1✔
2665
                            "type": "linearGradient",
1✔
2666
                            "breakpoints": [{
1✔
2667
                                "value": 1,
1✔
2668
                                "color": [0, 0, 0, 255]
1✔
2669
                            }, {
1✔
2670
                                "value": 255,
1✔
2671
                                "color": [255, 255, 255, 255]
1✔
2672
                            }],
1✔
2673
                            "noDataColor": [0, 0, 0, 0],
1✔
2674
                            "overColor": [255, 255, 255, 127],
1✔
2675
                            "underColor": [255, 255, 255, 127]
1✔
2676
                        }
1✔
2677
                    }
1✔
2678
                }
1✔
2679
            }]
1✔
2680
        }))
1✔
2681
        .unwrap();
1✔
2682

1✔
2683
        db.update_project(update).await.unwrap();
70✔
2684

1✔
2685
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2686
            "id": project_id.to_string(),
1✔
2687
            "layers": [{
1✔
2688
                "name": "NDVI",
1✔
2689
                "workflow": workflow_id.to_string(),
1✔
2690
                "visibility": {
1✔
2691
                    "data": true,
1✔
2692
                    "legend": false
1✔
2693
                },
1✔
2694
                "symbology": {
1✔
2695
                    "type": "raster",
1✔
2696
                    "opacity": 1,
1✔
2697
                    "rasterColorizer": {
1✔
2698
                        "type": "singleBand",
1✔
2699
                        "band": 0,
1✔
2700
                        "bandColorizer": {
1✔
2701
                            "type": "linearGradient",
1✔
2702
                            "breakpoints": [{
1✔
2703
                                "value": 1,
1✔
2704
                                "color": [0, 0, 4, 255]
1✔
2705
                            }, {
1✔
2706
                                "value": 17.866_666_666_666_667,
1✔
2707
                                "color": [11, 9, 36, 255]
1✔
2708
                            }, {
1✔
2709
                                "value": 34.733_333_333_333_334,
1✔
2710
                                "color": [32, 17, 75, 255]
1✔
2711
                            }, {
1✔
2712
                                "value": 51.6,
1✔
2713
                                "color": [59, 15, 112, 255]
1✔
2714
                            }, {
1✔
2715
                                "value": 68.466_666_666_666_67,
1✔
2716
                                "color": [87, 21, 126, 255]
1✔
2717
                            }, {
1✔
2718
                                "value": 85.333_333_333_333_33,
1✔
2719
                                "color": [114, 31, 129, 255]
1✔
2720
                            }, {
1✔
2721
                                "value": 102.199_999_999_999_99,
1✔
2722
                                "color": [140, 41, 129, 255]
1✔
2723
                            }, {
1✔
2724
                                "value": 119.066_666_666_666_65,
1✔
2725
                                "color": [168, 50, 125, 255]
1✔
2726
                            }, {
1✔
2727
                                "value": 135.933_333_333_333_34,
1✔
2728
                                "color": [196, 60, 117, 255]
1✔
2729
                            }, {
1✔
2730
                                "value": 152.799_999_999_999_98,
1✔
2731
                                "color": [222, 73, 104, 255]
1✔
2732
                            }, {
1✔
2733
                                "value": 169.666_666_666_666_66,
1✔
2734
                                "color": [241, 96, 93, 255]
1✔
2735
                            }, {
1✔
2736
                                "value": 186.533_333_333_333_33,
1✔
2737
                                "color": [250, 127, 94, 255]
1✔
2738
                            }, {
1✔
2739
                                "value": 203.399_999_999_999_98,
1✔
2740
                                "color": [254, 159, 109, 255]
1✔
2741
                            }, {
1✔
2742
                                "value": 220.266_666_666_666_65,
1✔
2743
                                "color": [254, 191, 132, 255]
1✔
2744
                            }, {
1✔
2745
                                "value": 237.133_333_333_333_3,
1✔
2746
                                "color": [253, 222, 160, 255]
1✔
2747
                            }, {
1✔
2748
                                "value": 254,
1✔
2749
                                "color": [252, 253, 191, 255]
1✔
2750
                            }],
1✔
2751
                            "noDataColor": [0, 0, 0, 0],
1✔
2752
                            "overColor": [255, 255, 255, 127],
1✔
2753
                            "underColor": [255, 255, 255, 127]
1✔
2754
                        }
1✔
2755
                    }
1✔
2756
                }
1✔
2757
            }]
1✔
2758
        }))
1✔
2759
        .unwrap();
1✔
2760

1✔
2761
        db.update_project(update).await.unwrap();
16✔
2762

1✔
2763
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2764
            "id": project_id.to_string(),
1✔
2765
            "layers": [{
1✔
2766
                "name": "NDVI",
1✔
2767
                "workflow": workflow_id.to_string(),
1✔
2768
                "visibility": {
1✔
2769
                    "data": true,
1✔
2770
                    "legend": false
1✔
2771
                },
1✔
2772
                "symbology": {
1✔
2773
                    "type": "raster",
1✔
2774
                    "opacity": 1,
1✔
2775
                    "rasterColorizer": {
1✔
2776
                        "type": "singleBand",
1✔
2777
                        "band": 0,
1✔
2778
                        "bandColorizer": {
1✔
2779
                            "type": "linearGradient",
1✔
2780
                            "breakpoints": [{
1✔
2781
                                "value": 1,
1✔
2782
                                "color": [0, 0, 4, 255]
1✔
2783
                            }, {
1✔
2784
                                "value": 17.866_666_666_666_667,
1✔
2785
                                "color": [11, 9, 36, 255]
1✔
2786
                            }, {
1✔
2787
                                "value": 34.733_333_333_333_334,
1✔
2788
                                "color": [32, 17, 75, 255]
1✔
2789
                            }, {
1✔
2790
                                "value": 51.6,
1✔
2791
                                "color": [59, 15, 112, 255]
1✔
2792
                            }, {
1✔
2793
                                "value": 68.466_666_666_666_67,
1✔
2794
                                "color": [87, 21, 126, 255]
1✔
2795
                            }, {
1✔
2796
                                "value": 85.333_333_333_333_33,
1✔
2797
                                "color": [114, 31, 129, 255]
1✔
2798
                            }, {
1✔
2799
                                "value": 102.199_999_999_999_99,
1✔
2800
                                "color": [140, 41, 129, 255]
1✔
2801
                            }, {
1✔
2802
                                "value": 119.066_666_666_666_65,
1✔
2803
                                "color": [168, 50, 125, 255]
1✔
2804
                            }, {
1✔
2805
                                "value": 135.933_333_333_333_34,
1✔
2806
                                "color": [196, 60, 117, 255]
1✔
2807
                            }, {
1✔
2808
                                "value": 152.799_999_999_999_98,
1✔
2809
                                "color": [222, 73, 104, 255]
1✔
2810
                            }, {
1✔
2811
                                "value": 169.666_666_666_666_66,
1✔
2812
                                "color": [241, 96, 93, 255]
1✔
2813
                            }, {
1✔
2814
                                "value": 186.533_333_333_333_33,
1✔
2815
                                "color": [250, 127, 94, 255]
1✔
2816
                            }, {
1✔
2817
                                "value": 203.399_999_999_999_98,
1✔
2818
                                "color": [254, 159, 109, 255]
1✔
2819
                            }, {
1✔
2820
                                "value": 220.266_666_666_666_65,
1✔
2821
                                "color": [254, 191, 132, 255]
1✔
2822
                            }, {
1✔
2823
                                "value": 237.133_333_333_333_3,
1✔
2824
                                "color": [253, 222, 160, 255]
1✔
2825
                            }, {
1✔
2826
                                "value": 254,
1✔
2827
                                "color": [252, 253, 191, 255]
1✔
2828
                            }],
1✔
2829
                            "noDataColor": [0, 0, 0, 0],
1✔
2830
                            "overColor": [255, 255, 255, 127],
1✔
2831
                            "underColor": [255, 255, 255, 127]
1✔
2832
                        }
1✔
2833
                    }
1✔
2834
                }
1✔
2835
            }]
1✔
2836
        }))
1✔
2837
        .unwrap();
1✔
2838

1✔
2839
        db.update_project(update).await.unwrap();
17✔
2840

1✔
2841
        let update: UpdateProject = serde_json::from_value(json!({
1✔
2842
            "id": project_id.to_string(),
1✔
2843
            "layers": [{
1✔
2844
                "name": "NDVI",
1✔
2845
                "workflow": workflow_id.to_string(),
1✔
2846
                "visibility": {
1✔
2847
                    "data": true,
1✔
2848
                    "legend": false
1✔
2849
                },
1✔
2850
                "symbology": {
1✔
2851
                    "type": "raster",
1✔
2852
                    "opacity": 1,
1✔
2853
                    "rasterColorizer": {
1✔
2854
                        "type": "singleBand",
1✔
2855
                        "band": 0,
1✔
2856
                        "bandColorizer": {
1✔
2857
                            "type": "linearGradient",
1✔
2858
                            "breakpoints": [{
1✔
2859
                                "value": 1,
1✔
2860
                                "color": [0, 0, 4, 255]
1✔
2861
                            }, {
1✔
2862
                                "value": 17.933_333_333_333_334,
1✔
2863
                                "color": [11, 9, 36, 255]
1✔
2864
                            }, {
1✔
2865
                                "value": 34.866_666_666_666_67,
1✔
2866
                                "color": [32, 17, 75, 255]
1✔
2867
                            }, {
1✔
2868
                                "value": 51.800_000_000_000_004,
1✔
2869
                                "color": [59, 15, 112, 255]
1✔
2870
                            }, {
1✔
2871
                                "value": 68.733_333_333_333_33,
1✔
2872
                                "color": [87, 21, 126, 255]
1✔
2873
                            }, {
1✔
2874
                                "value": 85.666_666_666_666_66,
1✔
2875
                                "color": [114, 31, 129, 255]
1✔
2876
                            }, {
1✔
2877
                                "value": 102.6,
1✔
2878
                                "color": [140, 41, 129, 255]
1✔
2879
                            }, {
1✔
2880
                                "value": 119.533_333_333_333_32,
1✔
2881
                                "color": [168, 50, 125, 255]
1✔
2882
                            }, {
1✔
2883
                                "value": 136.466_666_666_666_67,
1✔
2884
                                "color": [196, 60, 117, 255]
1✔
2885
                            }, {
1✔
2886
                                "value": 153.4,
1✔
2887
                                "color": [222, 73, 104, 255]
1✔
2888
                            }, {
1✔
2889
                                "value": 170.333_333_333_333_31,
1✔
2890
                                "color": [241, 96, 93, 255]
1✔
2891
                            }, {
1✔
2892
                                "value": 187.266_666_666_666_65,
1✔
2893
                                "color": [250, 127, 94, 255]
1✔
2894
                            }, {
1✔
2895
                                "value": 204.2,
1✔
2896
                                "color": [254, 159, 109, 255]
1✔
2897
                            }, {
1✔
2898
                                "value": 221.133_333_333_333_33,
1✔
2899
                                "color": [254, 191, 132, 255]
1✔
2900
                            }, {
1✔
2901
                                "value": 238.066_666_666_666_63,
1✔
2902
                                "color": [253, 222, 160, 255]
1✔
2903
                            }, {
1✔
2904
                                "value": 255,
1✔
2905
                                "color": [252, 253, 191, 255]
1✔
2906
                            }],
1✔
2907
                            "noDataColor": [0, 0, 0, 0],
1✔
2908
                            "overColor": [255, 255, 255, 127],
1✔
2909
                            "underColor": [255, 255, 255, 127]
1✔
2910
                        }
1✔
2911
                    }
1✔
2912
                }
1✔
2913
            }]
1✔
2914
        }))
1✔
2915
        .unwrap();
1✔
2916

2917
        // run two updates concurrently
2918
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
2919

2920
        assert!(r0.is_ok());
1✔
2921
        assert!(r1.is_ok());
1✔
2922
    }
1✔
2923

2924
    #[ge_context::test]
2✔
2925
    #[allow(clippy::too_many_lines)]
2926
    async fn it_resolves_dataset_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
2927
        let session = app_ctx.default_session().await.unwrap();
18✔
2928
        let db = app_ctx.session_context(session.clone()).db();
1✔
2929

1✔
2930
        let loading_info = OgrSourceDataset {
1✔
2931
            file_name: PathBuf::from("test.csv"),
1✔
2932
            layer_name: "test.csv".to_owned(),
1✔
2933
            data_type: Some(VectorDataType::MultiPoint),
1✔
2934
            time: OgrSourceDatasetTimeType::Start {
1✔
2935
                start_field: "start".to_owned(),
1✔
2936
                start_format: OgrSourceTimeFormat::Auto,
1✔
2937
                duration: OgrSourceDurationSpec::Zero,
1✔
2938
            },
1✔
2939
            default_geometry: None,
1✔
2940
            columns: Some(OgrSourceColumnSpec {
1✔
2941
                format_specifics: Some(FormatSpecifics::Csv {
1✔
2942
                    header: CsvHeader::Auto,
1✔
2943
                }),
1✔
2944
                x: "x".to_owned(),
1✔
2945
                y: None,
1✔
2946
                int: vec![],
1✔
2947
                float: vec![],
1✔
2948
                text: vec![],
1✔
2949
                bool: vec![],
1✔
2950
                datetime: vec![],
1✔
2951
                rename: None,
1✔
2952
            }),
1✔
2953
            force_ogr_time_filter: false,
1✔
2954
            force_ogr_spatial_filter: false,
1✔
2955
            on_error: OgrSourceErrorSpec::Ignore,
1✔
2956
            sql_query: None,
1✔
2957
            attribute_query: None,
1✔
2958
            cache_ttl: CacheTtlSeconds::default(),
1✔
2959
        };
1✔
2960

1✔
2961
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
2962
            OgrSourceDataset,
1✔
2963
            VectorResultDescriptor,
1✔
2964
            VectorQueryRectangle,
1✔
2965
        > {
1✔
2966
            loading_info: loading_info.clone(),
1✔
2967
            result_descriptor: VectorResultDescriptor {
1✔
2968
                data_type: VectorDataType::MultiPoint,
1✔
2969
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
2970
                columns: [(
1✔
2971
                    "foo".to_owned(),
1✔
2972
                    VectorColumnInfo {
1✔
2973
                        data_type: FeatureDataType::Float,
1✔
2974
                        measurement: Measurement::Unitless,
1✔
2975
                    },
1✔
2976
                )]
1✔
2977
                .into_iter()
1✔
2978
                .collect(),
1✔
2979
                time: None,
1✔
2980
                bbox: None,
1✔
2981
            },
1✔
2982
            phantom: Default::default(),
1✔
2983
        });
1✔
2984

2985
        let DatasetIdAndName {
2986
            id: dataset_id1,
1✔
2987
            name: dataset_name1,
1✔
2988
        } = db
1✔
2989
            .add_dataset(
1✔
2990
                AddDataset {
1✔
2991
                    name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
2992
                    display_name: "Ogr Test".to_owned(),
1✔
2993
                    description: "desc".to_owned(),
1✔
2994
                    source_operator: "OgrSource".to_owned(),
1✔
2995
                    symbology: None,
1✔
2996
                    provenance: Some(vec![Provenance {
1✔
2997
                        citation: "citation".to_owned(),
1✔
2998
                        license: "license".to_owned(),
1✔
2999
                        uri: "uri".to_owned(),
1✔
3000
                    }]),
1✔
3001
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3002
                },
1✔
3003
                meta_data.clone(),
1✔
3004
            )
1✔
3005
            .await
161✔
3006
            .unwrap();
1✔
3007

3008
        assert_eq!(
1✔
3009
            db.resolve_dataset_name_to_id(&dataset_name1)
1✔
3010
                .await
3✔
3011
                .unwrap()
1✔
3012
                .unwrap(),
1✔
3013
            dataset_id1
3014
        );
3015
    }
1✔
3016

3017
    #[ge_context::test]
2✔
3018
    #[allow(clippy::too_many_lines)]
3019
    async fn test_postgres_type_serialization(app_ctx: PostgresContext<NoTls>) {
1✔
3020
        let pool = app_ctx.pool.get().await.unwrap();
1✔
3021

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

3024
        assert_sql_type(
1✔
3025
            &pool,
1✔
3026
            "double precision",
1✔
3027
            [NotNanF64::from(NotNan::<f64>::new(1.0).unwrap())],
1✔
3028
        )
1✔
3029
        .await;
2✔
3030

3031
        assert_sql_type(
1✔
3032
            &pool,
1✔
3033
            "Breakpoint",
1✔
3034
            [Breakpoint {
1✔
3035
                value: NotNan::<f64>::new(1.0).unwrap(),
1✔
3036
                color: RgbaColor::new(0, 0, 0, 0),
1✔
3037
            }],
1✔
3038
        )
1✔
3039
        .await;
5✔
3040

3041
        assert_sql_type(
1✔
3042
            &pool,
1✔
3043
            "Colorizer",
1✔
3044
            [
1✔
3045
                Colorizer::LinearGradient {
1✔
3046
                    breakpoints: vec![
1✔
3047
                        Breakpoint {
1✔
3048
                            value: NotNan::<f64>::new(-10.0).unwrap(),
1✔
3049
                            color: RgbaColor::new(0, 0, 0, 0),
1✔
3050
                        },
1✔
3051
                        Breakpoint {
1✔
3052
                            value: NotNan::<f64>::new(2.0).unwrap(),
1✔
3053
                            color: RgbaColor::new(255, 0, 0, 255),
1✔
3054
                        },
1✔
3055
                    ],
1✔
3056
                    no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
3057
                    over_color: RgbaColor::new(1, 2, 3, 4),
1✔
3058
                    under_color: RgbaColor::new(5, 6, 7, 8),
1✔
3059
                },
1✔
3060
                Colorizer::LogarithmicGradient {
1✔
3061
                    breakpoints: vec![
1✔
3062
                        Breakpoint {
1✔
3063
                            value: NotNan::<f64>::new(1.0).unwrap(),
1✔
3064
                            color: RgbaColor::new(0, 0, 0, 0),
1✔
3065
                        },
1✔
3066
                        Breakpoint {
1✔
3067
                            value: NotNan::<f64>::new(2.0).unwrap(),
1✔
3068
                            color: RgbaColor::new(255, 0, 0, 255),
1✔
3069
                        },
1✔
3070
                    ],
1✔
3071
                    no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
3072
                    over_color: RgbaColor::new(1, 2, 3, 4),
1✔
3073
                    under_color: RgbaColor::new(5, 6, 7, 8),
1✔
3074
                },
1✔
3075
                Colorizer::palette(
1✔
3076
                    [
1✔
3077
                        (NotNan::<f64>::new(1.0).unwrap(), RgbaColor::new(0, 0, 0, 0)),
1✔
3078
                        (
1✔
3079
                            NotNan::<f64>::new(2.0).unwrap(),
1✔
3080
                            RgbaColor::new(255, 0, 0, 255),
1✔
3081
                        ),
1✔
3082
                        (
1✔
3083
                            NotNan::<f64>::new(3.0).unwrap(),
1✔
3084
                            RgbaColor::new(0, 10, 20, 30),
1✔
3085
                        ),
1✔
3086
                    ]
1✔
3087
                    .into(),
1✔
3088
                    RgbaColor::new(1, 2, 3, 4),
1✔
3089
                    RgbaColor::new(5, 6, 7, 8),
1✔
3090
                )
1✔
3091
                .unwrap(),
1✔
3092
            ],
1✔
3093
        )
1✔
3094
        .await;
12✔
3095

3096
        assert_sql_type(
1✔
3097
            &pool,
1✔
3098
            "ColorParam",
1✔
3099
            [
1✔
3100
                ColorParam::Static {
1✔
3101
                    color: RgbaColor::new(0, 10, 20, 30),
1✔
3102
                },
1✔
3103
                ColorParam::Derived(DerivedColor {
1✔
3104
                    attribute: "foobar".to_string(),
1✔
3105
                    colorizer: Colorizer::test_default(),
1✔
3106
                }),
1✔
3107
            ],
1✔
3108
        )
1✔
3109
        .await;
6✔
3110

3111
        assert_sql_type(
1✔
3112
            &pool,
1✔
3113
            "NumberParam",
1✔
3114
            [
1✔
3115
                NumberParam::Static { value: 42 },
1✔
3116
                NumberParam::Derived(DerivedNumber {
1✔
3117
                    attribute: "foobar".to_string(),
1✔
3118
                    factor: 1.0,
1✔
3119
                    default_value: 42.,
1✔
3120
                }),
1✔
3121
            ],
1✔
3122
        )
1✔
3123
        .await;
6✔
3124

3125
        assert_sql_type(
1✔
3126
            &pool,
1✔
3127
            "StrokeParam",
1✔
3128
            [StrokeParam {
1✔
3129
                width: NumberParam::Static { value: 42 },
1✔
3130
                color: ColorParam::Static {
1✔
3131
                    color: RgbaColor::new(0, 10, 20, 30),
1✔
3132
                },
1✔
3133
            }],
1✔
3134
        )
1✔
3135
        .await;
4✔
3136

3137
        assert_sql_type(
1✔
3138
            &pool,
1✔
3139
            "TextSymbology",
1✔
3140
            [TextSymbology {
1✔
3141
                attribute: "attribute".to_string(),
1✔
3142
                fill_color: ColorParam::Static {
1✔
3143
                    color: RgbaColor::new(0, 10, 20, 30),
1✔
3144
                },
1✔
3145
                stroke: StrokeParam {
1✔
3146
                    width: NumberParam::Static { value: 42 },
1✔
3147
                    color: ColorParam::Static {
1✔
3148
                        color: RgbaColor::new(0, 10, 20, 30),
1✔
3149
                    },
1✔
3150
                },
1✔
3151
            }],
1✔
3152
        )
1✔
3153
        .await;
4✔
3154

3155
        assert_sql_type(
1✔
3156
            &pool,
1✔
3157
            "RasterColorizer",
1✔
3158
            [RasterColorizer::SingleBand {
1✔
3159
                band: 0,
1✔
3160
                band_colorizer: Colorizer::LinearGradient {
1✔
3161
                    breakpoints: vec![
1✔
3162
                        Breakpoint {
1✔
3163
                            value: NotNan::<f64>::new(-10.0).unwrap(),
1✔
3164
                            color: RgbaColor::new(0, 0, 0, 0),
1✔
3165
                        },
1✔
3166
                        Breakpoint {
1✔
3167
                            value: NotNan::<f64>::new(2.0).unwrap(),
1✔
3168
                            color: RgbaColor::new(255, 0, 0, 255),
1✔
3169
                        },
1✔
3170
                    ],
1✔
3171
                    no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
3172
                    over_color: RgbaColor::new(1, 2, 3, 4),
1✔
3173
                    under_color: RgbaColor::new(5, 6, 7, 8),
1✔
3174
                },
1✔
3175
            }],
1✔
3176
        )
1✔
3177
        .await;
6✔
3178

3179
        assert_sql_type(
1✔
3180
            &pool,
1✔
3181
            "RasterColorizer",
1✔
3182
            [RasterColorizer::MultiBand {
1✔
3183
                red_band: 0,
1✔
3184
                green_band: 1,
1✔
3185
                blue_band: 2,
1✔
3186
                rgb_params: RgbParams {
1✔
3187
                    red_min: 0.,
1✔
3188
                    red_max: 255.,
1✔
3189
                    red_scale: 1.,
1✔
3190
                    green_min: 0.,
1✔
3191
                    green_max: 255.,
1✔
3192
                    green_scale: 1.,
1✔
3193
                    blue_min: 0.,
1✔
3194
                    blue_max: 255.,
1✔
3195
                    blue_scale: 1.,
1✔
3196
                    no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
3197
                },
1✔
3198
            }],
1✔
3199
        )
1✔
3200
        .await;
2✔
3201

3202
        assert_sql_type(
1✔
3203
            &pool,
1✔
3204
            "Symbology",
1✔
3205
            [
1✔
3206
                Symbology::Point(PointSymbology {
1✔
3207
                    fill_color: ColorParam::Static {
1✔
3208
                        color: RgbaColor::new(0, 10, 20, 30),
1✔
3209
                    },
1✔
3210
                    stroke: StrokeParam {
1✔
3211
                        width: NumberParam::Static { value: 42 },
1✔
3212
                        color: ColorParam::Static {
1✔
3213
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
3214
                        },
1✔
3215
                    },
1✔
3216
                    radius: NumberParam::Static { value: 42 },
1✔
3217
                    text: Some(TextSymbology {
1✔
3218
                        attribute: "attribute".to_string(),
1✔
3219
                        fill_color: ColorParam::Static {
1✔
3220
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
3221
                        },
1✔
3222
                        stroke: StrokeParam {
1✔
3223
                            width: NumberParam::Static { value: 42 },
1✔
3224
                            color: ColorParam::Static {
1✔
3225
                                color: RgbaColor::new(0, 10, 20, 30),
1✔
3226
                            },
1✔
3227
                        },
1✔
3228
                    }),
1✔
3229
                }),
1✔
3230
                Symbology::Line(LineSymbology {
1✔
3231
                    stroke: StrokeParam {
1✔
3232
                        width: NumberParam::Static { value: 42 },
1✔
3233
                        color: ColorParam::Static {
1✔
3234
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
3235
                        },
1✔
3236
                    },
1✔
3237
                    text: Some(TextSymbology {
1✔
3238
                        attribute: "attribute".to_string(),
1✔
3239
                        fill_color: ColorParam::Static {
1✔
3240
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
3241
                        },
1✔
3242
                        stroke: StrokeParam {
1✔
3243
                            width: NumberParam::Static { value: 42 },
1✔
3244
                            color: ColorParam::Static {
1✔
3245
                                color: RgbaColor::new(0, 10, 20, 30),
1✔
3246
                            },
1✔
3247
                        },
1✔
3248
                    }),
1✔
3249
                    auto_simplified: true,
1✔
3250
                }),
1✔
3251
                Symbology::Polygon(PolygonSymbology {
1✔
3252
                    fill_color: ColorParam::Static {
1✔
3253
                        color: RgbaColor::new(0, 10, 20, 30),
1✔
3254
                    },
1✔
3255
                    stroke: StrokeParam {
1✔
3256
                        width: NumberParam::Static { value: 42 },
1✔
3257
                        color: ColorParam::Static {
1✔
3258
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
3259
                        },
1✔
3260
                    },
1✔
3261
                    text: Some(TextSymbology {
1✔
3262
                        attribute: "attribute".to_string(),
1✔
3263
                        fill_color: ColorParam::Static {
1✔
3264
                            color: RgbaColor::new(0, 10, 20, 30),
1✔
3265
                        },
1✔
3266
                        stroke: StrokeParam {
1✔
3267
                            width: NumberParam::Static { value: 42 },
1✔
3268
                            color: ColorParam::Static {
1✔
3269
                                color: RgbaColor::new(0, 10, 20, 30),
1✔
3270
                            },
1✔
3271
                        },
1✔
3272
                    }),
1✔
3273
                    auto_simplified: true,
1✔
3274
                }),
1✔
3275
                Symbology::Raster(RasterSymbology {
1✔
3276
                    opacity: 1.0,
1✔
3277
                    raster_colorizer: RasterColorizer::SingleBand {
1✔
3278
                        band: 0,
1✔
3279
                        band_colorizer: Colorizer::LinearGradient {
1✔
3280
                            breakpoints: vec![
1✔
3281
                                Breakpoint {
1✔
3282
                                    value: NotNan::<f64>::new(-10.0).unwrap(),
1✔
3283
                                    color: RgbaColor::new(0, 0, 0, 0),
1✔
3284
                                },
1✔
3285
                                Breakpoint {
1✔
3286
                                    value: NotNan::<f64>::new(2.0).unwrap(),
1✔
3287
                                    color: RgbaColor::new(255, 0, 0, 255),
1✔
3288
                                },
1✔
3289
                            ],
1✔
3290
                            no_data_color: RgbaColor::new(0, 10, 20, 30),
1✔
3291
                            over_color: RgbaColor::new(1, 2, 3, 4),
1✔
3292
                            under_color: RgbaColor::new(5, 6, 7, 8),
1✔
3293
                        },
1✔
3294
                    },
1✔
3295
                }),
1✔
3296
            ],
1✔
3297
        )
1✔
3298
        .await;
18✔
3299

3300
        assert_sql_type(
1✔
3301
            &pool,
1✔
3302
            "RasterDataType",
1✔
3303
            [
1✔
3304
                RasterDataType::U8,
1✔
3305
                RasterDataType::U16,
1✔
3306
                RasterDataType::U32,
1✔
3307
                RasterDataType::U64,
1✔
3308
                RasterDataType::I8,
1✔
3309
                RasterDataType::I16,
1✔
3310
                RasterDataType::I32,
1✔
3311
                RasterDataType::I64,
1✔
3312
                RasterDataType::F32,
1✔
3313
                RasterDataType::F64,
1✔
3314
            ],
1✔
3315
        )
1✔
3316
        .await;
22✔
3317

3318
        assert_sql_type(
1✔
3319
            &pool,
1✔
3320
            "Measurement",
1✔
3321
            [
1✔
3322
                Measurement::Unitless,
1✔
3323
                Measurement::Continuous(ContinuousMeasurement {
1✔
3324
                    measurement: "Temperature".to_string(),
1✔
3325
                    unit: Some("°C".to_string()),
1✔
3326
                }),
1✔
3327
                Measurement::Classification(ClassificationMeasurement {
1✔
3328
                    measurement: "Color".to_string(),
1✔
3329
                    classes: [(1, "Grayscale".to_string()), (2, "Colorful".to_string())].into(),
1✔
3330
                }),
1✔
3331
            ],
1✔
3332
        )
1✔
3333
        .await;
15✔
3334

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

3337
        assert_sql_type(
1✔
3338
            &pool,
1✔
3339
            "SpatialPartition2D",
1✔
3340
            [
1✔
3341
                SpatialPartition2D::new(Coordinate2D::new(0.0f64, 1.), Coordinate2D::new(2., 0.5))
1✔
3342
                    .unwrap(),
1✔
3343
            ],
1✔
3344
        )
1✔
3345
        .await;
4✔
3346

3347
        assert_sql_type(
1✔
3348
            &pool,
1✔
3349
            "BoundingBox2D",
1✔
3350
            [
1✔
3351
                BoundingBox2D::new(Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0))
1✔
3352
                    .unwrap(),
1✔
3353
            ],
1✔
3354
        )
1✔
3355
        .await;
4✔
3356

3357
        assert_sql_type(
1✔
3358
            &pool,
1✔
3359
            "SpatialResolution",
1✔
3360
            [SpatialResolution { x: 1.2, y: 2.3 }],
1✔
3361
        )
1✔
3362
        .await;
4✔
3363

3364
        assert_sql_type(
1✔
3365
            &pool,
1✔
3366
            "VectorDataType",
1✔
3367
            [
1✔
3368
                VectorDataType::Data,
1✔
3369
                VectorDataType::MultiPoint,
1✔
3370
                VectorDataType::MultiLineString,
1✔
3371
                VectorDataType::MultiPolygon,
1✔
3372
            ],
1✔
3373
        )
1✔
3374
        .await;
10✔
3375

3376
        assert_sql_type(
1✔
3377
            &pool,
1✔
3378
            "FeatureDataType",
1✔
3379
            [
1✔
3380
                FeatureDataType::Category,
1✔
3381
                FeatureDataType::Int,
1✔
3382
                FeatureDataType::Float,
1✔
3383
                FeatureDataType::Text,
1✔
3384
                FeatureDataType::Bool,
1✔
3385
                FeatureDataType::DateTime,
1✔
3386
            ],
1✔
3387
        )
1✔
3388
        .await;
14✔
3389

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

3392
        assert_sql_type(
1✔
3393
            &pool,
1✔
3394
            "SpatialReference",
1✔
3395
            [
1✔
3396
                SpatialReferenceOption::Unreferenced,
1✔
3397
                SpatialReferenceOption::SpatialReference(SpatialReference::epsg_4326()),
1✔
3398
            ],
1✔
3399
        )
1✔
3400
        .await;
8✔
3401

3402
        assert_sql_type(
1✔
3403
            &pool,
1✔
3404
            "PlotResultDescriptor",
1✔
3405
            [PlotResultDescriptor {
1✔
3406
                spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
3407
                time: None,
1✔
3408
                bbox: None,
1✔
3409
            }],
1✔
3410
        )
1✔
3411
        .await;
4✔
3412

3413
        assert_sql_type(
1✔
3414
            &pool,
1✔
3415
            "VectorResultDescriptor",
1✔
3416
            [VectorResultDescriptor {
1✔
3417
                data_type: VectorDataType::MultiPoint,
1✔
3418
                spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3419
                    SpatialReference::epsg_4326(),
1✔
3420
                ),
1✔
3421
                columns: [(
1✔
3422
                    "foo".to_string(),
1✔
3423
                    VectorColumnInfo {
1✔
3424
                        data_type: FeatureDataType::Int,
1✔
3425
                        measurement: Measurement::Unitless,
1✔
3426
                    },
1✔
3427
                )]
1✔
3428
                .into(),
1✔
3429
                time: Some(TimeInterval::default()),
1✔
3430
                bbox: Some(
1✔
3431
                    BoundingBox2D::new(Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0))
1✔
3432
                        .unwrap(),
1✔
3433
                ),
1✔
3434
            }],
1✔
3435
        )
1✔
3436
        .await;
7✔
3437

3438
        assert_sql_type(
1✔
3439
            &pool,
1✔
3440
            "RasterResultDescriptor",
1✔
3441
            [RasterResultDescriptor {
1✔
3442
                data_type: RasterDataType::U8,
1✔
3443
                spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3444
                    SpatialReference::epsg_4326(),
1✔
3445
                ),
1✔
3446
                time: Some(TimeInterval::default()),
1✔
3447
                bbox: Some(
1✔
3448
                    SpatialPartition2D::new(
1✔
3449
                        Coordinate2D::new(0.0f64, 1.),
1✔
3450
                        Coordinate2D::new(2., 0.5),
1✔
3451
                    )
1✔
3452
                    .unwrap(),
1✔
3453
                ),
1✔
3454
                resolution: Some(SpatialResolution { x: 1.2, y: 2.3 }),
1✔
3455
                bands: RasterBandDescriptors::new_single_band(),
1✔
3456
            }],
1✔
3457
        )
1✔
3458
        .await;
7✔
3459

3460
        assert_sql_type(
1✔
3461
            &pool,
1✔
3462
            "ResultDescriptor",
1✔
3463
            [
1✔
3464
                TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
3465
                    data_type: VectorDataType::MultiPoint,
1✔
3466
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3467
                        SpatialReference::epsg_4326(),
1✔
3468
                    ),
1✔
3469
                    columns: [(
1✔
3470
                        "foo".to_string(),
1✔
3471
                        VectorColumnInfo {
1✔
3472
                            data_type: FeatureDataType::Int,
1✔
3473
                            measurement: Measurement::Unitless,
1✔
3474
                        },
1✔
3475
                    )]
1✔
3476
                    .into(),
1✔
3477
                    time: Some(TimeInterval::default()),
1✔
3478
                    bbox: Some(
1✔
3479
                        BoundingBox2D::new(
1✔
3480
                            Coordinate2D::new(0.0f64, 0.5),
1✔
3481
                            Coordinate2D::new(2., 1.0),
1✔
3482
                        )
1✔
3483
                        .unwrap(),
1✔
3484
                    ),
1✔
3485
                }),
1✔
3486
                TypedResultDescriptor::Raster(RasterResultDescriptor {
1✔
3487
                    data_type: RasterDataType::U8,
1✔
3488
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3489
                        SpatialReference::epsg_4326(),
1✔
3490
                    ),
1✔
3491
                    time: Some(TimeInterval::default()),
1✔
3492
                    bbox: Some(
1✔
3493
                        SpatialPartition2D::new(
1✔
3494
                            Coordinate2D::new(0.0f64, 1.),
1✔
3495
                            Coordinate2D::new(2., 0.5),
1✔
3496
                        )
1✔
3497
                        .unwrap(),
1✔
3498
                    ),
1✔
3499
                    resolution: Some(SpatialResolution { x: 1.2, y: 2.3 }),
1✔
3500
                    bands: RasterBandDescriptors::new_single_band(),
1✔
3501
                }),
1✔
3502
                TypedResultDescriptor::Plot(PlotResultDescriptor {
1✔
3503
                    spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
3504
                    time: None,
1✔
3505
                    bbox: None,
1✔
3506
                }),
1✔
3507
            ],
1✔
3508
        )
1✔
3509
        .await;
8✔
3510

3511
        assert_sql_type(
1✔
3512
            &pool,
1✔
3513
            "MockDatasetDataSourceLoadingInfo",
1✔
3514
            [MockDatasetDataSourceLoadingInfo {
1✔
3515
                points: vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3516
            }],
1✔
3517
        )
1✔
3518
        .await;
5✔
3519

3520
        assert_sql_type(
1✔
3521
            &pool,
1✔
3522
            "OgrSourceTimeFormat",
1✔
3523
            [
1✔
3524
                OgrSourceTimeFormat::Auto,
1✔
3525
                OgrSourceTimeFormat::Custom {
1✔
3526
                    custom_format: geoengine_datatypes::primitives::DateTimeParseFormat::custom(
1✔
3527
                        "%Y-%m-%dT%H:%M:%S%.3fZ".to_string(),
1✔
3528
                    ),
1✔
3529
                },
1✔
3530
                OgrSourceTimeFormat::UnixTimeStamp {
1✔
3531
                    timestamp_type: UnixTimeStampType::EpochSeconds,
1✔
3532
                    fmt: geoengine_datatypes::primitives::DateTimeParseFormat::unix(),
1✔
3533
                },
1✔
3534
            ],
1✔
3535
        )
1✔
3536
        .await;
16✔
3537

3538
        assert_sql_type(
1✔
3539
            &pool,
1✔
3540
            "OgrSourceDurationSpec",
1✔
3541
            [
1✔
3542
                OgrSourceDurationSpec::Infinite,
1✔
3543
                OgrSourceDurationSpec::Zero,
1✔
3544
                OgrSourceDurationSpec::Value(TimeStep {
1✔
3545
                    granularity: TimeGranularity::Millis,
1✔
3546
                    step: 1000,
1✔
3547
                }),
1✔
3548
            ],
1✔
3549
        )
1✔
3550
        .await;
12✔
3551

3552
        assert_sql_type(
1✔
3553
            &pool,
1✔
3554
            "OgrSourceDatasetTimeType",
1✔
3555
            [
1✔
3556
                OgrSourceDatasetTimeType::None,
1✔
3557
                OgrSourceDatasetTimeType::Start {
1✔
3558
                    start_field: "start".to_string(),
1✔
3559
                    start_format: OgrSourceTimeFormat::Auto,
1✔
3560
                    duration: OgrSourceDurationSpec::Zero,
1✔
3561
                },
1✔
3562
                OgrSourceDatasetTimeType::StartEnd {
1✔
3563
                    start_field: "start".to_string(),
1✔
3564
                    start_format: OgrSourceTimeFormat::Auto,
1✔
3565
                    end_field: "end".to_string(),
1✔
3566
                    end_format: OgrSourceTimeFormat::Auto,
1✔
3567
                },
1✔
3568
                OgrSourceDatasetTimeType::StartDuration {
1✔
3569
                    start_field: "start".to_string(),
1✔
3570
                    start_format: OgrSourceTimeFormat::Auto,
1✔
3571
                    duration_field: "duration".to_string(),
1✔
3572
                },
1✔
3573
            ],
1✔
3574
        )
1✔
3575
        .await;
16✔
3576

3577
        assert_sql_type(
1✔
3578
            &pool,
1✔
3579
            "FormatSpecifics",
1✔
3580
            [FormatSpecifics::Csv {
1✔
3581
                header: CsvHeader::Yes,
1✔
3582
            }],
1✔
3583
        )
1✔
3584
        .await;
8✔
3585

3586
        assert_sql_type(
1✔
3587
            &pool,
1✔
3588
            "OgrSourceColumnSpec",
1✔
3589
            [OgrSourceColumnSpec {
1✔
3590
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3591
                    header: CsvHeader::Auto,
1✔
3592
                }),
1✔
3593
                x: "x".to_string(),
1✔
3594
                y: Some("y".to_string()),
1✔
3595
                int: vec!["int".to_string()],
1✔
3596
                float: vec!["float".to_string()],
1✔
3597
                text: vec!["text".to_string()],
1✔
3598
                bool: vec!["bool".to_string()],
1✔
3599
                datetime: vec!["datetime".to_string()],
1✔
3600
                rename: Some(
1✔
3601
                    [
1✔
3602
                        ("xx".to_string(), "xx_renamed".to_string()),
1✔
3603
                        ("yx".to_string(), "yy_renamed".to_string()),
1✔
3604
                    ]
1✔
3605
                    .into(),
1✔
3606
                ),
1✔
3607
            }],
1✔
3608
        )
1✔
3609
        .await;
7✔
3610

3611
        assert_sql_type(
1✔
3612
            &pool,
1✔
3613
            "point[]",
1✔
3614
            [MultiPoint::new(vec![
1✔
3615
                Coordinate2D::new(0.0f64, 0.5),
1✔
3616
                Coordinate2D::new(2., 1.0),
1✔
3617
            ])
1✔
3618
            .unwrap()],
1✔
3619
        )
1✔
3620
        .await;
2✔
3621

3622
        assert_sql_type(
1✔
3623
            &pool,
1✔
3624
            "path[]",
1✔
3625
            [MultiLineString::new(vec![
1✔
3626
                vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3627
                vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3628
            ])
1✔
3629
            .unwrap()],
1✔
3630
        )
1✔
3631
        .await;
3✔
3632

3633
        assert_sql_type(
1✔
3634
            &pool,
1✔
3635
            "\"Polygon\"[]",
1✔
3636
            [MultiPolygon::new(vec![
1✔
3637
                vec![
1✔
3638
                    vec![
1✔
3639
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3640
                        Coordinate2D::new(2., 1.0),
1✔
3641
                        Coordinate2D::new(2., 1.0),
1✔
3642
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3643
                    ],
1✔
3644
                    vec![
1✔
3645
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3646
                        Coordinate2D::new(2., 1.0),
1✔
3647
                        Coordinate2D::new(2., 1.0),
1✔
3648
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3649
                    ],
1✔
3650
                ],
1✔
3651
                vec![
1✔
3652
                    vec![
1✔
3653
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3654
                        Coordinate2D::new(2., 1.0),
1✔
3655
                        Coordinate2D::new(2., 1.0),
1✔
3656
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3657
                    ],
1✔
3658
                    vec![
1✔
3659
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3660
                        Coordinate2D::new(2., 1.0),
1✔
3661
                        Coordinate2D::new(2., 1.0),
1✔
3662
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3663
                    ],
1✔
3664
                ],
1✔
3665
            ])
1✔
3666
            .unwrap()],
1✔
3667
        )
1✔
3668
        .await;
4✔
3669

3670
        assert_sql_type(
1✔
3671
            &pool,
1✔
3672
            "TypedGeometry",
1✔
3673
            [
1✔
3674
                TypedGeometry::Data(NoGeometry),
1✔
3675
                TypedGeometry::MultiPoint(
1✔
3676
                    MultiPoint::new(vec![
1✔
3677
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3678
                        Coordinate2D::new(2., 1.0),
1✔
3679
                    ])
1✔
3680
                    .unwrap(),
1✔
3681
                ),
1✔
3682
                TypedGeometry::MultiLineString(
1✔
3683
                    MultiLineString::new(vec![
1✔
3684
                        vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3685
                        vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3686
                    ])
1✔
3687
                    .unwrap(),
1✔
3688
                ),
1✔
3689
                TypedGeometry::MultiPolygon(
1✔
3690
                    MultiPolygon::new(vec![
1✔
3691
                        vec![
1✔
3692
                            vec![
1✔
3693
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3694
                                Coordinate2D::new(2., 1.0),
1✔
3695
                                Coordinate2D::new(2., 1.0),
1✔
3696
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3697
                            ],
1✔
3698
                            vec![
1✔
3699
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3700
                                Coordinate2D::new(2., 1.0),
1✔
3701
                                Coordinate2D::new(2., 1.0),
1✔
3702
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3703
                            ],
1✔
3704
                        ],
1✔
3705
                        vec![
1✔
3706
                            vec![
1✔
3707
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3708
                                Coordinate2D::new(2., 1.0),
1✔
3709
                                Coordinate2D::new(2., 1.0),
1✔
3710
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3711
                            ],
1✔
3712
                            vec![
1✔
3713
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3714
                                Coordinate2D::new(2., 1.0),
1✔
3715
                                Coordinate2D::new(2., 1.0),
1✔
3716
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3717
                            ],
1✔
3718
                        ],
1✔
3719
                    ])
1✔
3720
                    .unwrap(),
1✔
3721
                ),
1✔
3722
            ],
1✔
3723
        )
1✔
3724
        .await;
10✔
3725

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

3728
        assert_sql_type(
1✔
3729
            &pool,
1✔
3730
            "OgrSourceDataset",
1✔
3731
            [OgrSourceDataset {
1✔
3732
                file_name: "test".into(),
1✔
3733
                layer_name: "test".to_string(),
1✔
3734
                data_type: Some(VectorDataType::MultiPoint),
1✔
3735
                time: OgrSourceDatasetTimeType::Start {
1✔
3736
                    start_field: "start".to_string(),
1✔
3737
                    start_format: OgrSourceTimeFormat::Auto,
1✔
3738
                    duration: OgrSourceDurationSpec::Zero,
1✔
3739
                },
1✔
3740
                default_geometry: Some(TypedGeometry::MultiPoint(
1✔
3741
                    MultiPoint::new(vec![
1✔
3742
                        Coordinate2D::new(0.0f64, 0.5),
1✔
3743
                        Coordinate2D::new(2., 1.0),
1✔
3744
                    ])
1✔
3745
                    .unwrap(),
1✔
3746
                )),
1✔
3747
                columns: Some(OgrSourceColumnSpec {
1✔
3748
                    format_specifics: Some(FormatSpecifics::Csv {
1✔
3749
                        header: CsvHeader::Auto,
1✔
3750
                    }),
1✔
3751
                    x: "x".to_string(),
1✔
3752
                    y: Some("y".to_string()),
1✔
3753
                    int: vec!["int".to_string()],
1✔
3754
                    float: vec!["float".to_string()],
1✔
3755
                    text: vec!["text".to_string()],
1✔
3756
                    bool: vec!["bool".to_string()],
1✔
3757
                    datetime: vec!["datetime".to_string()],
1✔
3758
                    rename: Some(
1✔
3759
                        [
1✔
3760
                            ("xx".to_string(), "xx_renamed".to_string()),
1✔
3761
                            ("yx".to_string(), "yy_renamed".to_string()),
1✔
3762
                        ]
1✔
3763
                        .into(),
1✔
3764
                    ),
1✔
3765
                }),
1✔
3766
                force_ogr_time_filter: false,
1✔
3767
                force_ogr_spatial_filter: true,
1✔
3768
                on_error: OgrSourceErrorSpec::Abort,
1✔
3769
                sql_query: None,
1✔
3770
                attribute_query: Some("foo = 'bar'".to_string()),
1✔
3771
                cache_ttl: CacheTtlSeconds::new(5),
1✔
3772
            }],
1✔
3773
        )
1✔
3774
        .await;
6✔
3775

3776
        assert_sql_type(
1✔
3777
            &pool,
1✔
3778
            "MockMetaData",
1✔
3779
            [StaticMetaData::<
1✔
3780
                MockDatasetDataSourceLoadingInfo,
1✔
3781
                VectorResultDescriptor,
1✔
3782
                VectorQueryRectangle,
1✔
3783
            > {
1✔
3784
                loading_info: MockDatasetDataSourceLoadingInfo {
1✔
3785
                    points: vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
3786
                },
1✔
3787
                result_descriptor: VectorResultDescriptor {
1✔
3788
                    data_type: VectorDataType::MultiPoint,
1✔
3789
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3790
                        SpatialReference::epsg_4326(),
1✔
3791
                    ),
1✔
3792
                    columns: [(
1✔
3793
                        "foo".to_string(),
1✔
3794
                        VectorColumnInfo {
1✔
3795
                            data_type: FeatureDataType::Int,
1✔
3796
                            measurement: Measurement::Unitless,
1✔
3797
                        },
1✔
3798
                    )]
1✔
3799
                    .into(),
1✔
3800
                    time: Some(TimeInterval::default()),
1✔
3801
                    bbox: Some(
1✔
3802
                        BoundingBox2D::new(
1✔
3803
                            Coordinate2D::new(0.0f64, 0.5),
1✔
3804
                            Coordinate2D::new(2., 1.0),
1✔
3805
                        )
1✔
3806
                        .unwrap(),
1✔
3807
                    ),
1✔
3808
                },
1✔
3809
                phantom: PhantomData,
1✔
3810
            }],
1✔
3811
        )
1✔
3812
        .await;
4✔
3813

3814
        assert_sql_type(
1✔
3815
            &pool,
1✔
3816
            "OgrMetaData",
1✔
3817
            [
1✔
3818
                StaticMetaData::<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle> {
1✔
3819
                    loading_info: OgrSourceDataset {
1✔
3820
                        file_name: "test".into(),
1✔
3821
                        layer_name: "test".to_string(),
1✔
3822
                        data_type: Some(VectorDataType::MultiPoint),
1✔
3823
                        time: OgrSourceDatasetTimeType::Start {
1✔
3824
                            start_field: "start".to_string(),
1✔
3825
                            start_format: OgrSourceTimeFormat::Auto,
1✔
3826
                            duration: OgrSourceDurationSpec::Zero,
1✔
3827
                        },
1✔
3828
                        default_geometry: Some(TypedGeometry::MultiPoint(
1✔
3829
                            MultiPoint::new(vec![
1✔
3830
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3831
                                Coordinate2D::new(2., 1.0),
1✔
3832
                            ])
1✔
3833
                            .unwrap(),
1✔
3834
                        )),
1✔
3835
                        columns: Some(OgrSourceColumnSpec {
1✔
3836
                            format_specifics: Some(FormatSpecifics::Csv {
1✔
3837
                                header: CsvHeader::Auto,
1✔
3838
                            }),
1✔
3839
                            x: "x".to_string(),
1✔
3840
                            y: Some("y".to_string()),
1✔
3841
                            int: vec!["int".to_string()],
1✔
3842
                            float: vec!["float".to_string()],
1✔
3843
                            text: vec!["text".to_string()],
1✔
3844
                            bool: vec!["bool".to_string()],
1✔
3845
                            datetime: vec!["datetime".to_string()],
1✔
3846
                            rename: Some(
1✔
3847
                                [
1✔
3848
                                    ("xx".to_string(), "xx_renamed".to_string()),
1✔
3849
                                    ("yx".to_string(), "yy_renamed".to_string()),
1✔
3850
                                ]
1✔
3851
                                .into(),
1✔
3852
                            ),
1✔
3853
                        }),
1✔
3854
                        force_ogr_time_filter: false,
1✔
3855
                        force_ogr_spatial_filter: true,
1✔
3856
                        on_error: OgrSourceErrorSpec::Abort,
1✔
3857
                        sql_query: None,
1✔
3858
                        attribute_query: Some("foo = 'bar'".to_string()),
1✔
3859
                        cache_ttl: CacheTtlSeconds::new(5),
1✔
3860
                    },
1✔
3861
                    result_descriptor: VectorResultDescriptor {
1✔
3862
                        data_type: VectorDataType::MultiPoint,
1✔
3863
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3864
                            SpatialReference::epsg_4326(),
1✔
3865
                        ),
1✔
3866
                        columns: [(
1✔
3867
                            "foo".to_string(),
1✔
3868
                            VectorColumnInfo {
1✔
3869
                                data_type: FeatureDataType::Int,
1✔
3870
                                measurement: Measurement::Unitless,
1✔
3871
                            },
1✔
3872
                        )]
1✔
3873
                        .into(),
1✔
3874
                        time: Some(TimeInterval::default()),
1✔
3875
                        bbox: Some(
1✔
3876
                            BoundingBox2D::new(
1✔
3877
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3878
                                Coordinate2D::new(2., 1.0),
1✔
3879
                            )
1✔
3880
                            .unwrap(),
1✔
3881
                        ),
1✔
3882
                    },
1✔
3883
                    phantom: PhantomData,
1✔
3884
                },
1✔
3885
            ],
1✔
3886
        )
1✔
3887
        .await;
4✔
3888

3889
        assert_sql_type(
1✔
3890
            &pool,
1✔
3891
            "GdalDatasetGeoTransform",
1✔
3892
            [GdalDatasetGeoTransform {
1✔
3893
                origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3894
                x_pixel_size: 1.0,
1✔
3895
                y_pixel_size: 2.0,
1✔
3896
            }],
1✔
3897
        )
1✔
3898
        .await;
4✔
3899

3900
        assert_sql_type(
1✔
3901
            &pool,
1✔
3902
            "FileNotFoundHandling",
1✔
3903
            [FileNotFoundHandling::NoData, FileNotFoundHandling::Error],
1✔
3904
        )
1✔
3905
        .await;
6✔
3906

3907
        assert_sql_type(
1✔
3908
            &pool,
1✔
3909
            "GdalMetadataMapping",
1✔
3910
            [GdalMetadataMapping {
1✔
3911
                source_key: RasterPropertiesKey {
1✔
3912
                    domain: None,
1✔
3913
                    key: "foo".to_string(),
1✔
3914
                },
1✔
3915
                target_key: RasterPropertiesKey {
1✔
3916
                    domain: Some("bar".to_string()),
1✔
3917
                    key: "foo".to_string(),
1✔
3918
                },
1✔
3919
                target_type: RasterPropertiesEntryType::String,
1✔
3920
            }],
1✔
3921
        )
1✔
3922
        .await;
8✔
3923

3924
        assert_sql_type(
1✔
3925
            &pool,
1✔
3926
            "StringPair",
1✔
3927
            [StringPair::from(("foo".to_string(), "bar".to_string()))],
1✔
3928
        )
1✔
3929
        .await;
3✔
3930

3931
        assert_sql_type(
1✔
3932
            &pool,
1✔
3933
            "GdalDatasetParameters",
1✔
3934
            [GdalDatasetParameters {
1✔
3935
                file_path: "text".into(),
1✔
3936
                rasterband_channel: 1,
1✔
3937
                geo_transform: GdalDatasetGeoTransform {
1✔
3938
                    origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3939
                    x_pixel_size: 1.0,
1✔
3940
                    y_pixel_size: 2.0,
1✔
3941
                },
1✔
3942
                width: 42,
1✔
3943
                height: 23,
1✔
3944
                file_not_found_handling: FileNotFoundHandling::NoData,
1✔
3945
                no_data_value: Some(42.0),
1✔
3946
                properties_mapping: Some(vec![GdalMetadataMapping {
1✔
3947
                    source_key: RasterPropertiesKey {
1✔
3948
                        domain: None,
1✔
3949
                        key: "foo".to_string(),
1✔
3950
                    },
1✔
3951
                    target_key: RasterPropertiesKey {
1✔
3952
                        domain: Some("bar".to_string()),
1✔
3953
                        key: "foo".to_string(),
1✔
3954
                    },
1✔
3955
                    target_type: RasterPropertiesEntryType::String,
1✔
3956
                }]),
1✔
3957
                gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3958
                gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
3959
                allow_alphaband_as_mask: false,
1✔
3960
                retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
3961
            }],
1✔
3962
        )
1✔
3963
        .await;
8✔
3964

3965
        assert_sql_type(
1✔
3966
            &pool,
1✔
3967
            "GdalMetaDataRegular",
1✔
3968
            [GdalMetaDataRegular {
1✔
3969
                result_descriptor: RasterResultDescriptor {
1✔
3970
                    data_type: RasterDataType::U8,
1✔
3971
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3972
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3973
                    bbox: Some(
1✔
3974
                        SpatialPartition2D::new(
1✔
3975
                            Coordinate2D::new(0.0f64, 1.),
1✔
3976
                            Coordinate2D::new(2., 0.5),
1✔
3977
                        )
1✔
3978
                        .unwrap(),
1✔
3979
                    ),
1✔
3980
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
3981
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
3982
                        "band".into(),
1✔
3983
                        Measurement::Continuous(ContinuousMeasurement {
1✔
3984
                            measurement: "Temperature".to_string(),
1✔
3985
                            unit: Some("°C".to_string()),
1✔
3986
                        }),
1✔
3987
                    )])
1✔
3988
                    .unwrap(),
1✔
3989
                },
1✔
3990
                params: GdalDatasetParameters {
1✔
3991
                    file_path: "text".into(),
1✔
3992
                    rasterband_channel: 1,
1✔
3993
                    geo_transform: GdalDatasetGeoTransform {
1✔
3994
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
3995
                        x_pixel_size: 1.0,
1✔
3996
                        y_pixel_size: 2.0,
1✔
3997
                    },
1✔
3998
                    width: 42,
1✔
3999
                    height: 23,
1✔
4000
                    file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4001
                    no_data_value: Some(42.0),
1✔
4002
                    properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4003
                        source_key: RasterPropertiesKey {
1✔
4004
                            domain: None,
1✔
4005
                            key: "foo".to_string(),
1✔
4006
                        },
1✔
4007
                        target_key: RasterPropertiesKey {
1✔
4008
                            domain: Some("bar".to_string()),
1✔
4009
                            key: "foo".to_string(),
1✔
4010
                        },
1✔
4011
                        target_type: RasterPropertiesEntryType::String,
1✔
4012
                    }]),
1✔
4013
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4014
                    gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4015
                    allow_alphaband_as_mask: false,
1✔
4016
                    retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4017
                },
1✔
4018
                time_placeholders: [(
1✔
4019
                    "foo".to_string(),
1✔
4020
                    GdalSourceTimePlaceholder {
1✔
4021
                        format: geoengine_datatypes::primitives::DateTimeParseFormat::unix(),
1✔
4022
                        reference: TimeReference::Start,
1✔
4023
                    },
1✔
4024
                )]
1✔
4025
                .into(),
1✔
4026
                data_time: TimeInterval::new_unchecked(0, 1),
1✔
4027
                step: TimeStep {
1✔
4028
                    granularity: TimeGranularity::Millis,
1✔
4029
                    step: 1,
1✔
4030
                },
1✔
4031
                cache_ttl: CacheTtlSeconds::max(),
1✔
4032
            }],
1✔
4033
        )
1✔
4034
        .await;
11✔
4035

4036
        assert_sql_type(
1✔
4037
            &pool,
1✔
4038
            "GdalMetaDataStatic",
1✔
4039
            [GdalMetaDataStatic {
1✔
4040
                time: Some(TimeInterval::new_unchecked(0, 1)),
1✔
4041
                result_descriptor: RasterResultDescriptor {
1✔
4042
                    data_type: RasterDataType::U8,
1✔
4043
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4044
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4045
                    bbox: Some(
1✔
4046
                        SpatialPartition2D::new(
1✔
4047
                            Coordinate2D::new(0.0f64, 1.),
1✔
4048
                            Coordinate2D::new(2., 0.5),
1✔
4049
                        )
1✔
4050
                        .unwrap(),
1✔
4051
                    ),
1✔
4052
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
4053
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4054
                        "band".into(),
1✔
4055
                        Measurement::Continuous(ContinuousMeasurement {
1✔
4056
                            measurement: "Temperature".to_string(),
1✔
4057
                            unit: Some("°C".to_string()),
1✔
4058
                        }),
1✔
4059
                    )])
1✔
4060
                    .unwrap(),
1✔
4061
                },
1✔
4062
                params: GdalDatasetParameters {
1✔
4063
                    file_path: "text".into(),
1✔
4064
                    rasterband_channel: 1,
1✔
4065
                    geo_transform: GdalDatasetGeoTransform {
1✔
4066
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4067
                        x_pixel_size: 1.0,
1✔
4068
                        y_pixel_size: 2.0,
1✔
4069
                    },
1✔
4070
                    width: 42,
1✔
4071
                    height: 23,
1✔
4072
                    file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4073
                    no_data_value: Some(42.0),
1✔
4074
                    properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4075
                        source_key: RasterPropertiesKey {
1✔
4076
                            domain: None,
1✔
4077
                            key: "foo".to_string(),
1✔
4078
                        },
1✔
4079
                        target_key: RasterPropertiesKey {
1✔
4080
                            domain: Some("bar".to_string()),
1✔
4081
                            key: "foo".to_string(),
1✔
4082
                        },
1✔
4083
                        target_type: RasterPropertiesEntryType::String,
1✔
4084
                    }]),
1✔
4085
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4086
                    gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4087
                    allow_alphaband_as_mask: false,
1✔
4088
                    retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4089
                },
1✔
4090
                cache_ttl: CacheTtlSeconds::max(),
1✔
4091
            }],
1✔
4092
        )
1✔
4093
        .await;
4✔
4094

4095
        assert_sql_type(
1✔
4096
            &pool,
1✔
4097
            "GdalMetadataNetCdfCf",
1✔
4098
            [GdalMetadataNetCdfCf {
1✔
4099
                result_descriptor: RasterResultDescriptor {
1✔
4100
                    data_type: RasterDataType::U8,
1✔
4101
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4102
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4103
                    bbox: Some(
1✔
4104
                        SpatialPartition2D::new(
1✔
4105
                            Coordinate2D::new(0.0f64, 1.),
1✔
4106
                            Coordinate2D::new(2., 0.5),
1✔
4107
                        )
1✔
4108
                        .unwrap(),
1✔
4109
                    ),
1✔
4110
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
4111
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4112
                        "band".into(),
1✔
4113
                        Measurement::Continuous(ContinuousMeasurement {
1✔
4114
                            measurement: "Temperature".to_string(),
1✔
4115
                            unit: Some("°C".to_string()),
1✔
4116
                        }),
1✔
4117
                    )])
1✔
4118
                    .unwrap(),
1✔
4119
                },
1✔
4120
                params: GdalDatasetParameters {
1✔
4121
                    file_path: "text".into(),
1✔
4122
                    rasterband_channel: 1,
1✔
4123
                    geo_transform: GdalDatasetGeoTransform {
1✔
4124
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4125
                        x_pixel_size: 1.0,
1✔
4126
                        y_pixel_size: 2.0,
1✔
4127
                    },
1✔
4128
                    width: 42,
1✔
4129
                    height: 23,
1✔
4130
                    file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4131
                    no_data_value: Some(42.0),
1✔
4132
                    properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4133
                        source_key: RasterPropertiesKey {
1✔
4134
                            domain: None,
1✔
4135
                            key: "foo".to_string(),
1✔
4136
                        },
1✔
4137
                        target_key: RasterPropertiesKey {
1✔
4138
                            domain: Some("bar".to_string()),
1✔
4139
                            key: "foo".to_string(),
1✔
4140
                        },
1✔
4141
                        target_type: RasterPropertiesEntryType::String,
1✔
4142
                    }]),
1✔
4143
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4144
                    gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4145
                    allow_alphaband_as_mask: false,
1✔
4146
                    retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4147
                },
1✔
4148
                start: TimeInstance::from_millis(0).unwrap(),
1✔
4149
                end: TimeInstance::from_millis(1000).unwrap(),
1✔
4150
                cache_ttl: CacheTtlSeconds::max(),
1✔
4151
                step: TimeStep {
1✔
4152
                    granularity: TimeGranularity::Millis,
1✔
4153
                    step: 1,
1✔
4154
                },
1✔
4155
                band_offset: 3,
1✔
4156
            }],
1✔
4157
        )
1✔
4158
        .await;
4✔
4159

4160
        assert_sql_type(
1✔
4161
            &pool,
1✔
4162
            "GdalMetaDataList",
1✔
4163
            [GdalMetaDataList {
1✔
4164
                result_descriptor: RasterResultDescriptor {
1✔
4165
                    data_type: RasterDataType::U8,
1✔
4166
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4167
                    time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4168
                    bbox: Some(
1✔
4169
                        SpatialPartition2D::new(
1✔
4170
                            Coordinate2D::new(0.0f64, 1.),
1✔
4171
                            Coordinate2D::new(2., 0.5),
1✔
4172
                        )
1✔
4173
                        .unwrap(),
1✔
4174
                    ),
1✔
4175
                    resolution: Some(SpatialResolution::zero_point_one()),
1✔
4176
                    bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4177
                        "band".into(),
1✔
4178
                        Measurement::Continuous(ContinuousMeasurement {
1✔
4179
                            measurement: "Temperature".to_string(),
1✔
4180
                            unit: Some("°C".to_string()),
1✔
4181
                        }),
1✔
4182
                    )])
1✔
4183
                    .unwrap(),
1✔
4184
                },
1✔
4185
                params: vec![GdalLoadingInfoTemporalSlice {
1✔
4186
                    time: TimeInterval::new_unchecked(0, 1),
1✔
4187
                    params: Some(GdalDatasetParameters {
1✔
4188
                        file_path: "text".into(),
1✔
4189
                        rasterband_channel: 1,
1✔
4190
                        geo_transform: GdalDatasetGeoTransform {
1✔
4191
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4192
                            x_pixel_size: 1.0,
1✔
4193
                            y_pixel_size: 2.0,
1✔
4194
                        },
1✔
4195
                        width: 42,
1✔
4196
                        height: 23,
1✔
4197
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4198
                        no_data_value: Some(42.0),
1✔
4199
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4200
                            source_key: RasterPropertiesKey {
1✔
4201
                                domain: None,
1✔
4202
                                key: "foo".to_string(),
1✔
4203
                            },
1✔
4204
                            target_key: RasterPropertiesKey {
1✔
4205
                                domain: Some("bar".to_string()),
1✔
4206
                                key: "foo".to_string(),
1✔
4207
                            },
1✔
4208
                            target_type: RasterPropertiesEntryType::String,
1✔
4209
                        }]),
1✔
4210
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4211
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4212
                        allow_alphaband_as_mask: false,
1✔
4213
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4214
                    }),
1✔
4215
                    cache_ttl: CacheTtlSeconds::max(),
1✔
4216
                }],
1✔
4217
            }],
1✔
4218
        )
1✔
4219
        .await;
7✔
4220

4221
        assert_sql_type(
1✔
4222
            &pool,
1✔
4223
            "MetaDataDefinition",
1✔
4224
            [
1✔
4225
                MetaDataDefinition::MockMetaData(StaticMetaData::<
1✔
4226
                    MockDatasetDataSourceLoadingInfo,
1✔
4227
                    VectorResultDescriptor,
1✔
4228
                    VectorQueryRectangle,
1✔
4229
                > {
1✔
4230
                    loading_info: MockDatasetDataSourceLoadingInfo {
1✔
4231
                        points: vec![Coordinate2D::new(0.0f64, 0.5), Coordinate2D::new(2., 1.0)],
1✔
4232
                    },
1✔
4233
                    result_descriptor: VectorResultDescriptor {
1✔
4234
                        data_type: VectorDataType::MultiPoint,
1✔
4235
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
4236
                            SpatialReference::epsg_4326(),
1✔
4237
                        ),
1✔
4238
                        columns: [(
1✔
4239
                            "foo".to_string(),
1✔
4240
                            VectorColumnInfo {
1✔
4241
                                data_type: FeatureDataType::Int,
1✔
4242
                                measurement: Measurement::Unitless,
1✔
4243
                            },
1✔
4244
                        )]
1✔
4245
                        .into(),
1✔
4246
                        time: Some(TimeInterval::default()),
1✔
4247
                        bbox: Some(
1✔
4248
                            BoundingBox2D::new(
1✔
4249
                                Coordinate2D::new(0.0f64, 0.5),
1✔
4250
                                Coordinate2D::new(2., 1.0),
1✔
4251
                            )
1✔
4252
                            .unwrap(),
1✔
4253
                        ),
1✔
4254
                    },
1✔
4255
                    phantom: PhantomData,
1✔
4256
                }),
1✔
4257
                MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
4258
                    OgrSourceDataset,
1✔
4259
                    VectorResultDescriptor,
1✔
4260
                    VectorQueryRectangle,
1✔
4261
                > {
1✔
4262
                    loading_info: OgrSourceDataset {
1✔
4263
                        file_name: "test".into(),
1✔
4264
                        layer_name: "test".to_string(),
1✔
4265
                        data_type: Some(VectorDataType::MultiPoint),
1✔
4266
                        time: OgrSourceDatasetTimeType::Start {
1✔
4267
                            start_field: "start".to_string(),
1✔
4268
                            start_format: OgrSourceTimeFormat::Auto,
1✔
4269
                            duration: OgrSourceDurationSpec::Zero,
1✔
4270
                        },
1✔
4271
                        default_geometry: Some(TypedGeometry::MultiPoint(
1✔
4272
                            MultiPoint::new(vec![
1✔
4273
                                Coordinate2D::new(0.0f64, 0.5),
1✔
4274
                                Coordinate2D::new(2., 1.0),
1✔
4275
                            ])
1✔
4276
                            .unwrap(),
1✔
4277
                        )),
1✔
4278
                        columns: Some(OgrSourceColumnSpec {
1✔
4279
                            format_specifics: Some(FormatSpecifics::Csv {
1✔
4280
                                header: CsvHeader::Auto,
1✔
4281
                            }),
1✔
4282
                            x: "x".to_string(),
1✔
4283
                            y: Some("y".to_string()),
1✔
4284
                            int: vec!["int".to_string()],
1✔
4285
                            float: vec!["float".to_string()],
1✔
4286
                            text: vec!["text".to_string()],
1✔
4287
                            bool: vec!["bool".to_string()],
1✔
4288
                            datetime: vec!["datetime".to_string()],
1✔
4289
                            rename: Some(
1✔
4290
                                [
1✔
4291
                                    ("xx".to_string(), "xx_renamed".to_string()),
1✔
4292
                                    ("yx".to_string(), "yy_renamed".to_string()),
1✔
4293
                                ]
1✔
4294
                                .into(),
1✔
4295
                            ),
1✔
4296
                        }),
1✔
4297
                        force_ogr_time_filter: false,
1✔
4298
                        force_ogr_spatial_filter: true,
1✔
4299
                        on_error: OgrSourceErrorSpec::Abort,
1✔
4300
                        sql_query: None,
1✔
4301
                        attribute_query: Some("foo = 'bar'".to_string()),
1✔
4302
                        cache_ttl: CacheTtlSeconds::new(5),
1✔
4303
                    },
1✔
4304
                    result_descriptor: VectorResultDescriptor {
1✔
4305
                        data_type: VectorDataType::MultiPoint,
1✔
4306
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
4307
                            SpatialReference::epsg_4326(),
1✔
4308
                        ),
1✔
4309
                        columns: [(
1✔
4310
                            "foo".to_string(),
1✔
4311
                            VectorColumnInfo {
1✔
4312
                                data_type: FeatureDataType::Int,
1✔
4313
                                measurement: Measurement::Unitless,
1✔
4314
                            },
1✔
4315
                        )]
1✔
4316
                        .into(),
1✔
4317
                        time: Some(TimeInterval::default()),
1✔
4318
                        bbox: Some(
1✔
4319
                            BoundingBox2D::new(
1✔
4320
                                Coordinate2D::new(0.0f64, 0.5),
1✔
4321
                                Coordinate2D::new(2., 1.0),
1✔
4322
                            )
1✔
4323
                            .unwrap(),
1✔
4324
                        ),
1✔
4325
                    },
1✔
4326
                    phantom: PhantomData,
1✔
4327
                }),
1✔
4328
                MetaDataDefinition::GdalMetaDataRegular(GdalMetaDataRegular {
1✔
4329
                    result_descriptor: RasterResultDescriptor {
1✔
4330
                        data_type: RasterDataType::U8,
1✔
4331
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4332
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4333
                        bbox: Some(
1✔
4334
                            SpatialPartition2D::new(
1✔
4335
                                Coordinate2D::new(0.0f64, 1.),
1✔
4336
                                Coordinate2D::new(2., 0.5),
1✔
4337
                            )
1✔
4338
                            .unwrap(),
1✔
4339
                        ),
1✔
4340
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
4341
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4342
                            "band".into(),
1✔
4343
                            Measurement::Continuous(ContinuousMeasurement {
1✔
4344
                                measurement: "Temperature".to_string(),
1✔
4345
                                unit: Some("°C".to_string()),
1✔
4346
                            }),
1✔
4347
                        )])
1✔
4348
                        .unwrap(),
1✔
4349
                    },
1✔
4350
                    params: GdalDatasetParameters {
1✔
4351
                        file_path: "text".into(),
1✔
4352
                        rasterband_channel: 1,
1✔
4353
                        geo_transform: GdalDatasetGeoTransform {
1✔
4354
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4355
                            x_pixel_size: 1.0,
1✔
4356
                            y_pixel_size: 2.0,
1✔
4357
                        },
1✔
4358
                        width: 42,
1✔
4359
                        height: 23,
1✔
4360
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4361
                        no_data_value: Some(42.0),
1✔
4362
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4363
                            source_key: RasterPropertiesKey {
1✔
4364
                                domain: None,
1✔
4365
                                key: "foo".to_string(),
1✔
4366
                            },
1✔
4367
                            target_key: RasterPropertiesKey {
1✔
4368
                                domain: Some("bar".to_string()),
1✔
4369
                                key: "foo".to_string(),
1✔
4370
                            },
1✔
4371
                            target_type: RasterPropertiesEntryType::String,
1✔
4372
                        }]),
1✔
4373
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4374
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4375
                        allow_alphaband_as_mask: false,
1✔
4376
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4377
                    },
1✔
4378
                    time_placeholders: [(
1✔
4379
                        "foo".to_string(),
1✔
4380
                        GdalSourceTimePlaceholder {
1✔
4381
                            format: DateTimeParseFormat::unix(),
1✔
4382
                            reference: TimeReference::Start,
1✔
4383
                        },
1✔
4384
                    )]
1✔
4385
                    .into(),
1✔
4386
                    data_time: TimeInterval::new_unchecked(0, 1),
1✔
4387
                    step: TimeStep {
1✔
4388
                        granularity: TimeGranularity::Millis,
1✔
4389
                        step: 1,
1✔
4390
                    },
1✔
4391
                    cache_ttl: CacheTtlSeconds::max(),
1✔
4392
                }),
1✔
4393
                MetaDataDefinition::GdalStatic(GdalMetaDataStatic {
1✔
4394
                    time: Some(TimeInterval::new_unchecked(0, 1)),
1✔
4395
                    result_descriptor: RasterResultDescriptor {
1✔
4396
                        data_type: RasterDataType::U8,
1✔
4397
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4398
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4399
                        bbox: Some(
1✔
4400
                            SpatialPartition2D::new(
1✔
4401
                                Coordinate2D::new(0.0f64, 1.),
1✔
4402
                                Coordinate2D::new(2., 0.5),
1✔
4403
                            )
1✔
4404
                            .unwrap(),
1✔
4405
                        ),
1✔
4406
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
4407
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4408
                            "band".into(),
1✔
4409
                            Measurement::Continuous(ContinuousMeasurement {
1✔
4410
                                measurement: "Temperature".to_string(),
1✔
4411
                                unit: Some("°C".to_string()),
1✔
4412
                            }),
1✔
4413
                        )])
1✔
4414
                        .unwrap(),
1✔
4415
                    },
1✔
4416
                    params: GdalDatasetParameters {
1✔
4417
                        file_path: "text".into(),
1✔
4418
                        rasterband_channel: 1,
1✔
4419
                        geo_transform: GdalDatasetGeoTransform {
1✔
4420
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4421
                            x_pixel_size: 1.0,
1✔
4422
                            y_pixel_size: 2.0,
1✔
4423
                        },
1✔
4424
                        width: 42,
1✔
4425
                        height: 23,
1✔
4426
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4427
                        no_data_value: Some(42.0),
1✔
4428
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4429
                            source_key: RasterPropertiesKey {
1✔
4430
                                domain: None,
1✔
4431
                                key: "foo".to_string(),
1✔
4432
                            },
1✔
4433
                            target_key: RasterPropertiesKey {
1✔
4434
                                domain: Some("bar".to_string()),
1✔
4435
                                key: "foo".to_string(),
1✔
4436
                            },
1✔
4437
                            target_type: RasterPropertiesEntryType::String,
1✔
4438
                        }]),
1✔
4439
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4440
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4441
                        allow_alphaband_as_mask: false,
1✔
4442
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4443
                    },
1✔
4444
                    cache_ttl: CacheTtlSeconds::max(),
1✔
4445
                }),
1✔
4446
                MetaDataDefinition::GdalMetadataNetCdfCf(GdalMetadataNetCdfCf {
1✔
4447
                    result_descriptor: RasterResultDescriptor {
1✔
4448
                        data_type: RasterDataType::U8,
1✔
4449
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4450
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4451
                        bbox: Some(
1✔
4452
                            SpatialPartition2D::new(
1✔
4453
                                Coordinate2D::new(0.0f64, 1.),
1✔
4454
                                Coordinate2D::new(2., 0.5),
1✔
4455
                            )
1✔
4456
                            .unwrap(),
1✔
4457
                        ),
1✔
4458
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
4459
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4460
                            "band".into(),
1✔
4461
                            Measurement::Continuous(ContinuousMeasurement {
1✔
4462
                                measurement: "Temperature".to_string(),
1✔
4463
                                unit: Some("°C".to_string()),
1✔
4464
                            }),
1✔
4465
                        )])
1✔
4466
                        .unwrap(),
1✔
4467
                    },
1✔
4468
                    params: GdalDatasetParameters {
1✔
4469
                        file_path: "text".into(),
1✔
4470
                        rasterband_channel: 1,
1✔
4471
                        geo_transform: GdalDatasetGeoTransform {
1✔
4472
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4473
                            x_pixel_size: 1.0,
1✔
4474
                            y_pixel_size: 2.0,
1✔
4475
                        },
1✔
4476
                        width: 42,
1✔
4477
                        height: 23,
1✔
4478
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4479
                        no_data_value: Some(42.0),
1✔
4480
                        properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4481
                            source_key: RasterPropertiesKey {
1✔
4482
                                domain: None,
1✔
4483
                                key: "foo".to_string(),
1✔
4484
                            },
1✔
4485
                            target_key: RasterPropertiesKey {
1✔
4486
                                domain: Some("bar".to_string()),
1✔
4487
                                key: "foo".to_string(),
1✔
4488
                            },
1✔
4489
                            target_type: RasterPropertiesEntryType::String,
1✔
4490
                        }]),
1✔
4491
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4492
                        gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4493
                        allow_alphaband_as_mask: false,
1✔
4494
                        retry: Some(GdalRetryOptions { max_retries: 3 }),
1✔
4495
                    },
1✔
4496
                    start: TimeInstance::from_millis(0).unwrap(),
1✔
4497
                    end: TimeInstance::from_millis(1000).unwrap(),
1✔
4498
                    cache_ttl: CacheTtlSeconds::max(),
1✔
4499
                    step: TimeStep {
1✔
4500
                        granularity: TimeGranularity::Millis,
1✔
4501
                        step: 1,
1✔
4502
                    },
1✔
4503
                    band_offset: 3,
1✔
4504
                }),
1✔
4505
                MetaDataDefinition::GdalMetaDataList(GdalMetaDataList {
1✔
4506
                    result_descriptor: RasterResultDescriptor {
1✔
4507
                        data_type: RasterDataType::U8,
1✔
4508
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4509
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4510
                        bbox: Some(
1✔
4511
                            SpatialPartition2D::new(
1✔
4512
                                Coordinate2D::new(0.0f64, 1.),
1✔
4513
                                Coordinate2D::new(2., 0.5),
1✔
4514
                            )
1✔
4515
                            .unwrap(),
1✔
4516
                        ),
1✔
4517
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
4518
                        bands: RasterBandDescriptors::new(vec![RasterBandDescriptor::new(
1✔
4519
                            "band".into(),
1✔
4520
                            Measurement::Continuous(ContinuousMeasurement {
1✔
4521
                                measurement: "Temperature".to_string(),
1✔
4522
                                unit: Some("°C".to_string()),
1✔
4523
                            }),
1✔
4524
                        )])
1✔
4525
                        .unwrap(),
1✔
4526
                    },
1✔
4527
                    params: vec![GdalLoadingInfoTemporalSlice {
1✔
4528
                        time: TimeInterval::new_unchecked(0, 1),
1✔
4529
                        params: Some(GdalDatasetParameters {
1✔
4530
                            file_path: "text".into(),
1✔
4531
                            rasterband_channel: 1,
1✔
4532
                            geo_transform: GdalDatasetGeoTransform {
1✔
4533
                                origin_coordinate: Coordinate2D::new(0.0f64, 0.5),
1✔
4534
                                x_pixel_size: 1.0,
1✔
4535
                                y_pixel_size: 2.0,
1✔
4536
                            },
1✔
4537
                            width: 42,
1✔
4538
                            height: 23,
1✔
4539
                            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
4540
                            no_data_value: Some(42.0),
1✔
4541
                            properties_mapping: Some(vec![GdalMetadataMapping {
1✔
4542
                                source_key: RasterPropertiesKey {
1✔
4543
                                    domain: None,
1✔
4544
                                    key: "foo".to_string(),
1✔
4545
                                },
1✔
4546
                                target_key: RasterPropertiesKey {
1✔
4547
                                    domain: Some("bar".to_string()),
1✔
4548
                                    key: "foo".to_string(),
1✔
4549
                                },
1✔
4550
                                target_type: RasterPropertiesEntryType::String,
1✔
4551
                            }]),
1✔
4552
                            gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4553
                            gdal_config_options: Some(vec![("foo".to_string(), "bar".to_string())]),
1✔
4554
                            allow_alphaband_as_mask: false,
1✔
4555
                            retry: None,
1✔
4556
                        }),
1✔
4557
                        cache_ttl: CacheTtlSeconds::max(),
1✔
4558
                    }],
1✔
4559
                }),
1✔
4560
            ],
1✔
4561
        )
1✔
4562
        .await;
14✔
4563

4564
        assert_sql_type(
1✔
4565
            &pool,
1✔
4566
            "bytea",
1✔
4567
            [U96::from(
1✔
4568
                arr![u8; 13, 227, 191, 247, 123, 193, 214, 165, 185, 37, 101, 24],
1✔
4569
            )],
1✔
4570
        )
1✔
4571
        .await;
2✔
4572

4573
        test_data_provider_definition_types(&pool).await;
63✔
4574
    }
1✔
4575

4576
    #[test]
4577
    fn test_postgres_config_translation() {
1✔
4578
        let host = "localhost";
1✔
4579
        let port = 8095;
1✔
4580
        let ge_default = "geoengine";
1✔
4581
        let schema = "geoengine";
1✔
4582

1✔
4583
        let db_config = config::Postgres {
1✔
4584
            host: host.to_string(),
1✔
4585
            port,
1✔
4586
            database: ge_default.to_string(),
1✔
4587
            schema: schema.to_string(),
1✔
4588
            user: ge_default.to_string(),
1✔
4589
            password: ge_default.to_string(),
1✔
4590
            clear_database_on_start: false,
1✔
4591
        };
1✔
4592

1✔
4593
        let pg_config = Config::try_from(db_config).unwrap();
1✔
4594

1✔
4595
        assert_eq!(ge_default, pg_config.get_user().unwrap());
1✔
4596
        assert_eq!(
1✔
4597
            <str as AsRef<[u8]>>::as_ref(ge_default).to_vec(),
1✔
4598
            pg_config.get_password().unwrap()
1✔
4599
        );
1✔
4600
        assert_eq!(ge_default, pg_config.get_dbname().unwrap());
1✔
4601
        assert_eq!(
1✔
4602
            &format!("-c search_path={schema}"),
1✔
4603
            pg_config.get_options().unwrap()
1✔
4604
        );
1✔
4605
        assert_eq!(vec![Host::Tcp(host.to_string())], pg_config.get_hosts());
1✔
4606
        assert_eq!(vec![port], pg_config.get_ports());
1✔
4607
    }
1✔
4608

4609
    #[allow(clippy::too_many_lines)]
4610
    async fn test_data_provider_definition_types(
1✔
4611
        pool: &PooledConnection<'_, PostgresConnectionManager<tokio_postgres::NoTls>>,
1✔
4612
    ) {
1✔
4613
        assert_sql_type(
1✔
4614
            pool,
1✔
4615
            "ArunaDataProviderDefinition",
1✔
4616
            [ArunaDataProviderDefinition {
1✔
4617
                id: DataProviderId::from_str("86a7f7ce-1bab-4ce9-a32b-172c0f958ee0").unwrap(),
1✔
4618
                name: "NFDI".to_string(),
1✔
4619
                description: "NFDI".to_string(),
1✔
4620
                priority: Some(33),
1✔
4621
                api_url: "http://test".to_string(),
1✔
4622
                project_id: "project".to_string(),
1✔
4623
                api_token: "api_token".to_string(),
1✔
4624
                filter_label: "filter".to_string(),
1✔
4625
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4626
            }],
1✔
4627
        )
1✔
4628
        .await;
4✔
4629

4630
        assert_sql_type(
1✔
4631
            pool,
1✔
4632
            "GbifDataProviderDefinition",
1✔
4633
            [GbifDataProviderDefinition {
1✔
4634
                name: "GBIF".to_string(),
1✔
4635
                description: "GFBio".to_string(),
1✔
4636
                priority: None,
1✔
4637
                db_config: DatabaseConnectionConfig {
1✔
4638
                    host: "testhost".to_string(),
1✔
4639
                    port: 1234,
1✔
4640
                    database: "testdb".to_string(),
1✔
4641
                    schema: "testschema".to_string(),
1✔
4642
                    user: "testuser".to_string(),
1✔
4643
                    password: "testpass".to_string(),
1✔
4644
                },
1✔
4645
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4646
                autocomplete_timeout: 3,
1✔
4647
                columns: GbifDataProvider::all_columns(),
1✔
4648
            }],
1✔
4649
        )
1✔
4650
        .await;
6✔
4651

4652
        assert_sql_type(
1✔
4653
            pool,
1✔
4654
            "GfbioAbcdDataProviderDefinition",
1✔
4655
            [GfbioAbcdDataProviderDefinition {
1✔
4656
                name: "GFbio".to_string(),
1✔
4657
                description: "GFBio".to_string(),
1✔
4658
                priority: None,
1✔
4659
                db_config: DatabaseConnectionConfig {
1✔
4660
                    host: "testhost".to_string(),
1✔
4661
                    port: 1234,
1✔
4662
                    database: "testdb".to_string(),
1✔
4663
                    schema: "testschema".to_string(),
1✔
4664
                    user: "testuser".to_string(),
1✔
4665
                    password: "testpass".to_string(),
1✔
4666
                },
1✔
4667
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4668
            }],
1✔
4669
        )
1✔
4670
        .await;
4✔
4671

4672
        assert_sql_type(
1✔
4673
            pool,
1✔
4674
            "GfbioCollectionsDataProviderDefinition",
1✔
4675
            [GfbioCollectionsDataProviderDefinition {
1✔
4676
                name: "GFbio".to_string(),
1✔
4677
                description: "GFBio".to_string(),
1✔
4678
                priority: None,
1✔
4679
                collection_api_url: "http://testhost".try_into().unwrap(),
1✔
4680
                collection_api_auth_token: "token".to_string(),
1✔
4681
                abcd_db_config: DatabaseConnectionConfig {
1✔
4682
                    host: "testhost".to_string(),
1✔
4683
                    port: 1234,
1✔
4684
                    database: "testdb".to_string(),
1✔
4685
                    schema: "testschema".to_string(),
1✔
4686
                    user: "testuser".to_string(),
1✔
4687
                    password: "testpass".to_string(),
1✔
4688
                },
1✔
4689
                pangaea_url: "http://panaea".try_into().unwrap(),
1✔
4690
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4691
            }],
1✔
4692
        )
1✔
4693
        .await;
4✔
4694

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

4697
        assert_sql_type(
1✔
4698
            pool,
1✔
4699
            "EbvPortalDataProviderDefinition",
1✔
4700
            [EbvPortalDataProviderDefinition {
1✔
4701
                name: "ebv".to_string(),
1✔
4702
                description: "EBV".to_string(),
1✔
4703
                priority: None,
1✔
4704
                data: "a_path".into(),
1✔
4705
                base_url: "http://base".try_into().unwrap(),
1✔
4706
                overviews: "another_path".into(),
1✔
4707
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4708
            }],
1✔
4709
        )
1✔
4710
        .await;
4✔
4711

4712
        assert_sql_type(
1✔
4713
            pool,
1✔
4714
            "NetCdfCfDataProviderDefinition",
1✔
4715
            [NetCdfCfDataProviderDefinition {
1✔
4716
                name: "netcdfcf".to_string(),
1✔
4717
                description: "netcdfcf".to_string(),
1✔
4718
                priority: Some(33),
1✔
4719
                data: "a_path".into(),
1✔
4720
                overviews: "another_path".into(),
1✔
4721
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4722
            }],
1✔
4723
        )
1✔
4724
        .await;
4✔
4725

4726
        assert_sql_type(
1✔
4727
            pool,
1✔
4728
            "PangaeaDataProviderDefinition",
1✔
4729
            [PangaeaDataProviderDefinition {
1✔
4730
                name: "pangaea".to_string(),
1✔
4731
                description: "pangaea".to_string(),
1✔
4732
                priority: None,
1✔
4733
                base_url: "http://base".try_into().unwrap(),
1✔
4734
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4735
            }],
1✔
4736
        )
1✔
4737
        .await;
4✔
4738

4739
        assert_sql_type(
1✔
4740
            pool,
1✔
4741
            "DataProviderDefinition",
1✔
4742
            [
1✔
4743
                TypedDataProviderDefinition::ArunaDataProviderDefinition(
1✔
4744
                    ArunaDataProviderDefinition {
1✔
4745
                        id: DataProviderId::from_str("86a7f7ce-1bab-4ce9-a32b-172c0f958ee0")
1✔
4746
                            .unwrap(),
1✔
4747
                        name: "NFDI".to_string(),
1✔
4748
                        description: "NFDI".to_string(),
1✔
4749
                        priority: Some(33),
1✔
4750
                        api_url: "http://test".to_string(),
1✔
4751
                        project_id: "project".to_string(),
1✔
4752
                        api_token: "api_token".to_string(),
1✔
4753
                        filter_label: "filter".to_string(),
1✔
4754
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4755
                    },
1✔
4756
                ),
1✔
4757
                TypedDataProviderDefinition::GbifDataProviderDefinition(
1✔
4758
                    GbifDataProviderDefinition {
1✔
4759
                        name: "GBIF".to_string(),
1✔
4760
                        description: "GFBio".to_string(),
1✔
4761
                        priority: None,
1✔
4762
                        db_config: DatabaseConnectionConfig {
1✔
4763
                            host: "testhost".to_string(),
1✔
4764
                            port: 1234,
1✔
4765
                            database: "testdb".to_string(),
1✔
4766
                            schema: "testschema".to_string(),
1✔
4767
                            user: "testuser".to_string(),
1✔
4768
                            password: "testpass".to_string(),
1✔
4769
                        },
1✔
4770
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4771
                        autocomplete_timeout: 3,
1✔
4772
                        columns: GbifDataProvider::all_columns(),
1✔
4773
                    },
1✔
4774
                ),
1✔
4775
                TypedDataProviderDefinition::GfbioAbcdDataProviderDefinition(
1✔
4776
                    GfbioAbcdDataProviderDefinition {
1✔
4777
                        name: "GFbio".to_string(),
1✔
4778
                        description: "GFBio".to_string(),
1✔
4779
                        priority: None,
1✔
4780
                        db_config: DatabaseConnectionConfig {
1✔
4781
                            host: "testhost".to_string(),
1✔
4782
                            port: 1234,
1✔
4783
                            database: "testdb".to_string(),
1✔
4784
                            schema: "testschema".to_string(),
1✔
4785
                            user: "testuser".to_string(),
1✔
4786
                            password: "testpass".to_string(),
1✔
4787
                        },
1✔
4788
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4789
                    },
1✔
4790
                ),
1✔
4791
                TypedDataProviderDefinition::GfbioCollectionsDataProviderDefinition(
1✔
4792
                    GfbioCollectionsDataProviderDefinition {
1✔
4793
                        name: "GFbio".to_string(),
1✔
4794
                        description: "GFBio".to_string(),
1✔
4795
                        priority: None,
1✔
4796
                        collection_api_url: "http://testhost".try_into().unwrap(),
1✔
4797
                        collection_api_auth_token: "token".to_string(),
1✔
4798
                        abcd_db_config: DatabaseConnectionConfig {
1✔
4799
                            host: "testhost".to_string(),
1✔
4800
                            port: 1234,
1✔
4801
                            database: "testdb".to_string(),
1✔
4802
                            schema: "testschema".to_string(),
1✔
4803
                            user: "testuser".to_string(),
1✔
4804
                            password: "testpass".to_string(),
1✔
4805
                        },
1✔
4806
                        pangaea_url: "http://panaea".try_into().unwrap(),
1✔
4807
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4808
                    },
1✔
4809
                ),
1✔
4810
                TypedDataProviderDefinition::EbvPortalDataProviderDefinition(
1✔
4811
                    EbvPortalDataProviderDefinition {
1✔
4812
                        name: "ebv".to_string(),
1✔
4813
                        description: "ebv".to_string(),
1✔
4814
                        priority: Some(33),
1✔
4815
                        data: "a_path".into(),
1✔
4816
                        base_url: "http://base".try_into().unwrap(),
1✔
4817
                        overviews: "another_path".into(),
1✔
4818
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4819
                    },
1✔
4820
                ),
1✔
4821
                TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(
1✔
4822
                    NetCdfCfDataProviderDefinition {
1✔
4823
                        name: "netcdfcf".to_string(),
1✔
4824
                        description: "netcdfcf".to_string(),
1✔
4825
                        priority: Some(33),
1✔
4826
                        data: "a_path".into(),
1✔
4827
                        overviews: "another_path".into(),
1✔
4828
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4829
                    },
1✔
4830
                ),
1✔
4831
                TypedDataProviderDefinition::PangaeaDataProviderDefinition(
1✔
4832
                    PangaeaDataProviderDefinition {
1✔
4833
                        name: "pangaea".to_string(),
1✔
4834
                        description: "pangaea".to_string(),
1✔
4835
                        priority: None,
1✔
4836
                        base_url: "http://base".try_into().unwrap(),
1✔
4837
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4838
                    },
1✔
4839
                ),
1✔
4840
            ],
1✔
4841
        )
1✔
4842
        .await;
28✔
4843
    }
1✔
4844
}
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