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

geo-engine / geoengine / 16598781263

29 Jul 2025 02:17PM UTC coverage: 88.637% (-0.2%) from 88.818%
16598781263

push

github

web-flow
feat(services): provider management (#1050)

* Add provider management

* Implement API types and updates for WildLIVE! provider

* Update OpenAPI specification

* Fmt

* Update previous database version in migration

606 of 1131 new or added lines in 16 files covered. (53.58%)

4 existing lines in 1 file now uncovered.

112262 of 126653 relevant lines covered (88.64%)

79624.12 hits per line

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

93.62
/services/src/layers/postgres_layer_db.rs
1
use super::external::{DataProvider, TypedDataProviderDefinition};
2
use super::layer::{
3
    CollectionItem, Layer, LayerCollection, LayerCollectionListOptions, LayerCollectionListing,
4
    LayerListing, Property, ProviderLayerCollectionId, ProviderLayerId, UpdateLayer,
5
    UpdateLayerCollection,
6
};
7
use super::listing::{
8
    LayerCollectionProvider, ProviderCapabilities, SearchCapabilities, SearchParameters,
9
    SearchType, SearchTypes,
10
};
11
use super::storage::{
12
    INTERNAL_PROVIDER_ID, LayerDb, LayerProviderDb, LayerProviderListing,
13
    LayerProviderListingOptions,
14
};
15
use crate::contexts::PostgresDb;
16
use crate::error::Error::{
17
    ProviderIdAlreadyExists, ProviderIdUnmodifiable, ProviderTypeUnmodifiable,
18
};
19
use crate::layers::external::DataProviderDefinition;
20
use crate::permissions::{Permission, RoleId, TxPermissionDb};
21
use crate::workflows::registry::TxWorkflowRegistry;
22
use crate::{
23
    error::Result,
24
    layers::{
25
        LayerDbError,
26
        layer::{AddLayer, AddLayerCollection},
27
        listing::LayerCollectionId,
28
        storage::INTERNAL_LAYER_DB_ROOT_COLLECTION_ID,
29
    },
30
};
31
use bb8_postgres::PostgresConnectionManager;
32
use bb8_postgres::bb8::PooledConnection;
33
use bb8_postgres::tokio_postgres::{
34
    Socket,
35
    tls::{MakeTlsConnect, TlsConnect},
36
};
37
use geoengine_datatypes::dataset::{DataProviderId, LayerId};
38
use geoengine_datatypes::error::BoxedResultExt;
39
use geoengine_datatypes::util::HashMapTextTextDbType;
40
use snafu::ResultExt;
41
use std::str::FromStr;
42
use tokio_postgres::Transaction;
43
use tonic::async_trait;
44
use uuid::Uuid;
45

46
#[async_trait]
47
impl<Tls> LayerDb for PostgresDb<Tls>
48
where
49
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
50
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
51
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
52
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
53
{
54
    async fn add_layer(&self, layer: AddLayer, collection: &LayerCollectionId) -> Result<LayerId> {
56✔
55
        let layer_id = Uuid::new_v4();
28✔
56
        let layer_id = LayerId(layer_id.to_string());
28✔
57

58
        self.add_layer_with_id(&layer_id, layer, collection).await?;
28✔
59

60
        Ok(layer_id)
28✔
61
    }
56✔
62

63
    async fn update_layer(&self, id: &LayerId, layer: UpdateLayer) -> Result<()> {
2✔
64
        let layer_id =
1✔
65
            Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
1✔
66
                found: id.0.clone(),
×
67
            })?;
×
68

69
        let mut conn = self.conn_pool.get().await?;
1✔
70
        let transaction = conn.build_transaction().start().await?;
1✔
71

72
        self.ensure_permission_in_tx(id.clone().into(), Permission::Owner, &transaction)
1✔
73
            .await
1✔
74
            .boxed_context(crate::error::PermissionDb)?;
1✔
75

76
        let workflow_id = self
1✔
77
            .register_workflow_in_tx(layer.workflow, &transaction)
1✔
78
            .await?;
1✔
79

80
        transaction.execute(
1✔
81
                "
1✔
82
                UPDATE layers
1✔
83
                SET name = $1, description = $2, symbology = $3, properties = $4, metadata = $5, workflow_id = $6
1✔
84
                WHERE id = $7;",
1✔
85
                &[
1✔
86
                    &layer.name,
1✔
87
                    &layer.description,
1✔
88
                    &layer.symbology,
1✔
89
                    &layer.properties,
1✔
90
                    &HashMapTextTextDbType::from(&layer.metadata),
1✔
91
                    &workflow_id,
1✔
92
                    &layer_id,
1✔
93
                ],
1✔
94
            )
1✔
95
            .await?;
1✔
96

97
        transaction.commit().await.map_err(Into::into)
1✔
98
    }
2✔
99

100
    async fn remove_layer(&self, id: &LayerId) -> Result<()> {
2✔
101
        let layer_id =
1✔
102
            Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
1✔
103
                found: id.0.clone(),
×
104
            })?;
×
105

106
        let mut conn = self.conn_pool.get().await?;
1✔
107
        let transaction = conn.build_transaction().start().await?;
1✔
108

109
        self.ensure_permission_in_tx(id.clone().into(), Permission::Owner, &transaction)
1✔
110
            .await
1✔
111
            .boxed_context(crate::error::PermissionDb)?;
1✔
112

113
        transaction
1✔
114
            .execute(
1✔
115
                "
1✔
116
            DELETE FROM layers
1✔
117
            WHERE id = $1;",
1✔
118
                &[&layer_id],
1✔
119
            )
1✔
120
            .await?;
1✔
121

122
        transaction.commit().await.map_err(Into::into)
1✔
123
    }
2✔
124

125
    async fn add_layer_with_id(
126
        &self,
127
        id: &LayerId,
128
        layer: AddLayer,
129
        collection: &LayerCollectionId,
130
    ) -> Result<()> {
56✔
131
        let mut conn = self.conn_pool.get().await?;
28✔
132
        let trans = conn.build_transaction().start().await?;
28✔
133

134
        self.ensure_permission_in_tx(collection.clone().into(), Permission::Owner, &trans)
28✔
135
            .await
28✔
136
            .boxed_context(crate::error::PermissionDb)?;
28✔
137

138
        let layer_id = insert_layer(self, &trans, id, layer, collection).await?;
28✔
139

140
        // TODO: `ON CONFLICT DO NOTHING` means, we do not get an error if the permission already exists.
141
        //       Do we want that, or should we report an error and let the caller decide whether to ignore it?
142
        //       We should decide that and adjust all places where `ON CONFLICT DO NOTHING` is used.
143
        let stmt = trans
28✔
144
            .prepare(
28✔
145
                "
28✔
146
            INSERT INTO permissions (role_id, permission, layer_id)
28✔
147
            VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;",
28✔
148
            )
28✔
149
            .await?;
28✔
150

151
        trans
28✔
152
            .execute(
28✔
153
                &stmt,
28✔
154
                &[
28✔
155
                    &RoleId::from(self.session.user.id),
28✔
156
                    &Permission::Owner,
28✔
157
                    &layer_id,
28✔
158
                ],
28✔
159
            )
28✔
160
            .await?;
28✔
161

162
        trans.commit().await?;
28✔
163

164
        Ok(())
28✔
165
    }
