feat: Store and handle NULL partition_template database values
Treat them as the default partition template in the application, but save space and avoid having to backfill the tables by having the database values be NULL when no custom template has been specified.pull/24376/head
parent
c8712bbc90
commit
73b09d895f
|
@ -295,7 +295,6 @@ pub struct Namespace {
|
||||||
pub id: NamespaceId,
|
pub id: NamespaceId,
|
||||||
/// The unique name of the namespace
|
/// The unique name of the namespace
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[sqlx(default)]
|
|
||||||
/// The retention period in ns. None represents infinite duration (i.e. never drop data).
|
/// The retention period in ns. None represents infinite duration (i.e. never drop data).
|
||||||
pub retention_period_ns: Option<i64>,
|
pub retention_period_ns: Option<i64>,
|
||||||
/// The maximum number of tables that can exist in this namespace
|
/// The maximum number of tables that can exist in this namespace
|
||||||
|
@ -304,7 +303,6 @@ pub struct Namespace {
|
||||||
pub max_columns_per_table: i32,
|
pub max_columns_per_table: i32,
|
||||||
/// When this file was marked for deletion.
|
/// When this file was marked for deletion.
|
||||||
pub deleted_at: Option<Timestamp>,
|
pub deleted_at: Option<Timestamp>,
|
||||||
#[sqlx(default)]
|
|
||||||
/// The partition template to use for new tables in this namespace either created implicitly or
|
/// The partition template to use for new tables in this namespace either created implicitly or
|
||||||
/// created without specifying a partition template.
|
/// created without specifying a partition template.
|
||||||
pub partition_template: NamespacePartitionTemplateOverride,
|
pub partition_template: NamespacePartitionTemplateOverride,
|
||||||
|
@ -375,7 +373,6 @@ pub struct Table {
|
||||||
pub namespace_id: NamespaceId,
|
pub namespace_id: NamespaceId,
|
||||||
/// The name of the table, which is unique within the associated namespace
|
/// The name of the table, which is unique within the associated namespace
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[sqlx(default)]
|
|
||||||
/// The partition template to use for writes in this table.
|
/// The partition template to use for writes in this table.
|
||||||
pub partition_template: TablePartitionTemplateOverride,
|
pub partition_template: TablePartitionTemplateOverride,
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,19 +23,13 @@ pub static PARTITION_BY_DAY_PROTO: Lazy<Arc<proto::PartitionTemplate>> = Lazy::n
|
||||||
});
|
});
|
||||||
|
|
||||||
/// A partition template specified by a namespace record.
|
/// A partition template specified by a namespace record.
|
||||||
#[derive(Debug, PartialEq, Clone, sqlx::Type)]
|
#[derive(Debug, PartialEq, Clone, Default, sqlx::Type)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct NamespacePartitionTemplateOverride(SerializationWrapper);
|
pub struct NamespacePartitionTemplateOverride(Option<SerializationWrapper>);
|
||||||
|
|
||||||
impl Default for NamespacePartitionTemplateOverride {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(SerializationWrapper(Arc::clone(&PARTITION_BY_DAY_PROTO)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<proto::PartitionTemplate> for NamespacePartitionTemplateOverride {
|
impl From<proto::PartitionTemplate> for NamespacePartitionTemplateOverride {
|
||||||
fn from(partition_template: proto::PartitionTemplate) -> Self {
|
fn from(partition_template: proto::PartitionTemplate) -> Self {
|
||||||
Self(SerializationWrapper(Arc::new(partition_template)))
|
Self(Some(SerializationWrapper(Arc::new(partition_template))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,20 +37,19 @@ impl From<proto::PartitionTemplate> for NamespacePartitionTemplateOverride {
|
||||||
/// partition template, so the table will get the namespace's partition template.
|
/// partition template, so the table will get the namespace's partition template.
|
||||||
impl From<&NamespacePartitionTemplateOverride> for TablePartitionTemplateOverride {
|
impl From<&NamespacePartitionTemplateOverride> for TablePartitionTemplateOverride {
|
||||||
fn from(namespace_template: &NamespacePartitionTemplateOverride) -> Self {
|
fn from(namespace_template: &NamespacePartitionTemplateOverride) -> Self {
|
||||||
Self(SerializationWrapper(Arc::clone(&namespace_template.0 .0)))
|
Self(
|
||||||
|
namespace_template
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.map(|sw| SerializationWrapper(Arc::clone(&sw.0))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A partition template specified by a table record.
|
/// A partition template specified by a table record.
|
||||||
#[derive(Debug, PartialEq, Clone, sqlx::Type)]
|
#[derive(Debug, PartialEq, Clone, Default, sqlx::Type)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct TablePartitionTemplateOverride(SerializationWrapper);
|
pub struct TablePartitionTemplateOverride(Option<SerializationWrapper>);
|
||||||
|
|
||||||
impl Default for TablePartitionTemplateOverride {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(SerializationWrapper(Arc::clone(&PARTITION_BY_DAY_PROTO)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TablePartitionTemplateOverride {
|
impl TablePartitionTemplateOverride {
|
||||||
/// When a table is being explicitly created, the creation request might have contained a
|
/// When a table is being explicitly created, the creation request might have contained a
|
||||||
|
@ -70,15 +63,19 @@ impl TablePartitionTemplateOverride {
|
||||||
custom_table_template
|
custom_table_template
|
||||||
.map(Arc::new)
|
.map(Arc::new)
|
||||||
.map(SerializationWrapper)
|
.map(SerializationWrapper)
|
||||||
|
.map(Some)
|
||||||
.map(Self)
|
.map(Self)
|
||||||
.unwrap_or_else(|| namespace_template.into())
|
.unwrap_or_else(|| namespace_template.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate through the protobuf parts and lend out what the `mutable_batch` crate needs to
|
/// Iterate through the protobuf parts and lend out what the `mutable_batch` crate needs to
|
||||||
/// build `PartitionKey`s.
|
/// build `PartitionKey`s. If this table doesn't have a custom template, use the application
|
||||||
|
/// default of partitioning by day.
|
||||||
pub fn parts(&self) -> impl Iterator<Item = TemplatePart<'_>> {
|
pub fn parts(&self) -> impl Iterator<Item = TemplatePart<'_>> {
|
||||||
self.0
|
self.0
|
||||||
.0
|
.as_ref()
|
||||||
|
.map(|serialization_wrapper| &serialization_wrapper.0)
|
||||||
|
.unwrap_or_else(|| &PARTITION_BY_DAY_PROTO)
|
||||||
.parts
|
.parts
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|part| part.part.as_ref())
|
.flat_map(|part| part.part.as_ref())
|
||||||
|
@ -157,7 +154,7 @@ pub fn test_table_partition_override(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let proto = Arc::new(proto::PartitionTemplate { parts });
|
let proto = Arc::new(proto::PartitionTemplate { parts });
|
||||||
TablePartitionTemplateOverride(SerializationWrapper(proto))
|
TablePartitionTemplateOverride(Some(SerializationWrapper(proto)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -214,7 +211,7 @@ mod tests {
|
||||||
&namespace_template,
|
&namespace_template,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(table_template.0 .0.as_ref(), &custom_table_template);
|
assert_eq!(table_template.0.unwrap().0.as_ref(), &custom_table_template);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The JSON representation of the partition template protobuf is stored in the database, so
|
// The JSON representation of the partition template protobuf is stored in the database, so
|
||||||
|
|
|
@ -501,8 +501,6 @@ impl NamespaceRepo for PostgresTxn {
|
||||||
partition_template: Option<NamespacePartitionTemplateOverride>,
|
partition_template: Option<NamespacePartitionTemplateOverride>,
|
||||||
retention_period_ns: Option<i64>,
|
retention_period_ns: Option<i64>,
|
||||||
) -> Result<Namespace> {
|
) -> Result<Namespace> {
|
||||||
let partition_template = partition_template.unwrap_or_default();
|
|
||||||
|
|
||||||
let rec = sqlx::query_as::<_, Namespace>(
|
let rec = sqlx::query_as::<_, Namespace>(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO namespace (
|
INSERT INTO namespace (
|
||||||
|
@ -1620,7 +1618,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_helpers::{arbitrary_namespace, arbitrary_table};
|
use crate::test_helpers::{arbitrary_namespace, arbitrary_table};
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use data_types::{ColumnId, ColumnSet};
|
use data_types::{ColumnId, ColumnSet, TemplatePart};
|
||||||
|
use generated_types::influxdata::iox::partition_template::v1 as proto;
|
||||||
use metric::{Attributes, DurationHistogram, Metric};
|
use metric::{Attributes, DurationHistogram, Metric};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use sqlx::migrate::MigrateDatabase;
|
use sqlx::migrate::MigrateDatabase;
|
||||||
|
@ -2156,4 +2155,418 @@ mod tests {
|
||||||
.expect("fetch total file size failed");
|
.expect("fetch total file size failed");
|
||||||
assert_eq!(total_file_size_bytes, 1337 * 2);
|
assert_eq!(total_file_size_bytes, 1337 * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn namespace_partition_template_null_is_the_default_in_the_database() {
|
||||||
|
maybe_skip_integration!();
|
||||||
|
|
||||||
|
let postgres = setup_db().await;
|
||||||
|
let pool = postgres.pool.clone();
|
||||||
|
let postgres: Arc<dyn Catalog> = Arc::new(postgres);
|
||||||
|
let mut repos = postgres.repositories().await;
|
||||||
|
|
||||||
|
let namespace_name = "apples";
|
||||||
|
|
||||||
|
// Create a namespace record in the database that has `NULL` for its `partition_template`
|
||||||
|
// value, which is what records existing before the migration adding that column will have.
|
||||||
|
let insert_null_partition_template_namespace = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO namespace (
|
||||||
|
name, topic_id, query_pool_id, retention_period_ns, max_tables, partition_template
|
||||||
|
)
|
||||||
|
VALUES ( $1, $2, $3, $4, $5, NULL )
|
||||||
|
RETURNING id, name, retention_period_ns, max_tables, max_columns_per_table, deleted_at,
|
||||||
|
partition_template;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(namespace_name) // $1
|
||||||
|
.bind(SHARED_TOPIC_ID) // $2
|
||||||
|
.bind(SHARED_QUERY_POOL_ID) // $3
|
||||||
|
.bind(None::<Option<i64>>) // $4
|
||||||
|
.bind(DEFAULT_MAX_TABLES); // $5
|
||||||
|
|
||||||
|
insert_null_partition_template_namespace
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lookup_namespace = repos
|
||||||
|
.namespaces()
|
||||||
|
.get_by_name(namespace_name, SoftDeletedRows::ExcludeDeleted)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
// When fetching this namespace from the database, the `FromRow` impl should set its
|
||||||
|
// `partition_template` to the default.
|
||||||
|
assert_eq!(
|
||||||
|
lookup_namespace.partition_template,
|
||||||
|
NamespacePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// When creating a namespace through the catalog functions without specifying a custom
|
||||||
|
// partition template,
|
||||||
|
let created_without_custom_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&"lemons".try_into().unwrap(),
|
||||||
|
None, // no partition template
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the default template in the application,
|
||||||
|
assert_eq!(
|
||||||
|
created_without_custom_template.partition_template,
|
||||||
|
NamespacePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store NULL in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM namespace WHERE id = $1;")
|
||||||
|
.bind(created_without_custom_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(created_without_custom_template.name, name);
|
||||||
|
let partition_template: Option<NamespacePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert!(partition_template.is_none());
|
||||||
|
|
||||||
|
// When explicitly setting a template that happens to be equal to the application default,
|
||||||
|
// assume it's important that it's being specially requested and store it rather than NULL.
|
||||||
|
let namespace_custom_template_name = "kumquats";
|
||||||
|
let custom_partition_template_equal_to_default =
|
||||||
|
NamespacePartitionTemplateOverride::from(proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TimeFormat(
|
||||||
|
"%Y-%m-%d".to_owned(),
|
||||||
|
)),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
let namespace_custom_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&namespace_custom_template_name.try_into().unwrap(),
|
||||||
|
Some(custom_partition_template_equal_to_default.clone()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
namespace_custom_template.partition_template,
|
||||||
|
custom_partition_template_equal_to_default
|
||||||
|
);
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM namespace WHERE id = $1;")
|
||||||
|
.bind(namespace_custom_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(namespace_custom_template.name, name);
|
||||||
|
let partition_template: Option<NamespacePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
partition_template.unwrap(),
|
||||||
|
custom_partition_template_equal_to_default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn table_partition_template_null_is_the_default_in_the_database() {
|
||||||
|
maybe_skip_integration!();
|
||||||
|
|
||||||
|
let postgres = setup_db().await;
|
||||||
|
let pool = postgres.pool.clone();
|
||||||
|
let postgres: Arc<dyn Catalog> = Arc::new(postgres);
|
||||||
|
let mut repos = postgres.repositories().await;
|
||||||
|
|
||||||
|
let namespace_default_template_name = "oranges";
|
||||||
|
let namespace_default_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&namespace_default_template_name.try_into().unwrap(),
|
||||||
|
None, // no partition template
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let namespace_custom_template_name = "limes";
|
||||||
|
let namespace_custom_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&namespace_custom_template_name.try_into().unwrap(),
|
||||||
|
Some(NamespacePartitionTemplateOverride::from(
|
||||||
|
proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TimeFormat("year-%Y".into())),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// In a namespace that also has a NULL template, create a table record in the database that
|
||||||
|
// has `NULL` for its `partition_template` value, which is what records existing before the
|
||||||
|
// migration adding that column will have.
|
||||||
|
let table_name = "null_template";
|
||||||
|
let insert_null_partition_template_table = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO table_name ( name, namespace_id, partition_template )
|
||||||
|
VALUES ( $1, $2, NULL )
|
||||||
|
RETURNING *;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(table_name) // $1
|
||||||
|
.bind(namespace_default_template.id); // $2
|
||||||
|
|
||||||
|
insert_null_partition_template_table
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lookup_table = repos
|
||||||
|
.tables()
|
||||||
|
.get_by_namespace_and_name(namespace_default_template.id, table_name)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
// When fetching this table from the database, the `FromRow` impl should set its
|
||||||
|
// `partition_template` to the system default (because the namespace didn't have a template
|
||||||
|
// either).
|
||||||
|
assert_eq!(
|
||||||
|
lookup_table.partition_template,
|
||||||
|
TablePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// In a namespace that has a custom template, create a table record in the database that
|
||||||
|
// has `NULL` for its `partition_template` value.
|
||||||
|
//
|
||||||
|
// THIS ACTUALLY SHOULD BE IMPOSSIBLE because:
|
||||||
|
//
|
||||||
|
// * Namespaces have to exist before tables
|
||||||
|
// * `partition_tables` are immutable on both namespaces and tables
|
||||||
|
// * When the migration adding the `partition_table` column is deployed, namespaces can
|
||||||
|
// begin to be created with `partition_templates`
|
||||||
|
// * *Then* tables can be created with `partition_templates` or not
|
||||||
|
// * When tables don't get a custom table partition template but their namespace has one,
|
||||||
|
// their database record will get the namespace partition template.
|
||||||
|
//
|
||||||
|
// In other words, table `partition_template` values in the database is allowed to possibly
|
||||||
|
// be `NULL` IFF their namespace's `partition_template` is `NULL`.
|
||||||
|
//
|
||||||
|
// That said, this test creates this hopefully-impossible scenario to ensure that the
|
||||||
|
// defined, expected behavior if a table record somehow exists in the database with a `NULL`
|
||||||
|
// `partition_template` value is that it will have the application default partition
|
||||||
|
// template *even if the namespace `partition_template` is not null*.
|
||||||
|
let table_name = "null_template";
|
||||||
|
let insert_null_partition_template_table = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO table_name ( name, namespace_id, partition_template )
|
||||||
|
VALUES ( $1, $2, NULL )
|
||||||
|
RETURNING *;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(table_name) // $1
|
||||||
|
.bind(namespace_custom_template.id); // $2
|
||||||
|
|
||||||
|
insert_null_partition_template_table
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lookup_table = repos
|
||||||
|
.tables()
|
||||||
|
.get_by_namespace_and_name(namespace_custom_template.id, table_name)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
// When fetching this table from the database, the `FromRow` impl should set its
|
||||||
|
// `partition_template` to the system default *even though the namespace has a
|
||||||
|
// template*, because this should be impossible as detailed above.
|
||||||
|
assert_eq!(
|
||||||
|
lookup_table.partition_template,
|
||||||
|
TablePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template false, namespace template true
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *without* a custom table template in
|
||||||
|
// a namespace *with* a custom partition template,
|
||||||
|
let table_no_template_with_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"pomelo",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
None, // no custom partition template
|
||||||
|
&namespace_custom_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_custom_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the namespace's template
|
||||||
|
assert_eq!(
|
||||||
|
table_no_template_with_namespace_template.partition_template,
|
||||||
|
TablePartitionTemplateOverride::from(&namespace_custom_template.partition_template)
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store that value in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_no_template_with_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_no_template_with_namespace_template.name, name);
|
||||||
|
let partition_template: Option<TablePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
partition_template.unwrap(),
|
||||||
|
TablePartitionTemplateOverride::from(&namespace_custom_template.partition_template)
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template true, namespace template false
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *with* a custom table template in
|
||||||
|
// a namespace *without* a custom partition template,
|
||||||
|
let custom_table_template = proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TagValue("chemical".into())),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let table_with_template_no_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"tangerine",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
Some(custom_table_template), // with custom partition template
|
||||||
|
&namespace_default_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_default_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the custom table template
|
||||||
|
let table_template_parts: Vec<_> = table_with_template_no_namespace_template
|
||||||
|
.partition_template
|
||||||
|
.parts()
|
||||||
|
.collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "chemical"
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store that value in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_with_template_no_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_with_template_no_namespace_template.name, name);
|
||||||
|
let partition_template = record
|
||||||
|
.try_get::<Option<TablePartitionTemplateOverride>, _>("partition_template")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let table_template_parts: Vec<_> = partition_template.parts().collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "chemical"
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template true, namespace template true
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *with* a custom table template in
|
||||||
|
// a namespace *with* a custom partition template,
|
||||||
|
let custom_table_template = proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TagValue("vegetable".into())),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let table_with_template_with_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"nectarine",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
Some(custom_table_template), // with custom partition template
|
||||||
|
&namespace_custom_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_custom_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the custom table template
|
||||||
|
let table_template_parts: Vec<_> = table_with_template_with_namespace_template
|
||||||
|
.partition_template
|
||||||
|
.parts()
|
||||||
|
.collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "vegetable"
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store that value in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_with_template_with_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_with_template_with_namespace_template.name, name);
|
||||||
|
let partition_template = record
|
||||||
|
.try_get::<Option<TablePartitionTemplateOverride>, _>("partition_template")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let table_template_parts: Vec<_> = partition_template.parts().collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "vegetable"
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template false, namespace template false
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *without* a custom table template in
|
||||||
|
// a namespace *without* a custom partition template,
|
||||||
|
let table_no_template_no_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"grapefruit",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
None, // no custom partition template
|
||||||
|
&namespace_default_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_default_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the default template in the application,
|
||||||
|
assert_eq!(
|
||||||
|
table_no_template_no_namespace_template.partition_template,
|
||||||
|
TablePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store NULL in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_no_template_no_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_no_template_no_namespace_template.name, name);
|
||||||
|
let partition_template: Option<TablePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert!(partition_template.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,8 +264,6 @@ impl NamespaceRepo for SqliteTxn {
|
||||||
partition_template: Option<NamespacePartitionTemplateOverride>,
|
partition_template: Option<NamespacePartitionTemplateOverride>,
|
||||||
retention_period_ns: Option<i64>,
|
retention_period_ns: Option<i64>,
|
||||||
) -> Result<Namespace> {
|
) -> Result<Namespace> {
|
||||||
let partition_template = partition_template.unwrap_or_default();
|
|
||||||
|
|
||||||
let rec = sqlx::query_as::<_, Namespace>(
|
let rec = sqlx::query_as::<_, Namespace>(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO namespace ( name, topic_id, query_pool_id, retention_period_ns, max_tables, partition_template )
|
INSERT INTO namespace ( name, topic_id, query_pool_id, retention_period_ns, max_tables, partition_template )
|
||||||
|
@ -1492,6 +1490,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_helpers::{arbitrary_namespace, arbitrary_table};
|
use crate::test_helpers::{arbitrary_namespace, arbitrary_table};
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
use data_types::TemplatePart;
|
||||||
|
use generated_types::influxdata::iox::partition_template::v1 as proto;
|
||||||
use metric::{Attributes, DurationHistogram, Metric};
|
use metric::{Attributes, DurationHistogram, Metric};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -1800,4 +1800,414 @@ mod tests {
|
||||||
.expect("fetch total file size failed");
|
.expect("fetch total file size failed");
|
||||||
assert_eq!(total_file_size_bytes, 1337 * 2);
|
assert_eq!(total_file_size_bytes, 1337 * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn namespace_partition_template_null_is_the_default_in_the_database() {
|
||||||
|
let sqlite = setup_db().await;
|
||||||
|
let pool = sqlite.pool.clone();
|
||||||
|
let sqlite: Arc<dyn Catalog> = Arc::new(sqlite);
|
||||||
|
let mut repos = sqlite.repositories().await;
|
||||||
|
|
||||||
|
let namespace_name = "apples";
|
||||||
|
|
||||||
|
// Create a namespace record in the database that has `NULL` for its `partition_template`
|
||||||
|
// value, which is what records existing before the migration adding that column will have.
|
||||||
|
let insert_null_partition_template_namespace = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO namespace (
|
||||||
|
name, topic_id, query_pool_id, retention_period_ns, max_tables, partition_template
|
||||||
|
)
|
||||||
|
VALUES ( $1, $2, $3, $4, $5, NULL )
|
||||||
|
RETURNING id, name, retention_period_ns, max_tables, max_columns_per_table, deleted_at,
|
||||||
|
partition_template;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(namespace_name) // $1
|
||||||
|
.bind(SHARED_TOPIC_ID) // $2
|
||||||
|
.bind(SHARED_QUERY_POOL_ID) // $3
|
||||||
|
.bind(None::<Option<i64>>) // $4
|
||||||
|
.bind(DEFAULT_MAX_TABLES); // $5
|
||||||
|
|
||||||
|
insert_null_partition_template_namespace
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lookup_namespace = repos
|
||||||
|
.namespaces()
|
||||||
|
.get_by_name(namespace_name, SoftDeletedRows::ExcludeDeleted)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
// When fetching this namespace from the database, the `FromRow` impl should set its
|
||||||
|
// `partition_template` to the default.
|
||||||
|
assert_eq!(
|
||||||
|
lookup_namespace.partition_template,
|
||||||
|
NamespacePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// When creating a namespace through the catalog functions without specifying a custom
|
||||||
|
// partition template,
|
||||||
|
let created_without_custom_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&"lemons".try_into().unwrap(),
|
||||||
|
None, // no partition template
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the default template in the application,
|
||||||
|
assert_eq!(
|
||||||
|
created_without_custom_template.partition_template,
|
||||||
|
NamespacePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store NULL in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM namespace WHERE id = $1;")
|
||||||
|
.bind(created_without_custom_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(created_without_custom_template.name, name);
|
||||||
|
let partition_template: Option<NamespacePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert!(partition_template.is_none());
|
||||||
|
|
||||||
|
// When explicitly setting a template that happens to be equal to the application default,
|
||||||
|
// assume it's important that it's being specially requested and store it rather than NULL.
|
||||||
|
let namespace_custom_template_name = "kumquats";
|
||||||
|
let custom_partition_template_equal_to_default =
|
||||||
|
NamespacePartitionTemplateOverride::from(proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TimeFormat(
|
||||||
|
"%Y-%m-%d".to_owned(),
|
||||||
|
)),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
let namespace_custom_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&namespace_custom_template_name.try_into().unwrap(),
|
||||||
|
Some(custom_partition_template_equal_to_default.clone()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
namespace_custom_template.partition_template,
|
||||||
|
custom_partition_template_equal_to_default
|
||||||
|
);
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM namespace WHERE id = $1;")
|
||||||
|
.bind(namespace_custom_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(namespace_custom_template.name, name);
|
||||||
|
let partition_template: Option<NamespacePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
partition_template.unwrap(),
|
||||||
|
custom_partition_template_equal_to_default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn table_partition_template_null_is_the_default_in_the_database() {
|
||||||
|
let sqlite = setup_db().await;
|
||||||
|
let pool = sqlite.pool.clone();
|
||||||
|
let sqlite: Arc<dyn Catalog> = Arc::new(sqlite);
|
||||||
|
let mut repos = sqlite.repositories().await;
|
||||||
|
|
||||||
|
let namespace_default_template_name = "oranges";
|
||||||
|
let namespace_default_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&namespace_default_template_name.try_into().unwrap(),
|
||||||
|
None, // no partition template
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let namespace_custom_template_name = "limes";
|
||||||
|
let namespace_custom_template = repos
|
||||||
|
.namespaces()
|
||||||
|
.create(
|
||||||
|
&namespace_custom_template_name.try_into().unwrap(),
|
||||||
|
Some(NamespacePartitionTemplateOverride::from(
|
||||||
|
proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TimeFormat("year-%Y".into())),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// In a namespace that also has a NULL template, create a table record in the database that
|
||||||
|
// has `NULL` for its `partition_template` value, which is what records existing before the
|
||||||
|
// migration adding that column will have.
|
||||||
|
let table_name = "null_template";
|
||||||
|
let insert_null_partition_template_table = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO table_name ( name, namespace_id, partition_template )
|
||||||
|
VALUES ( $1, $2, NULL )
|
||||||
|
RETURNING *;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(table_name) // $1
|
||||||
|
.bind(namespace_default_template.id); // $2
|
||||||
|
|
||||||
|
insert_null_partition_template_table
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lookup_table = repos
|
||||||
|
.tables()
|
||||||
|
.get_by_namespace_and_name(namespace_default_template.id, table_name)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
// When fetching this table from the database, the `FromRow` impl should set its
|
||||||
|
// `partition_template` to the system default (because the namespace didn't have a template
|
||||||
|
// either).
|
||||||
|
assert_eq!(
|
||||||
|
lookup_table.partition_template,
|
||||||
|
TablePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// In a namespace that has a custom template, create a table record in the database that
|
||||||
|
// has `NULL` for its `partition_template` value.
|
||||||
|
//
|
||||||
|
// THIS ACTUALLY SHOULD BE IMPOSSIBLE because:
|
||||||
|
//
|
||||||
|
// * Namespaces have to exist before tables
|
||||||
|
// * `partition_tables` are immutable on both namespaces and tables
|
||||||
|
// * When the migration adding the `partition_table` column is deployed, namespaces can
|
||||||
|
// begin to be created with `partition_templates`
|
||||||
|
// * *Then* tables can be created with `partition_templates` or not
|
||||||
|
// * When tables don't get a custom table partition template but their namespace has one,
|
||||||
|
// their database record will get the namespace partition template.
|
||||||
|
//
|
||||||
|
// In other words, table `partition_template` values in the database is allowed to possibly
|
||||||
|
// be `NULL` IFF their namespace's `partition_template` is `NULL`.
|
||||||
|
//
|
||||||
|
// That said, this test creates this hopefully-impossible scenario to ensure that the
|
||||||
|
// defined, expected behavior if a table record somehow exists in the database with a `NULL`
|
||||||
|
// `partition_template` value is that it will have the application default partition
|
||||||
|
// template *even if the namespace `partition_template` is not null*.
|
||||||
|
let table_name = "null_template";
|
||||||
|
let insert_null_partition_template_table = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO table_name ( name, namespace_id, partition_template )
|
||||||
|
VALUES ( $1, $2, NULL )
|
||||||
|
RETURNING *;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(table_name) // $1
|
||||||
|
.bind(namespace_custom_template.id); // $2
|
||||||
|
|
||||||
|
insert_null_partition_template_table
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lookup_table = repos
|
||||||
|
.tables()
|
||||||
|
.get_by_namespace_and_name(namespace_custom_template.id, table_name)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
// When fetching this table from the database, the `FromRow` impl should set its
|
||||||
|
// `partition_template` to the system default *even though the namespace has a
|
||||||
|
// template*, because this should be impossible as detailed above.
|
||||||
|
assert_eq!(
|
||||||
|
lookup_table.partition_template,
|
||||||
|
TablePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template false, namespace template true
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *without* a custom table template in
|
||||||
|
// a namespace *with* a custom partition template,
|
||||||
|
let table_no_template_with_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"pomelo",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
None, // no custom partition template
|
||||||
|
&namespace_custom_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_custom_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the namespace's template
|
||||||
|
assert_eq!(
|
||||||
|
table_no_template_with_namespace_template.partition_template,
|
||||||
|
TablePartitionTemplateOverride::from(&namespace_custom_template.partition_template)
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store that value in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_no_template_with_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_no_template_with_namespace_template.name, name);
|
||||||
|
let partition_template: Option<TablePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
partition_template.unwrap(),
|
||||||
|
TablePartitionTemplateOverride::from(&namespace_custom_template.partition_template)
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template true, namespace template false
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *with* a custom table template in
|
||||||
|
// a namespace *without* a custom partition template,
|
||||||
|
let custom_table_template = proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TagValue("chemical".into())),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let table_with_template_no_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"tangerine",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
Some(custom_table_template), // with custom partition template
|
||||||
|
&namespace_default_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_default_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the custom table template
|
||||||
|
let table_template_parts: Vec<_> = table_with_template_no_namespace_template
|
||||||
|
.partition_template
|
||||||
|
.parts()
|
||||||
|
.collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "chemical"
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store that value in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_with_template_no_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_with_template_no_namespace_template.name, name);
|
||||||
|
let partition_template = record
|
||||||
|
.try_get::<Option<TablePartitionTemplateOverride>, _>("partition_template")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let table_template_parts: Vec<_> = partition_template.parts().collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "chemical"
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template true, namespace template true
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *with* a custom table template in
|
||||||
|
// a namespace *with* a custom partition template,
|
||||||
|
let custom_table_template = proto::PartitionTemplate {
|
||||||
|
parts: vec![proto::TemplatePart {
|
||||||
|
part: Some(proto::template_part::Part::TagValue("vegetable".into())),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let table_with_template_with_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"nectarine",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
Some(custom_table_template), // with custom partition template
|
||||||
|
&namespace_custom_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_custom_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the custom table template
|
||||||
|
let table_template_parts: Vec<_> = table_with_template_with_namespace_template
|
||||||
|
.partition_template
|
||||||
|
.parts()
|
||||||
|
.collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "vegetable"
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store that value in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_with_template_with_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_with_template_with_namespace_template.name, name);
|
||||||
|
let partition_template = record
|
||||||
|
.try_get::<Option<TablePartitionTemplateOverride>, _>("partition_template")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let table_template_parts: Vec<_> = partition_template.parts().collect();
|
||||||
|
assert_eq!(table_template_parts.len(), 1);
|
||||||
|
assert_matches!(
|
||||||
|
table_template_parts[0],
|
||||||
|
TemplatePart::TagValue(tag) if tag == "vegetable"
|
||||||
|
);
|
||||||
|
|
||||||
|
// # Table template false, namespace template false
|
||||||
|
//
|
||||||
|
// When creating a table through the catalog functions *without* a custom table template in
|
||||||
|
// a namespace *without* a custom partition template,
|
||||||
|
let table_no_template_no_namespace_template = repos
|
||||||
|
.tables()
|
||||||
|
.create(
|
||||||
|
"grapefruit",
|
||||||
|
TablePartitionTemplateOverride::new(
|
||||||
|
None, // no custom partition template
|
||||||
|
&namespace_default_template.partition_template,
|
||||||
|
),
|
||||||
|
namespace_default_template.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// it should have the default template in the application,
|
||||||
|
assert_eq!(
|
||||||
|
table_no_template_no_namespace_template.partition_template,
|
||||||
|
TablePartitionTemplateOverride::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
// and store NULL in the database record.
|
||||||
|
let record = sqlx::query("SELECT name, partition_template FROM table_name WHERE id = $1;")
|
||||||
|
.bind(table_no_template_no_namespace_template.id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name: String = record.try_get("name").unwrap();
|
||||||
|
assert_eq!(table_no_template_no_namespace_template.name, name);
|
||||||
|
let partition_template: Option<TablePartitionTemplateOverride> =
|
||||||
|
record.try_get("partition_template").unwrap();
|
||||||
|
assert!(partition_template.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue