LCOV - code coverage report
Current view: top level - compute_tools/src - installed_extensions.rs (source / functions) Coverage Total Hit
Test: b4ae4c4857f9ef3e144e982a35ee23bc84c71983.info Lines: 0.0 % 73 0
Test Date: 2024-10-22 22:13:45 Functions: 0.0 % 8 0

            Line data    Source code
       1              : use compute_api::responses::{InstalledExtension, InstalledExtensions};
       2              : use std::collections::HashMap;
       3              : use std::collections::HashSet;
       4              : use tracing::info;
       5              : use url::Url;
       6              : 
       7              : use anyhow::Result;
       8              : use postgres::{Client, NoTls};
       9              : use tokio::task;
      10              : 
      11              : /// We don't reuse get_existing_dbs() just for code clarity
      12              : /// and to make database listing query here more explicit.
      13              : ///
      14              : /// Limit the number of databases to 500 to avoid excessive load.
      15            0 : fn list_dbs(client: &mut Client) -> Result<Vec<String>> {
      16              :     // `pg_database.datconnlimit = -2` means that the database is in the
      17              :     // invalid state
      18            0 :     let databases = client
      19            0 :         .query(
      20            0 :             "SELECT datname FROM pg_catalog.pg_database
      21            0 :                 WHERE datallowconn
      22            0 :                 AND datconnlimit <> - 2
      23            0 :                 LIMIT 500",
      24            0 :             &[],
      25            0 :         )?
      26            0 :         .iter()
      27            0 :         .map(|row| {
      28            0 :             let db: String = row.get("datname");
      29            0 :             db
      30            0 :         })
      31            0 :         .collect();
      32            0 : 
      33            0 :     Ok(databases)
      34            0 : }
      35              : 
      36              : /// Connect to every database (see list_dbs above) and get the list of installed extensions.
      37              : ///
      38              : /// Same extension can be installed in multiple databases with different versions,
      39              : /// we only keep the highest and lowest version across all databases.
      40            0 : pub async fn get_installed_extensions(connstr: Url) -> Result<InstalledExtensions> {
      41            0 :     let mut connstr = connstr.clone();
      42            0 : 
      43            0 :     task::spawn_blocking(move || {
      44            0 :         let mut client = Client::connect(connstr.as_str(), NoTls)?;
      45            0 :         let databases: Vec<String> = list_dbs(&mut client)?;
      46              : 
      47            0 :         let mut extensions_map: HashMap<String, InstalledExtension> = HashMap::new();
      48            0 :         for db in databases.iter() {
      49            0 :             connstr.set_path(db);
      50            0 :             let mut db_client = Client::connect(connstr.as_str(), NoTls)?;
      51            0 :             let extensions: Vec<(String, String)> = db_client
      52            0 :                 .query(
      53            0 :                     "SELECT extname, extversion FROM pg_catalog.pg_extension;",
      54            0 :                     &[],
      55            0 :                 )?
      56            0 :                 .iter()
      57            0 :                 .map(|row| (row.get("extname"), row.get("extversion")))
      58            0 :                 .collect();
      59              : 
      60            0 :             for (extname, v) in extensions.iter() {
      61            0 :                 let version = v.to_string();
      62            0 :                 extensions_map
      63            0 :                     .entry(extname.to_string())
      64            0 :                     .and_modify(|e| {
      65            0 :                         e.versions.insert(version.clone());
      66            0 :                         // count the number of databases where the extension is installed
      67            0 :                         e.n_databases += 1;
      68            0 :                     })
      69            0 :                     .or_insert(InstalledExtension {
      70            0 :                         extname: extname.to_string(),
      71            0 :                         versions: HashSet::from([version.clone()]),
      72            0 :                         n_databases: 1,
      73            0 :                     });
      74            0 :             }
      75              :         }
      76              : 
      77            0 :         Ok(InstalledExtensions {
      78            0 :             extensions: extensions_map.values().cloned().collect(),
      79            0 :         })
      80            0 :     })
      81            0 :     .await?
      82            0 : }
      83              : 
      84              : // Gather info about installed extensions
      85            0 : pub fn get_installed_extensions_sync(connstr: Url) -> Result<()> {
      86            0 :     let rt = tokio::runtime::Builder::new_current_thread()
      87            0 :         .enable_all()
      88            0 :         .build()
      89            0 :         .expect("failed to create runtime");
      90            0 :     let result = rt
      91            0 :         .block_on(crate::installed_extensions::get_installed_extensions(
      92            0 :             connstr,
      93            0 :         ))
      94            0 :         .expect("failed to get installed extensions");
      95            0 : 
      96            0 :     info!(
      97            0 :         "[NEON_EXT_STAT] {}",
      98            0 :         serde_json::to_string(&result).expect("failed to serialize extensions list")
      99              :     );
     100              : 
     101            0 :     Ok(())
     102            0 : }
        

Generated by: LCOV version 2.1-beta