56✔
166

167
    async fn add_layer_to_collection(
168
        &self,
169
        layer: &LayerId,
170
        collection: &LayerCollectionId,
171
    ) -> Result<()> {
6✔
172
        let mut conn = self.conn_pool.get().await?;
3✔
173
        let tx = conn.build_transaction().start().await?;
3✔
174

175
        self.ensure_permission_in_tx(collection.clone().into(), Permission::Owner, &tx)
3✔
176
            .await
3✔
177
            .boxed_context(crate::error::PermissionDb)?;
3✔
178

179
        let layer_id =
2✔
180
            Uuid::from_str(&layer.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
3✔
181
                found: layer.0.clone(),
1✔
182
            })?;
1✔
183

184
        let collection_id =
2✔
185
            Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
186
                found: collection.0.clone(),
×
187
            })?;
×
188

189
        let stmt = tx
2✔
190
            .prepare(
2✔
191
                "
2✔
192
            INSERT INTO collection_layers (collection, layer)
2✔
193
            VALUES ($1, $2) ON CONFLICT DO NOTHING;",
2✔
194
            )
2✔
195
            .await?;
2✔
196

197
        tx.execute(&stmt, &[&collection_id, &layer_id]).await?;
2✔
198

199
        tx.commit().await?;
2✔
200

201
        Ok(())
2✔
202
    }
6✔
203

204
    async fn add_layer_collection(
205
        &self,
206
        collection: AddLayerCollection,
207
        parent: &LayerCollectionId,
208
    ) -> Result<LayerCollectionId> {
64✔
209
        let collection_id = Uuid::new_v4();
32✔
210
        let collection_id = LayerCollectionId(collection_id.to_string());
32✔
211

212
        self.add_layer_collection_with_id(&collection_id, collection, parent)
32✔
213
            .await?;
32✔
214

215
        Ok(collection_id)
32✔
216
    }
64✔
217

218
    async fn add_layer_collection_with_id(
219
        &self,
220
        id: &LayerCollectionId,
221
        collection: AddLayerCollection,
222
        parent: &LayerCollectionId,
223
    ) -> Result<()> {
64✔
224
        let mut conn = self.conn_pool.get().await?;
32✔
225
        let trans = conn.build_transaction().start().await?;
32✔
226

227
        self.ensure_permission_in_tx(parent.clone().into(), Permission::Owner, &trans)
32✔
228
            .await
32✔
229
            .boxed_context(crate::error::PermissionDb)?;
32✔
230

231
        let collection_id = insert_layer_collection_with_id(&trans, id, collection, parent).await?;
32✔
232

233
        let stmt = trans
32✔
234
            .prepare(
32✔
235
                "
32✔
236
            INSERT INTO permissions (role_id, permission, layer_collection_id)
32✔
237
            VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;",
32✔
238
            )
32✔
239
            .await?;
32✔
240

241
        trans
32✔
242
            .execute(
32✔
243
                &stmt,
32✔
244
                &[
32✔
245
                    &RoleId::from(self.session.user.id),
32✔
246
                    &Permission::Owner,
32✔
247
                    &collection_id,
32✔
248
                ],
32✔
249
            )
32✔
250
            .await?;
32✔
251

252
        trans.commit().await?;
32✔
253

254
        Ok(())
32✔
255
    }
64✔
256

257
    async fn add_collection_to_parent(
258
        &self,
259
        collection: &LayerCollectionId,
260
        parent: &LayerCollectionId,
261
    ) -> Result<()> {
4✔
262
        let conn = self.conn_pool.get().await?;
2✔
263
        insert_collection_parent(&conn, collection, parent).await
2✔
264
    }
4✔
265

266
    async fn remove_layer_collection(&self, collection: &LayerCollectionId) -> Result<()> {
10✔
267
        let mut conn = self.conn_pool.get().await?;
5✔
268
        let transaction = conn.build_transaction().start().await?;
5✔
269

270
        self.ensure_permission_in_tx(collection.clone().into(), Permission::Owner, &transaction)
5✔
271
            .await
5✔
272
            .boxed_context(crate::error::PermissionDb)?;
5✔
273

274
        delete_layer_collection(&transaction, collection).await?;
5✔
275

276
        transaction.commit().await.map_err(Into::into)
3✔
277
    }
10✔
278

279
    async fn remove_layer_from_collection(
280
        &self,
281
        layer: &LayerId,
282
        collection: &LayerCollectionId,
283
    ) -> Result<()> {
6✔
284
        let mut conn = self.conn_pool.get().await?;
3✔
285
        let transaction = conn.build_transaction().start().await?;
3✔
286

287
        self.ensure_permission_in_tx(collection.clone().into(), Permission::Owner, &transaction)
3✔
288
            .await
3✔
289
            .boxed_context(crate::error::PermissionDb)?;
3✔
290

291
        delete_layer_from_collection(&transaction, layer, collection).await?;
3✔
292

293
        transaction.commit().await.map_err(Into::into)
3✔
294
    }
6✔
295

296
    async fn remove_layer_collection_from_parent(
297
        &self,
298
        collection: &LayerCollectionId,
299
        parent: &LayerCollectionId,
300
    ) -> Result<()> {
4✔
301
        let mut conn = self.conn_pool.get().await?;
2✔
302
        let transaction = conn.build_transaction().start().await?;
2✔
303

304
        self.ensure_permission_in_tx(collection.clone().into(), Permission::Owner, &transaction)
2✔
305
            .await
2✔
306
            .boxed_context(crate::error::PermissionDb)?;
2✔
307

308
        delete_layer_collection_from_parent(&transaction, collection, parent).await?;
2✔
309

310
        transaction.commit().await.map_err(Into::into)
2✔
311
    }
4✔
312

313
    async fn update_layer_collection(
314
        &self,
315
        collection: &LayerCollectionId,
316
        update: UpdateLayerCollection,
317
    ) -> Result<()> {
2✔
318
        let collection_id =
1✔
319
            Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
1✔
320
                found: collection.0.clone(),
×
321
            })?;
