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

geo-engine / geoengine / 12907583488

22 Jan 2025 11:50AM UTC coverage: 90.062% (-0.6%) from 90.64%
12907583488

Pull #1008

github

web-flow
Merge 921681cfb into de81b44f7
Pull Request #1008: user ctx in ge_test

4564 of 4830 new or added lines in 65 files covered. (94.49%)

786 existing lines in 19 files now uncovered.

127252 of 141294 relevant lines covered (90.06%)

56970.82 hits per line

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

95.05
/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
    LayerDb, LayerProviderDb, LayerProviderListing, LayerProviderListingOptions,
13
    INTERNAL_PROVIDER_ID,
14
};
15
use crate::layers::external::DataProviderDefinition;
16
use crate::permissions::{Permission, RoleId, TxPermissionDb};
17
use crate::pro::contexts::PostgresDb;
18
use crate::pro::datasets::TypedProDataProviderDefinition;
19
use crate::workflows::registry::TxWorkflowRegistry;
20
use crate::{
21
    error::{self, Result},
22
    layers::{
23
        layer::{AddLayer, AddLayerCollection},
24
        listing::LayerCollectionId,
25
        storage::INTERNAL_LAYER_DB_ROOT_COLLECTION_ID,
26
        LayerDbError,
27
    },
28
};
29
use bb8_postgres::bb8::PooledConnection;
30
use bb8_postgres::tokio_postgres::{
31
    tls::{MakeTlsConnect, TlsConnect},
32
    Socket,
33
};
34
use bb8_postgres::PostgresConnectionManager;
35
use geoengine_datatypes::dataset::{DataProviderId, LayerId};
36
use geoengine_datatypes::error::BoxedResultExt;
37
use geoengine_datatypes::util::HashMapTextTextDbType;
38
use snafu::{ensure, ResultExt};
39
use std::str::FromStr;
40
use tokio_postgres::Transaction;
41
use tonic::async_trait;
42
use uuid::Uuid;
43

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

27✔
56
        self.add_layer_with_id(&layer_id, layer, collection).await?;
27✔
57

58
        Ok(layer_id)
27✔
59
    }
54✔
60

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

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

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

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

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

95
        transaction.commit().await.map_err(Into::into)
1✔
96
    }
2✔
97

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

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

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

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

120
        transaction.commit().await.map_err(Into::into)
1✔
121
    }
2✔
122

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

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

136
        let layer_id = insert_layer(self, &trans, id, layer, collection).await?;
27✔
137

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

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

160
        trans.commit().await?;
27✔
161

162
        Ok(())
27✔
163
    }
54✔
164

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

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

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

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

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

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

197
        tx.commit().await?;
2✔
198

199
        Ok(())
2✔
200
    }
6✔
201

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

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

213
        Ok(collection_id)
32✔
214
    }
64✔
215

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

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

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

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

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

250
        trans.commit().await?;
32✔
251

252
        Ok(())
32✔
253
    }
64✔
254

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

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

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

272
        delete_layer_collection(&transaction, collection).await?;
5✔
273

274
        transaction.commit().await.map_err(Into::into)
3✔
275
    }
10✔
276

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

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

289
        delete_layer_from_collection(&transaction, layer, collection).await?;
3✔
290

291
        transaction.commit().await.map_err(Into::into)
3✔
292
    }
6✔
293

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

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

306
        delete_layer_collection_from_parent(&transaction, collection, parent).await?;
2✔
307

308
        transaction.commit().await.map_err(Into::into)
2✔
309
    }
4✔
310

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

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

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

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

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

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

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

NEW
410
    fn name(&self) -> &str {
×
NEW
411
        "Postgres Layer Collection Provider (Pro)"
×
UNCOV
412
    }
×
413

NEW
414
    fn description(&self) -> &str {
×
NEW
415
        "Layer collection provider for Postgres (Pro)"
×
NEW
416
    }
×
417

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

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

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

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

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

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

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

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

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

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

530
        tx.commit().await?;
17✔
531

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

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

554
        self.ensure_permission_in_tx(collection_id.clone().into(), Permission::Read, &tx)
