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

geo-engine / geoengine / 6039131541

31 Aug 2023 02:19PM UTC coverage: 90.092% (+0.05%) from 90.041%
6039131541

push

github

web-flow
Merge pull request #866 from geo-engine/provider-def-mapping

refactor provider defs to pro/non-pro

991 of 991 new or added lines in 21 files covered. (100.0%)

106883 of 118637 relevant lines covered (90.09%)

61031.6 hits per line

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

98.17
/services/src/contexts/postgres.rs
1
use crate::api::model::datatypes::DatasetName;
2
use crate::contexts::{ApplicationContext, QueryContextImpl, SessionId, SimpleSession};
3
use crate::contexts::{GeoEngineDb, SessionContext};
4
use crate::datasets::add_from_directory::{
5
    add_datasets_from_directory, add_providers_from_directory,
6
};
7
use crate::datasets::upload::{Volume, Volumes};
8
use crate::error::{self, Error, Result};
9
use crate::layers::add_from_directory::{
10
    add_layer_collections_from_directory, add_layers_from_directory, UNSORTED_COLLECTION_ID,
11
};
12
use crate::layers::storage::INTERNAL_LAYER_DB_ROOT_COLLECTION_ID;
13

14
use crate::projects::{ProjectId, STRectangle};
15
use crate::tasks::{SimpleTaskManager, SimpleTaskManagerBackend, SimpleTaskManagerContext};
16
use crate::util::config;
17
use crate::util::config::get_config_element;
18
use async_trait::async_trait;
19
use bb8_postgres::{
20
    bb8::Pool,
21
    bb8::PooledConnection,
22
    tokio_postgres::{error::SqlState, tls::MakeTlsConnect, tls::TlsConnect, Config, Socket},
23
    PostgresConnectionManager,
24
};
25
use geoengine_datatypes::raster::TilingSpecification;
26
use geoengine_operators::engine::ChunkByteSize;
27
use geoengine_operators::util::create_rayon_thread_pool;
28
use log::{debug, info};
29
use rayon::ThreadPool;
30
use std::path::PathBuf;
31
use std::sync::Arc;
32

33
use super::{ExecutionContextImpl, Session, SimpleApplicationContext};
34

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

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

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

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

76
        let pool = Pool::builder().build(pg_mgr).await?;
228✔
77
        let created_schema = Self::create_schema(pool.get().await?).await?;
2,736✔
78

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

87
        Ok(PostgresContext {
228✔
88
            default_session_id: session.id(),
228✔
89
            task_manager: Default::default(),
228✔
90
            thread_pool: create_rayon_thread_pool(0),
228✔
91
            exe_ctx_tiling_spec,
228✔
92
            query_ctx_chunk_size,
228✔
93
            pool,
228✔
94
            volumes: Default::default(),
228✔
95
        })
228✔
96
    }
228✔
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_schema(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(
322✔
151
        conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
322✔
152
    ) -> Result<DatabaseStatus> {
322✔
153
        let stmt = match conn
322✔
154
            .prepare("SELECT clear_database_on_start from geoengine;")
322✔
155
            .await
322✔
156
        {
157
            Ok(stmt) => stmt,
×
158
            Err(e) => {
322✔
159
                if let Some(code) = e.code() {
322✔
160
                    if *code == SqlState::UNDEFINED_TABLE {
322✔
161
                        info!("Initializing schema.");
×
162
                        return Ok(DatabaseStatus::Unitialized);
322✔
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
    }
322✔
177

178
    #[allow(clippy::too_many_lines)]
179
    /// Creates the database schema. Returns true if the schema was created, false if it already existed.
180
    pub(crate) async fn create_schema(
322✔
181
        mut conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
322✔
182
    ) -> Result<bool> {
322✔
183
        let postgres_config = get_config_element::<crate::util::config::Postgres>()?;
322✔
184

185
        let database_status = Self::check_schema_status(&conn).await?;
322✔
186

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

205
        let tx = conn.build_transaction().start().await?;
322✔
206

207
        tx.batch_execute(include_str!("schema.sql")).await?;
322✔
208

209
        let stmt = tx
322✔
210
            .prepare(
322✔
211
                "
322✔
212
            INSERT INTO geoengine (clear_database_on_start) VALUES ($1);",
322✔
213
            )
322✔
214
            .await?;
322✔
215

216
        tx.execute(&stmt, &[&postgres_config.clear_database_on_start])
322✔
217
            .await?;
322✔
218

219
        let stmt = tx
322✔
220
            .prepare(
322✔
221
                r#"
322✔
222
            INSERT INTO layer_collections (
322✔
223
                id,
322✔
224
                name,
322✔
225
                description,
322✔
226
                properties
322✔
227
            ) VALUES (
322✔
228
                $1,
322✔
229
                'Layers',
322✔
230
                'All available Geo Engine layers',
322✔
231
                ARRAY[]::"PropertyType"[]
322✔
232
            );"#,
322✔
233
            )
322✔
234
            .await?;
322✔
235

236
        tx.execute(&stmt, &[&INTERNAL_LAYER_DB_ROOT_COLLECTION_ID])
322✔
237
            .await?;
322✔
238

239
        let stmt = tx
322✔
240
            .prepare(
322✔
241
                r#"INSERT INTO layer_collections (
322✔
242
                id,
322✔
243
                name,
322✔
244
                description,
322✔
245
                properties
322✔
246
            ) VALUES (
322✔
247
                $1,
322✔
248
                'Unsorted',
322✔
249
                'Unsorted Layers',
322✔
250
                ARRAY[]::"PropertyType"[]
322✔
251
            );"#,
322✔
252
            )
322✔
253
            .await?;
322✔
254

255
        tx.execute(&stmt, &[&UNSORTED_COLLECTION_ID]).await?;
322✔
256

257
        let stmt = tx
322✔
258
            .prepare(
322✔
259
                r#"
322✔
260
            INSERT INTO collection_children (parent, child) 
322✔
261
            VALUES ($1, $2);"#,
322✔
262
            )
322✔
263
            .await?;
322✔
264

265
        tx.execute(
322✔
266
            &stmt,
322✔
267
            &[
322✔
268
                &INTERNAL_LAYER_DB_ROOT_COLLECTION_ID,
322✔
269
                &UNSORTED_COLLECTION_ID,
322✔
270
            ],
322✔
271
        )
322✔
272
        .await?;
322✔
273

274
        tx.commit().await?;
322✔
275

276
        debug!("Created database schema");
×
277

278
        Ok(true)
322✔
279
    }
322✔
280

281
    async fn create_default_session(
228✔
282
        conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
228✔
283
        session_id: SessionId,
228✔
284
    ) -> Result<()> {
228✔
285
        let stmt = conn
228✔
286
            .prepare("INSERT INTO sessions (id, project_id, view) VALUES ($1, NULL ,NULL);")
228✔
287
            .await?;
228✔
288

289
        conn.execute(&stmt, &[&session_id]).await?;
228✔
290

291
        Ok(())
228✔
292
    }
228✔
293
    async fn load_default_session(
65✔
294
        conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
65✔
295
    ) -> Result<SimpleSession> {
65✔
296
        let stmt = conn
65✔
297
            .prepare("SELECT id, project_id, view FROM sessions LIMIT 1;")
65✔
298
            .await?;
365✔
299

300
        let row = conn.query_one(&stmt, &[]).await?;
65✔
301

302
        Ok(SimpleSession::new(row.get(0), row.get(1), row.get(2)))
65✔
303
    }
65✔
304
}
305

306
#[async_trait]
307
impl<Tls> SimpleApplicationContext for PostgresContext<Tls>
308
where
309
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
310
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
311
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
312
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
313
{
314
    async fn default_session_id(&self) -> SessionId {
78✔
315
        self.default_session_id
78✔
316
    }
78✔
317

318
    async fn default_session(&self) -> Result<SimpleSession> {
65✔
319
        Self::load_default_session(self.pool.get().await?).await
430✔
320
    }
130✔
321

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

325
        let stmt = conn
1✔
326
            .prepare("UPDATE sessions SET project_id = $1 WHERE id = $2;")
1✔
327
            .await?;
1✔
328

329
        conn.execute(&stmt, &[&project, &self.default_session_id])
1✔
330
            .await?;
1✔
331

332
        Ok(())
1✔
333
    }
2✔
334

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

338
        let stmt = conn
1✔
339
            .prepare("UPDATE sessions SET view = $1 WHERE id = $2;")
1✔
340
            .await?;
1✔
341

342
        conn.execute(&stmt, &[&view, &self.default_session_id])
1✔
343
            .await?;
1✔
344

345
        Ok(())
1✔
346
    }
2✔
347

348
    async fn default_session_context(&self) -> Result<Self::SessionContext> {
277✔
349
        Ok(self.session_context(self.session_by_id(self.default_session_id).await?))
4,023✔
350
    }
554✔
351
}
352

353
#[async_trait]
354
impl<Tls> ApplicationContext for PostgresContext<Tls>
355
where
356
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
357
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
358
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
359
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
360
{
361
    type SessionContext = PostgresSessionContext<Tls>;
362
    type Session = SimpleSession;
363

364
    fn session_context(&self, session: Self::Session) -> Self::SessionContext {
441✔
365
        PostgresSessionContext {
441✔
366
            session,
441✔
367
            context: self.clone(),
441✔
368
        }
441✔
369
    }
441✔
370

371
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
379✔
372
        let mut conn = self.pool.get().await?;
379✔
373

374
        let tx = conn.build_transaction().start().await?;
376✔
375

376
        let stmt = tx
374✔
377
            .prepare(
374✔
378
                "
374✔
379
            SELECT           
374✔
380
                project_id,
374✔
381
                view
374✔
382
            FROM sessions
374✔
383
            WHERE id = $1;",
374✔
384
            )
374✔
385
            .await?;
3,279✔
386

387
        let row = tx
374✔
388
            .query_one(&stmt, &[&session_id])
374✔
389
            .await
355✔
390
            .map_err(|_error| error::Error::InvalidSession)?;
374✔
391

392
        Ok(SimpleSession::new(session_id, row.get(0), row.get(1)))
374✔
393
    }
753✔
394
}
395

396
#[derive(Clone)]
×
397
pub struct PostgresSessionContext<Tls>
398
where
399
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
400
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
401
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
402
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
403
{
404
    session: SimpleSession,
405
    context: PostgresContext<Tls>,
406
}
407

408
#[async_trait]
409
impl<Tls> SessionContext for PostgresSessionContext<Tls>
410
where
411
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
412
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
413
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
414
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
415
{
416
    type Session = SimpleSession;
417
    type GeoEngineDB = PostgresDb<Tls>;
418

419
    type TaskContext = SimpleTaskManagerContext;
420
    type TaskManager = SimpleTaskManager; // this does not persist across restarts
421
    type QueryContext = QueryContextImpl;
422
    type ExecutionContext = ExecutionContextImpl<Self::GeoEngineDB>;
423

424
    fn db(&self) -> Self::GeoEngineDB {
388✔
425
        PostgresDb::new(self.context.pool.clone())
388✔
426
    }
388✔
427

428
    fn tasks(&self) -> Self::TaskManager {
36✔
429
        SimpleTaskManager::new(self.context.task_manager.clone())
36✔
430
    }
36✔
431

432
    fn query_context(&self) -> Result<Self::QueryContext> {
27✔
433
        Ok(QueryContextImpl::new(
27✔
434
            self.context.query_ctx_chunk_size,
27✔
435
            self.context.thread_pool.clone(),
27✔
436
        ))
27✔
437
    }
27✔
438

439
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
50✔
440
        Ok(ExecutionContextImpl::<PostgresDb<Tls>>::new(
50✔
441
            self.db(),
50✔
442
            self.context.thread_pool.clone(),
50✔
443
            self.context.exe_ctx_tiling_spec,
50✔
444
        ))
50✔
445
    }
50✔
446

447
    fn volumes(&self) -> Result<Vec<Volume>> {
×
448
        Ok(self.context.volumes.volumes.clone())
×
449
    }
×
450

451
    fn session(&self) -> &Self::Session {
110✔
452
        &self.session
110✔
453
    }
110✔
454
}
455

456
pub struct PostgresDb<Tls>
457
where
458
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
459
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
460
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
461
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
462
{
463
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
464
}
465

466
impl<Tls> PostgresDb<Tls>
467
where
468
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
469
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
470
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
471
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
472
{
473
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>) -> Self {
388✔
474
        Self { conn_pool }
388✔
475
    }
388✔
476

477
    /// Check whether the namepsace of the given dataset is allowed for insertion
478
    /// Check whether the namepsace of the given dataset is allowed for insertion
479
    pub(crate) fn check_namespace(id: &DatasetName) -> Result<()> {
68✔
480
        // due to a lack of users, etc., we only allow one namespace for now
68✔
481
        if id.namespace.is_none() {
68✔
482
            Ok(())
68✔
483
        } else {
484
            Err(Error::InvalidDatasetIdNamespace)
×
485
        }
486
    }
68✔
487
}
488

489
impl<Tls> GeoEngineDb for PostgresDb<Tls>
490
where
491
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
492
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
493
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
494
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
495
{
496
}
497

498
impl From<config::Postgres> for Config {
499
    fn from(db_config: config::Postgres) -> Self {
1✔
500
        let mut pg_config = Config::new();
1✔
501
        pg_config
1✔
502
            .user(&db_config.user)
1✔
503
            .password(&db_config.password)
1✔
504
            .host(&db_config.host)
1✔
505
            .dbname(&db_config.database)
1✔
506
            .port(db_config.port)
1✔
507
            // fix schema by providing `search_path` option
1✔
508
            .options(&format!("-c search_path={}", db_config.schema));
1✔
509
        pg_config
1✔
510
    }
1✔
511
}
512

513
#[cfg(test)]
514
mod tests {
515
    use std::collections::HashMap;
516
    use std::marker::PhantomData;
517
    use std::str::FromStr;
518

519
    use super::*;
520
    use crate::api::model::datatypes::{
521
        Breakpoint, ClassificationMeasurement, Colorizer, ContinuousMeasurement, DataProviderId,
522
        DatasetName, DefaultColors, LayerId, LinearGradient, LogarithmicGradient, Measurement,
523
        MultiLineString, MultiPoint, MultiPolygon, NoGeometry, NotNanF64, OverUnderColors, Palette,
524
        RasterPropertiesEntryType, RasterPropertiesKey, RgbaColor, SpatialPartition2D, StringPair,
525
    };
526
    use crate::api::model::operators::{
527
        GdalSourceTimePlaceholder, PlotResultDescriptor, TimeReference, UnixTimeStampType,
528
    };
529
    use crate::api::model::responses::datasets::DatasetIdAndName;
530
    use crate::api::model::services::AddDataset;
531
    use crate::api::model::{ColorizerTypeDbType, HashMapTextTextDbType};
532
    use crate::datasets::external::aruna::ArunaDataProviderDefinition;
533
    use crate::datasets::external::gbif::GbifDataProviderDefinition;
534
    use crate::datasets::external::gfbio_abcd::GfbioAbcdDataProviderDefinition;
535
    use crate::datasets::external::gfbio_collections::GfbioCollectionsDataProviderDefinition;
536
    use crate::datasets::external::netcdfcf::{
537
        EbvPortalDataProviderDefinition, NetCdfCfDataProviderDefinition,
538
    };
539
    use crate::datasets::external::pangaea::PangaeaDataProviderDefinition;
540
    use crate::datasets::listing::{DatasetListOptions, DatasetListing, ProvenanceOutput};
541
    use crate::datasets::listing::{DatasetProvider, Provenance};
542
    use crate::datasets::storage::{DatasetStore, MetaDataDefinition};
543
    use crate::datasets::upload::{FileId, UploadId};
544
    use crate::datasets::upload::{FileUpload, Upload, UploadDb};
545
    use crate::layers::external::TypedDataProviderDefinition;
546
    use crate::layers::layer::{
547
        AddLayer, AddLayerCollection, CollectionItem, LayerCollection, LayerCollectionListOptions,
548
        LayerCollectionListing, LayerListing, Property, ProviderLayerCollectionId, ProviderLayerId,
549
    };
550
    use crate::layers::listing::{LayerCollectionId, LayerCollectionProvider};
551
    use crate::layers::storage::{
552
        LayerDb, LayerProviderDb, LayerProviderListing, LayerProviderListingOptions,
553
        INTERNAL_PROVIDER_ID,
554
    };
555
    use crate::projects::{
556
        ColorParam, CreateProject, DerivedColor, DerivedNumber, LayerUpdate, LineSymbology,
557
        LoadVersion, NumberParam, OrderBy, Plot, PlotUpdate, PointSymbology, PolygonSymbology,
558
        ProjectDb, ProjectFilter, ProjectId, ProjectLayer, ProjectListOptions, ProjectListing,
559
        RasterSymbology, STRectangle, StrokeParam, Symbology, TextSymbology, UpdateProject,
560
    };
561
    use crate::util::postgres::{assert_sql_type, DatabaseConnectionConfig};
562
    use crate::util::tests::register_ndvi_workflow_helper;
563
    use crate::util::tests::with_temp_context;
564
    use crate::workflows::registry::WorkflowRegistry;
565
    use crate::workflows::workflow::Workflow;
566
    use bb8_postgres::tokio_postgres::NoTls;
567
    use futures::join;
568
    use geoengine_datatypes::collections::VectorDataType;
569
    use geoengine_datatypes::primitives::CacheTtlSeconds;
570
    use geoengine_datatypes::primitives::{
571
        BoundingBox2D, Coordinate2D, FeatureDataType, RasterQueryRectangle, SpatialResolution,
572
        TimeGranularity, TimeInstance, TimeInterval, TimeStep, VectorQueryRectangle,
573
    };
574
    use geoengine_datatypes::raster::RasterDataType;
575
    use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceOption};
576
    use geoengine_datatypes::test_data;
577
    use geoengine_operators::engine::{
578
        MetaData, MetaDataProvider, MultipleRasterOrSingleVectorSource, PlotOperator,
579
        RasterResultDescriptor, StaticMetaData, TypedOperator, TypedResultDescriptor,
580
        VectorColumnInfo, VectorOperator, VectorResultDescriptor,
581
    };
582
    use geoengine_operators::mock::{MockPointSource, MockPointSourceParams};
583
    use geoengine_operators::plot::{Statistics, StatisticsParams};
584
    use geoengine_operators::source::{
585
        CsvHeader, FileNotFoundHandling, FormatSpecifics, GdalDatasetGeoTransform,
586
        GdalDatasetParameters, GdalLoadingInfo, GdalMetaDataList, GdalMetaDataRegular,
587
        GdalMetaDataStatic, GdalMetadataNetCdfCf, OgrSourceColumnSpec, OgrSourceDataset,
588
        OgrSourceDatasetTimeType, OgrSourceDurationSpec, OgrSourceErrorSpec, OgrSourceTimeFormat,
589
    };
590
    use geoengine_operators::util::input::MultiRasterOrVectorOperator::Raster;
591
    use ordered_float::NotNan;
592
    use serde_json::json;
593
    use tokio_postgres::config::Host;
594

595
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
596
    async fn test() {
1✔
597
        with_temp_context(|app_ctx, _| async move {
1✔
598
            let session = app_ctx.default_session().await.unwrap();
18✔
599

1✔
600
            create_projects(&app_ctx, &session).await;
74✔
601

602
            let projects = list_projects(&app_ctx, &session).await;
11✔
603

604
            let project_id = projects[0].id;
1✔
605

1✔
606
            update_projects(&app_ctx, &session, project_id).await;
157✔
607

608
            delete_project(&app_ctx, &session, project_id).await;
6✔
609
        })
1✔
610
        .await;
12✔
611
    }
612

613
    async fn delete_project(
1✔
614
        app_ctx: &PostgresContext<NoTls>,
1✔
615
        session: &SimpleSession,
1✔
616
        project_id: ProjectId,
1✔
617
    ) {
1✔
618
        let db = app_ctx.session_context(session.clone()).db();
1✔
619

1✔
620
        db.delete_project(project_id).await.unwrap();
3✔
621

1✔
622
        assert!(db.load_project(project_id).await.is_err());
3✔
623
    }
1✔
624

625
    #[allow(clippy::too_many_lines)]
626
    async fn update_projects(
1✔
627
        app_ctx: &PostgresContext<NoTls>,
1✔
628
        session: &SimpleSession,
1✔
629
        project_id: ProjectId,
1✔
630
    ) {
1✔
631
        let db = app_ctx.session_context(session.clone()).db();
1✔
632

633
        let project = db
1✔
634
            .load_project_version(project_id, LoadVersion::Latest)
1✔
635
            .await
38✔
636
            .unwrap();
1✔
637

638
        let layer_workflow_id = db
1✔
639
            .register_workflow(Workflow {
1✔
640
                operator: TypedOperator::Vector(
1✔
641
                    MockPointSource {
1✔
642
                        params: MockPointSourceParams {
1✔
643
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
644
                        },
1✔
645
                    }
1✔
646
                    .boxed(),
1✔
647
                ),
1✔
648
            })
1✔
649
            .await
3✔
650
            .unwrap();
1✔
651

1✔
652
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
3✔
653

654
        let plot_workflow_id = db
1✔
655
            .register_workflow(Workflow {
1✔
656
                operator: Statistics {
1✔
657
                    params: StatisticsParams {
1✔
658
                        column_names: vec![],
1✔
659
                    },
1✔
660
                    sources: MultipleRasterOrSingleVectorSource {
1✔
661
                        source: Raster(vec![]),
1✔
662
                    },
1✔
663
                }
1✔
664
                .boxed()
1✔
665
                .into(),
1✔
666
            })
1✔
667
            .await
3✔
668
            .unwrap();
1✔
669

1✔
670
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
3✔
671

672
        // add a plot
673
        let update = UpdateProject {
1✔
674
            id: project.id,
1✔
675
            name: Some("Test9 Updated".into()),
1✔
676
            description: None,
1✔
677
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
1✔
678
                workflow: layer_workflow_id,
1✔
679
                name: "TestLayer".into(),
1✔
680
                symbology: PointSymbology::default().into(),
1✔
681
                visibility: Default::default(),
1✔
682
            })]),