×
322

323
        let mut conn = self.conn_pool.get().await?;
1✔
324
        let transaction = conn.build_transaction().start().await?;
1✔
325

326
        self.ensure_permission_in_tx(collection.clone().into(), Permission::Owner, &transaction)
1✔
327
            .await
1✔
328
            .boxed_context(crate::error::PermissionDb)?;
1✔
329

330
        transaction
1✔
331
            .execute(
1✔
332
                "UPDATE layer_collections
1✔
333
                SET name = $1, description = $2, properties = $3
1✔
334
                WHERE id = $4;",
1✔
335
                &[
1✔
336
                    &update.name,
1✔
337
                    &update.description,
1✔
338
                    &update.properties,
1✔
339
                    &collection_id,
1✔
340
                ],
1✔
341
            )
1✔
342
            .await?;
1✔
343

344
        transaction.commit().await.map_err(Into::into)
1✔
345
    }
2✔
346
}
347

348
fn create_search_query(full_info: bool) -> String {
32✔
349
    format!("
32✔
350
        WITH RECURSIVE parents AS (
32✔
351
            SELECT $1::uuid as id
32✔
352
            UNION ALL SELECT DISTINCT child FROM collection_children JOIN parents ON (id = parent)
32✔
353
        )
32✔
354
        SELECT DISTINCT *
32✔
355
        FROM (
32✔
356
            SELECT
32✔
357
                {}
32✔
358
            FROM user_permitted_layer_collections u
32✔
359
                JOIN layer_collections lc ON (u.layer_collection_id = lc.id)
32✔
360
                JOIN (SELECT DISTINCT child FROM collection_children JOIN parents ON (id = parent)) cc ON (id = cc.child)
32✔
361
            WHERE u.user_id = $4 AND name ILIKE $5
32✔
362
        ) u UNION (
32✔
363
            SELECT
32✔
364
                {}
32✔
365
            FROM user_permitted_layers ul
32✔
366
                JOIN layers uc ON (ul.layer_id = uc.id)
32✔
367
                JOIN (SELECT DISTINCT layer FROM collection_layers JOIN parents ON (collection = id)) cl ON (id = cl.layer)
32✔
368
            WHERE ul.user_id = $4 AND name ILIKE $5
32✔
369
        )
32✔
370
        ORDER BY {}name ASC
32✔
371
        LIMIT $2
32✔
372
        OFFSET $3;",
32✔
373
        if full_info {
32✔
374
            "concat(id, '') AS id,
16✔
375
        name,
16✔
376
        description,
16✔
377
        properties,
16✔
378
        FALSE AS is_layer"
16✔
379
        } else { "name" },
16✔
380
        if full_info {
32✔
381
            "concat(id, '') AS id,
16✔
382
        name,
16✔
383
        description,
16✔
384
        properties,
16✔
385
        TRUE AS is_layer"
16✔
386
        } else { "name" },
16✔
387
        if full_info { "is_layer ASC," } else { "" })
32✔
388
}
32✔
389

390
#[async_trait]
391
impl<Tls> LayerCollectionProvider for PostgresDb<Tls>
392
where
393
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
394
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
395
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
396
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
397
{
398
    fn capabilities(&self) -> ProviderCapabilities {
2✔
399
        ProviderCapabilities {
2✔
400
            listing: true,
2✔
401
            search: SearchCapabilities {
2✔
402
                search_types: SearchTypes {
2✔
403
                    fulltext: true,
2✔
404
                    prefix: true,
2✔
405
                },
2✔
406
                autocomplete: true,
2✔
407
                filters: None,
2✔
408
            },
2✔
409
        }
2✔
410
    }
2✔
411

412
    fn name(&self) -> &'static str {
×
413
        "Postgres Layer Collection Provider (Pro)"
×
414
    }
×
415

416
    fn description(&self) -> &'static str {
×
417
        "Layer collection provider for Postgres (Pro)"
×
418
    }
×
419

420
    #[allow(clippy::too_many_lines)]
421
    async fn load_layer_collection(
422
        &self,
423
        collection_id: &LayerCollectionId,
424
        options: LayerCollectionListOptions,
425
    ) -> Result<LayerCollection> {
42✔
426
        let mut conn = self.conn_pool.get().await?;
21✔
427
        let tx = conn.build_transaction().start().await?;
21✔
428

429
        self.ensure_permission_in_tx(collection_id.clone().into(), Permission::Read, &tx)
21✔
430
            .await
21✔
431
            .boxed_context(crate::error::PermissionDb)?;
21✔
432

433
        let collection = Uuid::from_str(&collection_id.0).map_err(|_| {
17✔
434
            crate::error::Error::IdStringMustBeUuid {
×
435
                found: collection_id.0.clone(),
×
436
            }
×
437
        })?;
×
438

439
        let stmt = tx
17✔
440
            .prepare(
17✔
441
                "
17✔
442
        SELECT name, description, properties
17✔
443
        FROM user_permitted_layer_collections p
17✔
444
            JOIN layer_collections c ON (p.layer_collection_id = c.id)
17✔
445
        WHERE p.user_id = $1 AND layer_collection_id = $2;",
17✔
446
            )
17✔
447
            .await?;
17✔
448

449
        let row = tx
17✔
450
            .query_one(&stmt, &[&self.session.user.id, &collection])
17✔
451
            .await?;
17✔
452

453
        let name: String = row.get(0);
17✔
454
        let description: String = row.get(1);
17✔
455
        let properties: Vec<Property> = row.get(2);
17✔
456

457
        let stmt = tx
17✔
458
            .prepare(
17✔
459
                "
17✔
460
        SELECT DISTINCT id, name, description, properties, is_layer
17✔
461
        FROM (
17✔
462
            SELECT 
17✔
463
                concat(id, '') AS id, 
17✔
464
                name, 
17✔
465
                description, 
17✔
466
                properties, 
17✔
467
                FALSE AS is_layer
17✔
468
            FROM user_permitted_layer_collections u
17✔
469
                JOIN layer_collections lc ON (u.layer_collection_id = lc.id)
17✔
470
                JOIN collection_children cc ON (layer_collection_id = cc.child)
17✔
471
            WHERE u.user_id = $4 AND cc.parent = $1
17✔
472
        ) u UNION (
17✔
473
            SELECT 
17✔
474
                concat(id, '') AS id, 
17✔
475
                name, 
17✔
476
                description, 
17✔
477
                properties, 
17✔
478
                TRUE AS is_layer
17✔
479
            FROM user_permitted_layers ul
17✔
480
                JOIN layers uc ON (ul.layer_id = uc.id)
17✔
481
                JOIN collection_layers cl ON (layer_id = cl.layer)
17✔
482
            WHERE ul.user_id = $4 AND cl.collection = $1
17✔
483
        )
17✔
484
        ORDER BY is_layer ASC, name ASC
17✔
485
        LIMIT $2 
17✔
486
        OFFSET $3;            
17✔
487
        ",
17✔
488
            )
17✔
489
            .await?;
17✔
490

491
        let rows = tx
17✔
492
            .query(
17✔
493
                &stmt,
17✔
494
                &[
17✔
495
                    &collection,
17✔
496
                    &i64::from(options.limit),
17✔
497
                    &i64::from(options.offset),
17✔
498
                    &self.session.user.id,
17✔
499
                ],
17✔
500
            )
17✔
501
            .await?;
17✔
502

503
        let items = rows
17✔
504
            .into_iter()
17✔
505
            .map(|row| {
19✔
506
                let is_layer: bool = row.get(4);
19✔
507

508
                if is_layer {
19✔
509
                    Ok(CollectionItem::Layer(LayerListing {
7✔
510
                        r#type: Default::default(),
7✔
511
                        id: ProviderLayerId {
7✔
512
                            provider_id: INTERNAL_PROVIDER_ID,
7✔
513
                            layer_id: LayerId(row.get(0)),
7✔
514
                        },
7✔
515
                        name: row.get(1),
7✔
516
                        description: row.get(2),
7✔
517
                        properties: row.get(3),
7✔
518
                    }))
7✔
519
                } else {
520
                    Ok(CollectionItem::Collection(LayerCollectionListing {
12✔
521
                        r#type: Default::default(),
12✔
522
                        id: ProviderLayerCollectionId {
12✔
523
                            provider_id: INTERNAL_PROVIDER_ID,
12✔
524
                            collection_id: LayerCollectionId(row.get(0)),
12✔
525
                        },
12✔
526
                        name: row.get(1),
12✔
527
                        description: row.get(2),
12✔
528
                        properties: row.get(3),
12✔
529
                    }))
12✔
530
                }
531
            })
19✔
532
            .collect::<Result<Vec<CollectionItem>>>()?;
17✔
533

534
        tx.commit().await?;
17✔
535

536
        Ok(LayerCollection {
17✔
537
            id: ProviderLayerCollectionId {
17✔
538
                provider_id: INTERNAL_PROVIDER_ID,
17✔
539
                collection_id: collection_id.clone(),
17✔
540
            },
17✔
541
            name,
17✔
542
            description,
17✔
543
            items,
17✔
544
            entry_label: None,
17✔
545
            properties,
17✔
546
        })
17✔
547
    }
