1use itertools::Itertools;
2
3use unitycatalog_common::models::ObjectLabel;
4use unitycatalog_common::models::registered_models::v1::*;
5use unitycatalog_common::models::{ResourceIdent, ResourceName};
6
7use super::staging_tables::{child_location, resolve_managed_parent_location};
8use super::{RequestContext, SecuredAction};
9pub use crate::codegen::registered_models::RegisteredModelHandler;
10use crate::policy::{Permission, Policy, process_resources};
11use crate::services::{ProvidesLocalStoragePolicy, ProvidesManagedStorageRoot};
12use crate::store::ResourceStore;
13use crate::{Error, Result};
14
15#[async_trait::async_trait]
16impl<
17 T: ResourceStore
18 + Policy<RequestContext>
19 + ProvidesLocalStoragePolicy
20 + ProvidesManagedStorageRoot,
21> RegisteredModelHandler<RequestContext> for T
22{
23 #[tracing::instrument(skip(self, context), fields(resource_name))]
24 async fn list_registered_models(
25 &self,
26 request: ListRegisteredModelsRequest,
27 context: RequestContext,
28 ) -> Result<ListRegisteredModelsResponse> {
29 self.check_required(&request, &context).await?;
30 let namespace = match (&request.catalog_name, &request.schema_name) {
34 (Some(catalog), Some(schema)) => Some(ResourceName::new([catalog, schema])),
35 (Some(catalog), None) => Some(ResourceName::new([catalog])),
36 _ => None,
37 };
38 let (mut resources, next_page_token) = self
39 .list(
40 &ObjectLabel::RegisteredModel,
41 namespace.as_ref(),
42 request.max_results.map(|v| v as usize),
43 request.page_token,
44 )
45 .await?;
46 process_resources(self, &context, &Permission::Read, &mut resources).await?;
47 Ok(ListRegisteredModelsResponse {
48 registered_models: resources.into_iter().map(|r| r.try_into()).try_collect()?,
49 next_page_token,
50 ..Default::default()
51 })
52 }
53
54 #[tracing::instrument(skip(self, context), fields(resource_name))]
55 async fn create_registered_model(
56 &self,
57 request: CreateRegisteredModelRequest,
58 context: RequestContext,
59 ) -> Result<RegisteredModel> {
60 self.check_required(&request, &context).await?;
61 tracing::Span::current().record("resource_name", &request.name);
62
63 let parent =
69 resolve_managed_parent_location(self, &request.catalog_name, &request.schema_name)
70 .await?;
71 let id = uuid::Uuid::now_v7().hyphenated().to_string();
72 let storage_location = child_location(&parent, "models", &id);
73
74 let full_name = format!(
75 "{}.{}.{}",
76 request.catalog_name, request.schema_name, request.name
77 );
78 let resource = RegisteredModel {
79 name: request.name,
80 catalog_name: request.catalog_name,
81 schema_name: request.schema_name,
82 full_name,
83 storage_location: Some(storage_location),
84 comment: request.comment,
85 id: Some(id),
86 ..Default::default()
87 };
88 Ok(self.create(resource.into()).await?.0.try_into()?)
89 }
90
91 #[tracing::instrument(skip(self, context), fields(resource_name))]
92 async fn get_registered_model(
93 &self,
94 request: GetRegisteredModelRequest,
95 context: RequestContext,
96 ) -> Result<RegisteredModel> {
97 tracing::Span::current().record("resource_name", &request.full_name);
98 self.check_required(&request, &context).await?;
99 Ok(self.get(&request.resource()).await?.0.try_into()?)
100 }
101
102 #[tracing::instrument(skip(self, context), fields(resource_name))]
103 async fn update_registered_model(
104 &self,
105 request: UpdateRegisteredModelRequest,
106 context: RequestContext,
107 ) -> Result<RegisteredModel> {
108 tracing::Span::current().record("resource_name", &request.full_name);
109 self.check_required(&request, &context).await?;
110 let ident = request.resource();
111 let name = ResourceName::from_naive_str_split(request.full_name.as_str());
112 let [catalog_name, schema_name, model_name] = name.as_ref() else {
113 return Err(Error::invalid_argument(
114 "Invalid model name - expected <catalog_name>.<schema_name>.<model_name>",
115 ));
116 };
117 let new_name = request.new_name.as_deref().unwrap_or(model_name);
118 let resource = RegisteredModel {
119 name: new_name.to_owned(),
120 catalog_name: catalog_name.to_owned(),
121 schema_name: schema_name.to_owned(),
122 full_name: format!("{}.{}.{}", catalog_name, schema_name, new_name),
123 comment: request.comment,
124 owner: request.owner,
125 ..Default::default()
126 };
127 Ok(self.update(&ident, resource.into()).await?.0.try_into()?)
128 }
129
130 #[tracing::instrument(skip(self, context), fields(resource_name))]
131 async fn delete_registered_model(
132 &self,
133 request: DeleteRegisteredModelRequest,
134 context: RequestContext,
135 ) -> Result<()> {
136 tracing::Span::current().record("resource_name", &request.full_name);
137 self.check_required(&request, &context).await?;
138 Ok(self.delete(&request.resource()).await?)
139 }
140}
141
142impl SecuredAction for CreateRegisteredModelRequest {
143 fn resource(&self) -> ResourceIdent {
144 ResourceIdent::registered_model(ResourceName::new([
145 self.catalog_name.as_str(),
146 self.schema_name.as_str(),
147 self.name.as_str(),
148 ]))
149 }
150
151 fn permission(&self) -> &'static Permission {
152 &Permission::Create
153 }
154}
155
156impl SecuredAction for ListRegisteredModelsRequest {
157 fn resource(&self) -> ResourceIdent {
158 ResourceIdent::registered_model(ResourceName::new(
159 [self.catalog_name.as_deref(), self.schema_name.as_deref()]
160 .into_iter()
161 .flatten(),
162 ))
163 }
164
165 fn permission(&self) -> &'static Permission {
166 &Permission::Read
167 }
168}
169
170impl SecuredAction for GetRegisteredModelRequest {
171 fn resource(&self) -> ResourceIdent {
172 ResourceIdent::registered_model(ResourceName::from_naive_str_split(self.full_name.as_str()))
173 }
174
175 fn permission(&self) -> &'static Permission {
176 &Permission::Read
177 }
178}
179
180impl SecuredAction for UpdateRegisteredModelRequest {
181 fn resource(&self) -> ResourceIdent {
182 ResourceIdent::registered_model(ResourceName::from_naive_str_split(self.full_name.as_str()))
183 }
184
185 fn permission(&self) -> &'static Permission {
186 &Permission::Manage
187 }
188}
189
190impl SecuredAction for DeleteRegisteredModelRequest {
191 fn resource(&self) -> ResourceIdent {
192 ResourceIdent::registered_model(ResourceName::from_naive_str_split(self.full_name.as_str()))
193 }
194
195 fn permission(&self) -> &'static Permission {
196 &Permission::Manage
197 }
198}