16✔
555
            .await
16✔
556
            .boxed_context(crate::error::PermissionDb)?;
16✔
557

558
        let collection = Uuid::from_str(&collection_id.0).map_err(|_| {
16✔
559
            crate::error::Error::IdStringMustBeUuid {
×
560
                found: collection_id.0.clone(),
×
561
            }
×
562
        })?;
16✔
563

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

574
        let row = tx
16✔
575
            .query_one(&stmt, &[&self.session.user.id, &collection])
16✔
576
            .await?;
16✔
577

578
        let name: String = row.get(0);
16✔
579
        let description: String = row.get(1);
16✔
580
        let properties: Vec<Property> = row.get(2);
16✔
581

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

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

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

606
        let items = rows
16✔
607
            .into_iter()
16✔
608
            .map(|row| {
31✔
609
                let is_layer: bool = row.get(4);
31✔
610

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

635
        tx.commit().await?;
16✔
636

637
        Ok(LayerCollection {
16✔
638
            id: ProviderLayerCollectionId {
16✔
639
                provider_id: INTERNAL_PROVIDER_ID,
16✔
640
                collection_id: collection_id.clone(),
16✔
641
            },
16✔
642
            name,
16✔
643
            description,
16✔
644
            items,
16✔
645
            entry_label: None,
16✔
646
            properties,
16✔
647
        })
16✔
648
    }
32✔
649

650
    #[allow(clippy::too_many_lines)]
651
    async fn autocomplete_search(
652
        &self,
653
        collection_id: &LayerCollectionId,
654
        search: SearchParameters,
655
    ) -> Result<Vec<String>> {
16✔
656
        let mut conn = self.conn_pool.get().await?;
16✔
657
        let tx = conn.build_transaction().start().await?;
16✔
658

659
        self.ensure_permission_in_tx(collection_id.clone().into(), Permission::Read, &tx)
16✔
660
            .await
16✔
661
            .boxed_context(crate::error::PermissionDb)?;
16✔
662

663
        let collection = Uuid::from_str(&collection_id.0).map_err(|_| {
16✔
664
            crate::error::Error::IdStringMustBeUuid {
×
665
                found: collection_id.0.clone(),
×
666
            }
×
667
        })?;
16✔
668

669
        let pattern = match search.search_type {
16✔
670
            SearchType::Fulltext => {
671
                format!("%{}%", search.search_string)
11✔
672
            }
673
            SearchType::Prefix => {
674
                format!("{}%", search.search_string)
5✔
675
            }
676
        };
677

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

680
        let rows = tx
16✔
681
            .query(
16✔
682
                &stmt,
16✔
683
                &[
16✔
684
                    &collection,
16✔
685
                    &i64::from(search.limit),
16✔
686
                    &i64::from(search.offset),
16✔
687
                    &self.session.user.id,
16✔
688
                    &pattern,
16✔
689
                ],
16✔
690
            )
16✔
691
            .await?;
16✔
692

693
        let items = rows
16✔
694
            .into_iter()
16✔
695
            .map(|row| Ok(row.get::<usize, &str>(0).to_string()))
31✔
696
            .collect::<Result<Vec<String>>>()?;
16✔
697

698
        tx.commit().await?;
16✔
699

700
        Ok(items)
16✔
701
    }
32✔
702

703
    async fn get_root_layer_collection_id(&self) -> Result<LayerCollectionId> {
30✔
704
        Ok(LayerCollectionId(
30✔
705
            INTERNAL_LAYER_DB_ROOT_COLLECTION_ID.to_string(),
30✔
706
        ))
30✔
707
    }
60✔
708

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

713
        self.ensure_permission_in_tx(id.clone().into(), Permission::Read, &tx)
20✔
714
            .await
20✔
715
            .boxed_context(crate::error::PermissionDb)?;
20✔
716

717
        let layer_id =
15✔
718
            Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
15✔
719
                found: id.0.clone(),
×
720
            })?;
15✔
721

722
        let stmt = tx