42✔
548

549
    #[allow(clippy::too_many_lines)]
550
    async fn search(
551
        &self,
552
        collection_id: &LayerCollectionId,
553
        search: SearchParameters,
554
    ) -> Result<LayerCollection> {
32✔
555
        let mut conn = self.conn_pool.get().await?;
16✔
556
        let tx = conn.build_transaction().start().await?;
16✔
557

558
        self.ensure_permission_in_tx(collection_id.clone().into(), Permission::Read, &tx)
16✔
559
            .await
16✔
560
            .boxed_context(crate::error::PermissionDb)?;
16✔
561

562
        let collection = Uuid::from_str(&collection_id.0).map_err(|_| {
16✔
563
            crate::error::Error::IdStringMustBeUuid {
×
564
                found: collection_id.0.clone(),
×
565
            }
×
566
        })?;
×
567

568
        let stmt = tx
16✔
569
            .prepare(
16✔
570
                "
16✔
571
        SELECT name, description, properties
16✔
572
        FROM user_permitted_layer_collections p
16✔
573
            JOIN layer_collections c ON (p.layer_collection_id = c.id)
16✔
574
        WHERE p.user_id = $1 AND layer_collection_id = $2;",
16✔
575
            )
16✔
576
            .await?;
16✔
577

578
        let row = tx
16✔
579
            .query_one(&stmt, &[&self.session.user.id, &collection])
16✔
580
            .await?;
16✔
581

582
        let name: String = row.get(0);
16✔
583
        let description: String = row.get(1);
16✔
584
        let properties: Vec<Property> = row.get(2);
16✔
585

586
        let pattern = match search.search_type {
16✔
587
            SearchType::Fulltext => {
588
                format!("%{}%", search.search_string)
11✔
589
            }
590
            SearchType::Prefix => {
591
                format!("{}%", search.search_string)
5✔
592
            }
593
        };
594

595
        let stmt = tx.prepare(&create_search_query(true)).await?;
16✔
596

597
        let rows = tx
16✔
598
            .query(
16✔
599
                &stmt,
16✔
600
                &[
16✔
601
                    &collection,
16✔
602
                    &i64::from(search.limit),
16✔
603
                    &i64::from(search.offset),
16✔
604
                    &self.session.user.id,
16✔
605
                    &pattern,
16✔
606
                ],
16✔
607
            )
16✔
608
            .await?;
16✔
609

610
        let items = rows
16✔
611
            .into_iter()
16✔
612
            .map(|row| {
31✔
613
                let is_layer: bool = row.get(4);
31✔
614

615
                if is_layer {
31✔
616
                    Ok(CollectionItem::Layer(LayerListing {
13✔
617
                        r#type: Default::default(),
13✔
618
                        id: ProviderLayerId {
13✔
619
                            provider_id: INTERNAL_PROVIDER_ID,
13✔
620
                            layer_id: LayerId(row.get(0)),
13✔
621
                        },
13✔
622
                        name: row.get(1),
13✔
623
                        description: row.get(2),
13✔
624
                        properties: row.get(3),
13✔
625
                    }))
13✔
626
                } else {
627
                    Ok(CollectionItem::Collection(LayerCollectionListing {
18✔
628
                        r#type: Default::default(),
18✔
629
                        id: ProviderLayerCollectionId {
18✔
630
                            provider_id: INTERNAL_PROVIDER_ID,
18✔
631
                            collection_id: LayerCollectionId(row.get(0)),
18✔
632
                        },
18✔
633
                        name: row.get(1),
18✔
634
                        description: row.get(2),
18✔
635
                        properties: row.get(3),
18✔
636
                    }))
18✔
637
                }
638
            })
31✔
639
            .collect::<Result<Vec<CollectionItem>>>()?;
16✔
640

641
        tx.commit().await?;
16✔
642

643
        Ok(LayerCollection {
16✔
644
            id: ProviderLayerCollectionId {
16✔
645
                provider_id: INTERNAL_PROVIDER_ID,
16✔
646
                collection_id: collection_id.clone(),
16✔
647
            },
16✔
648
            name,
16✔
649
            description,
16✔
650
            items,
16✔
651
            entry_label: None,
16✔
652
            properties,
16✔
653
        })
16✔
654
    }