1✔
683
            plots: Some(vec![PlotUpdate::UpdateOrInsert(Plot {
1✔
684
                workflow: plot_workflow_id,
1✔
685
                name: "Test Plot".into(),
1✔
686
            })]),
1✔
687
            bounds: None,
1✔
688
            time_step: None,
1✔
689
        };
1✔
690
        db.update_project(update).await.unwrap();
65✔
691

692
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
693
        assert_eq!(versions.len(), 2);
1✔
694

695
        // add second plot
696
        let update = UpdateProject {
1✔
697
            id: project.id,
1✔
698
            name: Some("Test9 Updated".into()),
1✔
699
            description: None,
1✔
700
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
1✔
701
                workflow: layer_workflow_id,
1✔
702
                name: "TestLayer".into(),
1✔
703
                symbology: PointSymbology::default().into(),
1✔
704
                visibility: Default::default(),
1✔
705
            })]),
1✔
706
            plots: Some(vec![
1✔
707
                PlotUpdate::UpdateOrInsert(Plot {
1✔
708
                    workflow: plot_workflow_id,
1✔
709
                    name: "Test Plot".into(),
1✔
710
                }),
1✔
711
                PlotUpdate::UpdateOrInsert(Plot {
1✔
712
                    workflow: plot_workflow_id,
1✔
713
                    name: "Test Plot".into(),
1✔
714
                }),
1✔
715
            ]),
1✔
716
            bounds: None,
1✔
717
            time_step: None,
1✔
718
        };
1✔
719
        db.update_project(update).await.unwrap();
19✔
720

721
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
722
        assert_eq!(versions.len(), 3);
1✔
723

724
        // delete plots
725
        let update = UpdateProject {
1✔
726
            id: project.id,
1✔
727
            name: None,
1✔
728
            description: None,
1✔
729
            layers: None,
1✔
730
            plots: Some(vec![]),
1✔
731
            bounds: None,
1✔
732
            time_step: None,
1✔
733
        };
1✔
734
        db.update_project(update).await.unwrap();
14✔
735

736
        let versions = db.list_project_versions(project_id).await.unwrap();
3✔
737
        assert_eq!(versions.len(), 4);
1✔
738
    }
1✔
739

740
    async fn list_projects(
1✔
741
        app_ctx: &PostgresContext<NoTls>,
1✔
742
        session: &SimpleSession,
1✔
743
    ) -> Vec<ProjectListing> {
1✔
744
        let options = ProjectListOptions {
1✔
745
            filter: ProjectFilter::None,
1✔
746
            order: OrderBy::NameDesc,
1✔
747
            offset: 0,
1✔
748
            limit: 2,
1✔
749
        };
1✔
750

1✔
751
        let db = app_ctx.session_context(session.clone()).db();
1✔
752

753
        let projects = db.list_projects(options).await.unwrap();
11✔
754

1✔
755
        assert_eq!(projects.len(), 2);
1✔
756
        assert_eq!(projects[0].name, "Test9");
1✔
757
        assert_eq!(projects[1].name, "Test8");
1✔
758
        projects
1✔
759
    }
1✔
760

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

764
        for i in 0..10 {
11✔
765
            let create = CreateProject {
10✔
766
                name: format!("Test{i}"),
10✔
767
                description: format!("Test{}", 10 - i),
10✔
768
                bounds: STRectangle::new(
10✔
769
                    SpatialReferenceOption::Unreferenced,
10✔
770
                    0.,
10✔
771
                    0.,
10✔
772
                    1.,
10✔
773
                    1.,
10✔
774
                    0,
10✔
775
                    1,
10✔
776
                )
10✔
777
                .unwrap(),
10✔
778
                time_step: None,
10✔
779
            };
10✔
780
            db.create_project(create).await.unwrap();
74✔
781
        }
782
    }
1✔
783

784
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
785
    async fn it_persists_workflows() {
1✔
786
        with_temp_context(|app_ctx, _pg_config| async move {
1✔
787
            let workflow = Workflow {
1✔
788
                operator: TypedOperator::Vector(
1✔
789
                    MockPointSource {
1✔
790
                        params: MockPointSourceParams {
1✔
791
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
792
                        },
1✔
793
                    }
1✔
794
                    .boxed(),
1✔
795
                ),
1✔
796
            };
1✔
797

798
            let session = app_ctx.default_session().await.unwrap();
18✔
799
        let ctx = app_ctx.session_context(session);
1✔
800

1✔
801
            let db = ctx
1✔
802
                .db();
1✔
803
            let id = db
1✔
804
                .register_workflow(workflow)
1✔
805
                .await
3✔
806
                .unwrap();
1✔
807

1✔
808
            drop(ctx);
1✔
809

810
            let workflow = db.load_workflow(&id).await.unwrap();
3✔
811

1✔
812
            let json = serde_json::to_string(&workflow).unwrap();
1✔
813
            assert_eq!(json, 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✔
814
        })
1✔
815
        .await;
12✔
816
    }
817

818
    #[allow(clippy::too_many_lines)]
819
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
820
    async fn it_persists_datasets() {
1✔
821
        with_temp_context(|app_ctx, _| async move {
1✔
822
            let loading_info = OgrSourceDataset {
1✔
823
                file_name: PathBuf::from("test.csv"),
1✔
824
                layer_name: "test.csv".to_owned(),
1✔
825
                data_type: Some(VectorDataType::MultiPoint),
1✔
826
                time: OgrSourceDatasetTimeType::Start {
1✔
827
                    start_field: "start".to_owned(),
1✔
828
                    start_format: OgrSourceTimeFormat::Auto,
1✔
829
                    duration: OgrSourceDurationSpec::Zero,
1✔
830
                },
1✔
831
                default_geometry: None,
1✔
832
                columns: Some(OgrSourceColumnSpec {
1✔
833
                    format_specifics: Some(FormatSpecifics::Csv {
1✔
834
                        header: CsvHeader::Auto,
1✔
835
                    }),
1✔
836
                    x: "x".to_owned(),
1✔
837
                    y: None,
1✔
838
                    int: vec![],
1✔
839
                    float: vec![],
1✔
840
                    text: vec![],
1✔
841
                    bool: vec![],
1✔
842
                    datetime: vec![],
1✔
843
                    rename: None,
1✔
844
                }),
1✔
845
                force_ogr_time_filter: false,
1✔
846
                force_ogr_spatial_filter: false,
1✔
847
                on_error: OgrSourceErrorSpec::Ignore,
1✔
848
                sql_query: None,
1✔
849
                attribute_query: None,
1✔
850
                cache_ttl: CacheTtlSeconds::default(),
1✔
851
            };
1✔
852

1✔
853
            let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
854
                OgrSourceDataset,
1✔
855
                VectorResultDescriptor,
1✔
856
                VectorQueryRectangle,
1✔
857
            > {
1✔
858
                loading_info: loading_info.clone(),
1✔
859
                result_descriptor: VectorResultDescriptor {
1✔
860
                    data_type: VectorDataType::MultiPoint,
1✔
861
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
862
                    columns: [(
1✔
863
                        "foo".to_owned(),
1✔
864
                        VectorColumnInfo {
1✔
865
                            data_type: FeatureDataType::Float,
1✔
866
                            measurement: Measurement::Unitless.into(),
1✔
867
                        },
1✔
868
                    )]
1✔
869
                    .into_iter()
1✔
870
                    .collect(),
1✔
871
                    time: None,
1✔
872
                    bbox: None,
1✔
873
                },
1✔
874
                phantom: Default::default(),
1✔
875
            });
1✔
876

877
            let session = app_ctx.default_session().await.unwrap();
18✔
878

1✔
879
            let dataset_name = DatasetName::new(None, "my_dataset");
1✔
880

1✔
881
            let db = app_ctx.session_context(session.clone()).db();
1✔
882
            let wrap = db.wrap_meta_data(meta_data);
1✔
883
            let DatasetIdAndName {
884
                id: dataset_id,
1✔
885
                name: dataset_name,
1✔
886
            } = db
1✔
887
                .add_dataset(
1✔
888
                    AddDataset {
1✔
889
                        name: Some(dataset_name.clone()),
1✔
890
                        display_name: "Ogr Test".to_owned(),
1✔
891
                        description: "desc".to_owned(),
1✔
892
                        source_operator: "OgrSource".to_owned(),
1✔
893
                        symbology: None,
1✔
894
                        provenance: Some(vec![Provenance {
1✔
895
                            citation: "citation".to_owned(),
1✔
896
                            license: "license".to_owned(),
1✔
897
                            uri: "uri".to_owned(),
1✔
898
                        }]),
1✔
899
                    },
1✔
900
                    wrap,
1✔
901
                )
1✔
902
                .await
151✔
903
                .unwrap();
1✔
904

905
            let datasets = db
1✔
906
                .list_datasets(DatasetListOptions {
1✔
907
                    filter: None,
1✔
908
                    order: crate::datasets::listing::OrderBy::NameAsc,
1✔
909
                    offset: 0,
1✔
910
                    limit: 10,
1✔
911
                })
1✔
912
                .await
3✔
913
                .unwrap();
1✔
914

1✔
915
            assert_eq!(datasets.len(), 1);
1✔
916

917
            assert_eq!(
1✔
918
                datasets[0],
1✔
919
                DatasetListing {
1✔
920
                    id: dataset_id,
1✔
921
                    name: dataset_name,
1✔
922
                    display_name: "Ogr Test".to_owned(),
1✔
923
                    description: "desc".to_owned(),
1✔
924
                    source_operator: "OgrSource".to_owned(),
1✔
925
                    symbology: None,
1✔
926
                    tags: vec![],
1✔
927
                    result_descriptor: TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
928
                        data_type: VectorDataType::MultiPoint,
1✔
929
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
930
                        columns: [(
1✔
931
                            "foo".to_owned(),
1✔
932
                            VectorColumnInfo {
1✔
933
                                data_type: FeatureDataType::Float,
1✔
934
                                measurement: Measurement::Unitless.into()
1✔
935
                            }
1✔
936
                        )]
1✔
937
                        .into_iter()
1✔
938
                        .collect(),
1✔
939
                        time: None,
1✔
940
                        bbox: None,
1✔
941
                    })
1✔
942
                    .into(),
1✔
943
                },
1✔
944
            );
1✔
945

946
            let provenance = db.load_provenance(&dataset_id).await.unwrap();
3✔
947

1✔
948
            assert_eq!(
1✔
949
                provenance,
1✔
950
                ProvenanceOutput {
1✔
951
                    data: dataset_id.into(),
1✔
952
                    provenance: Some(vec![Provenance {
1✔
953
                        citation: "citation".to_owned(),
1✔
954
                        license: "license".to_owned(),
1✔
955
                        uri: "uri".to_owned(),
1✔
956
                    }])
1✔
957
                }
1✔
958
            );
1✔
959

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

963
            assert_eq!(
1✔
964
                meta_data
1✔
965
                    .loading_info(VectorQueryRectangle {
1✔
966
                        spatial_bounds: BoundingBox2D::new_unchecked(
1✔
967
                            (-180., -90.).into(),
1✔
968
                            (180., 90.).into()
1✔
969
                        ),
1✔
970
                        time_interval: TimeInterval::default(),
1✔
971
                        spatial_resolution: SpatialResolution::zero_point_one(),
1✔
972
                    })
1✔
973
                    .await
×
974
                    .unwrap(),
1✔
975
                loading_info
976
            );
977
        })
1✔
978
        .await;
11✔
979
    }
980

981
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
982
    async fn it_persists_uploads() {
1✔
983
        with_temp_context(|app_ctx, _| async move {
1✔
984
            let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
985
            let input = Upload {
1✔
986
                id,
1✔
987
                files: vec![FileUpload {
1✔
988
                    id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
989
                    name: "test.csv".to_owned(),
1✔
990
                    byte_size: 1337,
1✔
991
                }],
1✔
992
            };
1✔
993

994
            let session = app_ctx.default_session().await.unwrap();
18✔
995

1✔
996
            let db = app_ctx.session_context(session.clone()).db();
1✔
997

1✔
998
            db.create_upload(input.clone()).await.unwrap();
6✔
999

1000
            let upload = db.load_upload(id).await.unwrap();
3✔
1001

1✔
1002
            assert_eq!(upload, input);
1✔
1003
        })
1✔
1004
        .await;
11✔
1005
    }
1006

1007
    #[allow(clippy::too_many_lines)]
1008
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1009
    async fn it_persists_layer_providers() {
1✔
1010
        with_temp_context(|app_ctx, _| async move {
1✔
1011
            let db = app_ctx.default_session_context().await.unwrap().db();
19✔
1012

1✔
1013
            let provider = NetCdfCfDataProviderDefinition {
1✔
1014
                name: "netcdfcf".to_string(),
1✔
1015
                path: test_data!("netcdf4d/").into(),
1✔
1016
                overviews: test_data!("netcdf4d/overviews/").into(),
1✔
1017
                cache_ttl: CacheTtlSeconds::new(0),
1✔
1018
            };
1✔
1019

1020
            let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
21✔
1021

1022
            let providers = db
1✔
1023
                .list_layer_providers(LayerProviderListingOptions {
1✔
1024
                    offset: 0,
1✔
1025
                    limit: 10,
1✔
1026
                })
1✔
1027
                .await
3✔
1028
                .unwrap();
1✔
1029

1✔
1030
            assert_eq!(providers.len(), 1);
1✔
1031

1032
            assert_eq!(
1✔
1033
                providers[0],
1✔
1034
                LayerProviderListing {
1✔
1035
                    id: provider_id,
1✔
1036
                    name: "netcdfcf".to_owned(),
1✔
1037
                    description: "NetCdfCfProviderDefinition".to_owned(),
1✔
1038
                }
1✔
1039
            );
1✔
1040

1041
            let provider = db.load_layer_provider(provider_id).await.unwrap();
3✔
1042

1043
            let datasets = provider
1✔
1044
                .load_layer_collection(
1045
                    &provider.get_root_layer_collection_id().await.unwrap(),
1✔
1046
                    LayerCollectionListOptions {
1✔
1047
                        offset: 0,
1✔
1048
                        limit: 10,
1✔
1049
                    },
1✔
1050
                )
1051
                .await
4✔
1052
                .unwrap();
1✔
1053

1✔
1054
            assert_eq!(datasets.items.len(), 3);
1✔
1055
        })
1✔
1056
        .await;
9✔
1057
    }
1058

1059
    #[allow(clippy::too_many_lines)]
1060
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1061
    async fn it_loads_all_meta_data_types() {
1✔
1062
        with_temp_context(|app_ctx, _| async move {
1✔
1063
            let session = app_ctx.default_session().await.unwrap();
18✔
1064

1✔
1065
            let db = app_ctx.session_context(session.clone()).db();
1✔
1066

1✔
1067
            let vector_descriptor = VectorResultDescriptor {
1✔
1068
                data_type: VectorDataType::Data,
1✔
1069
                spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1070
                columns: Default::default(),
1✔
1071
                time: None,
1✔
1072
                bbox: None,
1✔
1073
            };
1✔
1074

1✔
1075
            let raster_descriptor = RasterResultDescriptor {
1✔
1076
                data_type: RasterDataType::U8,
1✔
1077
                spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1078
                measurement: Default::default(),
1✔
1079
                time: None,
1✔
1080
                bbox: None,
1✔
1081
                resolution: None,
1✔
1082
            };
1✔
1083

1✔
1084
            let vector_ds = AddDataset {
1✔
1085
                name: None,
1✔
1086
                display_name: "OgrDataset".to_string(),
1✔
1087
                description: "My Ogr dataset".to_string(),
1✔
1088
                source_operator: "OgrSource".to_string(),
1✔
1089
                symbology: None,
1✔
1090
                provenance: None,
1✔
1091
            };
1✔
1092

1✔
1093
            let raster_ds = AddDataset {
1✔
1094
                name: None,
1✔
1095
                display_name: "GdalDataset".to_string(),
1✔
1096
                description: "My Gdal dataset".to_string(),
1✔
1097
                source_operator: "GdalSource".to_string(),
1✔
1098
                symbology: None,
1✔
1099
                provenance: None,
1✔
1100
            };
1✔
1101

1✔
1102
            let gdal_params = GdalDatasetParameters {
1✔
1103
                file_path: Default::default(),
1✔
1104
                rasterband_channel: 0,
1✔
1105
                geo_transform: GdalDatasetGeoTransform {
1✔
1106
                    origin_coordinate: Default::default(),
1✔
1107
                    x_pixel_size: 0.0,
1✔
1108
                    y_pixel_size: 0.0,
1✔
1109
                },
1✔
1110
                width: 0,
1✔
1111
                height: 0,
1✔
1112
                file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1113
                no_data_value: None,
1✔
1114
                properties_mapping: None,
1✔
1115
                gdal_open_options: None,
1✔
1116
                gdal_config_options: None,
1✔
1117
                allow_alphaband_as_mask: false,
1✔
1118
                retry: None,
1✔
1119
            };
1✔
1120

1✔
1121
            let meta = StaticMetaData {
1✔
1122
                loading_info: OgrSourceDataset {
1✔
1123
                    file_name: Default::default(),
1✔
1124
                    layer_name: String::new(),
1✔
1125
                    data_type: None,
1✔
1126
                    time: Default::default(),
1✔
1127
                    default_geometry: None,
1✔
1128
                    columns: None,
1✔
1129
                    force_ogr_time_filter: false,
1✔
1130
                    force_ogr_spatial_filter: false,
1✔
1131
                    on_error: OgrSourceErrorSpec::Ignore,
1✔
1132
                    sql_query: None,
1✔
1133
                    attribute_query: None,
1✔
1134
                    cache_ttl: CacheTtlSeconds::default(),
1✔
1135
                },
1✔
1136
                result_descriptor: vector_descriptor.clone(),
1✔
1137
                phantom: Default::default(),
1✔
1138
            };
1✔
1139

1✔
1140
            let meta = db.wrap_meta_data(MetaDataDefinition::OgrMetaData(meta));
1✔
1141

1142
            let id = db.add_dataset(vector_ds, meta).await.unwrap().id;
153✔
1143

1144
            let meta: geoengine_operators::util::Result<
1✔
1145
                Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1146
            > = db.meta_data(&id.into()).await;
3✔
1147

1148
            assert!(meta.is_ok());
1✔
1149

1150
            let meta = GdalMetaDataRegular {
1✔
1151
                result_descriptor: raster_descriptor.clone(),
1✔
1152
                params: gdal_params.clone(),
1✔
1153
                time_placeholders: Default::default(),
1✔
1154
                data_time: Default::default(),
1✔
1155
                step: TimeStep {
1✔
1156
                    granularity: TimeGranularity::Millis,
1✔
1157
                    step: 0,
1✔
1158
                },
1✔
1159
                cache_ttl: CacheTtlSeconds::default(),
1✔
1160
            };
1✔
1161

1✔
1162
            let meta = db.wrap_meta_data(MetaDataDefinition::GdalMetaDataRegular(meta));
1✔
1163

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

1166
            let meta: geoengine_operators::util::Result<
1✔
1167
                Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1168
            > = db.meta_data(&id.into()).await;
3✔
1169

1170
            assert!(meta.is_ok());
1✔
1171

1172
            let meta = GdalMetaDataStatic {
1✔
1173
                time: None,
1✔
1174
                params: gdal_params.clone(),
1✔
1175
                result_descriptor: raster_descriptor.clone(),
1✔
1176
                cache_ttl: CacheTtlSeconds::default(),
1✔
1177
            };
1✔
1178

1✔
1179
            let meta = db.wrap_meta_data(MetaDataDefinition::GdalStatic(meta));
1✔
1180

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

1183
            let meta: geoengine_operators::util::Result<
1✔
1184
                Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1185
            > = db.meta_data(&id.into()).await;
3✔
1186

1187
            assert!(meta.is_ok());
1✔
1188

1189
            let meta = GdalMetaDataList {
1✔
1190
                result_descriptor: raster_descriptor.clone(),
1✔
1191
                params: vec![],
1✔
1192
            };
1✔
1193

1✔
1194
            let meta = db.wrap_meta_data(MetaDataDefinition::GdalMetaDataList(meta));
1✔
1195

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

1198
            let meta: geoengine_operators::util::Result<
1✔
1199
                Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1200
            > = db.meta_data(&id.into()).await;
3✔
1201

1202
            assert!(meta.is_ok());
1✔
1203

1204
            let meta = GdalMetadataNetCdfCf {
1✔
1205
                result_descriptor: raster_descriptor.clone(),
1✔
1206
                params: gdal_params.clone(),
1✔
1207
                start: TimeInstance::MIN,
1✔
1208
                end: TimeInstance::MAX,
1✔
1209
                step: TimeStep {
1✔
1210
                    granularity: TimeGranularity::Millis,
1✔
1211
                    step: 0,
1✔
1212
                },
1✔
1213
                band_offset: 0,
1✔
1214
                cache_ttl: CacheTtlSeconds::default(),
1✔
1215
            };
1✔
1216

1✔
1217
            let meta = db.wrap_meta_data(MetaDataDefinition::GdalMetadataNetCdfCf(meta));
1✔
1218

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

1221
            let meta: geoengine_operators::util::Result<
1✔
1222
                Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1223
            > = db.meta_data(&id.into()).await;
3✔
1224

1225
            assert!(meta.is_ok());
1✔
1226
        })
1✔
1227
        .await;
10✔
1228
    }
1229

1230
    #[allow(clippy::too_many_lines)]
