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

geo-engine / geoengine / 18554766227

16 Oct 2025 08:12AM UTC coverage: 88.843% (+0.3%) from 88.543%
18554766227

push

github

web-flow
build: update dependencies (#1081)

* update sqlfluff

* clippy autofix

* manual clippy fixes

* removal of unused code

* update deps

* upgrade packages

* enable cargo lints

* make sqlfluff happy

* fix chrono parsin error

* clippy

* byte_size

* fix image cmp with tiffs

* remove debug

177 of 205 new or added lines in 38 files covered. (86.34%)

41 existing lines in 20 files now uncovered.

106415 of 119779 relevant lines covered (88.84%)

84190.21 hits per line

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

85.47
/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> {
28✔
55
        let layer_id = Uuid::new_v4();
56
        let layer_id = LayerId(layer_id.to_string());
57

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

60
        Ok(layer_id)
61
    }
28✔
62

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

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

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

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

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

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

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

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

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

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

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

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

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

138
        let layer_id = insert_layer(self, &trans, id, layer, collection).await?;
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
144
            .prepare(
145
                "
146
            INSERT INTO permissions (role_id, permission, layer_id)
147
            VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;",
148
            )
149
            .await?;
150

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

162
        trans.commit().await?;
163

164
        Ok(())
165
    }
28✔
166

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

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

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

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

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

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

199
        tx.commit().await?;
200

201
        Ok(())
202
    }
3✔
203

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

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

215
        Ok(collection_id)
216
    }
32✔
217

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

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

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

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

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

252
        trans.commit().await?;
253

254
        Ok(())
255
    }
32✔
256

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

344
        transaction.commit().await.map_err(Into::into)
345
    }
1✔
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> {
21✔
426
        let mut conn = self.conn_pool.get().await?;
427
        let tx = conn.build_transaction().start().await?;
428

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

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

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

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

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

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

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

503
        let items = rows
504
            .into_iter()
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>>>()?;
533

534
        tx.commit().await?;
535

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

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

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

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

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

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

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

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

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

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

610
        let items = rows
611
            .into_iter()
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>>>()?;
640

641
        tx.commit().await?;
642

643
        Ok(LayerCollection {
644
            id: ProviderLayerCollectionId {
645
                provider_id: INTERNAL_PROVIDER_ID,
646
                collection_id: collection_id.clone(),
647
            },
648
            name,
649
            description,
650
            items,
651
            entry_label: None,
652
            properties,
653
        })
654
    }
16✔
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>> {
16✔
662
        let mut conn = self.conn_pool.get().await?;
663
        let tx = conn.build_transaction().start().await?;
664

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

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

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

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

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

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

704
        tx.commit().await?;
705

706
        Ok(items)
707
    }
16✔
708

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

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

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

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

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

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

750
        tx.commit().await?;
751

752
        Ok(Layer {
753
            id: ProviderLayerId {
754
                provider_id: INTERNAL_PROVIDER_ID,
755
                layer_id: id.clone(),
756
            },
757
            name: row.get(0),
758
            description: row.get(1),
759
            workflow: serde_json::from_value(row.get(2)).context(crate::error::SerdeJson)?,
760
            symbology: row.get(3),
761
            properties: row.get(4),
762
            metadata: row.get::<_, HashMapTextTextDbType>(5).into(),
763
        })
764
    }
20✔
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✔
778
            tracing::warn!(
×
779
                "The priority of the provider {} is out of range! --> clamped {} to {}",
×
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> {
17✔
811
        let mut conn = self.conn_pool.get().await?;
812
        let tx = conn.build_transaction().start().await?;
813

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

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

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

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

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

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

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

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

864
        tx.commit().await?;
865

866
        Ok(id)
867
    }
17✔
868

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

983
        let prio = DataProviderDefinition::<Self>::priority(&provider);
984

985
        let clamp_prio = Self::clamp_prio(&provider, prio);
986

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

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

1011
        tx.commit().await?;
1012

1013
        Ok(())
1014
    }
4✔
1015

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

1020
        self.ensure_permission_in_tx(id.into(), Permission::Owner, &tx)
1021
            .await
1022
            .boxed_context(crate::error::PermissionDb)?;
1023

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

1033
        tx.execute(&stmt, &[&id]).await?;
1034

1035
        tx.commit().await?;
1036

1037
        Ok(())
1038
    }
1✔
1039
}
1040

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

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

1068
    Ok(())
5✔
1069
}
5✔
1070

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

1088
    Ok(())
8✔
1089
}
8✔
1090

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

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

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

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

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

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

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

1144
    Ok(layer_id)
28✔
1145
}
28✔
1146

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

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

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

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

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

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

1193
    Ok(collection_id)
32✔
1194
}
32✔
1195

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

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

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

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

1227
    Ok(())
2✔
1228
}
2✔
1229

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

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

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

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

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

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

1262
    Ok(())
3✔
1263
}
5✔
1264

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

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

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

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

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

1305
    Ok(())
3✔
1306
}
3✔
1307

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

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

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

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

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

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

1351
    Ok(())
2✔
1352
}
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