15✔
723
            .prepare(
15✔
724
                "
15✔
725
            SELECT 
15✔
726
                l.name,
15✔
727
                l.description,
15✔
728
                w.workflow,
15✔
729
                l.symbology,
15✔
730
                l.properties,
15✔
731
                l.metadata
15✔
732
            FROM 
15✔
733
                layers l JOIN workflows w ON (l.workflow_id = w.id)
15✔
734
            WHERE 
15✔
735
                l.id = $1;",
15✔
736
            )
15✔
737
            .await?;
15✔
738

739
        let row = tx
15✔
740
            .query_one(&stmt, &[&layer_id])
15✔
741
            .await
15✔
742
            .map_err(|_error| LayerDbError::NoLayerForGivenId { id: id.clone() })?;
15✔
743

744
        tx.commit().await?;
15✔
745

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

761
#[async_trait]
762
impl<Tls> LayerProviderDb for PostgresDb<Tls>
763
where
764
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
765
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
766
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
767
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
768
{
769
    async fn add_layer_provider(
770
        &self,
771
        provider: TypedDataProviderDefinition,
772
    ) -> Result<DataProviderId> {
8✔
773
        ensure!(self.session.is_admin(), error::PermissionDenied);
8✔
774

775
        let conn = self.conn_pool.get().await?;
8✔
776

777
        let prio = DataProviderDefinition::<Self>::priority(&provider);
8✔
778
        let clamp_prio = prio.clamp(-1000, 1000);
8✔
779

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

789
        let stmt = conn
8✔
790
            .prepare(
8✔
791
                "
8✔
792
              INSERT INTO layer_providers (
8✔
793
                  id, 
8✔
794
                  type_name, 
8✔
795
                  name,
8✔
796
                  definition,
8✔
797
                  priority
8✔
798
              )
8✔
799
              VALUES ($1, $2, $3, $4, $5)",
8✔
800
            )
8✔
801
            .await?;
8✔
802

803
        let id = DataProviderDefinition::<Self>::id(&provider);
8✔
804
        conn.execute(
8✔
805
            &stmt,
8✔
806
            &[
8✔
807
                &id,
8✔
808
                &DataProviderDefinition::<Self>::type_name(&provider),
8✔
809
                &DataProviderDefinition::<Self>::name(&provider),
8✔
810
                &provider,
8✔
811
                &clamp_prio,
8✔
812
            ],
8✔
813
        )
8✔
814
        .await?;
8✔
815
        Ok(id)
8✔
816
    }
16✔
817

818
    async fn list_layer_providers(
819
        &self,
820
        options: LayerProviderListingOptions,
821
    ) -> Result<Vec<LayerProviderListing>> {
2✔
822
        // TODO: permission
823
        let conn = self.conn_pool.get().await?;
2✔
824

825
        let stmt = conn
2✔
826
            .prepare(
2✔
827
                "(
2✔
828
                    SELECT 
2✔
829
                        id, 
2✔
830
                        name,
2✔
831
                        type_name,
2✔
832
                        priority
2✔
833
                    FROM 
2✔
834
                        layer_providers
2✔
835
                    WHERE
2✔
836
                        priority > -1000
2✔
837
                    UNION ALL
2✔
838
                    SELECT 
2✔
839
                        id, 
2✔
840
                        name,
2✔
841
                        type_name,
2✔
842
                        priority
2✔
843
                    FROM 
2✔
844
                        pro_layer_providers
2✔
845
                    WHERE
2✔
846
                        priority > -1000
2✔
847
                )
2✔
848
                ORDER BY priority desc, name ASC
2✔
849
                LIMIT $1 
2✔
850
                OFFSET $2;",
2✔
851
            )
2✔
852
            .await?;
2✔
853

854
        let rows = conn
2✔
855
            .query(
2✔
856
                &stmt,
2✔
857
                &[&i64::from(options.limit), &i64::from(options.offset)],
2✔
858
            )
2✔
859
            .await?;
2✔
860

861
        Ok(rows
2✔
862
            .iter()
2✔
863
            .map(|row| LayerProviderListing {
2✔
864
                id: row.get(0),
2✔
865
                name: row.get(1),
2✔
866
                priority: row.get(3),
2✔
867
            })
2✔
868
            .collect())
2✔
869
    }
