diff --git a/registry/migrations/20231218192146_add-v1-control-file.sql b/registry/migrations/20231218192146_add-v1-control-file.sql new file mode 100644 index 00000000..87f71d79 --- /dev/null +++ b/registry/migrations/20231218192146_add-v1-control-file.sql @@ -0,0 +1,12 @@ +-- Create control_file table +CREATE TABLE IF NOT EXISTS v1.control_file +( + id SERIAL PRIMARY KEY, + extension_version_id int REFERENCES v1.extension_versions(id), + absent boolean DEFAULT false, + content text DEFAULT '' +); + +-- For each record in v1.extension_versions, create a record in v1.control_file +INSERT INTO v1.control_file (extension_version_id) +SELECT id FROM v1.extension_versions; diff --git a/registry/sqlx-data.json b/registry/sqlx-data.json index 42ec98a2..06d6fb88 100644 --- a/registry/sqlx-data.json +++ b/registry/sqlx-data.json @@ -60,6 +60,27 @@ }, "query": "DELETE FROM extensions\n WHERE id = $1" }, + "02c3a514a9bbaf760bcb3ca523fcc79918ba443f2684fa3a986103fe437ff49f": { + "describe": { + "columns": [ + { + "name": "result", + "ordinal": 0, + "type_info": "Json" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + } + }, + "query": "SELECT json_build_object(\n 'name', tpv.trunk_project_name,\n 'description', tpv.description,\n 'version', tpv.version,\n 'documentation_link', tpv.documentation_link,\n 'repository_link', tpv.repository_link,\n 'postgres_versions', (\n SELECT json_agg(pg.major)\n FROM v1.trunk_project_postgres_support tpps\n JOIN v1.postgres_version pg ON tpps.postgres_version_id = pg.id\n WHERE tpps.trunk_project_version_id = tpv.id\n ),\n 'extensions', (\n SELECT json_agg(json_build_object(\n 'extension_name', ev.extension_name,\n 'version', ev.version,\n 'trunk_project_name', tpv.trunk_project_name,\n 'dependencies_extension_names', (\n SELECT json_agg(ed.depends_on_extension_name)\n FROM v1.extension_dependency ed\n WHERE ed.extension_version_id = ev.id\n ),\n 'loadable_libraries', (\n SELECT json_agg(json_build_object(\n 'library_name', ell.library_name,\n 'requires_restart', ell.requires_restart,\n 'priority', ell.priority\n ))\n FROM v1.extensions_loadable_libraries ell\n WHERE ell.extension_version_id = ev.id\n ),\n 'configurations', (\n SELECT json_agg(json_build_object(\n 'name', ec.configuration_name,\n 'is_required', ec.is_required,\n 'default', ec.recommended_default_value\n ))\n FROM v1.extension_configurations ec\n WHERE ec.extension_version_id = ev.id\n ),\n 'control_file', (\n SELECT json_build_object(\n 'absent', cf.absent,\n 'content', cf.content\n )\n FROM v1.control_file cf\n WHERE cf.extension_version_id = ev.id\n )\n ))\n FROM v1.extension_versions ev\n WHERE ev.trunk_project_version_id = tpv.id\n )\n ) AS result\n FROM\n v1.trunk_project_versions tpv\n WHERE\n tpv.trunk_project_name = $1\n AND tpv.version = $2" + }, "0a50fee432e167aa3029cebb8c6393ec5ef754131301b698199a4fefa43e2e33": { "describe": { "columns": [], @@ -246,27 +267,6 @@ }, "query": "SELECT *\n FROM extension_owners\n WHERE\n extension_id = $1" }, - "27587110b412c39be602676f5fbe6f51872ec9fa31e2eec1e216e21ddd7a6fcc": { - "describe": { - "columns": [ - { - "name": "result", - "ordinal": 0, - "type_info": "Json" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - } - }, - "query": "SELECT json_build_object(\n 'name', tpv.trunk_project_name,\n 'description', tpv.description,\n 'version', tpv.version,\n 'documentation_link', tpv.documentation_link,\n 'repository_link', tpv.repository_link,\n 'postgres_versions', (\n SELECT json_agg(pg.major)\n FROM v1.trunk_project_postgres_support tpps\n JOIN v1.postgres_version pg ON tpps.postgres_version_id = pg.id\n WHERE tpps.trunk_project_version_id = tpv.id\n ),\n 'extensions', (\n SELECT json_agg(json_build_object(\n 'extension_name', ev.extension_name,\n 'version', ev.version,\n 'trunk_project_name', tpv.trunk_project_name,\n 'dependencies_extension_names', (\n SELECT json_agg(ed.depends_on_extension_name)\n FROM v1.extension_dependency ed\n WHERE ed.extension_version_id = ev.id\n ),\n 'loadable_libraries', (\n SELECT json_agg(json_build_object(\n 'library_name', ell.library_name,\n 'requires_restart', ell.requires_restart,\n 'priority', ell.priority\n ))\n FROM v1.extensions_loadable_libraries ell\n WHERE ell.extension_version_id = ev.id\n ),\n 'configurations', (\n SELECT json_agg(json_build_object(\n 'name', ec.configuration_name,\n 'is_required', ec.is_required,\n 'default', ec.recommended_default_value\n ))\n FROM v1.extension_configurations ec\n WHERE ec.extension_version_id = ev.id\n )\n ))\n FROM v1.extension_versions ev\n WHERE ev.trunk_project_version_id = tpv.id\n )\n ) AS result\n FROM\n v1.trunk_project_versions tpv\n WHERE\n tpv.trunk_project_name = $1\n AND tpv.version = $2" - }, "2898d7f3b57a07860872d2675506faf25583f041b6862d77a3e6fab1dc54d847": { "describe": { "columns": [ @@ -722,6 +722,20 @@ }, "query": "INSERT INTO v1.extension_configurations (extension_version_id, is_required, configuration_name, recommended_default_value)\n VALUES ($1, $2, $3, $4)" }, + "6722d3fb3cbd849aad563fe00ab77363b17cb7a63a14752a88d6c162011dc652": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int4", + "Bool", + "Text" + ] + } + }, + "query": "INSERT INTO v1.control_file (extension_version_id, absent, content)\n VALUES ($1, $2, $3)" + }, "6991d9aae9d130b3d209ea5816847f661979e07c07f754c15e9b05eee3d61cb6": { "describe": { "columns": [ diff --git a/registry/src/v1/extractor.rs b/registry/src/v1/extractor.rs index 1b42384a..ee9d03ce 100644 --- a/registry/src/v1/extractor.rs +++ b/registry/src/v1/extractor.rs @@ -6,11 +6,12 @@ use std::{ }; use tar::EntryType; -use crate::views::extension_publish::ExtensionUpload; +use crate::views::extension_publish::{ControlFileMetadata, ExtensionUpload}; use super::repository::ExtensionView; pub struct ControlFile { + content: Option, extension_name: String, dependencies: Option>, default_version: Option, @@ -32,6 +33,17 @@ pub fn extract_extension_view( // TODO: should we clone this for every extension in a Trunk project? loadable_libraries: new_extension.libraries.clone(), configurations: new_extension.configurations.clone(), + control_file: if control_file.content.is_none() { + Some(ControlFileMetadata { + absent: true, + content: None, + }) + } else { + Some(ControlFileMetadata { + absent: false, + content: control_file.content, + }) + }, }) .collect(); @@ -96,6 +108,13 @@ fn parse_control_file(extension_name: String, control_file: String) -> ControlFi trimmed.trim_matches('\'') } + // Grab all lines from the control file and set as string. None if no lines. + let control_file_content = if control_file.is_empty() { + None + } else { + Some(control_file.clone()) + }; + for line in control_file.lines() { if let Some(rest) = line.strip_prefix("requires") { let requires = strip_value(rest) @@ -116,6 +135,7 @@ fn parse_control_file(extension_name: String, control_file: String) -> ControlFi } ControlFile { + content: control_file_content, extension_name, dependencies: if dependencies.is_empty() { None diff --git a/registry/src/v1/repository.rs b/registry/src/v1/repository.rs index e440d979..db762616 100644 --- a/registry/src/v1/repository.rs +++ b/registry/src/v1/repository.rs @@ -3,7 +3,9 @@ use utoipa::{ToResponse, ToSchema}; use crate::errors::Result; use crate::repository::Registry; -use crate::views::extension_publish::{ExtensionConfiguration, LoadableLibrary}; +use crate::views::extension_publish::{ + ControlFileMetadata, ExtensionConfiguration, LoadableLibrary, +}; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, ToResponse)] pub struct TrunkProjectView { @@ -24,6 +26,7 @@ pub struct ExtensionView { pub dependencies_extension_names: Option>, pub loadable_libraries: Option>, pub configurations: Option>, + pub control_file: Option, } impl Registry { @@ -281,6 +284,14 @@ impl Registry { )) FROM v1.extension_configurations ec WHERE ec.extension_version_id = ev.id + ), + 'control_file', ( + SELECT json_build_object( + 'absent', cf.absent, + 'content', cf.content + ) + FROM v1.control_file cf + WHERE cf.extension_version_id = ev.id ) )) FROM v1.extension_versions ev @@ -313,6 +324,7 @@ impl Registry { /// 4. insert extension dependencies /// 5. insert extension configurations /// 6. insert shared preload libraries + /// 7. Insert control file metadata pub async fn insert_trunk_project(&self, trunk_project: TrunkProjectView) -> Result<()> { // 1. insert trunk project name sqlx::query!( @@ -378,6 +390,12 @@ impl Registry { // 6. insert shared preload libraries self.insert_loadable_libraries(extension_version_id, loadable_libraries) .await?; + + // 7. Insert control file metadata + if let Some(control_file) = &extension.control_file { + self.insert_control_file(extension_version_id, control_file.clone()) + .await?; + } } Ok(()) } @@ -418,4 +436,22 @@ impl Registry { Ok(()) } + + async fn insert_control_file( + &self, + extension_version_id: i32, + control_file: ControlFileMetadata, + ) -> Result { + sqlx::query!( + "INSERT INTO v1.control_file (extension_version_id, absent, content) + VALUES ($1, $2, $3)", + extension_version_id, + control_file.absent, + control_file.content, + ) + .execute(&self.pool) + .await?; + + Ok(()) + } } diff --git a/registry/src/views/extension_publish.rs b/registry/src/views/extension_publish.rs index ea42e122..9146b317 100644 --- a/registry/src/views/extension_publish.rs +++ b/registry/src/views/extension_publish.rs @@ -20,6 +20,7 @@ pub struct ExtensionUpload { pub system_dependencies: Option, pub libraries: Option>, pub configurations: Option>, + pub control_file: Option, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] @@ -37,3 +38,9 @@ pub struct ExtensionConfiguration { #[serde(alias = "default")] pub recommended_default_value: Option, } + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct ControlFileMetadata { + pub absent: bool, + pub content: Option, +}