32✔
655

656
    #[allow(clippy::too_many_lines)]
657
    async fn autocomplete_search(
658
        &self,
659
        collection_id: &LayerCollectionId,
660
        search: SearchParameters,
661
    ) -> Result<Vec<String>> {
32✔
662
        let mut conn = self.conn_pool.get().await?;
16✔
663
        let tx = conn.build_transaction().start().await?;
16✔
664

665
        self.ensure_permission_in_tx(collection_id.clone().into(), Permission::Read, &tx)
16✔
666
            .await
16✔
667
            .boxed_context(crate::error::PermissionDb)?;
16✔
668

669
        let collection = Uuid::from_str(&collection_id.0).map_err(|_| {
16✔
670
            crate::error::Error::IdStringMustBeUuid {
×
671
                found: collection_id.0.clone(),
×
672
            }
×
673
        })?;
×
674

675
        let pattern = match search.search_type {
16✔
676
            SearchType::Fulltext => {
677
                format!("%{}%", search.search_string)
11✔
678
            }
679
            SearchType::Prefix => {
680
                format!("{}%", search.search_string)
5✔
681
            }
682
        };
683

684
        let stmt = tx.prepare(&create_search_query(false)).await?;
16✔
685

686
        let rows = tx
16✔
687
            .query(
16✔
688
                &stmt,
16✔
689
                &[
16✔
690
                    &collection,
16✔
691
                    &i64::from(search.limit),
16✔
692
                    &i64::from(search.offset),
16✔
693
                    &self.session.user.id,
16✔
694
                    &pattern,
16✔
695
                ],
16✔
696
            )
16✔
697
            .await?;
16✔
698

699
        let items = rows
16✔
700
            .into_iter()
16✔
701
            .map(|row| Ok(row.get::<usize, &str>(0).to_string()))
31✔
702
            .collect::<Result<Vec<String>>>()?;
16✔
703

704
        tx.commit().await?;
16✔
705

706
        Ok(items)
16✔
707
    }
32✔
708

709
    async fn get_root_layer_collection_id(&self) -> Result<LayerCollectionId> {
64✔
710
        Ok(LayerCollectionId(
32✔
711
            INTERNAL_LAYER_DB_ROOT_COLLECTION_ID.to_string(),
32✔
712
        ))
32✔
713
    }
64✔
714

715
    async fn load_layer(&self, id: &LayerId) -> Result<Layer> {
40✔
716
        let mut conn = self.conn_pool.get().await?;
20✔
717
        let tx = conn.build_transaction().start().await?;
20✔
718

719
        self.ensure_permission_in_tx(id.clone().into(), Permission::Read, &tx)
20✔
720
            .await
20✔
721
            .boxed_context(crate::error::PermissionDb)?;
20✔
722

723
        let layer_id =
15✔
724
            Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
15✔
725
                found: id.0.clone(),
×
726
            })?;
×
727

728
        let stmt = tx
15✔
729
            .prepare(
15✔
730
                "
15✔
731
            SELECT 
15✔
732
                l.name,
15✔
733
                l.description,
15✔
734
                w.workflow,
15✔
735
                l.symbology,
15✔
736
                l.properties,
15✔
737
                l.metadata
15✔
738
            FROM 
15✔
739
                layers l JOIN workflows w ON (l.workflow_id = w.id)
15✔
740
            WHERE 
15✔
741
                l.id = $1;",
15✔
742
            )
15✔
743
            .await?;
15✔
744

745
        let row = tx
15✔
746
            .query_one(&stmt, &[&layer_id])
15✔
747
            .await
15✔
748
            .map_err(|_error| LayerDbError::NoLayerForGivenId { id: id.clone() })?;
15✔
749

750
        tx.commit().await?;
15✔
751

752
        Ok(Layer {
753
            id: ProviderLayerId {
15✔
754
                provider_id: INTERNAL_PROVIDER_ID,
15✔
755
                layer_id: id.clone(),
15✔
756
            },
15✔
757
            name: row.get(0),
15✔
758
            description: row.get(1),
15✔
759
            workflow: serde_json::from_value(row.get(2)).context(crate::error::SerdeJson)?,
15✔
760
            symbology: row.get(3),
15✔
761
            properties: row.get(4),
15✔
762
            metadata: row.get::<_, HashMapTextTextDbType>(5).into(),
15✔
763
        })
764
    }
40✔
765
}
766

767
impl<Tls> PostgresDb<Tls>
768
where
769
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
770
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
771
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
772
    Tls: 'static + Clone + MakeTlsConnect<Socket> + Send + Sync + std::fmt::Debug,
773
{
774
    fn clamp_prio(provider: &TypedDataProviderDefinition, prio: i16) -> i16 {
17✔
775
        let clamp_prio = prio.clamp(-1000, 1000);
17✔
776

777
        if prio != clamp_prio {
17✔
NEW
778
            tracing::warn!(
×
NEW
779
                "The priority of the provider {} is out of range! --> clamped {} to {}",
×
NEW
780
                DataProviderDefinition::<Self>::name(provider),
×
781
                prio,
782
                clamp_prio
783
            );
784
        }
17✔
785
        clamp_prio
17✔
786
    }
17✔
787

788
    async fn id_exists(tx: &Transaction<'_>, id: &DataProviderId) -> Result<bool> {
17✔
789
        Ok(tx
17✔
790
            .query_one(
17✔
791
                "SELECT EXISTS(SELECT 1 FROM layer_providers WHERE id = $1)",
17✔
792
                &[&id],
17✔
793
            )
17✔
794
            .await?
17✔
795
            .get::<usize, bool>(0))
17✔
796
    }
17✔
797
}
798