1231
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1232
    async fn it_collects_layers() {
1✔
1233
        with_temp_context(|app_ctx, _| async move {
1✔
1234
            let session = app_ctx.default_session().await.unwrap();
18✔
1235

1✔
1236
            let layer_db = app_ctx.session_context(session).db();
1✔
1237

1✔
1238
            let workflow = Workflow {
1✔
1239
                operator: TypedOperator::Vector(
1✔
1240
                    MockPointSource {
1✔
1241
                        params: MockPointSourceParams {
1✔
1242
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1243
                        },
1✔
1244
                    }
1✔
1245
                    .boxed(),
1✔
1246
                ),
1✔
1247
            };
1✔
1248

1249
            let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1250

1251
            let layer1 = layer_db
1✔
1252
                .add_layer(
1✔
1253
                    AddLayer {
1✔
1254
                        name: "Layer1".to_string(),
1✔
1255
                        description: "Layer 1".to_string(),
1✔
1256
                        symbology: None,
1✔
1257
                        workflow: workflow.clone(),
1✔
1258
                        metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1259
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1260
                    },
1✔
1261
                    &root_collection_id,
1✔
1262
                )
1✔
1263
                .await
43✔
1264
                .unwrap();
1✔
1265

1266
            assert_eq!(
1✔
1267
                layer_db.load_layer(&layer1).await.unwrap(),
3✔
1268
                crate::layers::layer::Layer {
1✔
1269
                    id: ProviderLayerId {
1✔
1270
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1271
                        layer_id: layer1.clone(),
1✔
1272
                    },
1✔
1273
                    name: "Layer1".to_string(),
1✔
1274
                    description: "Layer 1".to_string(),
1✔
1275
                    symbology: None,
1✔
1276
                    workflow: workflow.clone(),
1✔
1277
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1278
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1279
                }
1✔
1280
            );
1281

1282
            let collection1_id = layer_db
1✔
1283
                .add_layer_collection(
1✔
1284
                    AddLayerCollection {
1✔
1285
                        name: "Collection1".to_string(),
1✔
1286
                        description: "Collection 1".to_string(),
1✔
1287
                        properties: Default::default(),
1✔
1288
                    },
1✔
1289
                    &root_collection_id,
1✔
1290
                )
1✔
1291
                .await
7✔
1292
                .unwrap();
1✔
1293

1294
            let layer2 = layer_db
1✔
1295
                .add_layer(
1✔
1296
                    AddLayer {
1✔
1297
                        name: "Layer2".to_string(),
1✔
1298
                        description: "Layer 2".to_string(),
1✔
1299
                        symbology: None,
1✔
1300
                        workflow: workflow.clone(),
1✔
1301
                        metadata: Default::default(),
1✔
1302
                        properties: Default::default(),
1✔
1303
                    },
1✔
1304
                    &collection1_id,
1✔
1305
                )
1✔
1306
                .await
9✔
1307
                .unwrap();
1✔
1308

1309
            let collection2_id = layer_db
1✔
1310
                .add_layer_collection(
1✔
1311
                    AddLayerCollection {
1✔
1312
                        name: "Collection2".to_string(),
1✔
1313
                        description: "Collection 2".to_string(),
1✔
1314
                        properties: Default::default(),
1✔
1315
                    },
1✔
1316
                    &collection1_id,
1✔
1317
                )
1✔
1318
                .await
7✔
1319
                .unwrap();
1✔
1320

1✔
1321
            layer_db
1✔
1322
                .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1323
                .await
3✔
1324
                .unwrap();
1✔
1325

1326
            let root_collection = layer_db
1✔
1327
                .load_layer_collection(
1✔
1328
                    &root_collection_id,
1✔
1329
                    LayerCollectionListOptions {
1✔
1330
                        offset: 0,
1✔
1331
                        limit: 20,
1✔
1332
                    },
1✔
1333
                )
1✔
1334
                .await
5✔
1335
                .unwrap();
1✔
1336

1✔
1337
            assert_eq!(
1✔
1338
                root_collection,
1✔
1339
                LayerCollection {
1✔
1340
                    id: ProviderLayerCollectionId {
1✔
1341
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1342
                        collection_id: root_collection_id,
1✔
1343
                    },
1✔
1344
                    name: "Layers".to_string(),
1✔
1345
                    description: "All available Geo Engine layers".to_string(),
1✔
1346
                    items: vec![
1✔
1347
                        CollectionItem::Collection(LayerCollectionListing {
1✔
1348
                            id: ProviderLayerCollectionId {
1✔
1349
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1350
                                collection_id: collection1_id.clone(),
1✔
1351
                            },
1✔
1352
                            name: "Collection1".to_string(),
1✔
1353
                            description: "Collection 1".to_string(),
1✔
1354
                            properties: Default::default(),
1✔
1355
                        }),
1✔
1356
                        CollectionItem::Collection(LayerCollectionListing {
1✔
1357
                            id: ProviderLayerCollectionId {
1✔
1358
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1359
                                collection_id: LayerCollectionId(
1✔
1360
                                    UNSORTED_COLLECTION_ID.to_string()
1✔
1361
                                ),
1✔
1362
                            },
1✔
1363
                            name: "Unsorted".to_string(),
1✔
1364
                            description: "Unsorted Layers".to_string(),
1✔
1365
                            properties: Default::default(),
1✔
1366
                        }),
1✔
1367
                        CollectionItem::Layer(LayerListing {
1✔
1368
                            id: ProviderLayerId {
1✔
1369
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1370
                                layer_id: layer1,
1✔
1371
                            },
1✔
1372
                            name: "Layer1".to_string(),
1✔
1373
                            description: "Layer 1".to_string(),
1✔
1374
                            properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1375
                        })
1✔
1376
                    ],
1✔
1377
                    entry_label: None,
1✔
1378
                    properties: vec![],
1✔
1379
                }
1✔
1380
            );
1✔
1381

1382
            let collection1 = layer_db
1✔
1383
                .load_layer_collection(
1✔
1384
                    &collection1_id,
1✔
1385
                    LayerCollectionListOptions {
1✔
1386
                        offset: 0,
1✔
1387
                        limit: 20,
1✔
1388
                    },
1✔
1389
                )
1✔
1390
                .await
5✔
1391
                .unwrap();
1✔
1392

1✔
1393
            assert_eq!(
1✔
1394
                collection1,
1✔
1395
                LayerCollection {
1✔
1396
                    id: ProviderLayerCollectionId {
1✔
1397
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1398
                        collection_id: collection1_id,
1✔
1399
                    },
1✔
1400
                    name: "Collection1".to_string(),
1✔
1401
                    description: "Collection 1".to_string(),
1✔
1402
                    items: vec![
1✔
1403
                        CollectionItem::Collection(LayerCollectionListing {
1✔
1404
                            id: ProviderLayerCollectionId {
1✔
1405
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1406
                                collection_id: collection2_id,
1✔
1407
                            },
1✔
1408
                            name: "Collection2".to_string(),
1✔
1409
                            description: "Collection 2".to_string(),
1✔
1410
                            properties: Default::default(),
1✔
1411
                        }),
1✔
1412
                        CollectionItem::Layer(LayerListing {
1✔
1413
                            id: ProviderLayerId {
1✔
1414
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1415
                                layer_id: layer2,
1✔
1416
                            },
1✔
1417
                            name: "Layer2".to_string(),
1✔
1418
                            description: "Layer 2".to_string(),
1✔
1419
                            properties: vec![],
1✔
1420
                        })
1✔
1421
                    ],
1✔
1422
                    entry_label: None,
1✔
1423
                    properties: vec![],
1✔
1424
                }
1✔
1425
            );
1✔
1426
        })
1✔
1427
        .await;
12✔
1428
    }
1429

1430
    #[allow(clippy::too_many_lines)]
1431
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1432
    async fn it_removes_layer_collections() {
1✔
1433
        with_temp_context(|app_ctx, _| async move {
1✔
1434
            let session = app_ctx.default_session().await.unwrap();
18✔
1435

1✔
1436
            let layer_db = app_ctx.session_context(session).db();
1✔
1437

1✔
1438
            let layer = AddLayer {
1✔
1439
                name: "layer".to_string(),
1✔
1440
                description: "description".to_string(),
1✔
1441
                workflow: Workflow {
1✔
1442
                    operator: TypedOperator::Vector(
1✔
1443
                        MockPointSource {
1✔
1444
                            params: MockPointSourceParams {
1✔
1445
                                points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1446
                            },
1✔
1447
                        }
1✔
1448
                        .boxed(),
1✔
1449
                    ),
1✔
1450
                },
1✔
1451
                symbology: None,
1✔
1452
                metadata: Default::default(),
1✔
1453
                properties: Default::default(),
1✔
1454
            };
1✔
1455

1456
            let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1457

1✔
1458
            let collection = AddLayerCollection {
1✔
1459
                name: "top collection".to_string(),
1✔
1460
                description: "description".to_string(),
1✔
1461
                properties: Default::default(),
1✔
1462
            };
1✔
1463

1464
            let top_c_id = layer_db
1✔
1465
                .add_layer_collection(collection, root_collection)
1✔
1466
                .await
10✔
1467
                .unwrap();
1✔
1468

1469
            let l_id = layer_db.add_layer(layer, &top_c_id).await.unwrap();
40✔
1470

1✔
1471
            let collection = AddLayerCollection {
1✔
1472
                name: "empty collection".to_string(),
1✔
1473
                description: "description".to_string(),
1✔
1474
                properties: Default::default(),
1✔
1475
            };
1✔
1476

1477
            let empty_c_id = layer_db
1✔
1478
                .add_layer_collection(collection, &top_c_id)
1✔
1479
                .await
7✔
1480
                .unwrap();
1✔
1481

1482
            let items = layer_db
1✔
1483
                .load_layer_collection(
1✔
1484
                    &top_c_id,
1✔
1485
                    LayerCollectionListOptions {
1✔
1486
                        offset: 0,
1✔
1487
                        limit: 20,
1✔
1488
                    },
1✔
1489
                )
1✔
1490
                .await
5✔
1491
                .unwrap();
1✔
1492

1✔
1493
            assert_eq!(
1✔
1494
                items,
1✔
1495
                LayerCollection {
1✔
1496
                    id: ProviderLayerCollectionId {
1✔
1497
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1498
                        collection_id: top_c_id.clone(),
1✔
1499
                    },
1✔
1500
                    name: "top collection".to_string(),
1✔
1501
                    description: "description".to_string(),
1✔
1502
                    items: vec![
1✔
1503
                        CollectionItem::Collection(LayerCollectionListing {
1✔
1504
                            id: ProviderLayerCollectionId {
1✔
1505
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1506
                                collection_id: empty_c_id.clone(),
1✔
1507
                            },
1✔
1508
                            name: "empty collection".to_string(),
1✔
1509
                            description: "description".to_string(),
1✔
1510
                            properties: Default::default(),
1✔
1511
                        }),
1✔
1512
                        CollectionItem::Layer(LayerListing {
1✔
1513
                            id: ProviderLayerId {
1✔
1514
                                provider_id: INTERNAL_PROVIDER_ID,
1✔
1515
                                layer_id: l_id.clone(),
1✔
1516
                            },
1✔
1517
                            name: "layer".to_string(),
1✔
1518
                            description: "description".to_string(),
1✔
1519
                            properties: vec![],
1✔
1520
                        })
1✔
1521
                    ],
1✔
1522
                    entry_label: None,
1✔
1523
                    properties: vec![],
1✔
1524
                }
1✔
1525
            );
1✔
1526

1527
            // remove empty collection
1528
            layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
9✔
1529

1530
            let items = layer_db
1✔
1531
                .load_layer_collection(
1✔
1532
                    &top_c_id,
1✔
1533
                    LayerCollectionListOptions {
1✔
1534
                        offset: 0,
1✔
1535
                        limit: 20,
1✔
1536
                    },
1✔
1537
                )
1✔
1538
                .await
5✔
1539
                .unwrap();
1✔
1540

1✔
1541
            assert_eq!(
1✔
1542
                items,
1✔
1543
                LayerCollection {
1✔
1544
                    id: ProviderLayerCollectionId {
1✔
1545
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
1546
                        collection_id: top_c_id.clone(),
1✔
1547
                    },
1✔
1548
                    name: "top collection".to_string(),
1✔
1549
                    description: "description".to_string(),
1✔
1550
                    items: vec![CollectionItem::Layer(LayerListing {
1✔
1551
                        id: ProviderLayerId {
1✔
1552
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1553
                            layer_id: l_id.clone(),
1✔
1554
                        },
1✔
1555
                        name: "layer".to_string(),
1✔
1556
                        description: "description".to_string(),
1✔
1557
                        properties: vec![],
1✔
1558
                    })],
1✔
1559
                    entry_label: None,
1✔
1560
                    properties: vec![],
1✔
1561
                }
1✔
1562
            );
1✔
1563

1564
            // remove top (not root) collection
1565
            layer_db.remove_layer_collection(&top_c_id).await.unwrap();
9✔
1566

1✔
1567
            layer_db
1✔
1568
                .load_layer_collection(
1✔
1569
                    &top_c_id,
1✔
1570
                    LayerCollectionListOptions {
1✔
1571
                        offset: 0,
1✔
1572
                        limit: 20,
1✔
1573
                    },
1✔
1574
                )
1✔
1575
                .await
3✔
1576
                .unwrap_err();
1✔
1577

1✔
1578
            // should be deleted automatically
1✔
1579
            layer_db.load_layer(&l_id).await.unwrap_err();
3✔
1580

1✔
1581
            // it is not allowed to remove the root collection
1✔
1582
            layer_db
1✔
1583
                .remove_layer_collection(root_collection)
1✔
1584
                .await
×
1585
                .unwrap_err();
1✔
1586
            layer_db
1✔
1587
                .load_layer_collection(
1✔
1588
                    root_collection,
1✔
1589
                    LayerCollectionListOptions {
1✔
1590
                        offset: 0,
1✔
1591
                        limit: 20,
1✔
1592
                    },
1✔
1593
                )
1✔
1594
                .await
5✔
1595
                .unwrap();
1✔
1596
        })
1✔
1597
        .await;
11✔
1598
    }
1599

1600
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1601
    #[allow(clippy::too_many_lines)]
1602
    async fn it_removes_collections_from_collections() {
1✔
1603
        with_temp_context(|app_ctx, _| async move {
1✔
1604
            let session = app_ctx.default_session().await.unwrap();
18✔
1605

1✔
1606
            let db = app_ctx.session_context(session).db();
1✔
1607

1608
            let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
1609

1610
            let mid_collection_id = db
1✔
1611
                .add_layer_collection(
1✔
1612
                    AddLayerCollection {
1✔
1613
                        name: "mid collection".to_string(),
1✔
1614
                        description: "description".to_string(),
1✔
1615
                        properties: Default::default(),
1✔
1616
                    },
1✔
1617
                    root_collection_id,
1✔
1618
                )
1✔
1619
                .await
10✔
1620
                .unwrap();
1✔
1621

1622
            let bottom_collection_id = db
1✔
1623
                .add_layer_collection(
1✔
1624
                    AddLayerCollection {
1✔
1625
                        name: "bottom collection".to_string(),
1✔
1626
                        description: "description".to_string(),
1✔
1627
                        properties: Default::default(),
1✔
1628
                    },
1✔
1629
                    &mid_collection_id,
1✔
1630
                )
1✔
1631
                .await
7✔
1632
                .unwrap();
1✔
1633

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

1✔
1658
            // removing the mid collection…
1✔
1659
            db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
1660
                .await
11✔
1661
                .unwrap();
1✔
1662

1✔
1663
            // …should remove itself
1✔
1664
            db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
1665
                .await
3✔
1666
                .unwrap_err();
1✔
1667

1✔
1668
            // …should remove the bottom collection
1✔
1669
            db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
1670
                .await
3✔
1671
                .unwrap_err();
1✔
1672

1✔
1673
            // … and should remove the layer of the bottom collection
1✔
1674
            db.load_layer(&layer_id).await.unwrap_err();
3✔
1675

1✔
1676
            // the root collection is still there
1✔
1677
            db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
1678
                .await
5✔
1679
                .unwrap();
1✔
1680
        })
1✔
1681
        .await;
10✔
1682
    }
1683

1684
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1685
    #[allow(clippy::too_many_lines)]
1686
    async fn it_removes_layers_from_collections() {
1✔
1687
        with_temp_context(|app_ctx, _| async move {
1✔
1688
            let session = app_ctx.default_session().await.unwrap();
18✔
1689

1✔
1690
            let db = app_ctx.session_context(session).db();
1✔
1691

1692
            let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
1693

1694
            let another_collection = db
1✔
1695
                .add_layer_collection(
1✔
1696
                    AddLayerCollection {
1✔
1697
                        name: "top collection".to_string(),
1✔
1698
                        description: "description".to_string(),
1✔
1699
                        properties: Default::default(),
1✔
1700
                    },
1✔
1701
                    root_collection,
1✔
1702
                )
1✔
1703
                .await
10✔
1704
                .unwrap();
1✔
1705

1706
            let layer_in_one_collection = db
1✔
1707
                .add_layer(
1✔
1708
                    AddLayer {
1✔
1709
                        name: "layer 1".to_string(),
1✔
1710
                        description: "description".to_string(),
1✔
1711
                        workflow: Workflow {
1✔
1712
                            operator: TypedOperator::Vector(
1✔
1713
                                MockPointSource {
1✔
1714
                                    params: MockPointSourceParams {
1✔
1715
                                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1716
                                    },
1✔
1717
                                }
1✔
1718
                                .boxed(),
1✔
1719
                            ),
1✔
1720
                        },
1✔
1721
                        symbology: None,
1✔
1722
                        metadata: Default::default(),
1✔
1723
                        properties: Default::default(),
1✔
1724
                    },
1✔
1725
                    &another_collection,
1✔
1726
                )
1✔
1727
                .await
41✔
1728
                .unwrap();
1✔
1729

1730
            let layer_in_two_collections = db
1✔
1731
                .add_layer(
1✔
1732
                    AddLayer {
1✔
1733
                        name: "layer 2".to_string(),
1✔
1734
                        description: "description".to_string(),
1✔
1735
                        workflow: Workflow {
1✔
1736
                            operator: TypedOperator::Vector(
1✔
1737
                                MockPointSource {
1✔
1738
                                    params: MockPointSourceParams {
1✔
1739
                                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1740
                                    },
1✔
1741
                                }
1✔
1742
                                .boxed(),
1✔
1743
                            ),
1✔
1744
                        },
1✔
1745
                        symbology: None,
1✔
1746
                        metadata: Default::default(),
1✔
1747
                        properties: Default::default(),
1✔
1748
                    },
1✔
1749
                    &another_collection,
1✔
1750
                )
1✔
1751
                .await
9✔
1752
                .unwrap();
1✔
1753

1✔
1754
            db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
1755
                .await
3✔
1756
                .unwrap();
1✔
1757

1✔
1758
            // remove first layer --> should be deleted entirely
1✔
1759

1✔
1760
            db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
1761
                .await
7✔
1762
                .unwrap();
1✔
1763

1764
            let number_of_layer_in_collection = db
1✔
1765
                .load_layer_collection(
1✔
1766
                    &another_collection,
1✔
1767
                    LayerCollectionListOptions {
1✔
1768
                        offset: 0,
1✔
1769
                        limit: 20,
1✔
1770
                    },
1✔
1771
                )
1✔
1772
                .await
5✔
1773
                .unwrap()
1✔
1774
                .items
1✔
1775
                .len();
1✔
1776
            assert_eq!(
1✔
1777
                number_of_layer_in_collection,
1✔
1778
                1 /* only the other collection should be here */
1✔
1779
            );
1✔
1780

1781
            db.load_layer(&layer_in_one_collection).await.unwrap_err();
3✔
1782

1✔
1783
            // remove second layer --> should only be gone in collection
1✔
1784

1✔
1785
            db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
1786
                .await
7✔
1787
                .unwrap();
1✔
1788

1789
            let number_of_layer_in_collection = db
1✔
1790
                .load_layer_collection(
1✔
1791
                    &another_collection,
1✔
1792
                    LayerCollectionListOptions {
1✔
1793
                        offset: 0,
1✔
1794
                        limit: 20,
1✔
1795
                    },
1✔
1796
                )
1✔
1797
                .await
5✔
1798
                .unwrap()
1✔
1799
                .items
1✔
1800
                .len();
1✔
1801
            assert_eq!(
1✔
1802
                number_of_layer_in_collection,
1✔
1803
                0 /* both layers were deleted */
1✔
1804
            );
1✔
1805

1806
            db.load_layer(&layer_in_two_collections).await.unwrap();
3✔
1807
        })
1✔
1808
        .await;
10✔
1809
    }
1810

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

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

1870
            let session = app_ctx.default_session().await.unwrap();
18✔
1871

1✔
1872
            let dataset_name = DatasetName::new(None, "my_dataset");
1✔
1873

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

1896
            assert!(db.load_dataset(&dataset_id).await.is_ok());
3✔
1897

1898
            db.delete_dataset(dataset_id).await.unwrap();
3✔
1899

1900
            assert!(db.load_dataset(&dataset_id).await.is_err());
3✔
1901
        })
1✔
1902
        .await;
11✔
1903
    }
1904

1905
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
1906
    #[allow(clippy::too_many_lines)]
