Skip to content

Commit

Permalink
H-3518: Differentiate a ClosedEntityType and ClosedMultiEntityType (
Browse files Browse the repository at this point in the history
#5479)

Co-authored-by: Ciaran Morinan <[email protected]>
  • Loading branch information
TimDiekmann and CiaranMn authored Oct 29, 2024
1 parent 56fcecf commit ef0aa53
Show file tree
Hide file tree
Showing 22 changed files with 1,736 additions and 431 deletions.
2 changes: 1 addition & 1 deletion apps/hash-graph/bins/cli/src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub struct ServerArgs {
#[clap(long, env = "HASH_TEMPORAL_SERVER_HOST")]
pub temporal_host: Option<String>,

/// The URL of the Temporal server.
/// The port of the Temporal server.
#[clap(long, env = "HASH_TEMPORAL_SERVER_PORT", default_value_t = 7233)]
pub temporal_port: u16,
}
Expand Down
4 changes: 2 additions & 2 deletions apps/hash-graph/libs/graph/src/snapshot/entity/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use graph_types::{
};
use hash_graph_store::filter::Filter;
use tokio_postgres::GenericClient;
use type_system::schema::ClosedEntityType;
use type_system::schema::{ClosedEntityType, ClosedMultiEntityType};
use validation::{EntityPreprocessor, Validate, ValidateEntityComponents};

use crate::{
Expand Down Expand Up @@ -311,7 +311,7 @@ where
)
.change_context(InsertionError)?;

let entity_type = ClosedEntityType::from_multi_type_closed_schema(
let entity_type = ClosedMultiEntityType::from_multi_type_closed_schema(
stream::iter(&entity.metadata.entity_type_ids)
.then(|entity_type_url| async {
OntologyTypeProvider::<ClosedEntityType>::provide_type(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use core::{
pin::Pin,
task::{Context, Poll, ready},
};
use std::collections::{HashMap, HashSet};

use authorization::schema::EntityTypeRelationAndSubject;
use error_stack::{Report, ResultExt};
Expand All @@ -12,7 +13,8 @@ use futures::{
};
use type_system::{
Valid,
schema::{ClosedEntityType, EntityTypeUuid},
schema::{ClosedEntityType, EntityConstraints, EntityTypeUuid, InverseEntityTypeMetadata},
url::{OntologyTypeVersion, VersionedUrl},
};

use crate::{
Expand Down Expand Up @@ -75,14 +77,32 @@ impl Sink<EntityTypeSnapshotRecord> for EntityTypeSender {
self.schema
.start_send_unpin(EntityTypeRow {
ontology_id,
// TODO: Validate ontology types in snapshots
// see https://linear.app/hash/issue/H-3038
schema: Valid::new_unchecked(entity_type.schema.clone()),
// An empty schema is inserted initially. This will be replaced later by the closed
// schema.
// TODO: Validate ontology types in snapshots
// see https://linear.app/hash/issue/H-3038
closed_schema: Valid::new_unchecked(ClosedEntityType::default()),
closed_schema: Valid::new_unchecked(ClosedEntityType {
id: VersionedUrl {
base_url: entity_type.schema.id.base_url.clone(),
version: OntologyTypeVersion::new(0),
},
title: String::new(),
title_plural: None,
description: None,
inverse: InverseEntityTypeMetadata {
title: None,
title_plural: None,
},
constraints: EntityConstraints {
properties: HashMap::new(),
required: HashSet::new(),
links: HashMap::new(),
},
all_of: Vec::new(),
}),
// TODO: Validate ontology types in snapshots
// see https://linear.app/hash/issue/H-3038
schema: Valid::new_unchecked(entity_type.schema),
})
.change_context(SnapshotRestoreError::Read)
.attach_printable("could not send schema")?;
Expand Down
4 changes: 2 additions & 2 deletions apps/hash-graph/libs/graph/src/store/knowledge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use hash_graph_store::{
};
use serde::{Deserialize, Serialize};
use temporal_versioning::{DecisionTime, Timestamp, TransactionTime};
use type_system::{schema::ClosedEntityType, url::VersionedUrl};
use type_system::{schema::ClosedMultiEntityType, url::VersionedUrl};
#[cfg(feature = "utoipa")]
use utoipa::{
ToSchema,
Expand All @@ -42,7 +42,7 @@ use crate::store::{
pub enum EntityValidationType<'a> {
Id(Cow<'a, HashSet<VersionedUrl>>),
#[serde(skip)]
ClosedSchema(Cow<'a, ClosedEntityType>),
ClosedSchema(Cow<'a, ClosedMultiEntityType>),
}

#[cfg(feature = "utoipa")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ use temporal_versioning::{
};
use tokio_postgres::{GenericClient, Row, error::SqlState};
use type_system::{
schema::{ClosedEntityType, EntityTypeUuid, InheritanceDepth, OntologyTypeUuid},
schema::{
ClosedEntityType, ClosedMultiEntityType, EntityTypeUuid, InheritanceDepth, OntologyTypeUuid,
},
url::VersionedUrl,
};
use uuid::Uuid;
Expand Down Expand Up @@ -620,6 +622,7 @@ where
let mut relationships = Vec::with_capacity(params.len());
let mut entity_type_ids = HashMap::new();
let mut checked_web_ids = HashSet::new();
let mut entity_edition_ids = Vec::with_capacity(params.len());

let mut entity_id_rows = Vec::with_capacity(params.len());
let mut entity_draft_rows = Vec::new();
Expand All @@ -641,7 +644,7 @@ where
};

for mut params in params {
let entity_type = ClosedEntityType::from_multi_type_closed_schema(
let entity_type = ClosedMultiEntityType::from_multi_type_closed_schema(
stream::iter(&params.entity_type_ids)
.then(|entity_type_url| async {
OntologyTypeProvider::<ClosedEntityType>::provide_type(
Expand Down Expand Up @@ -733,6 +736,7 @@ where
provenance: entity_provenance.edition.clone(),
property_metadata: property_metadata.clone(),
});
entity_edition_ids.push(entity_edition_id);

let temporal_versioning = EntityTemporalMetadata {
decision_time: LeftClosedTemporalInterval::new(
Expand All @@ -753,15 +757,13 @@ where
transaction_time: temporal_versioning.transaction_time,
});

for (entity_type_url, (depth, _)) in &entity_type.schemas {
let entity_type_id = EntityTypeUuid::from_url(entity_type_url);
if depth.inner() == 0 {
entity_type_ids.insert(entity_type_id, entity_type_url.clone());
}
for entity_type in &entity_type.all_of {
let entity_type_id = EntityTypeUuid::from_url(&entity_type.id);
entity_type_ids.insert(entity_type_id, entity_type.id.clone());
entity_is_of_type_rows.push(EntityIsOfTypeRow {
entity_edition_id,
entity_type_ontology_id: entity_type_id,
inheritance_depth: *depth,
inheritance_depth: InheritanceDepth::new(0),
});
}

Expand Down Expand Up @@ -925,6 +927,25 @@ where
.change_context(InsertionError)?;
}

transaction
.as_client()
.query(
"
INSERT INTO entity_is_of_type
SELECT entity_edition_id,
target_entity_type_ontology_id AS entity_type_ontology_id,
MIN(entity_type_inherits_from.depth + 1) AS inheritance_depth
FROM entity_is_of_type
JOIN entity_type_inherits_from
ON entity_type_ontology_id = source_entity_type_ontology_id
WHERE entity_edition_id = ANY($1)
GROUP BY entity_edition_id, target_entity_type_ontology_id;
",
&[&entity_edition_ids],
)
.await
.change_context(InsertionError)?;

transaction
.authorization_api
.modify_entity_relations(relationships.iter().copied().map(
Expand Down Expand Up @@ -1009,7 +1030,7 @@ where
let schema = match params.entity_types {
EntityValidationType::ClosedSchema(schema) => schema,
EntityValidationType::Id(entity_type_urls) => Cow::Owned(
ClosedEntityType::from_multi_type_closed_schema(
ClosedMultiEntityType::from_multi_type_closed_schema(
stream::iter(entity_type_urls.as_ref())
.then(|entity_type_url| async {
OntologyTypeProvider::<ClosedEntityType>::provide_type(
Expand All @@ -1027,7 +1048,7 @@ where
),
};

if schema.schemas.is_empty() {
if schema.all_of.is_empty() {
let error = Report::new(validation::EntityValidationError::EmptyEntityTypes);
status.append(error);
};
Expand Down Expand Up @@ -1487,7 +1508,7 @@ where
cache: StoreCache::default(),
authorization: Some((actor_id, Consistency::FullyConsistent)),
};
let entity_type = ClosedEntityType::from_multi_type_closed_schema(
let entity_type = ClosedMultiEntityType::from_multi_type_closed_schema(
stream::iter(&entity_type_ids)
.then(|entity_type_url| async {
OntologyTypeProvider::<ClosedEntityType>::provide_type(
Expand Down Expand Up @@ -1564,10 +1585,7 @@ where
let edition_id = transaction
.insert_entity_edition(
archived,
entity_type
.schemas
.iter()
.map(|(entity_type_id, (depth, _))| (entity_type_id, *depth)),
&entity_type_ids,
&properties,
params.confidence,
&edition_provenance,
Expand Down Expand Up @@ -1858,10 +1876,11 @@ where
INSERT INTO entity_is_of_type
SELECT entity_edition_id,
target_entity_type_ontology_id AS entity_type_ontology_id,
entity_type_inherits_from.depth + 1 AS inheritance_depth
MIN(entity_type_inherits_from.depth + 1) AS inheritance_depth
FROM entity_is_of_type
JOIN entity_type_inherits_from
ON entity_type_ontology_id = source_entity_type_ontology_id;
ON entity_type_ontology_id = source_entity_type_ontology_id
GROUP BY entity_edition_id, target_entity_type_ontology_id;
",
)
.await
Expand Down Expand Up @@ -1890,7 +1909,7 @@ where
async fn insert_entity_edition(
&self,
archived: bool,
entity_type_ids: impl IntoIterator<Item = (&VersionedUrl, InheritanceDepth)> + Send,
entity_type_ids: impl IntoIterator<Item = &VersionedUrl> + Send,
properties: &PropertyObject,
confidence: Option<Confidence>,
provenance: &EntityEditionProvenance,
Expand All @@ -1916,15 +1935,10 @@ where
.change_context(InsertionError)?
.get(0);

let (entity_type_ontology_ids, inheritance_depths): (Vec<_>, Vec<_>) = entity_type_ids
let entity_type_ontology_ids = entity_type_ids
.into_iter()
.map(|(entity_type_id, depth)| {
(
OntologyTypeUuid::from(EntityTypeUuid::from_url(entity_type_id)),
depth,
)
})
.unzip();
.map(|entity_type_id| OntologyTypeUuid::from(EntityTypeUuid::from_url(entity_type_id)))
.collect::<Vec<_>>();

self.as_client()
.query(
Expand All @@ -1933,9 +1947,26 @@ where
entity_edition_id,
entity_type_ontology_id,
inheritance_depth
) SELECT $1, UNNEST($2::UUID[]), UNNEST($3::Integer[]);
) SELECT $1, UNNEST($2::UUID[]), 0;
",
&[&edition_id, &entity_type_ontology_ids],
)
.await
.change_context(InsertionError)?;
self.as_client()
.query(
"
INSERT INTO entity_is_of_type
SELECT entity_edition_id,
target_entity_type_ontology_id AS entity_type_ontology_id,
MIN(entity_type_inherits_from.depth + 1) AS inheritance_depth
FROM entity_is_of_type
JOIN entity_type_inherits_from
ON entity_type_ontology_id = source_entity_type_ontology_id
WHERE entity_edition_id = $1
GROUP BY entity_edition_id, target_entity_type_ontology_id;
",
&[&edition_id, &entity_type_ontology_ids, &inheritance_depths],
&[&edition_id],
)
.await
.change_context(InsertionError)?;
Expand Down
46 changes: 39 additions & 7 deletions apps/hash-graph/libs/graph/src/store/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,31 +491,63 @@ where
A: AuthorizationApi,
{
#[expect(refining_impl_trait)]
async fn is_parent_of(
async fn is_super_type_of(
&self,
parent: &VersionedUrl,
child: &VersionedUrl,
parent: &BaseUrl,
) -> Result<bool, Report<QueryError>> {
let client = self.store.as_client().client();
let child_id = EntityTypeUuid::from_url(child);
let parent_id = EntityTypeUuid::from_url(parent);

Ok(client
.query_one(
"
SELECT EXISTS (
SELECT 1 FROM closed_entity_type_inherits_from
JOIN ontology_ids
ON ontology_ids.ontology_id = target_entity_type_ontology_id
SELECT 1 FROM entity_type_inherits_from
WHERE source_entity_type_ontology_id = $1
AND ontology_ids.base_url = $2
AND target_entity_type_ontology_id = $2
);
",
&[child_id.as_uuid(), &parent.as_str()],
&[&child_id, &parent_id],
)
.await
.change_context(QueryError)?
.get(0))
}

#[expect(refining_impl_trait)]
async fn find_parents(
&self,
entity_types: &[VersionedUrl],
) -> Result<Vec<VersionedUrl>, Report<QueryError>> {
let entity_type_ids = entity_types
.iter()
.map(EntityTypeUuid::from_url)
.collect::<Vec<_>>();

Ok(self
.store
.as_client()
.query(
"
SELECT base_url, version
FROM entity_type_inherits_from
JOIN ontology_ids ON target_entity_type_ontology_id = ontology_id
WHERE source_entity_type_ontology_id = ANY($1)
ORDER BY depth ASC;
",
&[&entity_type_ids],
)
.await
.change_context(QueryError)?
.into_iter()
.map(|row| VersionedUrl {
base_url: row.get(0),
version: row.get(1),
})
.collect())
}
}

impl<C, A> EntityProvider for StoreProvider<'_, PostgresStore<C, A>>
Expand Down
4 changes: 2 additions & 2 deletions apps/hash-graph/libs/store/src/entity_type/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub enum EntityTypeQueryPath<'p> {
///
/// [`EntityType::examples()`]: type_system::schema::EntityType::examples
Examples,
/// Corresponds to [`EntityType::required()`].
/// Corresponds to [`EntityConstraints::required`].
///
/// ```rust
/// # use serde::Deserialize;
Expand All @@ -158,7 +158,7 @@ pub enum EntityTypeQueryPath<'p> {
/// # Ok::<(), serde_json::Error>(())
/// ```
///
/// [`EntityType::required()`]: type_system::schema::EntityType::required
/// [`EntityConstraints::required`]: type_system::schema::EntityConstraints::required
Required,
/// The label property metadata of the entity type.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::schema::{
#[derive(Debug, Error)]
pub enum ObjectValidationError {}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
#[serde(rename_all = "camelCase")]
pub enum ObjectTypeTag {
Expand Down
Loading

0 comments on commit ef0aa53

Please sign in to comment.