4✔
870

871
    async fn load_layer_provider(&self, id: DataProviderId) -> Result<Box<dyn DataProvider>> {
19✔
872
        // TODO: permissions
873
        let conn = self.conn_pool.get().await?;
19✔
874

875
        let stmt = conn
19✔
876
            .prepare(
19✔
877
                "SELECT
19✔
878
                    definition, NULL AS pro_definition
19✔
879
                FROM
19✔
880
                    layer_providers
19✔
881
                WHERE
19✔
882
                    id = $1
19✔
883
                UNION ALL
19✔
884
                SELECT
19✔
885
                    NULL AS definition, definition AS pro_definition
19✔
886
                FROM
19✔
887
                    pro_layer_providers
19✔
888
                WHERE
19✔
889
                    id = $1",
19✔
890
            )
19✔
891
            .await?;
19✔
892

893
        let row = conn.query_one(&stmt, &[&id]).await?;
19✔
894

895
        if let Some(definition) = row.get::<_, Option<TypedDataProviderDefinition>>(0) {
12✔
896
            return Box::new(definition)
11✔
897
                .initialize(PostgresDb {
11✔
898
                    conn_pool: self.conn_pool.clone(),
11✔
899
                    session: self.session.clone(),
11✔
900
                })
11✔
901
                .await;
11✔
902
        }
1✔
903

1✔
904
        let pro_definition: TypedProDataProviderDefinition = row.get(1);
1✔
905
        Box::new(pro_definition)
1✔
906
            .initialize(PostgresDb {
1✔
907
                conn_pool: self.conn_pool.clone(),
1✔
908
                session: self.session.clone(),
1✔
909
            })
1✔
910
            .await
1✔
911
    }
38✔
912
}
913

914
#[async_trait]
915
pub trait ProLayerProviderDb: Send + Sync + 'static {
916
    async fn add_pro_layer_provider(
917
        &self,
918
        provider: TypedProDataProviderDefinition,
919
    ) -> Result<DataProviderId>;
920
}
921

922
#[async_trait]
923
impl<Tls> ProLayerProviderDb for PostgresDb<Tls>
924
where
925
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
926
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
927
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
928
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
929
{
930
    async fn add_pro_layer_provider(
931
        &self,
932
        provider: TypedProDataProviderDefinition,
933
    ) -> Result<DataProviderId> {
1✔
934
        ensure!(self.session.is_admin(), error::PermissionDenied);
1✔
935

936
        let conn = self.conn_pool.get().await?;
1✔
937

938
        let prio = DataProviderDefinition::<Self>::priority(&provider);
1✔
939
        let clamp_prio = prio.clamp(-1000, 1000);
1✔
940

1✔
941
        if prio != clamp_prio {
1✔
NEW
942
            log::warn!(
×
NEW
943
                "The priority of the provider {} is out of range! --> clamped {} to {}",
×
NEW
944
                DataProviderDefinition::<Self>::name(&provider),
×
945
                prio,
946
                clamp_prio
947
            );
948
        }
1✔
949

950
        let stmt = conn
1✔
951
            .prepare(
1✔
952
                "
1✔
953
              INSERT INTO pro_layer_providers (
1✔
954
                  id, 
1✔
955
                  type_name, 
1✔
956
                  name,
1✔
957
                  definition,
1✔
958
                  priority
1✔
959
              )
1✔
960
              VALUES ($1, $2, $3, $4, $5)",
1✔
961
            )
1✔
962
            .await?;
1✔
963

964
        let id = DataProviderDefinition::<Self>::id(&provider);
1✔
965
        conn.execute(
1✔
966
            &stmt,
1✔
967
            &[
1✔
968
                &id,
1✔
969
                &DataProviderDefinition::<Self>::type_name(&provider),
1✔
970
                &DataProviderDefinition::<Self>::name(&provider),
1✔
971
                &provider,
1✔
972
                &clamp_prio,
1✔
973
            ],
1✔
974
        )
1✔
975
        .await?;
1✔
976
        Ok(id)
1✔
977
    }
2✔
978
}
979