1907
    async fn it_deletes_admin_dataset() {
1✔
1908
        with_temp_context(|app_ctx, _| async move {
1✔
1909
            let dataset_name = DatasetName::new(None, "my_dataset");
1✔
1910

1✔
1911
            let loading_info = OgrSourceDataset {
1✔
1912
                file_name: PathBuf::from("test.csv"),
1✔
1913
                layer_name: "test.csv".to_owned(),
1✔
1914
                data_type: Some(VectorDataType::MultiPoint),
1✔
1915
                time: OgrSourceDatasetTimeType::Start {
1✔
1916
                    start_field: "start".to_owned(),
1✔
1917
                    start_format: OgrSourceTimeFormat::Auto,
1✔
1918
                    duration: OgrSourceDurationSpec::Zero,
1✔
1919
                },
1✔
1920
                default_geometry: None,
1✔
1921
                columns: Some(OgrSourceColumnSpec {
1✔
1922
                    format_specifics: Some(FormatSpecifics::Csv {
1✔
1923
                        header: CsvHeader::Auto,
1✔
1924
                    }),
1✔
1925
                    x: "x".to_owned(),
1✔
1926
                    y: None,
1✔
1927
                    int: vec![],
1✔
1928
                    float: vec![],
1✔
1929
                    text: vec![],
1✔
1930
                    bool: vec![],
1✔
1931
                    datetime: vec![],
1✔
1932
                    rename: None,
1✔
1933
                }),
1✔
1934
                force_ogr_time_filter: false,
1✔
1935
                force_ogr_spatial_filter: false,
1✔
1936
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1937
                sql_query: None,
1✔
1938
                attribute_query: None,
1✔
1939
                cache_ttl: CacheTtlSeconds::default(),
1✔
1940
            };
1✔
1941

1✔
1942
            let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
1943
                OgrSourceDataset,
1✔
1944
                VectorResultDescriptor,
1✔
1945
                VectorQueryRectangle,
1✔
1946
            > {
1✔
1947
                loading_info: loading_info.clone(),
1✔
1948
                result_descriptor: VectorResultDescriptor {
1✔
1949
                    data_type: VectorDataType::MultiPoint,
1✔
1950
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1951
                    columns: [(
1✔
1952
                        "foo".to_owned(),
1✔
1953
                        VectorColumnInfo {
1✔
1954
                            data_type: FeatureDataType::Float,
1✔
1955
                            measurement: Measurement::Unitless.into(),
1✔
1956
                        },
1✔
1957
                    )]
1✔
1958
                    .into_iter()
1✔
1959
                    .collect(),
1✔
1960
                    time: None,
1✔
1961
                    bbox: None,
1✔
1962
                },
1✔
1963
                phantom: Default::default(),
1✔
1964
            });
1✔
1965

1966
            let session = app_ctx.default_session().await.unwrap();
18✔
1967

1✔
1968
            let db = app_ctx.session_context(session).db();
1✔
1969
            let wrap = db.wrap_meta_data(meta_data);
1✔
1970
            let dataset_id = db
1✔
1971
                .add_dataset(
1✔
1972
                    AddDataset {
1✔
1973
                        name: Some(dataset_name),
1✔
1974
                        display_name: "Ogr Test".to_owned(),
1✔
1975
                        description: "desc".to_owned(),
1✔
1976
                        source_operator: "OgrSource".to_owned(),
1✔
1977
                        symbology: None,
1✔
1978
                        provenance: Some(vec![Provenance {
1✔
1979
                            citation: "citation".to_owned(),
1✔
1980
                            license: "license".to_owned(),
1✔
1981
                            uri: "uri".to_owned(),
1✔
1982
                        }]),
1✔
1983
                    },
1✔
1984
                    wrap,
1✔
1985
                )
1✔
1986
                .await
153✔
1987
                .unwrap()
1✔
1988
                .id;
1989

1990
            assert!(db.load_dataset(&dataset_id).await.is_ok());
3✔
1991

1992
            db.delete_dataset(dataset_id).await.unwrap();
3✔
1993

1994
            assert!(db.load_dataset(&dataset_id).await.is_err());
3✔
1995
        })
1✔
1996
        .await;
11✔
1997
    }
1998

1999
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
2000
    async fn test_missing_layer_dataset_in_collection_listing() {
1✔
2001
        with_temp_context(|app_ctx, _| async move {
1✔
2002
            let session = app_ctx.default_session().await.unwrap();
18✔
2003
            let db = app_ctx.session_context(session).db();
1✔
2004

2005
            let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
2006

2007
            let top_collection_id = db
1✔
2008
                .add_layer_collection(
1✔
2009
                    AddLayerCollection {
1✔
2010
                        name: "top collection".to_string(),
1✔
2011
                        description: "description".to_string(),
1✔
2012
                        properties: Default::default(),
1✔
2013
                    },
1✔
2014
                    root_collection_id,
1✔
2015
                )
1✔
2016
                .await
10✔
2017
                .unwrap();
1✔
2018

1✔
2019
            let faux_layer = LayerId("faux".to_string());
1✔
2020

1✔
2021
            // this should fail
1✔
2022
            db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
2023
                .await
×
2024
                .unwrap_err();
1✔
2025

2026
            let root_collection_layers = db
1✔
2027
                .load_layer_collection(
1✔
2028
                    &top_collection_id,
1✔
2029
                    LayerCollectionListOptions {
1✔
2030
                        offset: 0,
1✔
2031
                        limit: 20,
1✔
2032
                    },
1✔
2033
                )
1✔
2034
                .await
5✔
2035
                .unwrap();
1✔
2036

1✔
2037
            assert_eq!(
1✔
2038
                root_collection_layers,
1✔
2039
                LayerCollection {
1✔
2040
                    id: ProviderLayerCollectionId {
1✔
2041
                        provider_id: DataProviderId(
1✔
2042
                            "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
2043
                        ),
1✔
2044
                        collection_id: top_collection_id.clone(),
1✔
2045
                    },
1✔
2046
                    name: "top collection".to_string(),
1✔
2047
                    description: "description".to_string(),
1✔
2048
                    items: vec![],
1✔
2049
                    entry_label: None,
1✔
2050
                    properties: vec![],
1✔
2051
                }
1✔
2052
            );
1✔
2053
        })
1✔
2054
        .await;
11✔
2055
    }
2056

2057
    #[allow(clippy::too_many_lines)]
2058
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
2059
    async fn it_updates_project_layer_symbology() {
1✔
2060
        with_temp_context(|app_ctx, _| async move {
1✔
2061
            let session = app_ctx.default_session().await.unwrap();
18✔
2062

2063
            let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
161✔
2064

2065
            let db = app_ctx.session_context(session.clone()).db();
1✔
2066

1✔
2067
            let create_project: CreateProject = serde_json::from_value(json!({
1✔
2068
                "name": "Default",
1✔
2069
                "description": "Default project",
1✔
2070
                "bounds": {
1✔
2071
                    "boundingBox": {
1✔
2072
                        "lowerLeftCoordinate": {
1✔
2073
                            "x": -180,
1✔
2074
                            "y": -90
1✔
2075
                        },
1✔
2076
                        "upperRightCoordinate": {
1✔
2077
                            "x": 180,
1✔
2078
                            "y": 90
1✔
2079
                        }
1✔
2080
                    },
1✔
2081
                    "spatialReference": "EPSG:4326",
1✔
2082
                    "timeInterval": {
1✔
2083
                        "start": 1_396_353_600_000i64,
1✔
2084
                        "end": 1_396_353_600_000i64
1✔
2085
                    }
1✔
2086
                },
1✔
2087
                "timeStep": {
1✔
2088
                    "step": 1,
1✔
2089
                    "granularity": "months"
1✔
2090
                }
1✔
2091
            }))
1✔
2092
            .unwrap();
1✔
2093

2094
            let project_id = db.create_project(create_project).await.unwrap();
7✔
2095

1✔
2096
            let update: UpdateProject = serde_json::from_value(json!({
1✔
2097
                "id": project_id.to_string(),
1✔
2098
                "layers": [{
1✔
2099
                    "name": "NDVI",
1✔
2100
                    "workflow": workflow_id.to_string(),
1✔
2101
                    "visibility": {
1✔
2102
                        "data": true,
1✔
2103
                        "legend": false
1✔
2104
                    },
1✔
2105
                    "symbology": {
1✔
2106
                        "type": "raster",
1✔
2107
                        "opacity": 1,
1✔
2108
                        "colorizer": {
1✔
2109
                            "type": "linearGradient",
1✔
2110
                            "breakpoints": [{
1✔
2111
                                "value": 1,
1✔
2112
                                "color": [0, 0, 0, 255]
1✔
2113
                            }, {
1✔
2114
                                "value": 255,
1✔
2115
                                "color": [255, 255, 255, 255]
1✔
2116
                            }],
1✔
2117
                            "noDataColor": [0, 0, 0, 0],
1✔
2118
                            "overColor": [255, 255, 255, 127],
1✔
2119
                            "underColor": [255, 255, 255, 127]
1✔
2120
                        }
1✔
2121
                    }
1✔
2122
                }]
1✔
2123
            }))
1✔
2124
            .unwrap();
1✔
2125

1✔
2126
            db.update_project(update).await.unwrap();
65✔
2127

1✔
2128
            let update: UpdateProject = serde_json::from_value(json!({
1✔
2129
                "id": project_id.to_string(),
1✔
2130
                "layers": [{
1✔
2131
                    "name": "NDVI",
1✔
2132
                    "workflow": workflow_id.to_string(),
1✔
2133
                    "visibility": {
1✔
2134
                        "data": true,
1✔
2135
                        "legend": false
1✔
2136
                    },
1✔
2137
                    "symbology": {
1✔
2138
                        "type": "raster",
1✔
2139
                        "opacity": 1,
1✔
2140
                        "colorizer": {
1✔
2141
                            "type": "linearGradient",
1✔
2142
                            "breakpoints": [{
1✔
2143
                                "value": 1,
1✔
2144
                                "color": [0, 0, 4, 255]
1✔
2145
                            }, {
1✔
2146
                                "value": 17.866_666_666_666_667,
1✔
2147
                                "color": [11, 9, 36, 255]
1✔
2148
                            }, {
1✔
2149
                                "value": 34.733_333_333_333_334,
1✔
2150
                                "color": [32, 17, 75, 255]
1✔
2151
                            }, {
1✔
2152
                                "value": 51.6,
1✔
2153
                                "color": [59, 15, 112, 255]
1✔
2154
                            }, {
1✔
2155
                                "value": 68.466_666_666_666_67,
1✔
2156
                                "color": [87, 21, 126, 255]
1✔
2157
                            }, {
1✔
2158
                                "value": 85.333_333_333_333_33,
1✔
2159
                                "color": [114, 31, 129, 255]
1✔
2160
                            }, {
1✔
2161
                                "value": 102.199_999_999_999_99,
1✔
2162
                                "color": [140, 41, 129, 255]
1✔
2163
                            }, {
1✔
2164
                                "value": 119.066_666_666_666_65,
1✔
2165
                                "color": [168, 50, 125, 255]
1✔
2166
                            }, {
1✔
2167
                                "value": 135.933_333_333_333_34,
1✔
2168
                                "color": [196, 60, 117, 255]
1✔
2169
                            }, {
1✔
2170
                                "value": 152.799_999_999_999_98,
1✔
2171
                                "color": [222, 73, 104, 255]
1✔
2172
                            }, {
1✔
2173
                                "value": 169.666_666_666_666_66,
1✔
2174
                                "color": [241, 96, 93, 255]
1✔
2175
                            }, {
1✔
2176
                                "value": 186.533_333_333_333_33,
1✔
2177
                                "color": [250, 127, 94, 255]
1✔
2178
                            }, {
1✔
2179
                                "value": 203.399_999_999_999_98,
1✔
2180
                                "color": [254, 159, 109, 255]
1✔
2181
                            }, {
1✔
2182
                                "value": 220.266_666_666_666_65,
1✔
2183
                                "color": [254, 191, 132, 255]
1✔
2184
                            }, {
1✔
2185
                                "value": 237.133_333_333_333_3,
1✔
2186
                                "color": [253, 222, 160, 255]
1✔
2187
                            }, {
1✔
2188
                                "value": 254,
1✔
2189
                                "color": [252, 253, 191, 255]
1✔
2190
                            }],
1✔
2191
                            "noDataColor": [0, 0, 0, 0],
1✔
2192
                            "overColor": [255, 255, 255, 127],
1✔
2193
                            "underColor": [255, 255, 255, 127]
1✔
2194
                        }
1✔
2195
                    }
1✔
2196
                }]
1✔
2197
            }))
1✔
2198
            .unwrap();
1✔
2199

1✔
2200
            db.update_project(update).await.unwrap();
14✔
2201

1✔
2202
            let update: UpdateProject = serde_json::from_value(json!({
1✔
2203
                "id": project_id.to_string(),
1✔
2204
                "layers": [{
1✔
2205
                    "name": "NDVI",
1✔
2206
                    "workflow": workflow_id.to_string(),
1✔
2207
                    "visibility": {
1✔
2208
                        "data": true,
1✔
2209
                        "legend": false
1✔
2210
                    },
1✔
2211
                    "symbology": {
1✔
2212
                        "type": "raster",
1✔
2213
                        "opacity": 1,
1✔
2214
                        "colorizer": {
1✔
2215
                            "type": "linearGradient",
1✔
2216
                            "breakpoints": [{
1✔
2217
                                "value": 1,
1✔
2218
                                "color": [0, 0, 4, 255]
1✔
2219
                            }, {
1✔
2220
                                "value": 17.866_666_666_666_667,
1✔
2221
                                "color": [11, 9, 36, 255]
1✔
2222
                            }, {
1✔
2223
                                "value": 34.733_333_333_333_334,
1✔
2224
                                "color": [32, 17, 75, 255]
1✔
2225
                            }, {
1✔
2226
                                "value": 51.6,
1✔
2227
                                "color": [59, 15, 112, 255]
1✔
2228
                            }, {
1✔
2229
                                "value": 68.466_666_666_666_67,
1✔
2230
                                "color": [87, 21, 126, 255]
1✔
2231
                            }, {
1✔
2232
                                "value": 85.333_333_333_333_33,
1✔
2233
                                "color": [114, 31, 129, 255]
1✔
2234
                            }, {
1✔
2235
                                "value": 102.199_999_999_999_99,
1✔
2236
                                "color": [140, 41, 129, 255]
1✔
2237
                            }, {
1✔
2238
                                "value": 119.066_666_666_666_65,
1✔
2239
                                "color": [168, 50, 125, 255]
1✔
2240
                            }, {
1✔
2241
                                "value": 135.933_333_333_333_34,
1✔
2242
                                "color": [196, 60, 117, 255]
1✔
2243
                            }, {
1✔
2244
                                "value": 152.799_999_999_999_98,
1✔
2245
                                "color": [222, 73, 104, 255]
1✔
2246
                            }, {
1✔
2247
                                "value": 169.666_666_666_666_66,
1✔
2248
                                "color": [241, 96, 93, 255]
1✔
2249
                            }, {
1✔
2250
                                "value": 186.533_333_333_333_33,
1✔
2251
                                "color": [250, 127, 94, 255]
1✔
2252
                            }, {
1✔
2253
                                "value": 203.399_999_999_999_98,
1✔
2254
                                "color": [254, 159, 109, 255]
1✔
2255
                            }, {
1✔
2256
                                "value": 220.266_666_666_666_65,
1✔
2257
                                "color": [254, 191, 132, 255]
1✔
2258
                            }, {
1✔
2259
                                "value": 237.133_333_333_333_3,
1✔
2260
                                "color": [253, 222, 160, 255]
1✔
2261
                            }, {
1✔
2262
                                "value": 254,
1✔
2263
                                "color": [252, 253, 191, 255]
1✔
2264
                            }],
1✔
2265
                            "noDataColor": [0, 0, 0, 0],
1✔
2266
                            "overColor": [255, 255, 255, 127],
1✔
2267
                            "underColor": [255, 255, 255, 127]
1✔
2268
                        }
1✔
2269
                    }
1✔
2270
                }]
1✔
2271
            }))
1✔
2272
            .unwrap();
1✔
2273

1✔
2274
            db.update_project(update).await.unwrap();
14✔
2275

1✔
2276
            let update: UpdateProject = serde_json::from_value(json!({
1✔
2277
                "id": project_id.to_string(),
1✔
2278
                "layers": [{
1✔
2279
                    "name": "NDVI",
1✔
2280
                    "workflow": workflow_id.to_string(),
1✔
2281
                    "visibility": {
1✔
2282
                        "data": true,
1✔
2283
                        "legend": false
1✔
2284
                    },
1✔
2285
                    "symbology": {
1✔
2286
                        "type": "raster",
1✔
2287
                        "opacity": 1,
1✔
2288
                        "colorizer": {
1✔
2289
                            "type": "linearGradient",
1✔
2290
                            "breakpoints": [{
1✔
2291
                                "value": 1,
1✔
2292
                                "color": [0, 0, 4, 255]
1✔
2293
                            }, {
1✔
2294
                                "value": 17.933_333_333_333_334,
1✔
2295
                                "color": [11, 9, 36, 255]
1✔
2296
                            }, {
1✔
2297
                                "value": 34.866_666_666_666_67,
1✔
2298
                                "color": [32, 17, 75, 255]
1✔
2299
                            }, {
1✔
2300
                                "value": 51.800_000_000_000_004,
1✔
2301
                                "color": [59, 15, 112, 255]
1✔
2302
                            }, {
1✔
2303
                                "value": 68.733_333_333_333_33,
1✔
2304
                                "color": [87, 21, 126, 255]
1✔
2305
                            }, {
1✔
2306
                                "value": 85.666_666_666_666_66,
1✔
2307
                                "color": [114, 31, 129, 255]
1✔
2308
                            }, {
1✔
2309
                                "value": 102.6,
1✔
2310
                                "color": [140, 41, 129, 255]
1✔
2311
                            }, {
1✔
2312
                                "value": 119.533_333_333_333_32,
1✔
2313
                                "color": [168, 50, 125, 255]
1✔
2314
                            }, {
1✔
2315
                                "value": 136.466_666_666_666_67,
1✔
2316
                                "color": [196, 60, 117, 255]
1✔
2317
                            }, {
1✔
2318
                                "value": 153.4,
1✔
2319
                                "color": [222, 73, 104, 255]
1✔
2320
                            }, {
1✔
2321
                                "value": 170.333_333_333_333_31,
1✔
2322
                                "color": [241, 96, 93, 255]
1✔
2323
                            }, {
1✔
2324
                                "value": 187.266_666_666_666_65,
1✔
2325
                                "color": [250, 127, 94, 255]
1✔
2326
                            }, {
1✔
2327
                                "value": 204.2,
1✔
2328
                                "color": [254, 159, 109, 255]
1✔
2329
                            }, {
1✔
2330
                                "value": 221.133_333_333_333_33,
1✔
2331
                                "color": [254, 191, 132, 255]
1✔
2332
                            }, {
1✔
2333
                                "value": 238.066_666_666_666_63,
1✔
2334
                                "color": [253, 222, 160, 255]
1✔
2335
                            }, {
1✔
2336
                                "value": 255,
1✔
2337
                                "color": [252, 253, 191, 255]
1✔
2338
                            }],
1✔
2339
                            "noDataColor": [0, 0, 0, 0],
1✔
2340
                            "overColor": [255, 255, 255, 127],
1✔
2341
                            "underColor": [255, 255, 255, 127]
1✔
2342
                        }
1✔
2343
                    }
1✔
2344
                }]
1✔
2345
            }))
1✔
2346
            .unwrap();
1✔
2347

1✔
2348
            let update = update;
1✔
2349

2350
            // run two updates concurrently
2351
            let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
2352

2353
            assert!(r0.is_ok());
1✔
2354
            assert!(r1.is_ok());
1✔
2355
        })
1✔
2356
        .await;
12✔
2357
    }
2358

2359
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
2360
    #[allow(clippy::too_many_lines)]
2361
    async fn it_resolves_dataset_names_to_ids() {
1✔
2362
        with_temp_context(|app_ctx, _| async move {
1✔
2363
            let session = app_ctx.default_session().await.unwrap();
18✔
2364
            let db = app_ctx.session_context(session.clone()).db();
1✔
2365

1✔
2366
            let loading_info = OgrSourceDataset {
1✔
2367
                file_name: PathBuf::from("test.csv"),
1✔
2368
                layer_name: "test.csv".to_owned(),
1✔
2369
                data_type: Some(VectorDataType::MultiPoint),
1✔
2370
                time: OgrSourceDatasetTimeType::Start {
1✔
2371
                    start_field: "start".to_owned(),
1✔
2372
                    start_format: OgrSourceTimeFormat::Auto,
1✔
2373
                    duration: OgrSourceDurationSpec::Zero,
1✔
2374
                },
1✔
2375
                default_geometry: None,
1✔
2376
                columns: Some(OgrSourceColumnSpec {
1✔
2377
                    format_specifics: Some(FormatSpecifics::Csv {
1✔
2378
                        header: CsvHeader::Auto,
1✔
2379
                    }),
1✔
2380
                    x: "x".to_owned(),
1✔
2381
                    y: None,
1✔
2382
                    int: vec![],
1✔
2383
                    float: vec![],
1✔
2384
                    text: vec![],
1✔
2385
                    bool: vec![],
1✔
2386
                    datetime: vec![],
1✔
2387
                    rename: None,
1✔
2388
                }),
1✔
2389
                force_ogr_time_filter: false,
1✔
2390
                force_ogr_spatial_filter: false,
1✔
2391
                on_error: OgrSourceErrorSpec::Ignore,
1✔
2392
                sql_query: None,
1✔
2393
                attribute_query: None,
1✔
2394
                cache_ttl: CacheTtlSeconds::default(),
1✔
2395
            };
1✔
2396

1✔
2397
            let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
2398
                OgrSourceDataset,
1✔
2399
                VectorResultDescriptor,
1✔
2400
                VectorQueryRectangle,
1✔
2401
            > {
1✔
2402
                loading_info: loading_info.clone(),
1✔
2403
                result_descriptor: VectorResultDescriptor {
1✔
2404
                    data_type: VectorDataType::MultiPoint,
1✔
2405
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
2406
                    columns: [(
1✔
2407
                        "foo".to_owned(),
1✔
2408
                        VectorColumnInfo {
1✔
2409
                            data_type: FeatureDataType::Float,
1✔
2410
                            measurement: Measurement::Unitless.into(),
1✔
2411
                        },
1✔
2412
                    )]
1✔
2413
                    .into_iter()
1✔
2414
                    .collect(),
1✔
2415
                    time: None,
1✔
2416
                    bbox: None,
1✔
2417
                },
1✔
2418
                phantom: Default::default(),
1✔
2419
            });
1✔
2420

2421
            let DatasetIdAndName {
2422
                id: dataset_id1,
1✔
2423
                name: dataset_name1,
1✔
2424
            } = db
1✔
2425
                .add_dataset(
1✔
2426
                    AddDataset {
1✔
2427
                        name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
2428
                        display_name: "Ogr Test".to_owned(),
1✔
2429
                        description: "desc".to_owned(),
1✔
2430
                        source_operator: "OgrSource".to_owned(),
1✔
2431
                        symbology: None,
1✔
2432
                        provenance: Some(vec![Provenance {
1✔
2433
                            citation: "citation".to_owned(),
1✔
2434
                            license: "license".to_owned(),
1✔
2435
                            uri: "uri".to_owned(),
1✔
2436
                        }]),
1✔
2437
                    },
1✔
2438
                    db.wrap_meta_data(meta_data.clone()),
1✔
2439
                )
1✔
2440
                .await
153✔
2441
                .unwrap();
1✔
2442

2443
            assert_eq!(
1✔
2444
                db.resolve_dataset_name_to_id(&dataset_name1).await.unwrap(),
3✔
2445
                dataset_id1
2446
            );
2447
        })