799
#[async_trait]
800
impl<Tls> LayerProviderDb for PostgresDb<Tls>
801
where
802
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
803
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
804
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
805
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
806
{
807
    async fn add_layer_provider(
808
        &self,
809
        provider: TypedDataProviderDefinition,
810
    ) -> Result<DataProviderId> {
34✔
811
        let mut conn = self.conn_pool.get().await?;
17✔
812
        let tx = conn.build_transaction().start().await?;
17✔
813

814
        let id = DataProviderDefinition::<Self>::id(&provider);
17✔
815

816
        if Self::id_exists(&tx, &id).await? {
17✔
817
            return Err(ProviderIdAlreadyExists { provider_id: id });
1✔
818
        }
16✔
819

820
        let prio = DataProviderDefinition::<Self>::priority(&provider);
16✔
821

822
        let clamp_prio = Self::clamp_prio(&provider, prio);
16✔
823

824
        let stmt = tx
16✔
825
            .prepare(
16✔
826
                "
16✔
827
              INSERT INTO layer_providers (
16✔
828
                  id,
16✔
829
                  type_name,
16✔
830
                  name,
16✔
831
                  definition,
16✔
832
                  priority
16✔
833
              )
16✔
834
              VALUES ($1, $2, $3, $4, $5)",
16✔
835
            )
16✔
836
            .await?;
16✔
837

838
        tx.execute(
16✔
839
            &stmt,
16✔
840
            &[
16✔
841
                &id,
16✔
842
                &DataProviderDefinition::<Self>::type_name(&provider),
16✔
843
                &DataProviderDefinition::<Self>::name(&provider),
16✔
844
                &provider,
16✔
845
                &clamp_prio,
16✔
846
            ],
16✔
847
        )
16✔
848
        .await?;
16✔
849

850
        let stmt = tx
16✔
851
            .prepare(
16✔
852
                "
16✔
853
            INSERT INTO permissions (role_id, permission, provider_id)
16✔
854
            VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;",
16✔
855
            )
16✔
856
            .await?;
16✔
857

858
        tx.execute(
16✔
859
            &stmt,
16✔
860
            &[&RoleId::from(self.session.user.id), &Permission::Owner, &id],
16✔
861
        )
16✔
862
        .await?;
16✔
863

864
        tx.commit().await?;
16✔
865

866
        Ok(id)
16✔
867
    }
34✔
868

869
    async fn list_layer_providers(
870
        &self,
871
        options: LayerProviderListingOptions,
872
    ) -> Result<Vec<LayerProviderListing>> {
4✔
873
        let conn = self.conn_pool.get().await?;
2✔
874

875
        let stmt = conn
2✔
876
            .prepare(
2✔
877
                "
2✔
878
                SELECT
2✔
879
                    id,
2✔
880
                    name,
2✔
881
                    type_name,
2✔
882
                    priority
2✔
883
                FROM
2✔
884
                    user_permitted_providers p
2✔
885
                    JOIN layer_providers l ON (p.provider_id = l.id)
2✔
886
                WHERE
2✔
887
                    p.user_id = $3 AND priority > -1000
2✔
888
                ORDER BY priority desc, name ASC
2✔
889
                LIMIT $1
2✔
890
                OFFSET $2;
2✔
891
                ",
2✔
892
            )
2✔
893
            .await?;
2✔
894

895
        let rows = conn
2✔
896
            .query(
2✔
897
                &stmt,
2✔
898
                &[
2✔
899
                    &i64::from(options.limit),
2✔
900
                    &i64::from(options.offset),
2✔
901
                    &self.session.user.id,
2✔
902
                ],
2✔
903
            )
2✔
904
            .await?;
2✔
905

906
        Ok(rows
2✔
907
            .iter()
2✔
908
            .map(|row| LayerProviderListing {
2✔
909
                id: row.get(0),
2✔
910
                name: row.get(1),
2✔
911
                priority: row.get(3),
2✔
912
            })
2✔
913
            .collect())
2✔
914
    }
4✔
915

916
    async fn load_layer_provider(&self, id: DataProviderId) -> Result<Box<dyn DataProvider>> {
38✔
917
        let definition = self.get_layer_provider_definition(id).await?;
19✔
918

919
        return Box::new(definition)
12✔
920
            .initialize(PostgresDb {
12✔
921
                conn_pool: self.conn_pool.clone(),
12✔
922
                session: self.session.clone(),
12✔
923
            })
12✔
924
            .await;
12✔
925
    }
38✔
926

927
    async fn get_layer_provider_definition(
928
        &self,
929
        id: DataProviderId,
930
    ) -> Result<TypedDataProviderDefinition> {
50✔
931
        let conn = self.conn_pool.get().await?;
25✔
932

933
        let stmt = conn
25✔
934
            .prepare(
25✔
935
                "
25✔
936
                SELECT
25✔
937
                    definition
25✔
938
                FROM
25✔
939
                    user_permitted_providers p
25✔
940
                    JOIN layer_providers l ON (p.provider_id = l.id)
25✔
941
                WHERE
25✔
942
                    id = $1 AND p.user_id = $2
25✔
943
                ",
25✔
944
            )
25✔
945
            .await?;
25✔
946

947
        let row = conn.query_one(&stmt, &[&id, &self.session.user.id]).await?;
25✔
948

949
        Ok(row.get(0))
16✔
950
    }
50✔
951

952
    async fn update_layer_provider_definition(
953
        &self,
954
        id: DataProviderId,
955
        provider: TypedDataProviderDefinition,
956
    ) -> Result<()> {
8✔
957
        if id.0 != DataProviderDefinition::<Self>::id(&provider).0 {
4✔
958
            return Err(ProviderIdUnmodifiable);
1✔
959
        }
3✔
960

961
        let mut conn = self.conn_pool.get().await?;
3✔
962
        let tx = conn.build_transaction().start().await?;
3✔
963

964
        self.ensure_permission_in_tx(id.into(), Permission::Owner, &tx)
3✔
965
            .await
3✔
966
            .boxed_context(crate::error::PermissionDb)?;
3✔
967

968
        let type_name_matches: bool = tx
2✔
969
            .query_one(
2✔
970
                "SELECT type_name = $2 FROM layer_providers WHERE id = $1",
2✔
971
                &[&id, &DataProviderDefinition::<Self>::type_name(&provider)],
2✔
972
            )
2✔
973
            .await?
2✔
974
            .get(0);
2✔
975

976
        if !type_name_matches {
2✔
977
            return Err(ProviderTypeUnmodifiable);
1✔
978
        }
1✔
979

980
        let old_definition = self.get_layer_provider_definition(id).await?;
1✔
981
        let provider = DataProviderDefinition::<Self>::update(&old_definition, provider);
1✔
982

983
        println!("{:?}", provider);
1✔
984

985
        let prio = DataProviderDefinition::<Self>::priority(&provider);
1✔
986

987
        let clamp_prio = Self::clamp_prio(&provider, prio);
1✔
988

989
        let stmt = tx
1✔
990
            .prepare(
1✔
991
                "
1✔
992
              UPDATE layer_providers
1✔
993
              SET
1✔
994
                name = $2,
1✔
995
                definition = $3,
1✔
996
                priority = $4
1✔
997
              WHERE id = $1
1✔
998
              ",
1✔
999
            )
1✔
1000
            .await?;
1✔
1001

1002
        tx.execute(
1✔
1003
            &stmt,
1✔
1004
            &[
1✔
1005
                &id,
1✔
1006
                &DataProviderDefinition::<Self>::name(&provider),
1✔
1007
                &provider,
1✔
1008
                &clamp_prio,
1✔
1009
            ],
1✔
1010
        )
1✔
1011
        .await?;
1✔
1012

1013
        tx.commit().await?;
1✔
1014

1015
        Ok(())
1✔
1016
    }
8✔
1017

1018
    async fn delete_layer_provider(&self, id: DataProviderId) -> Result<()> {
2✔
1019
        let mut conn = self.conn_pool.get().await?;
1✔
1020
        let tx = conn.build_transaction().start().await?;
1✔
1021

1022
        self.ensure_permission_in_tx(id.into(), Permission::Owner, &tx)
1✔
1023
            .await
1✔
1024
            .boxed_context(crate::error::PermissionDb)?;
1✔
1025

1026
        let stmt = tx
1✔
1027
            .prepare(
1✔
1028
                "
1✔
1029
              DELETE FROM layer_providers
1✔
1030
              WHERE id = $1
1✔
1031
              ",
1✔
1032
            )
1✔
1033
            .await?;
1✔
1034

1035
        tx.execute(&stmt, &[&id]).await?;
1✔
1036

1037
        tx.commit().await?;
1✔
1038

1039
        Ok(())
1✔
1040
    }
2✔
1041
}
1042