980
/// delete all collections without parent collection
981
async fn _remove_collections_without_parent_collection(
5✔
982
    transaction: &tokio_postgres::Transaction<'_>,
5✔
983
) -> Result<()> {
5✔
984
    // HINT: a recursive delete statement seems reasonable, but hard to implement in postgres
985
    //       because you have a graph with potential loops
986

987
    let remove_layer_collections_without_parents_stmt = transaction
5✔
988
        .prepare(
5✔
989
            "DELETE FROM layer_collections
5✔
990
                 WHERE  id <> $1 -- do not delete root collection
5✔
991
                 AND    id NOT IN (
5✔
992
                    SELECT child FROM collection_children
5✔
993
                 );",
5✔
994
        )
5✔
995
        .await?;
5✔
996
    while 0 < transaction
8✔
997
        .execute(
8✔
998
            &remove_layer_collections_without_parents_stmt,
8✔
999
            &[&INTERNAL_LAYER_DB_ROOT_COLLECTION_ID],
8✔
1000
        )
8✔
1001
        .await?
8✔
1002
    {
3✔
1003
        // whenever one collection is deleted, we have to check again if there are more
3✔
1004
        // collections without parents
3✔
1005
    }
3✔
1006

1007
    Ok(())
5✔
1008
}
5✔
1009

1010
/// delete all layers without parent collection
1011
#[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1012
async fn _remove_layers_without_parent_collection(
8✔
1013
    transaction: &tokio_postgres::Transaction<'_>,
8✔
1014
) -> Result<()> {
8✔
1015
    let remove_layers_without_parents_stmt = transaction
8✔
1016
        .prepare(
8✔
1017
            "DELETE FROM layers
8✔
1018
                 WHERE id NOT IN (
8✔
1019
                    SELECT layer FROM collection_layers
8✔
1020
                 );",
8✔
1021
        )
8✔
1022
        .await?;
8✔
1023
    transaction
8✔
1024
        .execute(&remove_layers_without_parents_stmt, &[])
8✔
1025
        .await?;
8✔
1026

1027
    Ok(())
8✔
1028
}
8✔
1029

1030
pub async fn insert_layer<W: TxWorkflowRegistry>(
27✔
1031
    workflow_registry: &W,
27✔
1032
    trans: &Transaction<'_>,
27✔
1033
    id: &LayerId,
27✔
1034
    layer: AddLayer,
27✔
1035
    collection: &LayerCollectionId,
27✔
1036
) -> Result<Uuid> {
27✔
1037
    let layer_id = Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
27✔
NEW
1038
        found: collection.0.clone(),
×
1039
    })?;
27✔
1040

1041
    let collection_id =
27✔
1042
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
27✔
NEW
1043
            found: collection.0.clone(),
×
1044
        })?;
27✔
1045

1046
    let workflow_id = workflow_registry
27✔
1047
        .register_workflow_in_tx(layer.workflow, trans)
27✔
1048
        .await?;
27✔
1049

1050
    let stmt = trans
27✔
1051
        .prepare(
27✔
1052
            "
27✔
1053
            INSERT INTO layers (id, name, description, workflow_id, symbology, properties, metadata)
27✔
1054
            VALUES ($1, $2, $3, $4, $5, $6, $7);",
27✔
1055
        )
27✔
1056
        .await?;
27✔
1057

1058
    trans
27✔
1059
        .execute(
27✔
1060
            &stmt,
27✔
1061
            &[
27✔
1062
                &layer_id,
27✔
1063
                &layer.name,
27✔
1064
                &layer.description,
27✔
1065
                &workflow_id,
27✔
1066
                &layer.symbology,
27✔
1067
                &layer.properties,
27✔
1068
                &HashMapTextTextDbType::from(&layer.metadata),
27✔
1069
            ],
27✔
1070
        )
27✔
1071
        .await?;
27✔
1072

1073
    let stmt = trans