1✔
2448
        .await;
12✔
2449
    }
2450

2451
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
2452
    #[allow(clippy::too_many_lines)]
2453
    async fn test_postgres_type_serialization() {
1✔
2454
        with_temp_context(|app_ctx, _| async move {
1✔
2455
            let pool = app_ctx.pool.get().await.unwrap();
1✔
2456

1✔
2457
            assert_sql_type(&pool, "RgbaColor", [RgbaColor([0, 1, 2, 3])]).await;
4✔
2458

2459
            assert_sql_type(
1✔
2460
                &pool,
1✔
2461
                "double precision",
1✔
2462
                [NotNanF64::from(NotNan::<f64>::new(1.0).unwrap())],
1✔
2463
            )
1✔
2464
            .await;
2✔
2465

2466
            assert_sql_type(
1✔
2467
                &pool,
1✔
2468
                "Breakpoint",
1✔
2469
                [Breakpoint {
1✔
2470
                    value: NotNan::<f64>::new(1.0).unwrap().into(),
1✔
2471
                    color: RgbaColor([0, 0, 0, 0]),
1✔
2472
                }],
1✔
2473
            )
1✔
2474
            .await;
5✔
2475

2476
            assert_sql_type(
1✔
2477
                &pool,
1✔
2478
                "DefaultColors",
1✔
2479
                [
1✔
2480
                    DefaultColors::DefaultColor {
1✔
2481
                        default_color: RgbaColor([0, 10, 20, 30]),
1✔
2482
                    },
1✔
2483
                    DefaultColors::OverUnder(OverUnderColors {
1✔
2484
                        over_color: RgbaColor([1, 2, 3, 4]),
1✔
2485
                        under_color: RgbaColor([5, 6, 7, 8]),
1✔
2486
                    }),
1✔
2487
                ],
1✔
2488
            )
1✔
2489
            .await;
6✔
2490

2491
            assert_sql_type(
1✔
2492
                &pool,
1✔
2493
                "ColorizerType",
1✔
2494
                [
1✔
2495
                    ColorizerTypeDbType::LinearGradient,
1✔
2496
                    ColorizerTypeDbType::LogarithmicGradient,
1✔
2497
                    ColorizerTypeDbType::Palette,
1✔
2498
                    ColorizerTypeDbType::Rgba,
1✔
2499
                ],
1✔
2500
            )
1✔
2501
            .await;
11✔
2502

2503
            assert_sql_type(
1✔
2504
                &pool,
1✔
2505
                "Colorizer",
1✔
2506
                [
1✔
2507
                    Colorizer::LinearGradient(LinearGradient {
1✔
2508
                        breakpoints: vec![
1✔
2509
                            Breakpoint {
1✔
2510
                                value: NotNan::<f64>::new(-10.0).unwrap().into(),
1✔
2511
                                color: RgbaColor([0, 0, 0, 0]),
1✔
2512
                            },
1✔
2513
                            Breakpoint {
1✔
2514
                                value: NotNan::<f64>::new(2.0).unwrap().into(),
1✔
2515
                                color: RgbaColor([255, 0, 0, 255]),
1✔
2516
                            },
1✔
2517
                        ],
1✔
2518
                        no_data_color: RgbaColor([0, 10, 20, 30]),
1✔
2519
                        color_fields: DefaultColors::OverUnder(OverUnderColors {
1✔
2520
                            over_color: RgbaColor([1, 2, 3, 4]),
1✔
2521
                            under_color: RgbaColor([5, 6, 7, 8]),
1✔
2522
                        }),
1✔
2523
                    }),
1✔
2524
                    Colorizer::LogarithmicGradient(LogarithmicGradient {
1✔
2525
                        breakpoints: vec![
1✔
2526
                            Breakpoint {
1✔
2527
                                value: NotNan::<f64>::new(1.0).unwrap().into(),
1✔
2528
                                color: RgbaColor([0, 0, 0, 0]),
1✔
2529
                            },
1✔
2530
                            Breakpoint {
1✔
2531
                                value: NotNan::<f64>::new(2.0).unwrap().into(),
1✔
2532
                                color: RgbaColor([255, 0, 0, 255]),
1✔
2533
                            },
1✔
2534
                        ],
1✔
2535
                        no_data_color: RgbaColor([0, 10, 20, 30]),
1✔
2536
                        color_fields: DefaultColors::OverUnder(OverUnderColors {
1✔
2537
                            over_color: RgbaColor([1, 2, 3, 4]),
1✔
2538
                            under_color: RgbaColor([5, 6, 7, 8]),
1✔
2539
                        }),
1✔
2540
                    }),
1✔
2541
                    Colorizer::Palette {
1✔
2542
                        colors: Palette(
1✔
2543
                            [
1✔
2544
                                (NotNan::<f64>::new(1.0).unwrap(), RgbaColor([0, 0, 0, 0])),
1✔
2545
                                (
1✔
2546
                                    NotNan::<f64>::new(2.0).unwrap(),
1✔
2547
                                    RgbaColor([255, 0, 0, 255]),
1✔
2548
                                ),
1✔
2549
                                (NotNan::<f64>::new(3.0).unwrap(), RgbaColor([0, 10, 20, 30])),
1✔
2550
                            ]
1✔
2551
                            .into(),
1✔
2552
                        ),
1✔
2553
                        no_data_color: RgbaColor([1, 2, 3, 4]),
1✔
2554
                        default_color: RgbaColor([5, 6, 7, 8]),
1✔
2555
                    },
1✔
2556
                    Colorizer::Rgba,
1✔
2557
                ],
1✔
2558
            )
1✔
2559
            .await;
11✔
2560

2561
            assert_sql_type(
1✔
2562
                &pool,
1✔
2563
                "ColorParam",
1✔
2564
                [
1✔
2565
                    ColorParam::Static {
1✔
2566
                        color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2567
                    },
1✔
2568
                    ColorParam::Derived(DerivedColor {
1✔
2569
                        attribute: "foobar".to_string(),
1✔
2570
                        colorizer: Colorizer::Rgba,
1✔
2571
                    }),
1✔
2572
                ],
1✔
2573
            )
1✔
2574
            .await;
6✔
2575

2576
            assert_sql_type(
1✔
2577
                &pool,
1✔
2578
                "NumberParam",
1✔
2579
                [
1✔
2580
                    NumberParam::Static { value: 42 },
1✔
2581
                    NumberParam::Derived(DerivedNumber {
1✔
2582
                        attribute: "foobar".to_string(),
1✔
2583
                        factor: 1.0,
1✔
2584
                        default_value: 42.,
1✔
2585
                    }),
1✔
2586
                ],
1✔
2587
            )
1✔
2588
            .await;
6✔
2589

2590
            assert_sql_type(
1✔
2591
                &pool,
1✔
2592
                "StrokeParam",
1✔
2593
                [StrokeParam {
1✔
2594
                    width: NumberParam::Static { value: 42 },
1✔
2595
                    color: ColorParam::Static {
1✔
2596
                        color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2597
                    },
1✔
2598
                }],
1✔
2599
            )
1✔
2600
            .await;
4✔
2601

2602
            assert_sql_type(
1✔
2603
                &pool,
1✔
2604
                "TextSymbology",
1✔
2605
                [TextSymbology {
1✔
2606
                    attribute: "attribute".to_string(),
1✔
2607
                    fill_color: ColorParam::Static {
1✔
2608
                        color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2609
                    },
1✔
2610
                    stroke: StrokeParam {
1✔
2611
                        width: NumberParam::Static { value: 42 },
1✔
2612
                        color: ColorParam::Static {
1✔
2613
                            color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2614
                        },
1✔
2615
                    },
1✔
2616
                }],
1✔
2617
            )
1✔
2618
            .await;
4✔
2619

2620
            assert_sql_type(
1✔
2621
                &pool,
1✔
2622
                "Symbology",
1✔
2623
                [
1✔
2624
                    Symbology::Point(PointSymbology {
1✔
2625
                        fill_color: ColorParam::Static {
1✔
2626
                            color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2627
                        },
1✔
2628
                        stroke: StrokeParam {
1✔
2629
                            width: NumberParam::Static { value: 42 },
1✔
2630
                            color: ColorParam::Static {
1✔
2631
                                color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2632
                            },
1✔
2633
                        },
1✔
2634
                        radius: NumberParam::Static { value: 42 },
1✔
2635
                        text: Some(TextSymbology {
1✔
2636
                            attribute: "attribute".to_string(),
1✔
2637
                            fill_color: ColorParam::Static {
1✔
2638
                                color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2639
                            },
1✔
2640
                            stroke: StrokeParam {
1✔
2641
                                width: NumberParam::Static { value: 42 },
1✔
2642
                                color: ColorParam::Static {
1✔
2643
                                    color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2644
                                },
1✔
2645
                            },
1✔
2646
                        }),
1✔
2647
                    }),
1✔
2648
                    Symbology::Line(LineSymbology {
1✔
2649
                        stroke: StrokeParam {
1✔
2650
                            width: NumberParam::Static { value: 42 },
1✔
2651
                            color: ColorParam::Static {
1✔
2652
                                color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2653
                            },
1✔
2654
                        },
1✔
2655
                        text: Some(TextSymbology {
1✔
2656
                            attribute: "attribute".to_string(),
1✔
2657
                            fill_color: ColorParam::Static {
1✔
2658
                                color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2659
                            },
1✔
2660
                            stroke: StrokeParam {
1✔
2661
                                width: NumberParam::Static { value: 42 },
1✔
2662
                                color: ColorParam::Static {
1✔
2663
                                    color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2664
                                },
1✔
2665
                            },
1✔
2666
                        }),
1✔
2667
                        auto_simplified: true,
1✔
2668
                    }),
1✔
2669
                    Symbology::Polygon(PolygonSymbology {
1✔
2670
                        fill_color: ColorParam::Static {
1✔
2671
                            color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2672
                        },
1✔
2673
                        stroke: StrokeParam {
1✔
2674
                            width: NumberParam::Static { value: 42 },
1✔
2675
                            color: ColorParam::Static {
1✔
2676
                                color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2677
                            },
1✔
2678
                        },
1✔
2679
                        text: Some(TextSymbology {
1✔
2680
                            attribute: "attribute".to_string(),
1✔
2681
                            fill_color: ColorParam::Static {
1✔
2682
                                color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2683
                            },
1✔
2684
                            stroke: StrokeParam {
1✔
2685
                                width: NumberParam::Static { value: 42 },
1✔
2686
                                color: ColorParam::Static {
1✔
2687
                                    color: RgbaColor([0, 10, 20, 30]).into(),
1✔
2688
                                },
1✔
2689
                            },
1✔
2690
                        }),
1✔
2691
                        auto_simplified: true,
1✔
2692
                    }),
1✔
2693
                    Symbology::Raster(RasterSymbology {
1✔
2694
                        opacity: 1.0,
1✔
2695
                        colorizer: Colorizer::LinearGradient(LinearGradient {
1✔
2696
                            breakpoints: vec![
1✔
2697
                                Breakpoint {
1✔
2698
                                    value: NotNan::<f64>::new(-10.0).unwrap().into(),
1✔
2699
                                    color: RgbaColor([0, 0, 0, 0]),
1✔
2700
                                },
1✔
2701
                                Breakpoint {
1✔
2702
                                    value: NotNan::<f64>::new(2.0).unwrap().into(),
1✔
2703
                                    color: RgbaColor([255, 0, 0, 255]),
1✔
2704
                                },
1✔
2705
                            ],
1✔
2706
                            no_data_color: RgbaColor([0, 10, 20, 30]),
1✔
2707
                            color_fields: DefaultColors::OverUnder(OverUnderColors {
1✔
2708
                                over_color: RgbaColor([1, 2, 3, 4]),
1✔
2709
                                under_color: RgbaColor([5, 6, 7, 8]),
1✔
2710
                            }),
1✔
2711
                        }),
1✔
2712
                    }),
1✔
2713
                ],
1✔
2714
            )
1✔
2715
            .await;
18✔
2716

2717
            assert_sql_type(
1✔
2718
                &pool,
1✔
2719
                "RasterDataType",
1✔
2720
                [
1✔
2721
                    crate::api::model::datatypes::RasterDataType::U8,
1✔
2722
                    crate::api::model::datatypes::RasterDataType::U16,
1✔
2723
                    crate::api::model::datatypes::RasterDataType::U32,
1✔
2724
                    crate::api::model::datatypes::RasterDataType::U64,
1✔
2725
                    crate::api::model::datatypes::RasterDataType::I8,
1✔
2726
                    crate::api::model::datatypes::RasterDataType::I16,
1✔
2727
                    crate::api::model::datatypes::RasterDataType::I32,
1✔
2728
                    crate::api::model::datatypes::RasterDataType::I64,
1✔
2729
                    crate::api::model::datatypes::RasterDataType::F32,
1✔
2730
                    crate::api::model::datatypes::RasterDataType::F64,
1✔
2731
                ],
1✔
2732
            )
1✔
2733
            .await;
22✔
2734

2735
            assert_sql_type(
1✔
2736
                &pool,
1✔
2737
                "Measurement",
1✔
2738
                [
1✔
2739
                    Measurement::Unitless,
1✔
2740
                    Measurement::Continuous(ContinuousMeasurement {
1✔
2741
                        measurement: "Temperature".to_string(),
1✔
2742
                        unit: Some("°C".to_string()),
1✔
2743
                    }),
1✔
2744
                    Measurement::Classification(ClassificationMeasurement {
1✔
2745
                        measurement: "Color".to_string(),
1✔
2746
                        classes: [(1, "Grayscale".to_string()), (2, "Colorful".to_string())].into(),
1✔
2747
                    }),
1✔
2748
                ],
1✔
2749
            )
1✔
2750
            .await;
15✔
2751

2752
            assert_sql_type(
1✔
2753
                &pool,
1✔
2754
                "Coordinate2D",
1✔
2755
                [crate::api::model::datatypes::Coordinate2D::from(
1✔
2756
                    Coordinate2D::new(0.0f64, 1.),
1✔
2757
                )],
1✔
2758
            )
1✔
2759
            .await;
4✔
2760

2761
            assert_sql_type(
1✔
2762
                &pool,
1✔
2763
                "SpatialPartition2D",
1✔
2764
                [crate::api::model::datatypes::SpatialPartition2D {
1✔
2765
                    upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
2766
                    lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
2767
                }],
1✔
2768
            )
1✔
2769
            .await;
5✔
2770

2771
            assert_sql_type(
1✔
2772
                &pool,
1✔
2773
                "BoundingBox2D",
1✔
2774
                [crate::api::model::datatypes::BoundingBox2D {
1✔
2775
                    lower_left_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
2776
                    upper_right_coordinate: Coordinate2D::new(2., 1.0).into(),
1✔
2777
                }],
1✔
2778
            )
1✔
2779
            .await;
4✔
2780

2781
            assert_sql_type(
1✔
2782
                &pool,
1✔
2783
                "SpatialResolution",
1✔
2784
                [crate::api::model::datatypes::SpatialResolution { x: 1.2, y: 2.3 }],
1✔
2785
            )
1✔
2786
            .await;
4✔
2787

2788
            assert_sql_type(
1✔
2789
                &pool,
1✔
2790
                "VectorDataType",
1✔
2791
                [
1✔
2792
                    crate::api::model::datatypes::VectorDataType::Data,
1✔
2793
                    crate::api::model::datatypes::VectorDataType::MultiPoint,
1✔
2794
                    crate::api::model::datatypes::VectorDataType::MultiLineString,
1✔
2795
                    crate::api::model::datatypes::VectorDataType::MultiPolygon,
1✔
2796
                ],
1✔
2797
            )
1✔
2798
            .await;
10✔
2799

2800
            assert_sql_type(
1✔
2801
                &pool,
1✔
2802
                "FeatureDataType",
1✔
2803
                [
1✔
2804
                    crate::api::model::datatypes::FeatureDataType::Category,
1✔
2805
                    crate::api::model::datatypes::FeatureDataType::Int,
1✔
2806
                    crate::api::model::datatypes::FeatureDataType::Float,
1✔
2807
                    crate::api::model::datatypes::FeatureDataType::Text,
1✔
2808
                    crate::api::model::datatypes::FeatureDataType::Bool,
1✔
2809
                    crate::api::model::datatypes::FeatureDataType::DateTime,
1✔
2810
                ],
1✔
2811
            )
1✔
2812
            .await;
14✔
2813

2814
            assert_sql_type(
1✔
2815
                &pool,
1✔
2816
                "TimeInterval",
1✔
2817
                [crate::api::model::datatypes::TimeInterval::from(
1✔
2818
                    TimeInterval::default(),
1✔
2819
                )],
1✔
2820
            )
1✔
2821
            .await;
4✔
2822

2823
            assert_sql_type(
1✔
2824
                &pool,
1✔
2825
                "SpatialReference",
1✔
2826
                [
1✔
2827
                    crate::api::model::datatypes::SpatialReferenceOption::Unreferenced,
1✔
2828
                    crate::api::model::datatypes::SpatialReferenceOption::SpatialReference(
1✔
2829
                        SpatialReference::epsg_4326().into(),
1✔
2830
                    ),
1✔
2831
                ],
1✔
2832
            )
1✔
2833
            .await;
8✔
2834

2835
            assert_sql_type(
1✔
2836
                &pool,
1✔
2837
                "PlotResultDescriptor",
1✔
2838
                [PlotResultDescriptor {
1✔
2839
                    spatial_reference: SpatialReferenceOption::Unreferenced.into(),
1✔
2840
                    time: None,
1✔
2841
                    bbox: None,
1✔
2842
                }],
1✔
2843
            )
1✔
2844
            .await;
4✔
2845

2846
            assert_sql_type(
1✔
2847
                &pool,
1✔
2848
                "VectorResultDescriptor",
1✔
2849
                [crate::api::model::operators::VectorResultDescriptor {
1✔
2850
                    data_type: VectorDataType::MultiPoint.into(),
1✔
2851
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2852
                        SpatialReference::epsg_4326(),
1✔
2853
                    )
1✔
2854
                    .into(),
1✔
2855
                    columns: [(
1✔
2856
                        "foo".to_string(),
1✔
2857
                        VectorColumnInfo {
1✔
2858
                            data_type: FeatureDataType::Int,
1✔
2859
                            measurement: Measurement::Unitless.into(),
1✔
2860
                        }
1✔
2861
                        .into(),
1✔
2862
                    )]
1✔
2863
                    .into(),
1✔
2864
                    time: Some(TimeInterval::default().into()),
1✔
2865
                    bbox: Some(
1✔
2866
                        BoundingBox2D::new(
1✔
2867
                            Coordinate2D::new(0.0f64, 0.5),
1✔
2868
                            Coordinate2D::new(2., 1.0),
1✔
2869
                        )
1✔
2870
                        .unwrap()
1✔
2871
                        .into(),
1✔
2872
                    ),
1✔
2873
                }],
1✔
2874
            )
1✔
2875
            .await;
7✔
2876

2877
            assert_sql_type(
1✔
2878
                &pool,
1✔
2879
                "RasterResultDescriptor",
1✔
2880
                [crate::api::model::operators::RasterResultDescriptor {
1✔
2881
                    data_type: RasterDataType::U8.into(),
1✔
2882
                    spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2883
                        SpatialReference::epsg_4326(),
1✔
2884
                    )
1✔
2885
                    .into(),
1✔
2886
                    measurement: Measurement::Unitless,
1✔
2887
                    time: Some(TimeInterval::default().into()),
1✔
2888
                    bbox: Some(SpatialPartition2D {
1✔
2889
                        upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
2890
                        lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
2891
                    }),
1✔
2892
                    resolution: Some(SpatialResolution { x: 1.2, y: 2.3 }.into()),
1✔
2893
                }],
1✔
2894
            )
1✔
2895
            .await;
4✔
2896

2897
            assert_sql_type(
1✔
2898
                &pool,
1✔
2899
                "ResultDescriptor",
1✔
2900
                [
1✔
2901
                    crate::api::model::operators::TypedResultDescriptor::Vector(
1✔
2902
                        VectorResultDescriptor {
1✔
2903
                            data_type: VectorDataType::MultiPoint,
1✔
2904
                            spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2905
                                SpatialReference::epsg_4326(),
1✔
2906
                            ),
1✔
2907
                            columns: [(
1✔
2908
                                "foo".to_string(),
1✔
2909
                                VectorColumnInfo {
1✔
2910
                                    data_type: FeatureDataType::Int,
1✔
2911
                                    measurement: Measurement::Unitless.into(),
1✔
2912
                                },
1✔
2913
                            )]
1✔
2914
                            .into(),
1✔
2915
                            time: Some(TimeInterval::default()),
1✔
2916
                            bbox: Some(
1✔
2917
                                BoundingBox2D::new(
1✔
2918
                                    Coordinate2D::new(0.0f64, 0.5),
1✔
2919
                                    Coordinate2D::new(2., 1.0),
1✔
2920
                                )
1✔
2921
                                .unwrap(),
1✔
2922
                            ),
1✔
2923
                        }
1✔
2924
                        .into(),
1✔
2925
                    ),
1✔
2926
                    crate::api::model::operators::TypedResultDescriptor::Raster(
1✔
2927
                        crate::api::model::operators::RasterResultDescriptor {
1✔
2928
                            data_type: RasterDataType::U8.into(),
1✔
2929
                            spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
2930
                                SpatialReference::epsg_4326(),
1✔
2931
                            )
1✔
2932
                            .into(),
1✔
2933
                            measurement: Measurement::Unitless,
1✔
2934
                            time: Some(TimeInterval::default().into()),
1✔
2935
                            bbox: Some(SpatialPartition2D {
1✔
2936
                                upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
2937
                                lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
2938
                            }),
1✔
2939
                            resolution: Some(SpatialResolution { x: 1.2, y: 2.3 }.into()),
1✔
2940
                        },
1✔
2941
                    ),
1✔
2942
                    crate::api::model::operators::TypedResultDescriptor::Plot(
1✔
2943
                        PlotResultDescriptor {
1✔
2944
                            spatial_reference: SpatialReferenceOption::Unreferenced.into(),
1✔
2945
                            time: None,
1✔
2946
                            bbox: None,
1✔
2947
                        },
1✔
2948
                    ),
1✔
2949
                ],