1043
/// delete all collections without parent collection
1044
async fn _remove_collections_without_parent_collection(
5✔
1045
    transaction: &tokio_postgres::Transaction<'_>,
5✔
1046
) -> Result<()> {
5✔
1047
    // HINT: a recursive delete statement seems reasonable, but hard to implement in postgres
1048
    //       because you have a graph with potential loops
1049

1050
    let remove_layer_collections_without_parents_stmt = transaction
5✔
1051
        .prepare(
5✔
1052
            "DELETE FROM layer_collections
5✔
1053
                 WHERE  id <> $1 -- do not delete root collection
5✔
1054
                 AND    id NOT IN (
5✔
1055
                    SELECT child FROM collection_children
5✔
1056
                 );",
5✔
1057
        )
5✔
1058
        .await?;
5✔
1059
    while 0 < transaction
8✔
1060
        .execute(
8✔
1061
            &remove_layer_collections_without_parents_stmt,
8✔
1062
            &[&INTERNAL_LAYER_DB_ROOT_COLLECTION_ID],
8✔
1063
        )
8✔
1064
        .await?
8✔
1065
    {
3✔
1066
        // whenever one collection is deleted, we have to check again if there are more
3✔
1067
        // collections without parents
3✔
1068
    }
3✔
1069

1070
    Ok(())
5✔
1071
}
5✔
1072

1073
/// delete all layers without parent collection
1074
#[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1075
async fn _remove_layers_without_parent_collection(
8✔
1076
    transaction: &tokio_postgres::Transaction<'_>,
8✔
1077
) -> Result<()> {
8✔
1078
    let remove_layers_without_parents_stmt = transaction
8✔
1079
        .prepare(
8✔
1080
            "DELETE FROM layers
8✔
1081
                 WHERE id NOT IN (
8✔
1082
                    SELECT layer FROM collection_layers
8✔
1083
                 );",
8✔
1084
        )
8✔
1085
        .await?;
8✔
1086
    transaction
8✔
1087
        .execute(&remove_layers_without_parents_stmt, &[])
8✔
1088
        .await?;
8✔
1089

1090
    Ok(())
8✔
1091
}
8✔
1092

1093
pub async fn insert_layer<W: TxWorkflowRegistry>(
28✔
1094
    workflow_registry: &W,
28✔
1095
    trans: &Transaction<'_>,
28✔
1096
    id: &LayerId,
28✔
1097
    layer: AddLayer,
28✔
1098
    collection: &LayerCollectionId,
28✔
1099
) -> Result<Uuid> {
28✔
1100
    let layer_id = Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
28✔
1101
        found: collection.0.clone(),
×
1102
    })?;
×
1103

1104
    let collection_id =
28✔
1105
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
28✔
1106
            found: collection.0.clone(),
×
1107
        })?;
×
1108

1109
    let workflow_id = workflow_registry
28✔
1110
        .register_workflow_in_tx(layer.workflow, trans)
28✔
1111
        .await?;
28✔
1112

1113
    let stmt = trans
28✔
1114
        .prepare(
28✔
1115
            "
28✔
1116
            INSERT INTO layers (id, name, description, workflow_id, symbology, properties, metadata)
28✔
1117
            VALUES ($1, $2, $3, $4, $5, $6, $7);",
28✔
1118
        )
28✔
1119
        .await?;
28✔
1120

1121
    trans
28✔
1122
        .execute(
28✔
1123
            &stmt,
28✔
1124
            &[
28✔
1125
                &layer_id,
28✔
1126
                &layer.name,
28✔
1127
                &layer.description,
28✔
1128
                &workflow_id,
28✔
1129
                &layer.symbology,
28✔
1130
                &layer.properties,
28✔
1131
                &HashMapTextTextDbType::from(&layer.metadata),
28✔
1132
            ],
28✔
1133
        )
28✔
1134
        .await?;
28✔
1135

1136
    let stmt = trans
28✔
1137
        .prepare(
28✔
1138
            "
28✔
1139
            INSERT INTO collection_layers (collection, layer)
28✔
1140
            VALUES ($1, $2) ON CONFLICT DO NOTHING;",
28✔
1141
        )
28✔
1142
        .await?;
28✔
1143

1144
    trans.execute(&stmt, &[&collection_id, &layer_id]).await?;
28✔
1145

1146
    Ok(layer_id)
28✔
1147
}
28✔
1148

1149
pub async fn insert_layer_collection_with_id(
32✔
1150
    trans: &Transaction<'_>,
32✔
1151
    id: &LayerCollectionId,
32✔
1152
    collection: AddLayerCollection,
32✔
1153
    parent: &LayerCollectionId,
32✔
1154
) -> Result<Uuid> {
32✔
1155
    let collection_id =
32✔
1156
        Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
32✔
1157
            found: id.0.clone(),
×
1158
        })?;
×
1159

1160
    let parent =
32✔
1161
        Uuid::from_str(&parent.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
32✔
1162
            found: parent.0.clone(),
×
1163
        })?;
×
1164

1165
    let stmt = trans
32✔
1166
        .prepare(
32✔
1167
            "
32✔
1168
        INSERT INTO layer_collections (id, name, description, properties)
32✔
1169
        VALUES ($1, $2, $3, $4);",
32✔
1170
        )
32✔
1171
        .await?;
32✔
1172

