From c8242cd31c88007c05f509576326fb4d1fd83047 Mon Sep 17 00:00:00 2001 From: Tim Diekmann <21277928+TimDiekmann@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:40:33 +0200 Subject: [PATCH] H-3082(I): Allow querying inheritance data from database (#5300) --- Cargo.lock | 1 + .../hash-graph/libs/api/src/rest/data_type.rs | 4 +- apps/hash-graph/libs/graph/Cargo.toml | 2 +- ...V31__data_type_inheritance_aggregation.sql | 7 + .../hash-graph/libs/graph/src/snapshot/mod.rs | 4 +- .../src/snapshot/ontology/data_type/batch.rs | 2 +- .../snapshot/ontology/data_type/channel.rs | 5 +- .../ontology/property_type/channel.rs | 4 +- .../graph/src/snapshot/restore/channel.rs | 3 +- .../libs/graph/src/store/postgres/mod.rs | 16 +- .../src/store/postgres/ontology/data_type.rs | 212 ++++++++++-------- .../store/postgres/ontology/entity_type.rs | 9 +- .../store/postgres/ontology/ontology_id.rs | 3 +- .../store/postgres/ontology/property_type.rs | 4 +- .../graph/src/store/postgres/query/rows.rs | 6 +- .../src/store/postgres/traversal_context.rs | 3 +- .../libs/graph/src/store/validation.rs | 6 +- apps/hash-graph/libs/store/src/filter/mod.rs | 7 +- .../libs/store/src/filter/parameter.rs | 7 +- .../type-system/rust/Cargo.toml | 4 +- .../type-system/rust/src/lib.rs | 1 - .../rust/src/schema/data_type/closed.rs | 76 +++++-- .../rust/src/schema/data_type/mod.rs | 195 +++++++++------- .../type-system/rust/src/schema/mod.rs | 11 +- libs/@local/hash-authorization/Cargo.toml | 5 +- libs/@local/hash-authorization/package.json | 1 + libs/@local/hash-authorization/src/api.rs | 3 +- libs/@local/hash-authorization/src/lib.rs | 3 +- .../src/schema/data_type.rs | 3 +- .../hash-authorization/src/zanzibar/api.rs | 3 +- libs/@local/hash-graph-types/rust/Cargo.toml | 2 +- .../rust/src/ontology/data_type.rs | 42 ---- .../hash-graph-types/rust/src/ontology/mod.rs | 2 +- .../postgres/data_type.rs | 5 +- 34 files changed, 369 insertions(+), 292 deletions(-) create mode 100644 apps/hash-graph/libs/graph/postgres_migrations/V31__data_type_inheritance_aggregation.sql diff --git a/Cargo.lock b/Cargo.lock index 01932a36384..ad8cff11275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "type-system", "utoipa", "uuid", ] diff --git a/apps/hash-graph/libs/api/src/rest/data_type.rs b/apps/hash-graph/libs/api/src/rest/data_type.rs index 2afab8601d2..4e4f25ecb23 100644 --- a/apps/hash-graph/libs/api/src/rest/data_type.rs +++ b/apps/hash-graph/libs/api/src/rest/data_type.rs @@ -36,7 +36,7 @@ use graph::{ }; use graph_types::{ ontology::{ - DataTypeId, DataTypeMetadata, DataTypeWithMetadata, OntologyTemporalMetadata, + DataTypeMetadata, DataTypeWithMetadata, OntologyTemporalMetadata, OntologyTypeClassificationMetadata, OntologyTypeMetadata, OntologyTypeReference, ProvidedOntologyEditionProvenance, }, @@ -50,7 +50,7 @@ use time::OffsetDateTime; use type_system::{ schema::{ ConversionDefinition, ConversionExpression, ConversionValue, Conversions, DataType, - Operator, Variable, + DataTypeId, Operator, Variable, }, url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, }; diff --git a/apps/hash-graph/libs/graph/Cargo.toml b/apps/hash-graph/libs/graph/Cargo.toml index 498590d33ad..c105145fe38 100644 --- a/apps/hash-graph/libs/graph/Cargo.toml +++ b/apps/hash-graph/libs/graph/Cargo.toml @@ -49,7 +49,7 @@ tarpc = { workspace = true, features = ["serde-transport", "serde-transport-json time = { workspace = true } tracing = { workspace = true } utoipa = { workspace = true, features = ["uuid"], optional = true } -uuid = { workspace = true, features = ["v4", "v5", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } [dev-dependencies] tokio = { workspace = true, features = ["macros"] } diff --git a/apps/hash-graph/libs/graph/postgres_migrations/V31__data_type_inheritance_aggregation.sql b/apps/hash-graph/libs/graph/postgres_migrations/V31__data_type_inheritance_aggregation.sql new file mode 100644 index 00000000000..5f2130bb26f --- /dev/null +++ b/apps/hash-graph/libs/graph/postgres_migrations/V31__data_type_inheritance_aggregation.sql @@ -0,0 +1,7 @@ +CREATE VIEW data_type_inherits_from_aggregation AS + SELECT + source_data_type_ontology_id, + array_agg(target_data_type_ontology_id) AS target_data_type_ontology_ids, + array_agg(depth) AS depths + FROM data_type_inherits_from + GROUP BY source_data_type_ontology_id; diff --git a/apps/hash-graph/libs/graph/src/snapshot/mod.rs b/apps/hash-graph/libs/graph/src/snapshot/mod.rs index e5c6c31746a..ecee6a36d95 100644 --- a/apps/hash-graph/libs/graph/src/snapshot/mod.rs +++ b/apps/hash-graph/libs/graph/src/snapshot/mod.rs @@ -41,7 +41,7 @@ use graph_types::{ account::{AccountGroupId, AccountId}, knowledge::entity::{Entity, EntityId, EntityUuid}, ontology::{ - DataTypeId, DataTypeWithMetadata, EntityTypeId, EntityTypeWithMetadata, PropertyTypeId, + DataTypeWithMetadata, EntityTypeId, EntityTypeWithMetadata, PropertyTypeId, PropertyTypeWithMetadata, }, owned_by_id::OwnedById, @@ -51,7 +51,7 @@ use hash_status::StatusCode; use postgres_types::ToSql; use serde::{Deserialize, Serialize}; use tokio_postgres::error::SqlState; -use type_system::url::VersionedUrl; +use type_system::{schema::DataTypeId, url::VersionedUrl}; use crate::{ snapshot::{ diff --git a/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/batch.rs b/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/batch.rs index a6a4ca56f37..36c12e17142 100644 --- a/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/batch.rs +++ b/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/batch.rs @@ -4,8 +4,8 @@ use authorization::{ AuthorizationApi, backend::ZanzibarBackend, schema::DataTypeRelationAndSubject, }; use error_stack::{Result, ResultExt}; -use graph_types::ontology::DataTypeId; use tokio_postgres::GenericClient; +use type_system::schema::DataTypeId; use crate::{ snapshot::WriteBatch, diff --git a/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/channel.rs b/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/channel.rs index 503c2083301..52c6c1d5a17 100644 --- a/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/channel.rs +++ b/apps/hash-graph/libs/graph/src/snapshot/ontology/data_type/channel.rs @@ -10,8 +10,7 @@ use futures::{ channel::mpsc::{self, Receiver, Sender}, stream::{BoxStream, SelectAll, select_all}, }; -use graph_types::ontology::DataTypeId; -use type_system::Valid; +use type_system::{Valid, schema::DataTypeId}; use crate::{ snapshot::{ @@ -61,7 +60,7 @@ impl Sink for DataTypeSender { mut self: Pin<&mut Self>, data_type: DataTypeSnapshotRecord, ) -> Result<(), Self::Error> { - let ontology_id = DataTypeId::from_record_id(&data_type.metadata.record_id); + let ontology_id = DataTypeId::from_url(&data_type.schema.id); self.metadata .start_send_unpin(OntologyTypeMetadata { diff --git a/apps/hash-graph/libs/graph/src/snapshot/ontology/property_type/channel.rs b/apps/hash-graph/libs/graph/src/snapshot/ontology/property_type/channel.rs index 0863a6e0040..e24f4daa774 100644 --- a/apps/hash-graph/libs/graph/src/snapshot/ontology/property_type/channel.rs +++ b/apps/hash-graph/libs/graph/src/snapshot/ontology/property_type/channel.rs @@ -10,8 +10,8 @@ use futures::{ channel::mpsc::{self, Receiver, Sender}, stream::{BoxStream, SelectAll, select_all}, }; -use graph_types::ontology::{DataTypeId, PropertyTypeId}; -use type_system::Valid; +use graph_types::ontology::PropertyTypeId; +use type_system::{Valid, schema::DataTypeId}; use crate::{ snapshot::{ diff --git a/apps/hash-graph/libs/graph/src/snapshot/restore/channel.rs b/apps/hash-graph/libs/graph/src/snapshot/restore/channel.rs index 73b2ccab4f4..30555e56daa 100644 --- a/apps/hash-graph/libs/graph/src/snapshot/restore/channel.rs +++ b/apps/hash-graph/libs/graph/src/snapshot/restore/channel.rs @@ -13,8 +13,9 @@ use futures::{ }; use graph_types::{ knowledge::entity::EntityUuid, - ontology::{DataTypeId, EntityTypeId, PropertyTypeId}, + ontology::{EntityTypeId, PropertyTypeId}, }; +use type_system::schema::DataTypeId; use crate::{ snapshot::{ diff --git a/apps/hash-graph/libs/graph/src/store/postgres/mod.rs b/apps/hash-graph/libs/graph/src/store/postgres/mod.rs index 64d8a2d3dc7..39c741dd0e7 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/mod.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/mod.rs @@ -23,7 +23,7 @@ use error_stack::{Report, Result, ResultExt}; use graph_types::{ account::{AccountGroupId, AccountId, EditionArchivedById}, ontology::{ - DataTypeId, OntologyEditionProvenance, OntologyProvenance, OntologyTemporalMetadata, + OntologyEditionProvenance, OntologyProvenance, OntologyTemporalMetadata, OntologyTypeClassificationMetadata, OntologyTypeRecordId, }, owned_by_id::OwnedById, @@ -44,8 +44,8 @@ use tokio_postgres::{GenericClient, error::SqlState}; use type_system::{ Valid, schema::{ - ClosedDataTypeMetadata, ClosedEntityType, Conversions, DataType, DataTypeReference, - EntityType, EntityTypeReference, PropertyType, PropertyTypeReference, + ClosedEntityType, Conversions, DataType, DataTypeId, DataTypeInheritanceData, + DataTypeReference, EntityType, EntityTypeReference, PropertyType, PropertyTypeReference, }, url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, }; @@ -459,9 +459,9 @@ where pub async fn insert_data_type_references( &self, ontology_id: DataTypeId, - metadata: &ClosedDataTypeMetadata, + metadata: &DataTypeInheritanceData, ) -> Result<(), InsertionError> { - for (target, &depth) in &metadata.inheritance_depths { + for (target, depth) in &metadata.inheritance_depths { self.as_client() .query( " @@ -475,11 +475,7 @@ where $3 ); ", - &[ - &ontology_id, - &DataTypeId::from_url(target), - &i32::try_from(depth).change_context(InsertionError)?, - ], + &[&ontology_id, target, depth], ) .await .change_context(InsertionError)?; diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs index 93d66ae1fb5..c66501c4f0e 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs @@ -14,9 +14,8 @@ use graph_types::{ Embedding, account::{AccountId, EditionArchivedById, EditionCreatedById}, ontology::{ - DataTypeId, DataTypeMetadata, DataTypeWithMetadata, OntologyEditionProvenance, - OntologyProvenance, OntologyTemporalMetadata, OntologyTypeClassificationMetadata, - OntologyTypeRecordId, + DataTypeMetadata, DataTypeWithMetadata, OntologyEditionProvenance, OntologyProvenance, + OntologyTemporalMetadata, OntologyTypeClassificationMetadata, OntologyTypeRecordId, }, }; use hash_graph_store::{ @@ -39,7 +38,10 @@ use tokio_postgres::{GenericClient, Row}; use tracing::instrument; use type_system::{ Validator, - schema::{ConversionDefinition, Conversions, DataTypeValidator, OntologyTypeResolver}, + schema::{ + ConversionDefinition, Conversions, DataTypeId, DataTypeInheritanceData, DataTypeValidator, + InheritanceDepth, OntologyTypeResolver, + }, url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, }; @@ -111,6 +113,33 @@ where })) } + async fn get_data_type_inheritance_metadata( + &self, + data_types: &[DataTypeId], + ) -> Result, QueryError> { + Ok(self + .as_client() + .query( + " + SELECT source_data_type_ontology_id, target_data_type_ontology_ids, depths + FROM data_type_inherits_from_aggregation + WHERE source_data_type_ontology_id = ANY($1) + ", + &[&data_types], + ) + .await + .change_context(QueryError)? + .into_iter() + .map(|row| { + let source: DataTypeId = row.get(0); + let targets: Vec = row.get(1); + let depths: Vec = row.get(2); + (source, DataTypeInheritanceData { + inheritance_depths: targets.into_iter().zip(depths).collect(), + }) + })) + } + async fn get_data_types_impl( &self, actor_id: AccountId, @@ -406,7 +435,7 @@ where .data_type_references() .map(|(reference, _)| DataTypeId::from_url(&reference.url)), ); - inserted_data_types.push(Arc::new(parameters.schema)); + inserted_data_types.push((data_type_id, Arc::new(parameters.schema))); data_type_conversions_rows.extend(parameters.conversions.iter().map( |(base_url, conversions)| DataTypeConversionsRow { source_data_type_ontology_id: data_type_id, @@ -428,9 +457,13 @@ where let mut ontology_type_resolver = OntologyTypeResolver::default(); let required_parent_ids = data_type_reference_ids.into_iter().collect::>(); - // TODO: Read the closed schemas directly instead - // see https://linear.app/hash/issue/H-3082/allow-querying-of-closed-data-schema - // We need need the parents itself ... + let mut parent_inheritance_data = transaction + .get_data_type_inheritance_metadata(&required_parent_ids) + .await + .change_context(InsertionError) + .attach_printable("Could not read parent data type inheritance data")? + .collect::>(); + transaction .get_data_types(actor_id, GetDataTypesParams { filter: Filter::In( @@ -453,67 +486,60 @@ where .attach_printable("Could not read parent data types")? .data_types .into_iter() - .chain( - // ... and their parents (recursively) - transaction - .get_data_types(actor_id, GetDataTypesParams { - filter: Filter::for_data_type_parents(&required_parent_ids, None), - temporal_axes: QueryTemporalAxesUnresolved::DecisionTime { - pinned: PinnedTemporalAxisUnresolved::new(None), - variable: VariableTemporalAxisUnresolved::new(None, None), - }, - include_drafts: false, - after: None, - limit: None, - include_count: false, - }) - .await - .change_context(InsertionError) - .attach_printable("Could not read parent data types")? - .data_types, - ) - .for_each(|data_type| { - ontology_type_resolver.add_open(Arc::new(data_type.schema)); + .for_each(|parent| { + let parent_id = DataTypeId::from_url(&parent.schema.id); + if let Some(inheritance_data) = parent_inheritance_data.remove(&parent_id) { + ontology_type_resolver.add_closed( + parent_id, + Arc::new(parent.schema), + Arc::new(inheritance_data), + ); + } else { + if !parent.schema.all_of.is_empty() { + tracing::warn!("No inheritance data found for `{}`", parent.schema.id); + } + ontology_type_resolver.add_open(parent_id, Arc::new(parent.schema)); + } }); - for inserted_data_type in &inserted_data_types { - ontology_type_resolver.add_open(Arc::clone(inserted_data_type)); + for (data_type_id, inserted_data_type) in &inserted_data_types { + ontology_type_resolver.add_open(*data_type_id, Arc::clone(inserted_data_type)); } let schema_metadata = inserted_data_types .iter() - .map(|inserted_data_type| { + .map(|(data_type_id, _)| { ontology_type_resolver - .resolve_data_type_metadata(&inserted_data_type.id) + .resolve_data_type_metadata(*data_type_id) .change_context(InsertionError) }) .collect::, _>>()?; let data_type_validator = DataTypeValidator; - for data_type in &inserted_data_types { - let closed_schema = data_type_validator - .validate( - ontology_type_resolver - .get_closed_data_type(&data_type.id) - .change_context(InsertionError)?, - ) - .await - .attach(StatusCode::InvalidArgument) - .change_context(InsertionError)?; + for (data_type_id, data_type) in &inserted_data_types { + // TODO: Validate ontology types on creation + // see https://linear.app/hash/issue/H-2976/validate-ontology-types-on-creation + // let closed_schema = data_type_validator + // .validate( + // ontology_type_resolver + // .get_closed_data_type(*data_type_id) + // .change_context(InsertionError)?, + // ) + // .await + // .attach(StatusCode::InvalidArgument) + // .change_context(InsertionError)?; let schema = data_type_validator - .validate_ref(&closed_schema.schema) + .validate_ref(&**data_type) .await .attach(StatusCode::InvalidArgument) .change_context(InsertionError)?; transaction - .insert_data_type_with_id( - DataTypeId::from_url(&schema.id), - closed_schema.data_type(), - ) + .insert_data_type_with_id(*data_type_id, schema) .await?; } - for (schema_metadata, data_type) in schema_metadata.iter().zip(&inserted_data_types) { + for (schema_metadata, (data_type_id, _)) in schema_metadata.iter().zip(&inserted_data_types) + { transaction - .insert_data_type_references(DataTypeId::from_url(&data_type.id), schema_metadata) + .insert_data_type_references(*data_type_id, schema_metadata) .await?; } @@ -575,7 +601,7 @@ where &inserted_data_types .iter() .zip(&inserted_data_type_metadata) - .map(|(schema, metadata)| DataTypeWithMetadata { + .map(|((_, schema), metadata)| DataTypeWithMetadata { schema: (**schema).clone(), metadata: metadata.clone(), }) @@ -759,6 +785,7 @@ where )?, ), }); + let new_ontology_id = DataTypeId::from_url(¶ms.schema.id); self.authorization_api .check_data_type_permission( actor_id, @@ -792,9 +819,14 @@ where .data_type_references() .map(|(reference, _)| DataTypeId::from_url(&reference.url)) .collect::>(); - // TODO: Read the closed schemas directly instead - // see https://linear.app/hash/issue/H-3082/allow-querying-of-closed-data-schema - // We need need the parents itself ... + + let mut parent_inheritance_data = transaction + .get_data_type_inheritance_metadata(&required_parent_ids) + .await + .change_context(UpdateError) + .attach_printable("Could not read parent data type inheritance data")? + .collect::>(); + transaction .get_data_types(actor_id, GetDataTypesParams { filter: Filter::In( @@ -817,42 +849,37 @@ where .attach_printable("Could not read parent data types")? .data_types .into_iter() - .chain( - // ... and their parents (recursively) - transaction - .get_data_types(actor_id, GetDataTypesParams { - filter: Filter::for_data_type_parents(&required_parent_ids, None), - temporal_axes: QueryTemporalAxesUnresolved::DecisionTime { - pinned: PinnedTemporalAxisUnresolved::new(None), - variable: VariableTemporalAxisUnresolved::new(None, None), - }, - include_drafts: false, - after: None, - limit: None, - include_count: false, - }) - .await - .change_context(UpdateError) - .attach_printable("Could not read parent data types")? - .data_types, - ) - .for_each(|data_type| { - ontology_type_resolver.add_open(Arc::new(data_type.schema)); + .for_each(|parent| { + let parent_id = DataTypeId::from_url(&parent.schema.id); + if let Some(inheritance_data) = parent_inheritance_data.remove(&parent_id) { + ontology_type_resolver.add_closed( + parent_id, + Arc::new(parent.schema), + Arc::new(inheritance_data), + ); + } else { + if !parent.schema.all_of.is_empty() { + tracing::warn!("No inheritance data found for `{}`", parent.schema.id); + } + ontology_type_resolver.add_open(parent_id, Arc::new(parent.schema)); + } }); - ontology_type_resolver.add_open(Arc::new(schema.clone().into_inner())); + ontology_type_resolver.add_open(new_ontology_id, Arc::new(schema.clone().into_inner())); let metadata = ontology_type_resolver - .resolve_data_type_metadata(&schema.id) + .resolve_data_type_metadata(new_ontology_id) .change_context(UpdateError)?; - let closed_schema = data_type_validator - .validate( - ontology_type_resolver - .get_closed_data_type(&schema.id) - .change_context(UpdateError)?, - ) - .await - .change_context(UpdateError)?; + // TODO: Validate ontology types on creation + // see https://linear.app/hash/issue/H-2976/validate-ontology-types-on-creation + // let closed_schema = data_type_validator + // .validate( + // ontology_type_resolver + // .get_closed_data_type(new_ontology_id) + // .change_context(UpdateError)?, + // ) + // .await + // .change_context(UpdateError)?; let (ontology_id, owned_by_id, temporal_versioning) = transaction .update_owned_ontology_id(&schema.id, &provenance.edition) .await?; @@ -860,7 +887,7 @@ where let data_type_id = DataTypeId::from(ontology_id); transaction - .insert_data_type_with_id(data_type_id, closed_schema.data_type()) + .insert_data_type_with_id(data_type_id, schema) .await .change_context(UpdateError)?; transaction @@ -1068,7 +1095,7 @@ where let mut ontology_type_resolver = OntologyTypeResolver::default(); - let data_types = Read::::read_vec( + let data_type_ids = Read::::read_vec( &transaction, &Filter::All(Vec::new()), None, @@ -1079,18 +1106,19 @@ where .into_iter() .map(|data_type| { let schema = Arc::new(data_type.schema); - ontology_type_resolver.add_open(Arc::clone(&schema)); - schema + let data_type_id = DataTypeId::from_url(&schema.id); + ontology_type_resolver.add_open(data_type_id, Arc::clone(&schema)); + data_type_id }) .collect::>(); - for data_type in &data_types { + for data_type_id in data_type_ids { let schema_metadata = ontology_type_resolver - .resolve_data_type_metadata(&data_type.id) + .resolve_data_type_metadata(data_type_id) .change_context(UpdateError)?; transaction - .insert_data_type_references(DataTypeId::from_url(&data_type.id), &schema_metadata) + .insert_data_type_references(data_type_id, &schema_metadata) .await .change_context(UpdateError)?; } diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs index 386b2353f39..77ccf53b99f 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs @@ -15,10 +15,9 @@ use graph_types::{ Embedding, account::{AccountId, EditionArchivedById, EditionCreatedById}, ontology::{ - DataTypeId, EntityTypeId, EntityTypeMetadata, EntityTypeWithMetadata, - OntologyEditionProvenance, OntologyProvenance, OntologyTemporalMetadata, - OntologyTypeClassificationMetadata, OntologyTypeRecordId, PartialEntityTypeMetadata, - PropertyTypeId, + EntityTypeId, EntityTypeMetadata, EntityTypeWithMetadata, OntologyEditionProvenance, + OntologyProvenance, OntologyTemporalMetadata, OntologyTypeClassificationMetadata, + OntologyTypeRecordId, PartialEntityTypeMetadata, PropertyTypeId, }, }; use hash_graph_store::{ @@ -40,7 +39,7 @@ use tokio_postgres::{GenericClient, Row}; use tracing::instrument; use type_system::{ Validator, - schema::{ClosedEntityType, EntityType, EntityTypeValidator}, + schema::{ClosedEntityType, DataTypeId, EntityType, EntityTypeValidator}, url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, }; diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/ontology_id.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/ontology_id.rs index 0aa74237229..29459eff717 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/ontology_id.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/ontology_id.rs @@ -1,8 +1,9 @@ use core::fmt; -use graph_types::ontology::{DataTypeId, EntityTypeId, OntologyTypeRecordId, PropertyTypeId}; +use graph_types::ontology::{EntityTypeId, OntologyTypeRecordId, PropertyTypeId}; use postgres_types::{FromSql, ToSql}; use serde::{Deserialize, Serialize}; +use type_system::schema::DataTypeId; #[cfg(feature = "utoipa")] use utoipa::ToSchema; use uuid::Uuid; diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs index a4268037196..bbb848ff204 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs @@ -16,7 +16,7 @@ use graph_types::{ Embedding, account::{AccountId, EditionArchivedById, EditionCreatedById}, ontology::{ - DataTypeId, OntologyEditionProvenance, OntologyProvenance, OntologyTemporalMetadata, + OntologyEditionProvenance, OntologyProvenance, OntologyTemporalMetadata, OntologyTypeClassificationMetadata, OntologyTypeRecordId, PropertyTypeId, PropertyTypeMetadata, PropertyTypeWithMetadata, }, @@ -36,7 +36,7 @@ use tokio_postgres::{GenericClient, Row}; use tracing::instrument; use type_system::{ Validator, - schema::PropertyTypeValidator, + schema::{DataTypeId, PropertyTypeValidator}, url::{OntologyTypeVersion, VersionedUrl}, }; diff --git a/apps/hash-graph/libs/graph/src/store/postgres/query/rows.rs b/apps/hash-graph/libs/graph/src/store/postgres/query/rows.rs index 18e739b6c91..52123320b02 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/query/rows.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/query/rows.rs @@ -8,7 +8,7 @@ use graph_types::{ }, property::{PropertyMetadataObject, PropertyObject, PropertyProvenance}, }, - ontology::{DataTypeId, EntityTypeId, OntologyEditionProvenance, PropertyTypeId}, + ontology::{EntityTypeId, OntologyEditionProvenance, PropertyTypeId}, owned_by_id::OwnedById, }; use postgres_types::ToSql; @@ -16,7 +16,9 @@ use temporal_versioning::{DecisionTime, LeftClosedTemporalInterval, Timestamp, T use time::OffsetDateTime; use type_system::{ Valid, - schema::{ClosedEntityType, ConversionDefinition, DataType, EntityType, PropertyType}, + schema::{ + ClosedEntityType, ConversionDefinition, DataType, DataTypeId, EntityType, PropertyType, + }, url::{BaseUrl, OntologyTypeVersion}, }; diff --git a/apps/hash-graph/libs/graph/src/store/postgres/traversal_context.rs b/apps/hash-graph/libs/graph/src/store/postgres/traversal_context.rs index b9413bced86..92df2429bd0 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/traversal_context.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/traversal_context.rs @@ -5,7 +5,7 @@ use error_stack::Result; use graph_types::{ knowledge::entity::{Entity, EntityEditionId}, ontology::{ - DataTypeId, DataTypeWithMetadata, EntityTypeId, EntityTypeWithMetadata, PropertyTypeId, + DataTypeWithMetadata, EntityTypeId, EntityTypeWithMetadata, PropertyTypeId, PropertyTypeWithMetadata, }, }; @@ -18,6 +18,7 @@ use hash_graph_store::{ subgraph::{Subgraph, SubgraphRecord, edges::GraphResolveDepths, temporal_axes::VariableAxis}, }; use temporal_versioning::RightBoundedTemporalInterval; +use type_system::schema::DataTypeId; use crate::store::{AsClient, PostgresStore, QueryError, crud::Read}; diff --git a/apps/hash-graph/libs/graph/src/store/validation.rs b/apps/hash-graph/libs/graph/src/store/validation.rs index 7a4b9e6b78b..27a23eaa34f 100644 --- a/apps/hash-graph/libs/graph/src/store/validation.rs +++ b/apps/hash-graph/libs/graph/src/store/validation.rs @@ -14,7 +14,7 @@ use graph_types::{ account::AccountId, knowledge::entity::{Entity, EntityId}, ontology::{ - DataTypeId, DataTypeProvider, DataTypeWithMetadata, EntityTypeId, EntityTypeProvider, + DataTypeProvider, DataTypeWithMetadata, EntityTypeId, EntityTypeProvider, EntityTypeWithMetadata, OntologyTypeProvider, PropertyTypeId, PropertyTypeProvider, PropertyTypeWithMetadata, }, @@ -28,7 +28,9 @@ use hash_graph_store::{ use tokio::sync::RwLock; use tokio_postgres::GenericClient; use type_system::{ - schema::{ClosedEntityType, ConversionDefinition, ConversionExpression, PropertyType}, + schema::{ + ClosedEntityType, ConversionDefinition, ConversionExpression, DataTypeId, PropertyType, + }, url::{BaseUrl, VersionedUrl}, }; use validation::EntityProvider; diff --git a/apps/hash-graph/libs/store/src/filter/mod.rs b/apps/hash-graph/libs/store/src/filter/mod.rs index 14f0bca125a..f86ea460fad 100644 --- a/apps/hash-graph/libs/store/src/filter/mod.rs +++ b/apps/hash-graph/libs/store/src/filter/mod.rs @@ -9,10 +9,13 @@ use derive_where::derive_where; use error_stack::{Report, ResultExt, bail}; use graph_types::{ knowledge::entity::{Entity, EntityId}, - ontology::{DataTypeId, DataTypeProvider, DataTypeWithMetadata}, + ontology::{DataTypeProvider, DataTypeWithMetadata}, }; use serde::{Deserialize, de, de::IntoDeserializer}; -use type_system::url::{BaseUrl, OntologyTypeVersion, VersionedUrl}; +use type_system::{ + schema::DataTypeId, + url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, +}; pub use self::{ parameter::{Parameter, ParameterConversionError, ParameterList, ParameterType}, diff --git a/apps/hash-graph/libs/store/src/filter/parameter.rs b/apps/hash-graph/libs/store/src/filter/parameter.rs index dbf1e5831bb..55bcb0026fa 100644 --- a/apps/hash-graph/libs/store/src/filter/parameter.rs +++ b/apps/hash-graph/libs/store/src/filter/parameter.rs @@ -5,12 +5,15 @@ use error_stack::{Context, Report, ResultExt, bail}; use graph_types::{ Embedding, knowledge::entity::EntityEditionId, - ontology::{DataTypeId, EntityTypeId, PropertyTypeId}, + ontology::{EntityTypeId, PropertyTypeId}, }; use serde::Deserialize; use serde_json::{Number as JsonNumber, Value as JsonValue}; use temporal_versioning::Timestamp; -use type_system::url::{OntologyTypeVersion, VersionedUrl}; +use type_system::{ + schema::DataTypeId, + url::{OntologyTypeVersion, VersionedUrl}, +}; use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Deserialize)] diff --git a/libs/@blockprotocol/type-system/rust/Cargo.toml b/libs/@blockprotocol/type-system/rust/Cargo.toml index 31cd7e8a7de..8c898704e0b 100644 --- a/libs/@blockprotocol/type-system/rust/Cargo.toml +++ b/libs/@blockprotocol/type-system/rust/Cargo.toml @@ -23,11 +23,11 @@ codec = { workspace = true, public = true, features = ["serde"] } bytes = { workspace = true, public = true } email_address = { workspace = true, public = true } iso8601-duration = { workspace = true, public = true } -postgres-types = { workspace = true, public = true, features = ["derive", "with-serde_json-1"], optional = true } +postgres-types = { workspace = true, public = true, features = ["derive", "with-uuid-1", "with-serde_json-1"], optional = true } serde_json = { workspace = true, public = true } url = { workspace = true, public = true } utoipa = { workspace = true, public = true, features = ["url"], optional = true } -uuid = { workspace = true, public = true, features = ["std"] } +uuid = { workspace = true, public = true, features = ["v5", "serde", "std"] } # Private workspace dependencies futures = { workspace = true } diff --git a/libs/@blockprotocol/type-system/rust/src/lib.rs b/libs/@blockprotocol/type-system/rust/src/lib.rs index 2d34b24321f..8668de2a581 100644 --- a/libs/@blockprotocol/type-system/rust/src/lib.rs +++ b/libs/@blockprotocol/type-system/rust/src/lib.rs @@ -1,5 +1,4 @@ #![feature(extend_one)] -#![feature(hash_raw_entry)] #![expect(unsafe_code)] #![cfg_attr( target_arch = "wasm32", diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/closed.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/closed.rs index c12098951ac..49869001947 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/closed.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/closed.rs @@ -1,12 +1,18 @@ use alloc::sync::Arc; use core::cmp; -use std::collections::{HashMap, hash_map::RawEntryMut}; +#[cfg(feature = "postgres")] +use core::error::Error; +use std::collections::{HashMap, hash_map::Entry}; +#[cfg(feature = "postgres")] +use bytes::BytesMut; +#[cfg(feature = "postgres")] +use postgres_types::{FromSql, IsNull, ToSql, Type}; use serde::{Deserialize, Serialize}; use crate::{ Valid, - schema::{DataType, data_type::DataTypeEdge}, + schema::{DataType, DataTypeId, data_type::DataTypeEdge}, url::VersionedUrl, }; @@ -27,24 +33,62 @@ impl ClosedDataType { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct ClosedDataTypeMetadata { - pub inheritance_depths: HashMap, +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[repr(transparent)] +pub struct InheritanceDepth(u16); + +impl InheritanceDepth { + #[must_use] + pub const fn new(inner: u16) -> Self { + Self(inner) + } + + #[must_use] + pub const fn inner(self) -> u16 { + self.0 + } +} + +#[cfg(feature = "postgres")] +impl ToSql for InheritanceDepth { + postgres_types::accepts!(INT4); + + postgres_types::to_sql_checked!(); + + fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result> + where + Self: Sized, + { + i32::from(self.0).to_sql(ty, out) + } } -impl ClosedDataTypeMetadata { - pub fn add_edge(&mut self, edge: DataTypeEdge, target: &VersionedUrl, depth: u32) { +#[cfg(feature = "postgres")] +impl<'a> FromSql<'a> for InheritanceDepth { + postgres_types::accepts!(INT4); + + fn from_sql(ty: &Type, raw: &'a [u8]) -> Result> { + Ok(Self::new(i32::from_sql(ty, raw)?.try_into()?)) + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct DataTypeInheritanceData { + pub inheritance_depths: HashMap, +} + +impl DataTypeInheritanceData { + pub fn add_edge(&mut self, edge: DataTypeEdge, target: DataTypeId, depth: u16) { match edge { - DataTypeEdge::Inheritance => { - match self.inheritance_depths.raw_entry_mut().from_key(target) { - RawEntryMut::Occupied(mut entry) => { - *entry.get_mut() = cmp::min(depth, *entry.get()); - } - RawEntryMut::Vacant(entry) => { - entry.insert(target.clone(), depth); - } + DataTypeEdge::Inheritance => match self.inheritance_depths.entry(target) { + Entry::Occupied(mut entry) => { + *entry.get_mut() = InheritanceDepth::new(cmp::min(depth, entry.get().inner())); + } + Entry::Vacant(entry) => { + entry.insert(InheritanceDepth::new(depth)); } - } + }, } } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs index 9e05a4e8b6d..1d50d648b86 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs @@ -2,7 +2,7 @@ mod constraint; mod conversion; pub use self::{ - closed::{ClosedDataType, ClosedDataTypeMetadata}, + closed::{ClosedDataType, DataTypeInheritanceData, InheritanceDepth}, constraint::{ AnyOfConstraints, ArrayConstraints, ArraySchema, ArrayTypeTag, ArrayValidationError, BooleanTypeTag, ConstraintError, NullTypeTag, NumberConstraints, NumberSchema, @@ -25,15 +25,51 @@ mod validation; use alloc::sync::Arc; use core::{fmt, mem}; -use std::collections::{HashMap, HashSet, hash_map::RawEntryMut}; +use std::collections::{HashMap, HashSet}; use error_stack::{Report, bail}; +#[cfg(feature = "postgres")] +use postgres_types::{FromSql, ToSql}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use thiserror::Error; +#[cfg(feature = "utoipa")] +use utoipa::ToSchema; +use uuid::Uuid; use crate::{schema::data_type::constraint::ValueConstraints, url::VersionedUrl}; +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "postgres", derive(FromSql, ToSql), postgres(transparent))] +#[repr(transparent)] +pub struct DataTypeId(Uuid); + +impl DataTypeId { + #[must_use] + pub const fn new(uuid: Uuid) -> Self { + Self(uuid) + } + + #[must_use] + pub fn from_url(url: &VersionedUrl) -> Self { + Self(Uuid::new_v5( + &Uuid::NAMESPACE_URL, + url.to_string().as_bytes(), + )) + } + + #[must_use] + pub const fn as_uuid(&self) -> &Uuid { + &self.0 + } + + #[must_use] + pub const fn into_uuid(self) -> Uuid { + self.0 + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))] #[serde(rename_all = "kebab-case")] @@ -405,65 +441,72 @@ impl DataType { #[derive(Debug, Error)] pub enum DataTypeResolveError { + #[error("The data type ID is unknown")] + UnknownDataTypeId, #[error("The data types have unresolved references: {}", serde_json::json!(schemas))] MissingSchemas { schemas: HashSet }, - #[error("The closed data type metadata for `{id}` is missing")] - MissingClosedDataType { id: VersionedUrl }, + #[error("The closed data type metadata is missing")] + MissingClosedDataType, + #[error("Not all schemas are contained in the resolver")] + MissingDataTypes, } #[derive(Debug, Serialize, Deserialize)] struct DataTypeCacheEntry { pub data_type: Arc, - pub metadata: Option>, + pub metadata: Option>, } #[derive(Debug, Default)] pub struct OntologyTypeResolver { - data_types: HashMap, + data_types: HashMap, } impl OntologyTypeResolver { - pub fn add_open(&mut self, data_type: Arc) { + pub fn add_open(&mut self, data_type_id: DataTypeId, data_type: Arc) { + debug_assert_eq!( + data_type_id, + DataTypeId::from_url(&data_type.id), + "The data type ID must match the URL" + ); self.data_types - .raw_entry_mut() - .from_key(&data_type.id) - .or_insert_with(|| { - (data_type.id.clone(), DataTypeCacheEntry { - data_type, - metadata: None, - }) + .entry(data_type_id) + .or_insert_with(|| DataTypeCacheEntry { + data_type, + metadata: None, }); } - pub fn add_closed(&mut self, data_type: Arc, metadata: Arc) { - match self.data_types.raw_entry_mut().from_key(&data_type.id) { - RawEntryMut::Vacant(entry) => { - entry.insert(data_type.id.clone(), DataTypeCacheEntry { - data_type, - metadata: Some(metadata), - }); - } - RawEntryMut::Occupied(mut entry) => { - entry.insert(DataTypeCacheEntry { - data_type, - metadata: Some(metadata), - }); - } - } + pub fn add_closed( + &mut self, + data_type_id: DataTypeId, + data_type: Arc, + metadata: Arc, + ) { + debug_assert_eq!( + data_type_id, + DataTypeId::from_url(&data_type.id), + "The data type ID must match the URL" + ); + self.data_types + .insert(DataTypeId::from_url(&data_type.id), DataTypeCacheEntry { + data_type, + metadata: Some(metadata), + }); } pub fn update_metadata( &mut self, - data_type_id: &VersionedUrl, - metadata: Arc, - ) -> Option> { + data_type_id: DataTypeId, + metadata: Arc, + ) -> Option> { self.data_types - .get_mut(data_type_id) + .get_mut(&data_type_id) .map(|entry| Arc::clone(entry.metadata.insert(metadata))) } - fn get(&self, id: &VersionedUrl) -> Option<&DataTypeCacheEntry> { - self.data_types.get(id) + fn get(&self, id: DataTypeId) -> Option<&DataTypeCacheEntry> { + self.data_types.get(&id) } /// Resolves the metadata for the given data types. @@ -476,12 +519,10 @@ impl OntologyTypeResolver { /// Returns an error if the metadata for any of the data types could not be resolved. pub fn resolve_data_type_metadata( &mut self, - data_type_id: &VersionedUrl, - ) -> Result, Report> { + data_type_id: DataTypeId, + ) -> Result, Report> { let Some(data_type_entry) = self.get(data_type_id) else { - bail!(DataTypeResolveError::MissingSchemas { - schemas: HashSet::from([data_type_id.clone()]), - }); + bail!(DataTypeResolveError::UnknownDataTypeId); }; if let Some(metadata) = &data_type_entry.metadata { @@ -504,10 +545,10 @@ impl OntologyTypeResolver { // We also keep a list of all schemas that we already processed. This is used to prevent // infinite loops in the inheritance chain. New values are added to this list as we add new // schemas to resolve. - let mut processed_schemas = HashSet::from([data_type_id.clone()]); + let mut processed_schemas = HashSet::from([data_type_id]); // The currently closed schema being resolved. This can be used later to resolve - let mut in_progress_schema = ClosedDataTypeMetadata { + let mut in_progress_schema = DataTypeInheritanceData { inheritance_depths: HashMap::new(), }; @@ -520,17 +561,18 @@ impl OntologyTypeResolver { See https://github.com/rust-lang/rust-clippy/issues/8539" )] for data_type in data_types_to_resolve.drain(..) { + let data_type_id = DataTypeId::from_url(&data_type.id); for (data_type_reference, edge) in data_type.data_type_references() { - if processed_schemas.contains(&data_type_reference.url) { + let data_type_reference_id = DataTypeId::from_url(&data_type_reference.url); + if processed_schemas.contains(&data_type_reference_id) { // We ignore the already processed schemas to prevent infinite loops. continue; } - in_progress_schema.add_edge(edge, &data_type_reference.url, current_depth); - processed_schemas.insert(data_type_reference.url.clone()); + in_progress_schema.add_edge(edge, data_type_reference_id, current_depth); + processed_schemas.insert(data_type_reference_id); - let Some(data_type_entry) = self.data_types.get(&data_type_reference.url) - else { + let Some(data_type_entry) = self.data_types.get(&data_type_reference_id) else { // If the data type is not in the cache, we add it to the list of missing // schemas. missing_schemas.insert(data_type_reference.url.clone()); @@ -540,11 +582,11 @@ impl OntologyTypeResolver { if let Some(metadata) = &data_type_entry.metadata { // If the metadata is already resolved, we can reuse it. for (data_type_ref, depth) in &metadata.inheritance_depths { - if data_type.id != *data_type_ref { + if data_type_id != *data_type_ref { in_progress_schema.add_edge( edge, - data_type_ref, - *depth + current_depth + 1, + *data_type_ref, + depth.inner() + current_depth + 1, ); } } @@ -587,48 +629,33 @@ impl OntologyTypeResolver { /// Returns an error if the closed data type could not be resolved. pub fn get_closed_data_type( &self, - data_type_id: &VersionedUrl, + data_type_id: DataTypeId, ) -> Result> { - let Some(entry) = self.get(data_type_id) else { - bail!(DataTypeResolveError::MissingSchemas { - schemas: HashSet::from([data_type_id.clone()]), - }); + let Some(data_type_entry) = self.get(data_type_id) else { + bail!(DataTypeResolveError::UnknownDataTypeId); }; - let metadata = - entry - .metadata - .as_ref() - .ok_or_else(|| DataTypeResolveError::MissingClosedDataType { - id: data_type_id.clone(), - })?; - - let mut missing_schemas = HashSet::new(); + let metadata = data_type_entry + .metadata + .as_ref() + .ok_or_else(|| DataTypeResolveError::MissingClosedDataType)?; - let closed_type = ClosedDataType { - schema: Arc::clone(&entry.data_type), + Ok(ClosedDataType { + schema: Arc::clone(&data_type_entry.data_type), definitions: metadata .inheritance_depths .keys() - .cloned() - .filter_map(|id| { - let Some(definition_entry) = self.get(&id) else { - missing_schemas.insert(id); - return None; - }; - Some((id, Arc::clone(&definition_entry.data_type))) - }) - .collect(), - }; - - missing_schemas - .is_empty() - .then_some(closed_type) - .ok_or_else(|| { - Report::from(DataTypeResolveError::MissingSchemas { - schemas: missing_schemas, + .map(|&id| { + let definition_entry = self + .get(id) + .ok_or(DataTypeResolveError::MissingClosedDataType)?; + Ok(( + definition_entry.data_type.id.clone(), + Arc::clone(&definition_entry.data_type), + )) }) - }) + .collect::>()?, + }) } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/mod.rs b/libs/@blockprotocol/type-system/rust/src/schema/mod.rs index 085be3f590e..bfa3d56bf9f 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/mod.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/mod.rs @@ -17,11 +17,12 @@ pub use self::{ array::{PropertyArraySchema, PropertyValueArray, ValueOrArray}, data_type::{ AnyOfConstraints, ArrayConstraints, ArraySchema, ArrayTypeTag, ArrayValidationError, - BooleanTypeTag, ClosedDataType, ClosedDataTypeMetadata, ConstraintError, - ConversionDefinition, ConversionExpression, ConversionValue, Conversions, DataType, - DataTypeReference, DataTypeValidator, JsonSchemaValueType, NullTypeTag, NumberConstraints, - NumberSchema, NumberTypeTag, NumberValidationError, ObjectTypeTag, OntologyTypeResolver, - Operator, SingleValueConstraints, SingleValueSchema, StringConstraints, StringFormat, + BooleanTypeTag, ClosedDataType, ConstraintError, ConversionDefinition, + ConversionExpression, ConversionValue, Conversions, DataType, DataTypeId, + DataTypeInheritanceData, DataTypeReference, DataTypeValidator, InheritanceDepth, + JsonSchemaValueType, NullTypeTag, NumberConstraints, NumberSchema, NumberTypeTag, + NumberValidationError, ObjectTypeTag, OntologyTypeResolver, Operator, + SingleValueConstraints, SingleValueSchema, StringConstraints, StringFormat, StringFormatError, StringSchema, StringTypeTag, StringValidationError, TupleConstraints, ValidateDataTypeError, ValueLabel, Variable, }, diff --git a/libs/@local/hash-authorization/Cargo.toml b/libs/@local/hash-authorization/Cargo.toml index bf2007142c2..aab0e87e2e0 100644 --- a/libs/@local/hash-authorization/Cargo.toml +++ b/libs/@local/hash-authorization/Cargo.toml @@ -17,7 +17,8 @@ futures-core = { workspace = true, public = true } # Private workspace dependencies codec = { workspace = true } -error-stack = { workspace = true, features = ["unstable"]} +type-system = { workspace = true } +error-stack = { workspace = true, features = ["unstable"] } # Private third-party dependencies derive-where = { workspace = true } @@ -30,7 +31,7 @@ tokio = { workspace = true } tokio-util = { workspace = true, features = ["io"] } tracing = { workspace = true, features = ["attributes"] } utoipa = { workspace = true, optional = true } -uuid = { workspace = true, default-features = false, features = ["v5"] } +uuid = { workspace = true, default-features = false } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/libs/@local/hash-authorization/package.json b/libs/@local/hash-authorization/package.json index 51cb8ab7a7b..30a03a96c1b 100644 --- a/libs/@local/hash-authorization/package.json +++ b/libs/@local/hash-authorization/package.json @@ -10,6 +10,7 @@ "test:unit:disabled": "cargo hack nextest run --feature-powerset --lib && cargo test --all-features --doc" }, "dependencies": { + "@blockprotocol/type-system-rs": "0.0.0-private", "@rust/codec": "0.0.0-private", "@rust/error-stack": "0.5.0", "@rust/graph-types": "0.0.0-private" diff --git a/libs/@local/hash-authorization/src/api.rs b/libs/@local/hash-authorization/src/api.rs index 89b544910c6..587ea5c8763 100644 --- a/libs/@local/hash-authorization/src/api.rs +++ b/libs/@local/hash-authorization/src/api.rs @@ -4,9 +4,10 @@ use error_stack::{Context, Result}; use graph_types::{ account::{AccountGroupId, AccountId}, knowledge::entity::{EntityId, EntityUuid}, - ontology::{DataTypeId, EntityTypeId, PropertyTypeId}, + ontology::{EntityTypeId, PropertyTypeId}, owned_by_id::OwnedById, }; +use type_system::schema::DataTypeId; use crate::{ backend::{ diff --git a/libs/@local/hash-authorization/src/lib.rs b/libs/@local/hash-authorization/src/lib.rs index f59fa17e8de..e53c9681b17 100644 --- a/libs/@local/hash-authorization/src/lib.rs +++ b/libs/@local/hash-authorization/src/lib.rs @@ -22,9 +22,10 @@ use error_stack::Result; use graph_types::{ account::{AccountGroupId, AccountId}, knowledge::entity::{EntityId, EntityUuid}, - ontology::{DataTypeId, EntityTypeId, PropertyTypeId}, + ontology::{EntityTypeId, PropertyTypeId}, owned_by_id::OwnedById, }; +use type_system::schema::DataTypeId; use crate::{ backend::{ diff --git a/libs/@local/hash-authorization/src/schema/data_type.rs b/libs/@local/hash-authorization/src/schema/data_type.rs index ffa2c3a1251..fd559904dee 100644 --- a/libs/@local/hash-authorization/src/schema/data_type.rs +++ b/libs/@local/hash-authorization/src/schema/data_type.rs @@ -1,7 +1,8 @@ use core::error::Error; -use graph_types::{ontology::DataTypeId, owned_by_id::OwnedById}; +use graph_types::owned_by_id::OwnedById; use serde::{Deserialize, Serialize}; +use type_system::schema::DataTypeId; use uuid::Uuid; use crate::{ diff --git a/libs/@local/hash-authorization/src/zanzibar/api.rs b/libs/@local/hash-authorization/src/zanzibar/api.rs index fb24f0b3230..7c71ef25bd4 100644 --- a/libs/@local/hash-authorization/src/zanzibar/api.rs +++ b/libs/@local/hash-authorization/src/zanzibar/api.rs @@ -5,10 +5,11 @@ use futures::{Stream, TryStreamExt}; use graph_types::{ account::{AccountGroupId, AccountId}, knowledge::entity::{EntityId, EntityUuid}, - ontology::{DataTypeId, EntityTypeId, PropertyTypeId}, + ontology::{EntityTypeId, PropertyTypeId}, owned_by_id::OwnedById, }; use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use type_system::schema::DataTypeId; use crate::{ AuthorizationApi, diff --git a/libs/@local/hash-graph-types/rust/Cargo.toml b/libs/@local/hash-graph-types/rust/Cargo.toml index 48392d42497..2f4d3177669 100644 --- a/libs/@local/hash-graph-types/rust/Cargo.toml +++ b/libs/@local/hash-graph-types/rust/Cargo.toml @@ -30,7 +30,7 @@ thiserror = { workspace = true } time = { workspace = true, default-features = false, features = ["serde", "parsing", "formatting", "macros"] } url = { workspace = true, features = ["serde"] } utoipa = { workspace = true, optional = true } -uuid = { workspace = true, default-features = false, features = ["serde", "v5"] } +uuid = { workspace = true, default-features = false, features = ["serde"] } [dev-dependencies] graph-test-data = { workspace = true } diff --git a/libs/@local/hash-graph-types/rust/src/ontology/data_type.rs b/libs/@local/hash-graph-types/rust/src/ontology/data_type.rs index d9c06de9c42..c21778d0d99 100644 --- a/libs/@local/hash-graph-types/rust/src/ontology/data_type.rs +++ b/libs/@local/hash-graph-types/rust/src/ontology/data_type.rs @@ -1,7 +1,5 @@ use std::collections::HashMap; -#[cfg(feature = "postgres")] -use postgres_types::{FromSql, ToSql}; use serde::{Deserialize, Serialize}; use type_system::{ schema::{Conversions, DataType}, @@ -12,52 +10,12 @@ use utoipa::{ ToSchema, openapi::{ObjectBuilder, Ref, RefOr, Schema, schema}, }; -use uuid::Uuid; use crate::ontology::{ OntologyProvenance, OntologyTemporalMetadata, OntologyType, OntologyTypeClassificationMetadata, OntologyTypeRecordId, OntologyTypeReference, OntologyTypeWithMetadata, }; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[cfg_attr(feature = "utoipa", derive(ToSchema))] -#[cfg_attr(feature = "postgres", derive(FromSql, ToSql), postgres(transparent))] -#[repr(transparent)] -pub struct DataTypeId(Uuid); - -impl DataTypeId { - #[must_use] - pub const fn new(uuid: Uuid) -> Self { - Self(uuid) - } - - #[must_use] - pub fn from_url(url: &VersionedUrl) -> Self { - Self(Uuid::new_v5( - &Uuid::NAMESPACE_URL, - url.to_string().as_bytes(), - )) - } - - #[must_use] - pub fn from_record_id(record_id: &OntologyTypeRecordId) -> Self { - Self(Uuid::new_v5( - &Uuid::NAMESPACE_URL, - record_id.to_string().as_bytes(), - )) - } - - #[must_use] - pub const fn as_uuid(&self) -> &Uuid { - &self.0 - } - - #[must_use] - pub const fn into_uuid(self) -> Uuid { - self.0 - } -} - /// A [`DataTypeMetadata`] that has not yet been fully resolved. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PartialDataTypeMetadata { diff --git a/libs/@local/hash-graph-types/rust/src/ontology/mod.rs b/libs/@local/hash-graph-types/rust/src/ontology/mod.rs index 279f4bf63a7..d3e4760cf33 100644 --- a/libs/@local/hash-graph-types/rust/src/ontology/mod.rs +++ b/libs/@local/hash-graph-types/rust/src/ontology/mod.rs @@ -19,7 +19,7 @@ use type_system::{ }; pub use self::{ - data_type::{DataTypeId, DataTypeMetadata, DataTypeWithMetadata, PartialDataTypeMetadata}, + data_type::{DataTypeMetadata, DataTypeWithMetadata, PartialDataTypeMetadata}, entity_type::{ EntityTypeEmbedding, EntityTypeId, EntityTypeMetadata, EntityTypeWithMetadata, PartialEntityTypeMetadata, diff --git a/tests/hash-graph-integration/postgres/data_type.rs b/tests/hash-graph-integration/postgres/data_type.rs index 7e11cffd931..796faadedda 100644 --- a/tests/hash-graph-integration/postgres/data_type.rs +++ b/tests/hash-graph-integration/postgres/data_type.rs @@ -16,8 +16,7 @@ use graph_types::{ }, }, ontology::{ - DataTypeId, DataTypeWithMetadata, OntologyTypeClassificationMetadata, - ProvidedOntologyEditionProvenance, + DataTypeWithMetadata, OntologyTypeClassificationMetadata, ProvidedOntologyEditionProvenance, }, owned_by_id::OwnedById, }; @@ -32,7 +31,7 @@ use serde_json::json; use temporal_versioning::TemporalBound; use time::OffsetDateTime; use type_system::{ - schema::DataType, + schema::{DataType, DataTypeId}, url::{BaseUrl, VersionedUrl}, };