1✔
2950
            )
1✔
2951
            .await;
8✔
2952

2953
            assert_sql_type(
1✔
2954
                &pool,
1✔
2955
                "\"TextTextKeyValue\"[]",
1✔
2956
                [HashMapTextTextDbType::from(
1✔
2957
                    &HashMap::<String, String>::from([
1✔
2958
                        ("foo".to_string(), "bar".to_string()),
1✔
2959
                        ("baz".to_string(), "fuu".to_string()),
1✔
2960
                    ]),
1✔
2961
                )],
1✔
2962
            )
1✔
2963
            .await;
5✔
2964

2965
            assert_sql_type(
1✔
2966
                &pool,
1✔
2967
                "MockDatasetDataSourceLoadingInfo",
1✔
2968
                [
1✔
2969
                    crate::api::model::operators::MockDatasetDataSourceLoadingInfo {
1✔
2970
                        points: vec![
1✔
2971
                            Coordinate2D::new(0.0f64, 0.5).into(),
1✔
2972
                            Coordinate2D::new(2., 1.0).into(),
1✔
2973
                        ],
1✔
2974
                    },
1✔
2975
                ],
1✔
2976
            )
1✔
2977
            .await;
6✔
2978

2979
            assert_sql_type(
1✔
2980
                &pool,
1✔
2981
                "OgrSourceTimeFormat",
1✔
2982
                [
1✔
2983
                    crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
2984
                    crate::api::model::operators::OgrSourceTimeFormat::Custom {
1✔
2985
                        custom_format:
1✔
2986
                            geoengine_datatypes::primitives::DateTimeParseFormat::custom(
1✔
2987
                                "%Y-%m-%dT%H:%M:%S%.3fZ".to_string(),
1✔
2988
                            )
1✔
2989
                            .into(),
1✔
2990
                    },
1✔
2991
                    crate::api::model::operators::OgrSourceTimeFormat::UnixTimeStamp {
1✔
2992
                        timestamp_type: UnixTimeStampType::EpochSeconds,
1✔
2993
                        fmt: geoengine_datatypes::primitives::DateTimeParseFormat::unix().into(),
1✔
2994
                    },
1✔
2995
                ],
1✔
2996
            )
1✔
2997
            .await;
16✔
2998

2999
            assert_sql_type(
1✔
3000
                &pool,
1✔
3001
                "OgrSourceDurationSpec",
1✔
3002
                [
1✔
3003
                    crate::api::model::operators::OgrSourceDurationSpec::Infinite,
1✔
3004
                    crate::api::model::operators::OgrSourceDurationSpec::Zero,
1✔
3005
                    crate::api::model::operators::OgrSourceDurationSpec::Value(
1✔
3006
                        TimeStep {
1✔
3007
                            granularity: TimeGranularity::Millis,
1✔
3008
                            step: 1000,
1✔
3009
                        }
1✔
3010
                        .into(),
1✔
3011
                    ),
1✔
3012
                ],
1✔
3013
            )
1✔
3014
            .await;
12✔
3015

3016
            assert_sql_type(
1✔
3017
                &pool,
1✔
3018
                "OgrSourceDatasetTimeType",
1✔
3019
                [
1✔
3020
                    crate::api::model::operators::OgrSourceDatasetTimeType::None,
1✔
3021
                    crate::api::model::operators::OgrSourceDatasetTimeType::Start {
1✔
3022
                        start_field: "start".to_string(),
1✔
3023
                        start_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3024
                        duration: crate::api::model::operators::OgrSourceDurationSpec::Zero,
1✔
3025
                    },
1✔
3026
                    crate::api::model::operators::OgrSourceDatasetTimeType::StartEnd {
1✔
3027
                        start_field: "start".to_string(),
1✔
3028
                        start_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3029
                        end_field: "end".to_string(),
1✔
3030
                        end_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3031
                    },
1✔
3032
                    crate::api::model::operators::OgrSourceDatasetTimeType::StartDuration {
1✔
3033
                        start_field: "start".to_string(),
1✔
3034
                        start_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3035
                        duration_field: "duration".to_string(),
1✔
3036
                    },
1✔
3037
                ],
1✔
3038
            )
1✔
3039
            .await;
16✔
3040

3041
            assert_sql_type(
1✔
3042
                &pool,
1✔
3043
                "FormatSpecifics",
1✔
3044
                [crate::api::model::operators::FormatSpecifics::Csv {
1✔
3045
                    header: CsvHeader::Yes.into(),
1✔
3046
                }],
1✔
3047
            )
1✔
3048
            .await;
8✔
3049

3050
            assert_sql_type(
1✔
3051
                &pool,
1✔
3052
                "OgrSourceColumnSpec",
1✔
3053
                [crate::api::model::operators::OgrSourceColumnSpec {
1✔
3054
                    format_specifics: Some(crate::api::model::operators::FormatSpecifics::Csv {
1✔
3055
                        header: CsvHeader::Auto.into(),
1✔
3056
                    }),
1✔
3057
                    x: "x".to_string(),
1✔
3058
                    y: Some("y".to_string()),
1✔
3059
                    int: vec!["int".to_string()],
1✔
3060
                    float: vec!["float".to_string()],
1✔
3061
                    text: vec!["text".to_string()],
1✔
3062
                    bool: vec!["bool".to_string()],
1✔
3063
                    datetime: vec!["datetime".to_string()],
1✔
3064
                    rename: Some(
1✔
3065
                        [
1✔
3066
                            ("xx".to_string(), "xx_renamed".to_string()),
1✔
3067
                            ("yx".to_string(), "yy_renamed".to_string()),
1✔
3068
                        ]
1✔
3069
                        .into(),
1✔
3070
                    ),
1✔
3071
                }],
1✔
3072
            )
1✔
3073
            .await;
4✔
3074

3075
            assert_sql_type(
1✔
3076
                &pool,
1✔
3077
                "point[]",
1✔
3078
                [MultiPoint {
1✔
3079
                    coordinates: vec![
1✔
3080
                        Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3081
                        Coordinate2D::new(2., 1.0).into(),
1✔
3082
                    ],
1✔
3083
                }],
1✔
3084
            )
1✔
3085
            .await;
2✔
3086

3087
            assert_sql_type(
1✔
3088
                &pool,
1✔
3089
                "path[]",
1✔
3090
                [MultiLineString {
1✔
3091
                    coordinates: vec![
1✔
3092
                        vec![
1✔
3093
                            Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3094
                            Coordinate2D::new(2., 1.0).into(),
1✔
3095
                        ],
1✔
3096
                        vec![
1✔
3097
                            Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3098
                            Coordinate2D::new(2., 1.0).into(),
1✔
3099
                        ],
1✔
3100
                    ],
1✔
3101
                }],
1✔
3102
            )
1✔
3103
            .await;
2✔
3104

3105
            assert_sql_type(
1✔
3106
                &pool,
1✔
3107
                "\"Polygon\"[]",
1✔
3108
                [MultiPolygon {
1✔
3109
                    polygons: vec![
1✔
3110
                        vec![
1✔
3111
                            vec![
1✔
3112
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3113
                                Coordinate2D::new(2., 1.0).into(),
1✔
3114
                                Coordinate2D::new(2., 1.0).into(),
1✔
3115
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3116
                            ],
1✔
3117
                            vec![
1✔
3118
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3119
                                Coordinate2D::new(2., 1.0).into(),
1✔
3120
                                Coordinate2D::new(2., 1.0).into(),
1✔
3121
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3122
                            ],
1✔
3123
                        ],
1✔
3124
                        vec![
1✔
3125
                            vec![
1✔
3126
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3127
                                Coordinate2D::new(2., 1.0).into(),
1✔
3128
                                Coordinate2D::new(2., 1.0).into(),
1✔
3129
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3130
                            ],
1✔
3131
                            vec![
1✔
3132
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3133
                                Coordinate2D::new(2., 1.0).into(),
1✔
3134
                                Coordinate2D::new(2., 1.0).into(),
1✔
3135
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3136
                            ],
1✔
3137
                        ],
1✔
3138
                    ],
1✔
3139
                }],
1✔
3140
            )
1✔
3141
            .await;
4✔
3142

3143
            assert_sql_type(
1✔
3144
                &pool,
1✔
3145
                "TypedGeometry",
1✔
3146
                [
1✔
3147
                    crate::api::model::operators::TypedGeometry::Data(NoGeometry),
1✔
3148
                    crate::api::model::operators::TypedGeometry::MultiPoint(MultiPoint {
1✔
3149
                        coordinates: vec![
1✔
3150
                            Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3151
                            Coordinate2D::new(2., 1.0).into(),
1✔
3152
                        ],
1✔
3153
                    }),
1✔
3154
                    crate::api::model::operators::TypedGeometry::MultiLineString(MultiLineString {
1✔
3155
                        coordinates: vec![
1✔
3156
                            vec![
1✔
3157
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3158
                                Coordinate2D::new(2., 1.0).into(),
1✔
3159
                            ],
1✔
3160
                            vec![
1✔
3161
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3162
                                Coordinate2D::new(2., 1.0).into(),
1✔
3163
                            ],
1✔
3164
                        ],
1✔
3165
                    }),
1✔
3166
                    crate::api::model::operators::TypedGeometry::MultiPolygon(MultiPolygon {
1✔
3167
                        polygons: vec![
1✔
3168
                            vec![
1✔
3169
                                vec![
1✔
3170
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3171
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3172
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3173
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3174
                                ],
1✔
3175
                                vec![
1✔
3176
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3177
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3178
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3179
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3180
                                ],
1✔
3181
                            ],
1✔
3182
                            vec![
1✔
3183
                                vec![
1✔
3184
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3185
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3186
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3187
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3188
                                ],
1✔
3189
                                vec![
1✔
3190
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3191
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3192
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3193
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3194
                                ],
1✔
3195
                            ],
1✔
3196
                        ],
1✔
3197
                    }),
1✔
3198
                ],
1✔
3199
            )
1✔
3200
            .await;
10✔
3201

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

3204
            assert_sql_type(
1✔
3205
                &pool,
1✔
3206
                "OgrSourceDataset",
1✔
3207
                [crate::api::model::operators::OgrSourceDataset {
1✔
3208
                    file_name: "test".into(),
1✔
3209
                    layer_name: "test".to_string(),
1✔
3210
                    data_type: Some(VectorDataType::MultiPoint.into()),
1✔
3211
                    time: crate::api::model::operators::OgrSourceDatasetTimeType::Start {
1✔
3212
                        start_field: "start".to_string(),
1✔
3213
                        start_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3214
                        duration: crate::api::model::operators::OgrSourceDurationSpec::Zero,
1✔
3215
                    },
1✔
3216
                    default_geometry: Some(
1✔
3217
                        crate::api::model::operators::TypedGeometry::MultiPoint(MultiPoint {
1✔
3218
                            coordinates: vec![
1✔
3219
                                Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3220
                                Coordinate2D::new(2., 1.0).into(),
1✔
3221
                            ],
1✔
3222
                        }),
1✔
3223
                    ),
1✔
3224
                    columns: Some(crate::api::model::operators::OgrSourceColumnSpec {
1✔
3225
                        format_specifics: Some(
1✔
3226
                            crate::api::model::operators::FormatSpecifics::Csv {
1✔
3227
                                header: CsvHeader::Auto.into(),
1✔
3228
                            },
1✔
3229
                        ),
1✔
3230
                        x: "x".to_string(),
1✔
3231
                        y: Some("y".to_string()),
1✔
3232
                        int: vec!["int".to_string()],
1✔
3233
                        float: vec!["float".to_string()],
1✔
3234
                        text: vec!["text".to_string()],
1✔
3235
                        bool: vec!["bool".to_string()],
1✔
3236
                        datetime: vec!["datetime".to_string()],
1✔
3237
                        rename: Some(
1✔
3238
                            [
1✔
3239
                                ("xx".to_string(), "xx_renamed".to_string()),
1✔
3240
                                ("yx".to_string(), "yy_renamed".to_string()),
1✔
3241
                            ]
1✔
3242
                            .into(),
1✔
3243
                        ),
1✔
3244
                    }),
1✔
3245
                    force_ogr_time_filter: false,
1✔
3246
                    force_ogr_spatial_filter: true,
1✔
3247
                    on_error: crate::api::model::operators::OgrSourceErrorSpec::Abort,
1✔
3248
                    sql_query: None,
1✔
3249
                    attribute_query: Some("foo = 'bar'".to_string()),
1✔
3250
                    cache_ttl: CacheTtlSeconds::new(5),
1✔
3251
                }],
1✔
3252
            )
1✔
3253
            .await;
6✔
3254

3255
            assert_sql_type(
1✔
3256
                &pool,
1✔
3257
                "MockMetaData",
1✔
3258
                [crate::api::model::operators::MockMetaData {
1✔
3259
                    loading_info: crate::api::model::operators::MockDatasetDataSourceLoadingInfo {
1✔
3260
                        points: vec![
1✔
3261
                            Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3262
                            Coordinate2D::new(2., 1.0).into(),
1✔
3263
                        ],
1✔
3264
                    },
1✔
3265
                    result_descriptor: VectorResultDescriptor {
1✔
3266
                        data_type: VectorDataType::MultiPoint,
1✔
3267
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3268
                            SpatialReference::epsg_4326(),
1✔
3269
                        ),
1✔
3270
                        columns: [(
1✔
3271
                            "foo".to_string(),
1✔
3272
                            VectorColumnInfo {
1✔
3273
                                data_type: FeatureDataType::Int,
1✔
3274
                                measurement: Measurement::Unitless.into(),
1✔
3275
                            },
1✔
3276
                        )]
1✔
3277
                        .into(),
1✔
3278
                        time: Some(TimeInterval::default()),
1✔
3279
                        bbox: Some(
1✔
3280
                            BoundingBox2D::new(
1✔
3281
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3282
                                Coordinate2D::new(2., 1.0),
1✔
3283
                            )
1✔
3284
                            .unwrap(),
1✔
3285
                        ),
1✔
3286
                    }
1✔
3287
                    .into(),
1✔
3288
                    phantom: PhantomData,
1✔
3289
                }],
1✔
3290
            )
1✔
3291
            .await;
4✔
3292

3293
            assert_sql_type(
1✔
3294
                &pool,
1✔
3295
                "OgrMetaData",
1✔
3296
                [crate::api::model::operators::OgrMetaData {
1✔
3297
                    loading_info: crate::api::model::operators::OgrSourceDataset {
1✔
3298
                        file_name: "test".into(),
1✔
3299
                        layer_name: "test".to_string(),
1✔
3300
                        data_type: Some(VectorDataType::MultiPoint.into()),
1✔
3301
                        time: crate::api::model::operators::OgrSourceDatasetTimeType::Start {
1✔
3302
                            start_field: "start".to_string(),
1✔
3303
                            start_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3304
                            duration: crate::api::model::operators::OgrSourceDurationSpec::Zero,
1✔
3305
                        },
1✔
3306
                        default_geometry: Some(
1✔
3307
                            crate::api::model::operators::TypedGeometry::MultiPoint(MultiPoint {
1✔
3308
                                coordinates: vec![
1✔
3309
                                    Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3310
                                    Coordinate2D::new(2., 1.0).into(),
1✔
3311
                                ],
1✔
3312
                            }),
1✔
3313
                        ),
1✔
3314
                        columns: Some(crate::api::model::operators::OgrSourceColumnSpec {
1✔
3315
                            format_specifics: Some(
1✔
3316
                                crate::api::model::operators::FormatSpecifics::Csv {
1✔
3317
                                    header: CsvHeader::Auto.into(),
1✔
3318
                                },
1✔
3319
                            ),
1✔
3320
                            x: "x".to_string(),
1✔
3321
                            y: Some("y".to_string()),
1✔
3322
                            int: vec!["int".to_string()],
1✔
3323
                            float: vec!["float".to_string()],
1✔
3324
                            text: vec!["text".to_string()],
1✔
3325
                            bool: vec!["bool".to_string()],
1✔
3326
                            datetime: vec!["datetime".to_string()],
1✔
3327
                            rename: Some(
1✔
3328
                                [
1✔
3329
                                    ("xx".to_string(), "xx_renamed".to_string()),
1✔
3330
                                    ("yx".to_string(), "yy_renamed".to_string()),
1✔
3331
                                ]
1✔
3332
                                .into(),
1✔
3333
                            ),
1✔
3334
                        }),
1✔
3335
                        force_ogr_time_filter: false,
1✔
3336
                        force_ogr_spatial_filter: true,
1✔
3337
                        on_error: crate::api::model::operators::OgrSourceErrorSpec::Abort,
1✔
3338
                        sql_query: None,
1✔
3339
                        attribute_query: Some("foo = 'bar'".to_string()),
1✔
3340
                        cache_ttl: CacheTtlSeconds::new(5),
1✔
3341
                    },
1✔
3342
                    result_descriptor: VectorResultDescriptor {
1✔
3343
                        data_type: VectorDataType::MultiPoint,
1✔
3344
                        spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3345
                            SpatialReference::epsg_4326(),
1✔
3346
                        ),
1✔
3347
                        columns: [(
1✔
3348
                            "foo".to_string(),
1✔
3349
                            VectorColumnInfo {
1✔
3350
                                data_type: FeatureDataType::Int,
1✔
3351
                                measurement: Measurement::Unitless.into(),
1✔
3352
                            },
1✔
3353
                        )]
1✔
3354
                        .into(),
1✔
3355
                        time: Some(TimeInterval::default()),
1✔
3356
                        bbox: Some(
1✔
3357
                            BoundingBox2D::new(
1✔
3358
                                Coordinate2D::new(0.0f64, 0.5),
1✔
3359
                                Coordinate2D::new(2., 1.0),
1✔
3360
                            )
1✔
3361
                            .unwrap(),
1✔
3362
                        ),
1✔
3363
                    }
1✔
3364
                    .into(),
1✔
3365
                    phantom: PhantomData,
1✔
3366
                }],
1✔
3367
            )
1✔
3368
            .await;
4✔
3369

3370
            assert_sql_type(
1✔
3371
                &pool,
1✔
3372
                "GdalDatasetGeoTransform",
1✔
3373
                [crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3374
                    origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3375
                    x_pixel_size: 1.0,
1✔
3376
                    y_pixel_size: 2.0,
1✔
3377
                }],
1✔
3378
            )
1✔
3379
            .await;
4✔
3380

3381
            assert_sql_type(
1✔
3382
                &pool,
1✔
3383
                "FileNotFoundHandling",
1✔
3384
                [
1✔
3385
                    crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3386
                    crate::api::model::operators::FileNotFoundHandling::Error,
1✔
3387
                ],
1✔
3388
            )
1✔
3389
            .await;
6✔
3390

3391
            assert_sql_type(
1✔
3392
                &pool,
1✔
3393
                "GdalMetadataMapping",
1✔
3394
                [crate::api::model::operators::GdalMetadataMapping {
1✔
3395
                    source_key: RasterPropertiesKey {
1✔
3396
                        domain: None,
1✔
3397
                        key: "foo".to_string(),
1✔
3398
                    },
1✔
3399
                    target_key: RasterPropertiesKey {
1✔
3400
                        domain: Some("bar".to_string()),
1✔
3401
                        key: "foo".to_string(),
1✔
3402
                    },
1✔
3403
                    target_type: RasterPropertiesEntryType::String,
1✔
3404
                }],
1✔
3405
            )
1✔
3406
            .await;
8✔
3407

3408
            assert_sql_type(
1✔
3409
                &pool,
1✔
3410
                "StringPair",
1✔
3411
                [StringPair::from(("foo".to_string(), "bar".to_string()))],
1✔
3412
            )
1✔
3413
            .await;
3✔
3414