1173
    trans
32✔
1174
        .execute(
32✔
1175
            &stmt,
32✔
1176
            &[
32✔
1177
                &collection_id,
32✔
1178
                &collection.name,
32✔
1179
                &collection.description,
32✔
1180
                &collection.properties,
32✔
1181
            ],
32✔
1182
        )
32✔
1183
        .await?;
32✔
1184

1185
    let stmt = trans
32✔
1186
        .prepare(
32✔
1187
            "
32✔
1188
        INSERT INTO collection_children (parent, child)
32✔
1189
        VALUES ($1, $2) ON CONFLICT DO NOTHING;",
32✔
1190
        )
32✔
1191
        .await?;
32✔
1192

1193
    trans.execute(&stmt, &[&parent, &collection_id]).await?;
32✔
1194

1195
    Ok(collection_id)
32✔
1196
}
32✔
1197

1198
pub async fn insert_collection_parent<Tls>(
2✔
1199
    conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
2✔
1200
    collection: &LayerCollectionId,
2✔
1201
    parent: &LayerCollectionId,
2✔
1202
) -> Result<()>
2✔
1203
where
2✔
1204
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
2✔
1205
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
2✔
1206
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
2✔
1207
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
2✔
1208
{
2✔
1209
    let collection =
2✔
1210
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
1211
            found: collection.0.clone(),
×
1212
        })?;
×
1213

1214
    let parent =
2✔
1215
        Uuid::from_str(&parent.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
1216
            found: parent.0.clone(),
×
1217
        })?;
×
1218

1219
    let stmt = conn
2✔
1220
        .prepare(
2✔
1221
            "
2✔
1222
        INSERT INTO collection_children (parent, child)
2✔
1223
        VALUES ($1, $2) ON CONFLICT DO NOTHING;",
2✔
1224
        )
2✔
1225
        .await?;
2✔
1226

1227
    conn.execute(&stmt, &[&parent, &collection]).await?;
2✔
1228

1229
    Ok(())
2✔
1230
}
2✔
1231

1232
pub async fn delete_layer_collection(
5✔
1233
    transaction: &Transaction<'_>,
5✔
1234
    collection: &LayerCollectionId,
5✔
1235
) -> Result<()> {
5✔
1236
    let collection =
5✔
1237
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
5✔
1238
            found: collection.0.clone(),
×
1239
        })?;
×
1240

1241
    if collection == INTERNAL_LAYER_DB_ROOT_COLLECTION_ID {
5✔
1242
        return Err(LayerDbError::CannotRemoveRootCollection.into());
2✔
1243
    }
3✔
1244

1245
    // delete the collection!
1246
    // on delete cascade removes all entries from `collection_children` and `collection_layers`
1247

1248
    let remove_layer_collection_stmt = transaction
3✔
1249
        .prepare(
3✔
1250
            "DELETE FROM layer_collections
3✔
1251
             WHERE id = $1;",
3✔
1252
        )
3✔
1253
        .await?;
3✔
1254
    transaction
3✔
1255
        .execute(&remove_layer_collection_stmt, &[&collection])
3✔
1256
        .await?;
3✔
1257

1258
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1259
    _remove_collections_without_parent_collection(transaction).await?;
3✔
1260

1261
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1262
    _remove_layers_without_parent_collection(transaction).await?;
3✔
1263

1264
    Ok(())
3✔
1265
}
5✔
1266

1267
pub async fn delete_layer_from_collection(
3✔
1268
    transaction: &Transaction<'_>,
3✔
1269
    layer: &LayerId,
3✔
1270
    collection: &LayerCollectionId,
3✔
1271
) -> Result<()> {
3✔
1272
    let collection_uuid =
3✔
1273
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
3✔
1274
            found: collection.0.clone(),
×
1275
        })?;
×
1276

1277
    let layer_uuid =
3✔
1278
        Uuid::from_str(&layer.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
3✔
1279
            found: layer.0.clone(),
×
1280
        })?;
×
1281

1282
    let remove_layer_collection_stmt = transaction
3✔
1283
        .prepare(
3✔
1284
            "DELETE FROM collection_layers
3✔
1285
             WHERE collection = $1
3✔
1286
             AND layer = $2;",
3✔
1287
        )
3✔
1288
        .await?;
3✔
1289
    let num_results = transaction
3✔
1290
        .execute(
3✔
1291
            &remove_layer_collection_stmt,
3✔
1292
            &[&collection_uuid, &layer_uuid],
3✔
1293
        )
3✔
1294
        .await?;
3✔
1295

1296
    if num_results == 0 {
3✔
1297
        return Err(LayerDbError::NoLayerForGivenIdInCollection {
×
1298
            layer: layer.clone(),
×
1299
            collection: collection.clone(),
×
1300
        }
×
1301
        .into());
×
1302
    }
3✔
1303

1304
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1305
    _remove_layers_without_parent_collection(transaction).await?;
3✔
1306

1307
    Ok(())
3✔
1308
}
3✔
1309

1310
pub async fn delete_layer_collection_from_parent(
2✔
1311
    transaction: &Transaction<'_>,
2✔
1312
    collection: &LayerCollectionId,
2✔
1313
    parent: &LayerCollectionId,
2✔
1314
) -> Result<()> {
2✔
1315
    let collection_uuid =
2✔
1316
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
1317
            found: collection.0.clone(),
×
1318
        })?;
×
1319

1320
    let parent_collection_uuid =
2✔
1321
        Uuid::from_str(&parent.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
1322
            found: parent.0.clone(),
×
1323
        })?;
×
1324

1325
    let remove_layer_collection_stmt = transaction
2✔
1326
        .prepare(
2✔
1327
            "DELETE FROM collection_children
2✔
1328
             WHERE child = $1
2✔
1329
             AND parent = $2;",
2✔
1330
        )
2✔
1331
        .await?;
2✔
1332
    let num_results = transaction
2✔
1333
        .execute(
2✔
1334
            &remove_layer_collection_stmt,
2✔
1335
            &[&collection_uuid, &parent_collection_uuid],
2✔
1336
        )
2✔
1337
        .await?;
2✔
1338

1339
    if num_results == 0 {
2✔
1340
        return Err(LayerDbError::NoCollectionForGivenIdInCollection {
×
1341
            collection: collection.clone(),
×
1342
            parent: parent.clone(),
×
1343
        }
×
1344
        .into());
×
1345
    }
2✔
1346

1347
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1348
    _remove_collections_without_parent_collection(transaction).await?;
2✔
1349

1350
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1351
    _remove_layers_without_parent_collection(transaction).await?;
2✔
1352

1353
    Ok(())
2✔
1354
}
2✔
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