27✔
1074
        .prepare(
27✔
1075
            "
27✔
1076
            INSERT INTO collection_layers (collection, layer)
27✔
1077
            VALUES ($1, $2) ON CONFLICT DO NOTHING;",
27✔
1078
        )
27✔
1079
        .await?;
27✔
1080

1081
    trans.execute(&stmt, &[&collection_id, &layer_id]).await?;
27✔
1082

1083
    Ok(layer_id)
27✔
1084
}
27✔
1085

1086
pub async fn insert_layer_collection_with_id(
32✔
1087
    trans: &Transaction<'_>,
32✔
1088
    id: &LayerCollectionId,
32✔
1089
    collection: AddLayerCollection,
32✔
1090
    parent: &LayerCollectionId,
32✔
1091
) -> Result<Uuid> {
32✔
1092
    let collection_id =
32✔
1093
        Uuid::from_str(&id.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
32✔
NEW
1094
            found: id.0.clone(),
×
1095
        })?;
32✔
1096

1097
    let parent =
32✔
1098
        Uuid::from_str(&parent.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
32✔
NEW
1099
            found: parent.0.clone(),
×
1100
        })?;
32✔
1101

1102
    let stmt = trans
32✔
1103
        .prepare(
32✔
1104
            "
32✔
1105
        INSERT INTO layer_collections (id, name, description, properties)
32✔
1106
        VALUES ($1, $2, $3, $4);",
32✔
1107
        )
32✔
1108
        .await?;
32✔
1109

1110
    trans
32✔
1111
        .execute(
32✔
1112
            &stmt,
32✔
1113
            &[
32✔
1114
                &collection_id,
32✔
1115
                &collection.name,
32✔
1116
                &collection.description,
32✔
1117
                &collection.properties,
32✔
1118
            ],
32✔
1119
        )
32✔
1120
        .await?;
32✔
1121

1122
    let stmt = trans
32✔
1123
        .prepare(
32✔
1124
            "
32✔
1125
        INSERT INTO collection_children (parent, child)
32✔
1126
        VALUES ($1, $2) ON CONFLICT DO NOTHING;",
32✔
1127
        )
32✔
1128
        .await?;
32✔
1129

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

1132
    Ok(collection_id)
32✔
1133
}
32✔
1134

1135
pub async fn insert_collection_parent<Tls>(
2✔
1136
    conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
2✔
1137
    collection: &LayerCollectionId,
2✔
1138
    parent: &LayerCollectionId,
2✔
1139
) -> Result<()>
2✔
1140
where
2✔
1141
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
2✔
1142
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
2✔
1143
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
2✔
1144
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
2✔
1145
{
2✔
1146
    let collection =
2✔
1147
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
NEW
1148
            found: collection.0.clone(),
×
1149
        })?;
2✔
1150

1151
    let parent =
2✔
1152
        Uuid::from_str(&parent.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
NEW
1153
            found: parent.0.clone(),
×
1154
        })?;
2✔
1155

1156
    let stmt = conn
2✔
1157
        .prepare(
2✔
1158
            "
2✔
1159
        INSERT INTO collection_children (parent, child)
2✔
1160
        VALUES ($1, $2) ON CONFLICT DO NOTHING;",
2✔
1161
        )
2✔
1162
        .await?;
2✔
1163

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

1166
    Ok(())
2✔
1167
}
2✔
1168

1169
pub async fn delete_layer_collection(
5✔
1170
    transaction: &Transaction<'_>,
5✔
1171
    collection: &LayerCollectionId,
5✔
1172
) -> Result<()> {
5✔
1173
    let collection =
5✔
1174
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
5✔
NEW
1175
            found: collection.0.clone(),
×
1176
        })?;
5✔
1177

1178
    if collection == INTERNAL_LAYER_DB_ROOT_COLLECTION_ID {
5✔
1179
        return Err(LayerDbError::CannotRemoveRootCollection.into());
2✔
1180
    }
3✔
1181

1182
    // delete the collection!
1183
    // on delete cascade removes all entries from `collection_children` and `collection_layers`
1184

1185
    let remove_layer_collection_stmt = transaction