3415
            assert_sql_type(
1✔
3416
                &pool,
1✔
3417
                "GdalDatasetParameters",
1✔
3418
                [crate::api::model::operators::GdalDatasetParameters {
1✔
3419
                    file_path: "text".into(),
1✔
3420
                    rasterband_channel: 1,
1✔
3421
                    geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3422
                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3423
                        x_pixel_size: 1.0,
1✔
3424
                        y_pixel_size: 2.0,
1✔
3425
                    },
1✔
3426
                    width: 42,
1✔
3427
                    height: 23,
1✔
3428
                    file_not_found_handling:
1✔
3429
                        crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3430
                    no_data_value: Some(42.0),
1✔
3431
                    properties_mapping: Some(vec![
1✔
3432
                        crate::api::model::operators::GdalMetadataMapping {
1✔
3433
                            source_key: RasterPropertiesKey {
1✔
3434
                                domain: None,
1✔
3435
                                key: "foo".to_string(),
1✔
3436
                            },
1✔
3437
                            target_key: RasterPropertiesKey {
1✔
3438
                                domain: Some("bar".to_string()),
1✔
3439
                                key: "foo".to_string(),
1✔
3440
                            },
1✔
3441
                            target_type: RasterPropertiesEntryType::String,
1✔
3442
                        },
1✔
3443
                    ]),
1✔
3444
                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3445
                    gdal_config_options: Some(vec![
1✔
3446
                        crate::api::model::operators::GdalConfigOption::from((
1✔
3447
                            "foo".to_string(),
1✔
3448
                            "bar".to_string(),
1✔
3449
                        )),
1✔
3450
                    ]),
1✔
3451
                    allow_alphaband_as_mask: false,
1✔
3452
                }],
1✔
3453
            )
1✔
3454
            .await;
6✔
3455

3456
            assert_sql_type(
1✔
3457
                &pool,
1✔
3458
                "TextGdalSourceTimePlaceholderKeyValue",
1✔
3459
                [crate::api::model::TextGdalSourceTimePlaceholderKeyValue {
1✔
3460
                    key: "foo".to_string(),
1✔
3461
                    value: GdalSourceTimePlaceholder {
1✔
3462
                        format: geoengine_datatypes::primitives::DateTimeParseFormat::unix().into(),
1✔
3463
                        reference: TimeReference::Start,
1✔
3464
                    },
1✔
3465
                }],
1✔
3466
            )
1✔
3467
            .await;
8✔
3468

3469
            assert_sql_type(
1✔
3470
                &pool,
1✔
3471
                "GdalMetaDataRegular",
1✔
3472
                [crate::api::model::operators::GdalMetaDataRegular {
1✔
3473
                    result_descriptor: RasterResultDescriptor {
1✔
3474
                        data_type: RasterDataType::U8,
1✔
3475
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3476
                        measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3477
                            measurement: "Temperature".to_string(),
1✔
3478
                            unit: Some("°C".to_string()),
1✔
3479
                        })
1✔
3480
                        .into(),
1✔
3481
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3482
                        bbox: Some(
1✔
3483
                            crate::api::model::datatypes::SpatialPartition2D {
1✔
3484
                                upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
3485
                                lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
3486
                            }
1✔
3487
                            .into(),
1✔
3488
                        ),
1✔
3489
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3490
                    }
1✔
3491
                    .into(),
1✔
3492
                    params: crate::api::model::operators::GdalDatasetParameters {
1✔
3493
                        file_path: "text".into(),
1✔
3494
                        rasterband_channel: 1,
1✔
3495
                        geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3496
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3497
                            x_pixel_size: 1.0,
1✔
3498
                            y_pixel_size: 2.0,
1✔
3499
                        },
1✔
3500
                        width: 42,
1✔
3501
                        height: 23,
1✔
3502
                        file_not_found_handling:
1✔
3503
                            crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3504
                        no_data_value: Some(42.0),
1✔
3505
                        properties_mapping: Some(vec![
1✔
3506
                            crate::api::model::operators::GdalMetadataMapping {
1✔
3507
                                source_key: RasterPropertiesKey {
1✔
3508
                                    domain: None,
1✔
3509
                                    key: "foo".to_string(),
1✔
3510
                                },
1✔
3511
                                target_key: RasterPropertiesKey {
1✔
3512
                                    domain: Some("bar".to_string()),
1✔
3513
                                    key: "foo".to_string(),
1✔
3514
                                },
1✔
3515
                                target_type: RasterPropertiesEntryType::String,
1✔
3516
                            },
1✔
3517
                        ]),
1✔
3518
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3519
                        gdal_config_options: Some(vec![
1✔
3520
                            crate::api::model::operators::GdalConfigOption::from((
1✔
3521
                                "foo".to_string(),
1✔
3522
                                "bar".to_string(),
1✔
3523
                            )),
1✔
3524
                        ]),
1✔
3525
                        allow_alphaband_as_mask: false,
1✔
3526
                    },
1✔
3527
                    time_placeholders: [(
1✔
3528
                        "foo".to_string(),
1✔
3529
                        GdalSourceTimePlaceholder {
1✔
3530
                            format: geoengine_datatypes::primitives::DateTimeParseFormat::unix()
1✔
3531
                                .into(),
1✔
3532
                            reference: TimeReference::Start,
1✔
3533
                        },
1✔
3534
                    )]
1✔
3535
                    .into(),
1✔
3536
                    data_time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3537
                    step: TimeStep {
1✔
3538
                        granularity: TimeGranularity::Millis,
1✔
3539
                        step: 1,
1✔
3540
                    }
1✔
3541
                    .into(),
1✔
3542
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3543
                }],
1✔
3544
            )
1✔
3545
            .await;
5✔
3546

3547
            assert_sql_type(
1✔
3548
                &pool,
1✔
3549
                "GdalMetaDataStatic",
1✔
3550
                [crate::api::model::operators::GdalMetaDataStatic {
1✔
3551
                    time: Some(TimeInterval::new_unchecked(0, 1).into()),
1✔
3552
                    result_descriptor: RasterResultDescriptor {
1✔
3553
                        data_type: RasterDataType::U8,
1✔
3554
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3555
                        measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3556
                            measurement: "Temperature".to_string(),
1✔
3557
                            unit: Some("°C".to_string()),
1✔
3558
                        })
1✔
3559
                        .into(),
1✔
3560
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3561
                        bbox: Some(
1✔
3562
                            crate::api::model::datatypes::SpatialPartition2D {
1✔
3563
                                upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
3564
                                lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
3565
                            }
1✔
3566
                            .into(),
1✔
3567
                        ),
1✔
3568
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3569
                    }
1✔
3570
                    .into(),
1✔
3571
                    params: crate::api::model::operators::GdalDatasetParameters {
1✔
3572
                        file_path: "text".into(),
1✔
3573
                        rasterband_channel: 1,
1✔
3574
                        geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3575
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3576
                            x_pixel_size: 1.0,
1✔
3577
                            y_pixel_size: 2.0,
1✔
3578
                        },
1✔
3579
                        width: 42,
1✔
3580
                        height: 23,
1✔
3581
                        file_not_found_handling:
1✔
3582
                            crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3583
                        no_data_value: Some(42.0),
1✔
3584
                        properties_mapping: Some(vec![
1✔
3585
                            crate::api::model::operators::GdalMetadataMapping {
1✔
3586
                                source_key: RasterPropertiesKey {
1✔
3587
                                    domain: None,
1✔
3588
                                    key: "foo".to_string(),
1✔
3589
                                },
1✔
3590
                                target_key: RasterPropertiesKey {
1✔
3591
                                    domain: Some("bar".to_string()),
1✔
3592
                                    key: "foo".to_string(),
1✔
3593
                                },
1✔
3594
                                target_type: RasterPropertiesEntryType::String,
1✔
3595
                            },
1✔
3596
                        ]),
1✔
3597
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3598
                        gdal_config_options: Some(vec![
1✔
3599
                            crate::api::model::operators::GdalConfigOption::from((
1✔
3600
                                "foo".to_string(),
1✔
3601
                                "bar".to_string(),
1✔
3602
                            )),
1✔
3603
                        ]),
1✔
3604
                        allow_alphaband_as_mask: false,
1✔
3605
                    },
1✔
3606
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3607
                }],
1✔
3608
            )
1✔
3609
            .await;
4✔
3610

3611
            assert_sql_type(
1✔
3612
                &pool,
1✔
3613
                "GdalMetadataNetCdfCf",
1✔
3614
                [crate::api::model::operators::GdalMetadataNetCdfCf {
1✔
3615
                    result_descriptor: RasterResultDescriptor {
1✔
3616
                        data_type: RasterDataType::U8,
1✔
3617
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3618
                        measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3619
                            measurement: "Temperature".to_string(),
1✔
3620
                            unit: Some("°C".to_string()),
1✔
3621
                        })
1✔
3622
                        .into(),
1✔
3623
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3624
                        bbox: Some(
1✔
3625
                            crate::api::model::datatypes::SpatialPartition2D {
1✔
3626
                                upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
3627
                                lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
3628
                            }
1✔
3629
                            .into(),
1✔
3630
                        ),
1✔
3631
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3632
                    }
1✔
3633
                    .into(),
1✔
3634
                    params: crate::api::model::operators::GdalDatasetParameters {
1✔
3635
                        file_path: "text".into(),
1✔
3636
                        rasterband_channel: 1,
1✔
3637
                        geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3638
                            origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3639
                            x_pixel_size: 1.0,
1✔
3640
                            y_pixel_size: 2.0,
1✔
3641
                        },
1✔
3642
                        width: 42,
1✔
3643
                        height: 23,
1✔
3644
                        file_not_found_handling:
1✔
3645
                            crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3646
                        no_data_value: Some(42.0),
1✔
3647
                        properties_mapping: Some(vec![
1✔
3648
                            crate::api::model::operators::GdalMetadataMapping {
1✔
3649
                                source_key: RasterPropertiesKey {
1✔
3650
                                    domain: None,
1✔
3651
                                    key: "foo".to_string(),
1✔
3652
                                },
1✔
3653
                                target_key: RasterPropertiesKey {
1✔
3654
                                    domain: Some("bar".to_string()),
1✔
3655
                                    key: "foo".to_string(),
1✔
3656
                                },
1✔
3657
                                target_type: RasterPropertiesEntryType::String,
1✔
3658
                            },
1✔
3659
                        ]),
1✔
3660
                        gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3661
                        gdal_config_options: Some(vec![
1✔
3662
                            crate::api::model::operators::GdalConfigOption::from((
1✔
3663
                                "foo".to_string(),
1✔
3664
                                "bar".to_string(),
1✔
3665
                            )),
1✔
3666
                        ]),
1✔
3667
                        allow_alphaband_as_mask: false,
1✔
3668
                    },
1✔
3669
                    start: TimeInstance::from_millis(0).unwrap().into(),
1✔
3670
                    end: TimeInstance::from_millis(1000).unwrap().into(),
1✔
3671
                    cache_ttl: CacheTtlSeconds::max(),
1✔
3672
                    step: TimeStep {
1✔
3673
                        granularity: TimeGranularity::Millis,
1✔
3674
                        step: 1,
1✔
3675
                    }
1✔
3676
                    .into(),
1✔
3677
                    band_offset: 3,
1✔
3678
                }],
1✔
3679
            )
1✔
3680
            .await;
4✔
3681

3682
            assert_sql_type(
1✔
3683
                &pool,
1✔
3684
                "GdalMetaDataList",
1✔
3685
                [crate::api::model::operators::GdalMetaDataList {
1✔
3686
                    result_descriptor: RasterResultDescriptor {
1✔
3687
                        data_type: RasterDataType::U8,
1✔
3688
                        spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3689
                        measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3690
                            measurement: "Temperature".to_string(),
1✔
3691
                            unit: Some("°C".to_string()),
1✔
3692
                        })
1✔
3693
                        .into(),
1✔
3694
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3695
                        bbox: Some(
1✔
3696
                            crate::api::model::datatypes::SpatialPartition2D {
1✔
3697
                                upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
3698
                                lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
3699
                            }
1✔
3700
                            .into(),
1✔
3701
                        ),
1✔
3702
                        resolution: Some(SpatialResolution::zero_point_one()),
1✔
3703
                    }
1✔
3704
                    .into(),
1✔
3705
                    params: vec![crate::api::model::operators::GdalLoadingInfoTemporalSlice {
1✔
3706
                        time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3707
                        params: Some(crate::api::model::operators::GdalDatasetParameters {
1✔
3708
                            file_path: "text".into(),
1✔
3709
                            rasterband_channel: 1,
1✔
3710
                            geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3711
                                origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3712
                                x_pixel_size: 1.0,
1✔
3713
                                y_pixel_size: 2.0,
1✔
3714
                            },
1✔
3715
                            width: 42,
1✔
3716
                            height: 23,
1✔
3717
                            file_not_found_handling:
1✔
3718
                                crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3719
                            no_data_value: Some(42.0),
1✔
3720
                            properties_mapping: Some(vec![
1✔
3721
                                crate::api::model::operators::GdalMetadataMapping {
1✔
3722
                                    source_key: RasterPropertiesKey {
1✔
3723
                                        domain: None,
1✔
3724
                                        key: "foo".to_string(),
1✔
3725
                                    },
1✔
3726
                                    target_key: RasterPropertiesKey {
1✔
3727
                                        domain: Some("bar".to_string()),
1✔
3728
                                        key: "foo".to_string(),
1✔
3729
                                    },
1✔
3730
                                    target_type: RasterPropertiesEntryType::String,
1✔
3731
                                },
1✔
3732
                            ]),
1✔
3733
                            gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3734
                            gdal_config_options: Some(vec![
1✔
3735
                                crate::api::model::operators::GdalConfigOption::from((
1✔
3736
                                    "foo".to_string(),
1✔
3737
                                    "bar".to_string(),
1✔
3738
                                )),
1✔
3739
                            ]),
1✔
3740
                            allow_alphaband_as_mask: false,
1✔
3741
                        }),
1✔
3742
                        cache_ttl: CacheTtlSeconds::max(),
1✔
3743
                    }],
1✔
3744
                }],
1✔
3745
            )
1✔
3746
            .await;
7✔
3747

3748
            assert_sql_type(
1✔
3749
                &pool,
1✔
3750
                "MetaDataDefinition",
1✔
3751
                [
1✔
3752
                    crate::datasets::storage::MetaDataDefinition::MockMetaData(
1✔
3753
                        crate::api::model::operators::MockMetaData {
1✔
3754
                            loading_info:
1✔
3755
                                crate::api::model::operators::MockDatasetDataSourceLoadingInfo {
1✔
3756
                                    points: vec![
1✔
3757
                                        Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3758
                                        Coordinate2D::new(2., 1.0).into(),
1✔
3759
                                    ],
1✔
3760
                                },
1✔
3761
                            result_descriptor: VectorResultDescriptor {
1✔
3762
                                data_type: VectorDataType::MultiPoint,
1✔
3763
                                spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3764
                                    SpatialReference::epsg_4326(),
1✔
3765
                                ),
1✔
3766
                                columns: [(
1✔
3767
                                    "foo".to_string(),
1✔
3768
                                    VectorColumnInfo {
1✔
3769
                                        data_type: FeatureDataType::Int,
1✔
3770
                                        measurement: Measurement::Unitless.into(),
1✔
3771
                                    },
1✔
3772
                                )]
1✔
3773
                                .into(),
1✔
3774
                                time: Some(TimeInterval::default()),
1✔
3775
                                bbox: Some(
1✔
3776
                                    BoundingBox2D::new(
1✔
3777
                                        Coordinate2D::new(0.0f64, 0.5),
1✔
3778
                                        Coordinate2D::new(2., 1.0),
1✔
3779
                                    )
1✔
3780
                                    .unwrap(),
1✔
3781
                                ),
1✔
3782
                            }
1✔
3783
                            .into(),
1✔
3784
                            phantom: PhantomData,
1✔
3785
                        }.into(),
1✔
3786
                    ),
1✔
3787
                    crate::api::model::services::MetaDataDefinition::OgrMetaData(crate::api::model::operators::OgrMetaData {
1✔
3788
                        loading_info: crate::api::model::operators::OgrSourceDataset {
1✔
3789
                            file_name: "test".into(),
1✔
3790
                            layer_name: "test".to_string(),
1✔
3791
                            data_type: Some(VectorDataType::MultiPoint.into()),
1✔
3792
                            time: crate::api::model::operators::OgrSourceDatasetTimeType::Start {
1✔
3793
                                start_field: "start".to_string(),
1✔
3794
                                start_format: crate::api::model::operators::OgrSourceTimeFormat::Auto,
1✔
3795
                                duration: crate::api::model::operators::OgrSourceDurationSpec::Zero,
1✔
3796
                            },
1✔
3797
                            default_geometry: Some(
1✔
3798
                                crate::api::model::operators::TypedGeometry::MultiPoint(MultiPoint {
1✔
3799
                                    coordinates: vec![
1✔
3800
                                        Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3801
                                        Coordinate2D::new(2., 1.0).into(),
1✔
3802
                                    ],
1✔
3803
                                }),
1✔
3804
                            ),
1✔
3805
                            columns: Some(crate::api::model::operators::OgrSourceColumnSpec {
1✔
3806
                                format_specifics: Some(
1✔
3807
                                    crate::api::model::operators::FormatSpecifics::Csv {
1✔
3808
                                        header: CsvHeader::Auto.into(),
1✔
3809
                                    },
1✔
3810
                                ),
1✔
3811
                                x: "x".to_string(),
1✔
3812
                                y: Some("y".to_string()),
1✔
3813
                                int: vec!["int".to_string()],
1✔
3814
                                float: vec!["float".to_string()],
1✔
3815
                                text: vec!["text".to_string()],
1✔
3816
                                bool: vec!["bool".to_string()],
1✔
3817
                                datetime: vec!["datetime".to_string()],
1✔
3818
                                rename: Some(
1✔
3819
                                    [
1✔
3820
                                        ("xx".to_string(), "xx_renamed".to_string()),
1✔
3821
                                        ("yx".to_string(), "yy_renamed".to_string()),
1✔
3822
                                    ]
1✔
3823
                                    .into(),
1✔
3824
                                ),
1✔
3825
                            }),
1✔
3826
                            force_ogr_time_filter: false,
1✔
3827
                            force_ogr_spatial_filter: true,
1✔
3828
                            on_error: crate::api::model::operators::OgrSourceErrorSpec::Abort,
1✔
3829
                            sql_query: None,
1✔
3830
                            attribute_query: Some("foo = 'bar'".to_string()),
1✔
3831
                            cache_ttl: CacheTtlSeconds::new(5),
1✔
3832
                        },
1✔
3833
                        result_descriptor: VectorResultDescriptor {
1✔
3834
                            data_type: VectorDataType::MultiPoint,
1✔
3835
                            spatial_reference: SpatialReferenceOption::SpatialReference(
1✔
3836
                                SpatialReference::epsg_4326(),
1✔
3837
                            ),
1✔
3838
                            columns: [(
1✔
3839
                                "foo".to_string(),
1✔
3840
                                VectorColumnInfo {
1✔
3841
                                    data_type: FeatureDataType::Int,
1✔
3842
                                    measurement: Measurement::Unitless.into(),
1✔
3843
                                },
1✔
3844
                            )]
1✔
3845
                            .into(),
1✔
3846
                            time: Some(TimeInterval::default()),
1✔
3847
                            bbox: Some(
1✔
3848
                                BoundingBox2D::new(
1✔
3849
                                    Coordinate2D::new(0.0f64, 0.5),
1✔
3850
                                    Coordinate2D::new(2., 1.0),
1✔
3851
                                )
1✔
3852
                                .unwrap(),
1✔
3853
                            ),
1✔
3854
                        }
1✔
3855
                        .into(),
1✔
3856
                        phantom: PhantomData,
1✔
3857
                    }).into(),
1✔
3858
                    crate::api::model::services::MetaDataDefinition::GdalMetaDataRegular(crate::api::model::operators::GdalMetaDataRegular {
1✔
3859
                        result_descriptor: RasterResultDescriptor {
1✔
3860
                            data_type: RasterDataType::U8,
1✔
3861
                            spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3862
                            measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3863
                                measurement: "Temperature".to_string(),
1✔
3864
                                unit: Some("°C".to_string()),
1✔
3865
                            })
1✔
3866
                            .into(),
1✔
3867
                            time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3868
                            bbox: Some(
1✔
3869
                                crate::api::model::datatypes::SpatialPartition2D {
1✔
3870
                                    upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
3871
                                    lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
3872
                                }
1✔
3873
                                .into(),
1✔
3874
                            ),
1✔
3875
                            resolution: Some(SpatialResolution::zero_point_one()),
1✔
3876
                        }
1✔
3877
                        .into(),
1✔
3878
                        params: crate::api::model::operators::GdalDatasetParameters {
1✔
3879
                            file_path: "text".into(),
1✔
3880
                            rasterband_channel: 1,
1✔
3881
                            geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3882
                                origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3883
                                x_pixel_size: 1.0,
1✔
3884
                                y_pixel_size: 2.0,
1✔
3885
                            },
1✔
3886
                            width: 42,
1✔
3887
                            height: 23,
1✔
3888
                            file_not_found_handling:
1✔
3889
                                crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3890
                            no_data_value: Some(42.0),
1✔
3891
                            properties_mapping: Some(vec![
1✔
3892
                                crate::api::model::operators::GdalMetadataMapping {
1✔
3893
                                    source_key: RasterPropertiesKey {
1✔
3894
                                        domain: None,
1✔
3895
                                        key: "foo".to_string(),
1✔
3896
                                    },
1✔
3897
                                    target_key: RasterPropertiesKey {
1✔
3898
                                        domain: Some("bar".to_string()),
1✔
3899
                                        key: "foo".to_string(),
1✔
3900
                                    },
1✔
3901
                                    target_type: RasterPropertiesEntryType::String,
1✔
3902
                                },
1✔
3903
                            ]),
1✔
3904
                            gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3905
                            gdal_config_options: Some(vec![
1✔
3906
                                crate::api::model::operators::GdalConfigOption::from((
1✔
3907
                                    "foo".to_string(),
1✔
3908
                                    "bar".to_string(),
1✔
3909
                                )),
1✔
3910
                            ]),
1✔
3911
                            allow_alphaband_as_mask: false,
1✔
3912
                        },
1✔
3913
                        time_placeholders: [(
1✔
3914
                            "foo".to_string(),
1✔
3915
                            GdalSourceTimePlaceholder {
1✔
3916
                                format: geoengine_datatypes::primitives::DateTimeParseFormat::unix()
1✔
3917
                                    .into(),
1✔
3918
                                reference: TimeReference::Start,
1✔
3919
                            },
1✔
3920
                        )]
1✔
3921
                        .into(),
1✔
3922
                        data_time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3923
                        step: TimeStep {
1✔
3924
                            granularity: TimeGranularity::Millis,
1✔
3925
                            step: 1,
1✔
3926
                        }
1✔
3927
                        .into(),
1✔
3928
                        cache_ttl: CacheTtlSeconds::max(),
1✔
3929
                    }).into(),
1✔
3930
                    crate::api::model::services::MetaDataDefinition::GdalStatic(crate::api::model::operators::GdalMetaDataStatic {
1✔
3931
                        time: Some(TimeInterval::new_unchecked(0, 1).into()),
1✔
3932
                        result_descriptor: RasterResultDescriptor {
1✔
3933
                            data_type: RasterDataType::U8,
1✔
3934
                            spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3935
                            measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3936
                                measurement: "Temperature".to_string(),
1✔
3937
                                unit: Some("°C".to_string()),
1✔
3938
                            })
1✔
3939
                            .into(),
1✔
3940
                            time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3941
                            bbox: Some(
1✔
3942
                                crate::api::model::datatypes::SpatialPartition2D {
1✔
3943
                                    upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
3944
                                    lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
3945
                                }
1✔
3946
                                .into(),
1✔
3947
                            ),
1✔
3948
                            resolution: Some(SpatialResolution::zero_point_one()),
1✔
3949
                        }
1✔
3950
                        .into(),
1✔
3951
                        params: crate::api::model::operators::GdalDatasetParameters {
1✔
3952
                            file_path: "text".into(),
1✔
3953
                            rasterband_channel: 1,
1✔
3954
                            geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
3955
                                origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
3956
                                x_pixel_size: 1.0,
1✔
3957
                                y_pixel_size: 2.0,
1✔
3958
                            },
1✔
3959
                            width: 42,
1✔
3960
                            height: 23,
1✔
3961
                            file_not_found_handling:
1✔
3962
                                crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
3963
                            no_data_value: Some(42.0),
1✔
3964
                            properties_mapping: Some(vec![
1✔
3965
                                crate::api::model::operators::GdalMetadataMapping {
1✔
3966
                                    source_key: RasterPropertiesKey {
1✔
3967
                                        domain: None,
1✔
3968
                                        key: "foo".to_string(),
1✔
3969
                                    },
1✔
3970
                                    target_key: RasterPropertiesKey {
1✔
3971
                                        domain: Some("bar".to_string()),
1✔
3972
                                        key: "foo".to_string(),
1✔
3973
                                    },
1✔
3974
                                    target_type: RasterPropertiesEntryType::String,
1✔
3975
                                },
1✔
3976
                            ]),
1✔
3977
                            gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
3978
                            gdal_config_options: Some(vec![
1✔
3979
                                crate::api::model::operators::GdalConfigOption::from((
1✔
3980
                                    "foo".to_string(),
1✔
3981
                                    "bar".to_string(),
1✔
3982
                                )),
1✔
3983
                            ]),
1✔
3984
                            allow_alphaband_as_mask: false,
1✔
3985
                        },
1✔
3986
                        cache_ttl: CacheTtlSeconds::max(),
1✔
3987
                    }).into(),
1✔
3988
                    crate::api::model::services::MetaDataDefinition::GdalMetadataNetCdfCf(crate::api::model::operators::GdalMetadataNetCdfCf {
1✔
3989
                        result_descriptor: RasterResultDescriptor {
1✔
3990
                            data_type: RasterDataType::U8,
1✔
3991
                            spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3992
                            measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
3993
                                measurement: "Temperature".to_string(),
1✔
3994
                                unit: Some("°C".to_string()),
1✔
3995
                            })
1✔
3996
                            .into(),
1✔
3997
                            time: TimeInterval::new_unchecked(0, 1).into(),
1✔
3998
                            bbox: Some(
1✔
3999
                                crate::api::model::datatypes::SpatialPartition2D {
1✔
4000
                                    upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
4001
                                    lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
4002
                                }
1✔
4003
                                .into(),
1✔
4004
                            ),
1✔
4005
                            resolution: Some(SpatialResolution::zero_point_one()),
1✔
4006
                        }
1✔
4007
                        .into(),
1✔
4008
                        params: crate::api::model::operators::GdalDatasetParameters {
1✔
4009
                            file_path: "text".into(),
1✔
4010
                            rasterband_channel: 1,
1✔
4011
                            geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
4012
                                origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
4013
                                x_pixel_size: 1.0,
1✔
4014
                                y_pixel_size: 2.0,
1✔
4015
                            },
1✔
4016
                            width: 42,
1✔
4017
                            height: 23,
1✔
4018
                            file_not_found_handling:
1✔
4019
                                crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
4020
                            no_data_value: Some(42.0),
1✔
4021
                            properties_mapping: Some(vec![
1✔
4022
                                crate::api::model::operators::GdalMetadataMapping {
1✔
4023
                                    source_key: RasterPropertiesKey {
1✔
4024
                                        domain: None,
1✔
4025
                                        key: "foo".to_string(),
1✔
4026
                                    },
1✔
4027
                                    target_key: RasterPropertiesKey {
1✔
4028
                                        domain: Some("bar".to_string()),
1✔
4029
                                        key: "foo".to_string(),
1✔
4030
                                    },
1✔
4031
                                    target_type: RasterPropertiesEntryType::String,
1✔
4032
                                },
1✔
4033
                            ]),
1✔
4034
                            gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4035
                            gdal_config_options: Some(vec![
1✔
4036
                                crate::api::model::operators::GdalConfigOption::from((
1✔
4037
                                    "foo".to_string(),
1✔
4038
                                    "bar".to_string(),
1✔
4039
                                )),
1✔
4040
                            ]),
1✔
4041
                            allow_alphaband_as_mask: false,
1✔
4042
                        },
1✔
4043
                        start: TimeInstance::from_millis(0).unwrap().into(),
1✔
4044
                        end: TimeInstance::from_millis(1000).unwrap().into(),
1✔
4045
                        cache_ttl: CacheTtlSeconds::max(),
1✔
4046
                        step: TimeStep {
1✔
4047
                            granularity: TimeGranularity::Millis,
1✔
4048
                            step: 1,
1✔
4049
                        }
1✔
4050
                        .into(),
1✔
4051
                        band_offset: 3,
1✔
4052
                    }).into(),
1✔
4053
                    crate::api::model::services::MetaDataDefinition::GdalMetaDataList(
1✔
4054
                        crate::api::model::operators::GdalMetaDataList {
1✔
4055
                            result_descriptor: RasterResultDescriptor {
1✔
4056
                                data_type: RasterDataType::U8,
1✔
4057
                                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4058
                                measurement: Measurement::Continuous(ContinuousMeasurement {
1✔
4059
                                    measurement: "Temperature".to_string(),
1✔
4060
                                    unit: Some("°C".to_string()),
1✔
4061
                                })
1✔
4062
                                .into(),
1✔
4063
                                time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4064
                                bbox: Some(
1✔
4065
                                    crate::api::model::datatypes::SpatialPartition2D {
1✔
4066
                                        upper_left_coordinate: Coordinate2D::new(0.0f64, 1.).into(),
1✔
4067
                                        lower_right_coordinate: Coordinate2D::new(2., 0.5).into(),
1✔
4068
                                    }
1✔
4069
                                    .into(),
1✔
4070
                                ),
1✔
4071
                                resolution: Some(SpatialResolution::zero_point_one()),
1✔
4072
                            }
1✔
4073
                            .into(),
1✔
4074
                            params: vec![crate::api::model::operators::GdalLoadingInfoTemporalSlice {
1✔
4075
                                time: TimeInterval::new_unchecked(0, 1).into(),
1✔
4076
                                params: Some(crate::api::model::operators::GdalDatasetParameters {
1✔
4077
                                    file_path: "text".into(),
1✔
4078
                                    rasterband_channel: 1,
1✔
4079
                                    geo_transform: crate::api::model::operators::GdalDatasetGeoTransform {
1✔
4080
                                        origin_coordinate: Coordinate2D::new(0.0f64, 0.5).into(),
1✔
4081
                                        x_pixel_size: 1.0,
1✔
4082
                                        y_pixel_size: 2.0,
1✔
4083
                                    },
1✔
4084
                                    width: 42,
1✔
4085
                                    height: 23,
1✔
4086
                                    file_not_found_handling:
1✔
4087
                                        crate::api::model::operators::FileNotFoundHandling::NoData,
1✔
4088
                                    no_data_value: Some(42.0),
1✔
4089
                                    properties_mapping: Some(vec![
1✔
4090
                                        crate::api::model::operators::GdalMetadataMapping {
1✔
4091
                                            source_key: RasterPropertiesKey {
1✔
4092
                                                domain: None,
1✔
4093
                                                key: "foo".to_string(),
1✔
4094
                                            },
1✔
4095
                                            target_key: RasterPropertiesKey {
1✔
4096
                                                domain: Some("bar".to_string()),
1✔
4097
                                                key: "foo".to_string(),
1✔
4098
                                            },
1✔
4099
                                            target_type: RasterPropertiesEntryType::String,
1✔
4100
                                        },
1✔
4101
                                    ]),
1✔
4102
                                    gdal_open_options: Some(vec!["foo".to_string(), "bar".to_string()]),
1✔
4103
                                    gdal_config_options: Some(vec![
1✔
4104
                                        crate::api::model::operators::GdalConfigOption::from((
1✔
4105
                                            "foo".to_string(),
1✔
4106
                                            "bar".to_string(),
1✔
4107
                                        )),
1✔
4108
                                    ]),
1✔
4109
                                    allow_alphaband_as_mask: false,
1✔
4110
                                }),
1✔
4111
                                cache_ttl: CacheTtlSeconds::max(),
1✔
4112
                            }],
1✔
4113
                        },
1✔
4114
                    ).into(),
1✔
4115
                ],
1✔
4116
            )
1✔
4117
            .await;
14✔
4118

4119
            test_data_provider_definition_types(&pool).await;
51✔
4120

4121
        })
1✔
4122
        .await;
11✔
4123
    }
4124

4125
    #[test]
1✔
4126
    fn test_postgres_config_translation() {
1✔
4127
        let host = "localhost";
1✔
4128
        let port = 8095;
1✔
4129
        let ge_default = "geoengine";
1✔
4130
        let schema = "public";
1✔
4131

1✔
4132
        let db_config = config::Postgres {
1✔
4133
            host: host.to_string(),
1✔
4134
            port,
1✔
4135
            database: ge_default.to_string(),
1✔
4136
            schema: schema.to_string(),
1✔
4137
            user: ge_default.to_string(),
1✔
4138
            password: ge_default.to_string(),
1✔
4139
            clear_database_on_start: false,
1✔
4140
        };
1✔
4141

1✔
4142
        let pg_config = Config::from(db_config);
1✔
4143

1✔
4144
        assert_eq!(ge_default, pg_config.get_user().unwrap());
1✔
4145
        assert_eq!(
1✔
4146
            <str as AsRef<[u8]>>::as_ref(ge_default).to_vec(),
1✔
4147
            pg_config.get_password().unwrap()
1✔
4148
        );
1✔
4149
        assert_eq!(ge_default, pg_config.get_dbname().unwrap());
1✔
4150
        assert_eq!(
1✔
4151
            &format!("-c search_path={schema}"),
1✔
4152
            pg_config.get_options().unwrap()
1✔
4153
        );
1✔
4154
        assert_eq!(vec![Host::Tcp(host.to_string())], pg_config.get_hosts());
1✔
4155
        assert_eq!(vec![port], pg_config.get_ports());
1✔
4156
    }
1✔
4157

4158
    #[allow(clippy::too_many_lines)]
4159
    async fn test_data_provider_definition_types(
1✔
4160
        pool: &PooledConnection<'_, PostgresConnectionManager<tokio_postgres::NoTls>>,
1✔
4161
    ) {
1✔
4162
        assert_sql_type(
1✔
4163
            pool,
1✔
4164
            "ArunaDataProviderDefinition",
1✔
4165
            [ArunaDataProviderDefinition {
1✔
4166
                id: DataProviderId::from_str("86a7f7ce-1bab-4ce9-a32b-172c0f958ee0").unwrap(),
1✔
4167
                name: "NFDI".to_string(),
1✔
4168
                api_url: "http://test".to_string(),
1✔
4169
                project_id: "project".to_string(),
1✔
4170
                api_token: "api_token".to_string(),
1✔
4171
                filter_label: "filter".to_string(),
1✔
4172
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4173
            }],
1✔
4174
        )
1✔
4175
        .await;
4✔
4176

4177
        assert_sql_type(
1✔
4178
            pool,
1✔
4179
            "GbifDataProviderDefinition",
1✔
4180
            [GbifDataProviderDefinition {
1✔
4181
                name: "GBIF".to_string(),
1✔
4182
                db_config: DatabaseConnectionConfig {
1✔
4183
                    host: "testhost".to_string(),
1✔
4184
                    port: 1234,
1✔
4185
                    database: "testdb".to_string(),
1✔
4186
                    schema: "testschema".to_string(),
1✔
4187
                    user: "testuser".to_string(),
1✔
4188
                    password: "testpass".to_string(),
1✔
4189
                },
1✔
4190
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4191
            }],
1✔
4192
        )
1✔
4193
        .await;
6✔
4194

4195
        assert_sql_type(
1✔
4196
            pool,
1✔
4197
            "GfbioAbcdDataProviderDefinition",
1✔
4198
            [GfbioAbcdDataProviderDefinition {
1✔
4199
                name: "GFbio".to_string(),
1✔
4200
                db_config: DatabaseConnectionConfig {
1✔
4201
                    host: "testhost".to_string(),
1✔
4202
                    port: 1234,
1✔
4203
                    database: "testdb".to_string(),
1✔
4204
                    schema: "testschema".to_string(),
1✔
4205
                    user: "testuser".to_string(),
1✔
4206
                    password: "testpass".to_string(),
1✔
4207
                },
1✔
4208
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4209
            }],
1✔
4210
        )
1✔
4211
        .await;
4✔
4212

4213
        assert_sql_type(
1✔
4214
            pool,
1✔
4215
            "GfbioCollectionsDataProviderDefinition",
1✔
4216
            [GfbioCollectionsDataProviderDefinition {
1✔
4217
                name: "GFbio".to_string(),
1✔
4218
                collection_api_url: "http://testhost".try_into().unwrap(),
1✔
4219
                collection_api_auth_token: "token".to_string(),
1✔
4220
                abcd_db_config: DatabaseConnectionConfig {
1✔
4221
                    host: "testhost".to_string(),
1✔
4222
                    port: 1234,
1✔
4223
                    database: "testdb".to_string(),
1✔
4224
                    schema: "testschema".to_string(),
1✔
4225
                    user: "testuser".to_string(),
1✔
4226
                    password: "testpass".to_string(),
1✔
4227
                },
1✔
4228
                pangaea_url: "http://panaea".try_into().unwrap(),
1✔
4229
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4230
            }],
1✔
4231
        )
1✔
4232
        .await;
4✔
4233

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

4236
        assert_sql_type(
1✔
4237
            pool,
1✔
4238
            "EbvPortalDataProviderDefinition",
1✔
4239
            [EbvPortalDataProviderDefinition {
1✔
4240
                name: "ebv".to_string(),
1✔
4241
                path: "a_path".into(),
1✔
4242
                base_url: "http://base".try_into().unwrap(),
1✔
4243
                overviews: "another_path".into(),
1✔
4244
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4245
            }],
1✔
4246
        )
1✔
4247
        .await;
4✔
4248

4249
        assert_sql_type(
1✔
4250
            pool,
1✔
4251
            "NetCdfCfDataProviderDefinition",
1✔
4252
            [NetCdfCfDataProviderDefinition {
1✔
4253
                name: "netcdfcf".to_string(),
1✔
4254
                path: "a_path".into(),
1✔
4255
                overviews: "another_path".into(),
1✔
4256
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4257
            }],
1✔
4258
        )
1✔
4259
        .await;
4✔
4260

4261
        assert_sql_type(
1✔
4262
            pool,
1✔
4263
            "PangaeaDataProviderDefinition",
1✔
4264
            [PangaeaDataProviderDefinition {
1✔
4265
                name: "pangaea".to_string(),
1✔
4266
                base_url: "http://base".try_into().unwrap(),
1✔
4267
                cache_ttl: CacheTtlSeconds::new(0),
1✔
4268
            }],
1✔
4269
        )
1✔
4270
        .await;
4✔
4271

4272
        assert_sql_type(
1✔
4273
            pool,
1✔
4274
            "DataProviderDefinition",
1✔
4275
            [
1✔
4276
                TypedDataProviderDefinition::ArunaDataProviderDefinition(
1✔
4277
                    ArunaDataProviderDefinition {
1✔
4278
                        id: DataProviderId::from_str("86a7f7ce-1bab-4ce9-a32b-172c0f958ee0")
1✔
4279
                            .unwrap(),
1✔
4280
                        name: "NFDI".to_string(),
1✔
4281
                        api_url: "http://test".to_string(),
1✔
4282
                        project_id: "project".to_string(),
1✔
4283
                        api_token: "api_token".to_string(),
1✔
4284
                        filter_label: "filter".to_string(),
1✔
4285
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4286
                    },
1✔
4287
                ),
1✔
4288
                TypedDataProviderDefinition::GbifDataProviderDefinition(
1✔
4289
                    GbifDataProviderDefinition {
1✔
4290
                        name: "GBIF".to_string(),
1✔
4291
                        db_config: DatabaseConnectionConfig {
1✔
4292
                            host: "testhost".to_string(),
1✔
4293
                            port: 1234,
1✔
4294
                            database: "testdb".to_string(),
1✔
4295
                            schema: "testschema".to_string(),
1✔
4296
                            user: "testuser".to_string(),
1✔
4297
                            password: "testpass".to_string(),
1✔
4298
                        },
1✔
4299
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4300
                    },
1✔
4301
                ),
1✔
4302
                TypedDataProviderDefinition::GfbioAbcdDataProviderDefinition(
1✔
4303
                    GfbioAbcdDataProviderDefinition {
1✔
4304
                        name: "GFbio".to_string(),
1✔
4305
                        db_config: DatabaseConnectionConfig {
1✔
4306
                            host: "testhost".to_string(),
1✔
4307
                            port: 1234,
1✔
4308
                            database: "testdb".to_string(),
1✔
4309
                            schema: "testschema".to_string(),
1✔
4310
                            user: "testuser".to_string(),
1✔
4311
                            password: "testpass".to_string(),
1✔
4312
                        },
1✔
4313
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4314
                    },
1✔
4315
                ),
1✔
4316
                TypedDataProviderDefinition::GfbioCollectionsDataProviderDefinition(
1✔
4317
                    GfbioCollectionsDataProviderDefinition {
1✔
4318
                        name: "GFbio".to_string(),
1✔
4319
                        collection_api_url: "http://testhost".try_into().unwrap(),
1✔
4320
                        collection_api_auth_token: "token".to_string(),
1✔
4321
                        abcd_db_config: DatabaseConnectionConfig {
1✔
4322
                            host: "testhost".to_string(),
1✔
4323
                            port: 1234,
1✔
4324
                            database: "testdb".to_string(),
1✔
4325
                            schema: "testschema".to_string(),
1✔
4326
                            user: "testuser".to_string(),
1✔
4327
                            password: "testpass".to_string(),
1✔
4328
                        },
1✔
4329
                        pangaea_url: "http://panaea".try_into().unwrap(),
1✔
4330
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4331
                    },
1✔
4332
                ),
1✔
4333
                TypedDataProviderDefinition::EbvPortalDataProviderDefinition(
1✔
4334
                    EbvPortalDataProviderDefinition {
1✔
4335
                        name: "ebv".to_string(),
1✔
4336
                        path: "a_path".into(),
1✔
4337
                        base_url: "http://base".try_into().unwrap(),
1✔
4338
                        overviews: "another_path".into(),
1✔
4339
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4340
                    },
1✔
4341
                ),
1✔
4342
                TypedDataProviderDefinition::NetCdfCfDataProviderDefinition(
1✔
4343
                    NetCdfCfDataProviderDefinition {
1✔
4344
                        name: "netcdfcf".to_string(),
1✔
4345
                        path: "a_path".into(),
1✔
4346
                        overviews: "another_path".into(),
1✔
4347
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4348
                    },
1✔
4349
                ),
1✔
4350
                TypedDataProviderDefinition::PangaeaDataProviderDefinition(
1✔
4351
                    PangaeaDataProviderDefinition {
1✔
4352
                        name: "pangaea".to_string(),
1✔
4353
                        base_url: "http://base".try_into().unwrap(),
1✔
4354
                        cache_ttl: CacheTtlSeconds::new(0),
1✔
4355
                    },
1✔
4356
                ),
1✔
4357
            ],
1✔
4358
        )
1✔
4359
        .await;
16✔
4360
    }
1✔
4361
}
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