3✔
1186
        .prepare(
3✔
1187
            "DELETE FROM layer_collections
3✔
1188
             WHERE id = $1;",
3✔
1189
        )
3✔
1190
        .await?;
3✔
1191
    transaction
3✔
1192
        .execute(&remove_layer_collection_stmt, &[&collection])
3✔
1193
        .await?;
3✔
1194

1195
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1196
    _remove_collections_without_parent_collection(transaction).await?;
3✔
1197

1198
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1199
    _remove_layers_without_parent_collection(transaction).await?;
3✔
1200

1201
    Ok(())
3✔
1202
}
5✔
1203

1204
pub async fn delete_layer_from_collection(
3✔
1205
    transaction: &Transaction<'_>,
3✔
1206
    layer: &LayerId,
3✔
1207
    collection: &LayerCollectionId,
3✔
1208
) -> Result<()> {
3✔
1209
    let collection_uuid =
3✔
1210
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
3✔
NEW
1211
            found: collection.0.clone(),
×
1212
        })?;
3✔
1213

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

1219
    let remove_layer_collection_stmt = transaction
3✔
1220
        .prepare(
3✔
1221
            "DELETE FROM collection_layers
3✔
1222
             WHERE collection = $1
3✔
1223
             AND layer = $2;",
3✔
1224
        )
3✔
1225
        .await?;
3✔
1226
    let num_results = transaction
3✔
1227
        .execute(
3✔
1228
            &remove_layer_collection_stmt,
3✔
1229
            &[&collection_uuid, &layer_uuid],
3✔
1230
        )
3✔
1231
        .await?;
3✔
1232

1233
    if num_results == 0 {
3✔
NEW
1234
        return Err(LayerDbError::NoLayerForGivenIdInCollection {
×
NEW
1235
            layer: layer.clone(),
×
NEW
1236
            collection: collection.clone(),
×
NEW
1237
        }
×
NEW
1238
        .into());
×
1239
    }
3✔
1240

3✔
1241
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
3✔
1242
    _remove_layers_without_parent_collection(transaction).await?;
3✔
1243

1244
    Ok(())
3✔
1245
}
3✔
1246

1247
pub async fn delete_layer_collection_from_parent(
2✔
1248
    transaction: &Transaction<'_>,
2✔
1249
    collection: &LayerCollectionId,
2✔
1250
    parent: &LayerCollectionId,
2✔
1251
) -> Result<()> {
2✔
1252
    let collection_uuid =
2✔
1253
        Uuid::from_str(&collection.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
NEW
1254
            found: collection.0.clone(),
×
1255
        })?;
2✔
1256

1257
    let parent_collection_uuid =
2✔
1258
        Uuid::from_str(&parent.0).map_err(|_| crate::error::Error::IdStringMustBeUuid {
2✔
NEW
1259
            found: parent.0.clone(),
×
1260
        })?;
2✔
1261

1262
    let remove_layer_collection_stmt = transaction
2✔
1263
        .prepare(
2✔
1264
            "DELETE FROM collection_children
2✔
1265
             WHERE child = $1
2✔
1266
             AND parent = $2;",
2✔
1267
        )
2✔
1268
        .await?;
2✔
1269
    let num_results = transaction
2✔
1270
        .execute(
2✔
1271
            &remove_layer_collection_stmt,
2✔
1272
            &[&collection_uuid, &parent_collection_uuid],
2✔
1273
        )
2✔
1274
        .await?;
2✔
1275

1276
    if num_results == 0 {
2✔
NEW
1277
        return Err(LayerDbError::NoCollectionForGivenIdInCollection {
×
NEW
1278
            collection: collection.clone(),
×
NEW
1279
            parent: parent.clone(),
×
NEW
1280
        }
×
NEW
1281
        .into());
×
1282
    }
2✔
1283

2✔
1284
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
2✔
1285
    _remove_collections_without_parent_collection(transaction).await?;
2✔
1286

1287
    #[allow(clippy::used_underscore_items)] // TODO: maybe rename?
1288
    _remove_layers_without_parent_collection(transaction).await?;
2✔
1289

1290
    Ok(())
2✔
1291
}
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