From 8c8dbb4bcc19704c633bfc50ddbb67bdd49f9952 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 17 Apr 2024 18:24:06 -0700 Subject: [PATCH 1/7] graph, store: Avoid using to_jsonb when looking up a single entity Our queries all ultimately get their data by doing something like `select to_jsonb(c.*) from ( ... complicated query ... ) c` because when these queries were written it was far from obvious how to generate queries with Diesel that select columns whose number and types aren't known at compile time. The call to `to_jsonb` forces Postgres to encode all data as JSON, which graph-node then has to deserialize which is pretty wasteful both in terms of memory and CPU. This commit is focused on the groundwork for getting rid of these JSON conversions and querying data in a more compact and native form with fewer conversions. It only uses it in the fairly simple case of `Layout.find`, but future changes will expand that use --- Cargo.toml | 4 +- graph/src/data/store/scalar/bigdecimal.rs | 24 +- graph/src/data/store/scalar/bytes.rs | 8 + graph/src/data/store/scalar/timestamp.rs | 9 + graph/src/data/value.rs | 18 + graph/src/data_source/causality_region.rs | 6 +- store/postgres/src/primary.rs | 6 + store/postgres/src/relational.rs | 67 +- store/postgres/src/relational/dsl.rs | 761 ++++++++++++++++++++++ store/postgres/src/relational/value.rs | 263 ++++++++ store/postgres/src/relational_queries.rs | 68 +- 11 files changed, 1155 insertions(+), 79 deletions(-) create mode 100644 store/postgres/src/relational/dsl.rs create mode 100644 store/postgres/src/relational/value.rs diff --git a/Cargo.toml b/Cargo.toml index 0421a32e274..57eb13c4698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,9 +33,9 @@ axum = "0.7.5" chrono = "0.4.38" clap = { version = "4.5.4", features = ["derive", "env"] } derivative = "2.2.0" -diesel = { version = "2.2.4", features = ["postgres", "serde_json", "numeric", "r2d2", "chrono", "uuid"] } +diesel = { version = "2.2.4", features = ["postgres", "serde_json", "numeric", "r2d2", "chrono", "uuid", "i-implement-a-third-party-backend-and-opt-into-breaking-changes"] } diesel-derive-enum = { version = "2.1.0", features = ["postgres"] } -diesel-dynamic-schema = "0.2.1" +diesel-dynamic-schema = { version = "0.2.1", features = ["postgres"] } diesel_derives = "2.1.4" diesel_migrations = "2.1.0" graph = { path = "./graph" } diff --git a/graph/src/data/store/scalar/bigdecimal.rs b/graph/src/data/store/scalar/bigdecimal.rs index 27af887851f..b8b62f573fb 100644 --- a/graph/src/data/store/scalar/bigdecimal.rs +++ b/graph/src/data/store/scalar/bigdecimal.rs @@ -1,6 +1,6 @@ use diesel::deserialize::FromSqlRow; use diesel::expression::AsExpression; -use num_bigint; +use num_bigint::{self, ToBigInt}; use num_traits::FromPrimitive; use serde::{self, Deserialize, Serialize}; use stable_hash::{FieldAddress, StableHash}; @@ -10,8 +10,8 @@ use std::fmt::{self, Display, Formatter}; use std::ops::{Add, Div, Mul, Sub}; use std::str::FromStr; +use crate::anyhow::anyhow; use crate::runtime::gas::{Gas, GasSizeOf}; - use old_bigdecimal::BigDecimal as OldBigDecimal; pub use old_bigdecimal::ToPrimitive; @@ -60,6 +60,26 @@ impl BigDecimal { self.0.as_bigint_and_exponent() } + pub fn is_integer(&self) -> bool { + self.0.is_integer() + } + + /// Convert this `BigDecimal` to a `BigInt` if it is an integer, and + /// return an error if it is not. Also return an error if the integer + /// would use too many digits as definied by `BigInt::new` + pub fn to_bigint(&self) -> Result { + if !self.is_integer() { + return Err(anyhow!( + "Cannot convert non-integer `BigDecimal` to `BigInt`: {:?}", + self + )); + } + let bi = self.0.to_bigint().ok_or_else(|| { + anyhow!("The implementation of `to_bigint` for `OldBigDecimal` always returns `Some`") + })?; + BigInt::new(bi) + } + pub fn digits(&self) -> u64 { self.0.digits() } diff --git a/graph/src/data/store/scalar/bytes.rs b/graph/src/data/store/scalar/bytes.rs index dd76cb29589..585b548f931 100644 --- a/graph/src/data/store/scalar/bytes.rs +++ b/graph/src/data/store/scalar/bytes.rs @@ -1,3 +1,5 @@ +use diesel::deserialize::FromSql; +use diesel::pg::PgValue; use diesel::serialize::ToSql; use hex; use serde::{self, Deserialize, Serialize}; @@ -115,3 +117,9 @@ impl ToSql for Bytes { <_ as ToSql>::to_sql(self.as_slice(), &mut out.reborrow()) } } + +impl FromSql for Bytes { + fn from_sql(value: PgValue) -> diesel::deserialize::Result { + as FromSql>::from_sql(value).map(Bytes::from) + } +} diff --git a/graph/src/data/store/scalar/timestamp.rs b/graph/src/data/store/scalar/timestamp.rs index 13d71f354a6..0bbf72e36e5 100644 --- a/graph/src/data/store/scalar/timestamp.rs +++ b/graph/src/data/store/scalar/timestamp.rs @@ -1,4 +1,6 @@ use chrono::{DateTime, Utc}; +use diesel::deserialize::FromSql; +use diesel::pg::PgValue; use diesel::serialize::ToSql; use serde::{self, Deserialize, Serialize}; use stable_hash::StableHash; @@ -107,3 +109,10 @@ impl GasSizeOf for Timestamp { Some(Gas::new(std::mem::size_of::().saturating_into())) } } + +impl FromSql for Timestamp { + fn from_sql(value: PgValue) -> diesel::deserialize::Result { + as FromSql>::from_sql(value) + .map(Timestamp) + } +} diff --git a/graph/src/data/value.rs b/graph/src/data/value.rs index b4ede7540a4..af2629a1f18 100644 --- a/graph/src/data/value.rs +++ b/graph/src/data/value.rs @@ -115,6 +115,24 @@ impl PartialEq<&str> for Word { } } +impl PartialEq for Word { + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl PartialEq for Word { + fn eq(&self, other: &String) -> bool { + self.as_str() == other + } +} + +impl PartialEq for String { + fn eq(&self, other: &Word) -> bool { + self.as_str() == other.as_str() + } +} + impl PartialEq for &str { fn eq(&self, other: &Word) -> bool { self == &other.as_str() diff --git a/graph/src/data_source/causality_region.rs b/graph/src/data_source/causality_region.rs index bc8fc89cef2..489247c1b9b 100644 --- a/graph/src/data_source/causality_region.rs +++ b/graph/src/data_source/causality_region.rs @@ -4,6 +4,7 @@ use diesel::{ serialize::{Output, ToSql}, sql_types::Integer, }; +use diesel_derives::AsExpression; use std::fmt; use crate::components::subgraph::Entity; @@ -20,7 +21,10 @@ use crate::derive::CacheWeight; /// This necessary for determinism because offchain data sources don't have a deterministic order of /// execution, for example an IPFS file may become available at any point in time. The isolation /// rules make the indexing result reproducible, given a set of available files. -#[derive(Debug, CacheWeight, Copy, Clone, PartialEq, Eq, FromSqlRow, Hash, PartialOrd, Ord)] +#[derive( + Debug, CacheWeight, Copy, Clone, PartialEq, Eq, FromSqlRow, Hash, PartialOrd, Ord, AsExpression, +)] +#[diesel(sql_type = Integer)] pub struct CausalityRegion(i32); impl fmt::Display for CausalityRegion { diff --git a/store/postgres/src/primary.rs b/store/postgres/src/primary.rs index 6017fc093ec..b13d1608bf3 100644 --- a/store/postgres/src/primary.rs +++ b/store/postgres/src/primary.rs @@ -296,6 +296,12 @@ impl Borrow for Namespace { } } +impl Borrow for &Namespace { + fn borrow(&self) -> &str { + &self.0 + } +} + /// A marker that an `i32` references a deployment. Values of this type hold /// the primary key from the `deployment_schemas` table #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, AsExpression, FromSqlRow)] diff --git a/store/postgres/src/relational.rs b/store/postgres/src/relational.rs index 593ad386889..860449bd42a 100644 --- a/store/postgres/src/relational.rs +++ b/store/postgres/src/relational.rs @@ -14,16 +14,20 @@ mod ddl_tests; #[cfg(test)] mod query_tests; +pub(crate) mod dsl; pub(crate) mod index; mod prune; mod rollup; +pub(crate) mod value; use diesel::deserialize::FromSql; use diesel::pg::Pg; use diesel::serialize::{Output, ToSql}; use diesel::sql_types::Text; use diesel::{connection::SimpleConnection, Connection}; -use diesel::{debug_query, sql_query, OptionalExtension, PgConnection, QueryResult, RunQueryDsl}; +use diesel::{ + debug_query, sql_query, OptionalExtension, PgConnection, QueryDsl, QueryResult, RunQueryDsl, +}; use graph::blockchain::BlockTime; use graph::cheap_clone::CheapClone; use graph::components::store::write::{RowGroup, WriteChunk}; @@ -50,6 +54,7 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; +use crate::relational::value::{FromOidRow, OidRow}; use crate::relational_queries::{ ConflictingEntitiesData, ConflictingEntitiesQuery, FindChangesQuery, FindDerivedQuery, FindPossibleDeletionsQuery, ReturnedEntityData, @@ -58,10 +63,10 @@ use crate::{ primary::{Namespace, Site}, relational_queries::{ ClampRangeQuery, EntityData, EntityDeletion, FilterCollection, FilterQuery, FindManyQuery, - FindQuery, InsertQuery, RevertClampQuery, RevertRemoveQuery, + InsertQuery, RevertClampQuery, RevertRemoveQuery, }, }; -use graph::components::store::DerivedEntityQuery; +use graph::components::store::{AttributeNames, DerivedEntityQuery}; use graph::data::store::{Id, IdList, IdType, BYTES_SCALAR}; use graph::data::subgraph::schema::POI_TABLE; use graph::prelude::{ @@ -172,6 +177,12 @@ impl From for SqlName { } } +impl From for Word { + fn from(name: SqlName) -> Self { + Word::from(name.0) + } +} + impl fmt::Display for SqlName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -184,6 +195,12 @@ impl Borrow for &SqlName { } } +impl PartialEq for SqlName { + fn eq(&self, other: &str) -> bool { + self.0 == other + } +} + impl FromSql for SqlName { fn from_sql(bytes: diesel::pg::PgValue) -> diesel::deserialize::Result { >::from_sql(bytes).map(|s| SqlName::verbatim(s)) @@ -361,9 +378,11 @@ impl Layout { } let table_name = SqlName::verbatim(POI_TABLE.to_owned()); + let nsp = catalog.site.namespace.clone(); Table { object: poi_type.to_owned(), qualified_name: SqlName::qualified_name(&catalog.site.namespace, &table_name), + nsp, name: table_name, columns, // The position of this table in all the tables for this layout; this @@ -469,11 +488,19 @@ impl Layout { key: &EntityKey, block: BlockNumber, ) -> Result, StoreError> { - let table = self.table_for_entity(&key.entity_type)?; - FindQuery::new(table.as_ref(), key, block) - .get_result::(conn) + let table = self.table_for_entity(&key.entity_type)?.dsl_table(); + let columns = table.selected_columns::(&AttributeNames::All, None)?; + + let query = table + .select_cols(&columns) + .filter(table.id_eq(&key.entity_id)) + .filter(table.at_block(block)) + .filter(table.belongs_to_causality_region(key.causality_region)); + + query + .get_result::(conn) .optional()? - .map(|entity_data| entity_data.deserialize_with_layout(self, None)) + .map(|row| Entity::from_oid_row(row, &self.input_schema, &columns)) .transpose() } @@ -1348,6 +1375,21 @@ impl Column { }) } + pub fn pseudo_column(name: &str, column_type: ColumnType) -> Column { + let field_type = q::Type::NamedType(column_type.to_string()); + let name = SqlName::verbatim(name.to_string()); + let field = Word::from(name.as_str()); + Column { + name, + field, + field_type, + column_type, + fulltext_fields: None, + is_reference: false, + use_prefix_comparison: false, + } + } + fn new_fulltext(def: &FulltextDefinition) -> Result { SqlName::check_valid_identifier(&def.name, "attribute")?; let sql_name = SqlName::from(def.name.as_str()); @@ -1440,6 +1482,9 @@ pub struct Table { /// `Stats_hour`, not the overall aggregation type `Stats`. pub object: EntityType, + /// The namespace in which the table lives + nsp: Namespace, + /// The name of the database table for this type ('thing'), snakecased /// version of `object` pub name: SqlName, @@ -1494,10 +1539,11 @@ impl Table { .collect::, StoreError>>()?; let qualified_name = SqlName::qualified_name(&catalog.site.namespace, &table_name); let immutable = defn.is_immutable(); - + let nsp = catalog.site.namespace.clone(); let table = Table { object: defn.cheap_clone(), name: table_name, + nsp, qualified_name, // Default `is_account_like` to `false`; the caller should call // `refresh` after constructing the layout, but that requires a @@ -1516,6 +1562,7 @@ impl Table { pub fn new_like(&self, namespace: &Namespace, name: &SqlName) -> Arc { let other = Table { object: self.object.clone(), + nsp: self.nsp.clone(), name: name.clone(), qualified_name: SqlName::qualified_name(namespace, name), columns: self.columns.clone(), @@ -1590,6 +1637,10 @@ impl Table { &crate::block_range::BLOCK_RANGE_COLUMN_SQL } } + + pub fn dsl_table(&self) -> dsl::Table<'_> { + dsl::Table::new(self) + } } #[derive(Clone)] diff --git a/store/postgres/src/relational/dsl.rs b/store/postgres/src/relational/dsl.rs new file mode 100644 index 00000000000..67c9cc781b1 --- /dev/null +++ b/store/postgres/src/relational/dsl.rs @@ -0,0 +1,761 @@ +//! Helpers for creating relational queries using diesel. A lot of this code +//! is copied from `diesel_dynamic_schema` and adapted to our data +//! structures, especially the `Table` and `Column` types. + +use std::marker::PhantomData; + +use diesel::backend::Backend; +use diesel::dsl::sql; +use diesel::expression::{expression_types, is_aggregate, TypedExpressionType, ValidGrouping}; +use diesel::pg::Pg; +use diesel::query_builder::{ + AsQuery, AstPass, BoxedSelectStatement, FromClause, Query, QueryFragment, QueryId, + SelectStatement, +}; +use diesel::query_dsl::methods::SelectDsl; +use diesel::query_source::QuerySource; + +use diesel::sql_types::{ + Array, BigInt, Binary, Bool, Integer, Nullable, Numeric, SingleValue, Text, Timestamptz, + Untyped, +}; +use diesel::{AppearsOnTable, Expression, QueryDsl, QueryResult, SelectableExpression}; +use diesel_dynamic_schema::DynamicSelectClause; +use graph::components::store::{AttributeNames, BlockNumber, StoreError, BLOCK_NUMBER_MAX}; +use graph::data::store::{Id, IdType, ID}; +use graph::data_source::CausalityRegion; +use graph::prelude::{lazy_static, ENV_VARS}; + +use crate::relational::ColumnType; +use crate::relational_queries::PARENT_ID; + +use super::value::FromOidRow; +use super::Column as RelColumn; +use super::SqlName; +use super::{BLOCK_COLUMN, BLOCK_RANGE_COLUMN}; + +const TYPENAME: &str = "__typename"; + +lazy_static! { + pub static ref TYPENAME_SQL: SqlName = TYPENAME.into(); + pub static ref VID_SQL: SqlName = "vid".into(); + pub static ref PARENT_SQL: SqlName = PARENT_ID.into(); + pub static ref TYPENAME_COL: RelColumn = RelColumn::pseudo_column(TYPENAME, ColumnType::String); + pub static ref VID_COL: RelColumn = RelColumn::pseudo_column("vid", ColumnType::Int8); + pub static ref BLOCK_COL: RelColumn = RelColumn::pseudo_column(BLOCK_COLUMN, ColumnType::Int8); + // The column type is a placeholder, we can't deserialize in4range; but + // we also never try to use it when we get data from the database + pub static ref BLOCK_RANGE_COL: RelColumn = + RelColumn::pseudo_column(BLOCK_RANGE_COLUMN, ColumnType::Bytes); + pub static ref PARENT_STRING_COL: RelColumn = RelColumn::pseudo_column(PARENT_ID, ColumnType::String); + pub static ref PARENT_BYTES_COL: RelColumn = RelColumn::pseudo_column(PARENT_ID, ColumnType::Bytes); + pub static ref PARENT_INT_COL: RelColumn = RelColumn::pseudo_column(PARENT_ID, ColumnType::Int8); + + pub static ref META_COLS: [&'static RelColumn; 2] = [&*TYPENAME_COL, &*VID_COL]; +} + +#[doc(hidden)] +/// A dummy expression. +pub struct DummyExpression; + +impl DummyExpression { + pub(crate) fn new() -> Self { + DummyExpression + } +} + +impl SelectableExpression for DummyExpression {} + +impl AppearsOnTable for DummyExpression {} + +impl Expression for DummyExpression { + type SqlType = expression_types::NotSelectable; +} + +impl ValidGrouping<()> for DummyExpression { + type IsAggregate = is_aggregate::No; +} + +/// A fixed size string for the table alias. We want to make sure that +/// converting these to `&str` doesn't allocate and that they are small +/// enough that the `Table` struct is only 16 bytes and can be `Copy` +#[derive(Debug, Clone, Copy)] +pub struct ChildAliasStr { + alias: [u8; 4], +} + +impl ChildAliasStr { + fn new(idx: u8) -> Self { + let c = 'i' as u8; + let alias = if idx == 0 { + [c, 0, 0, 0] + } else if idx < 10 { + let ones = char::from_digit(idx as u32, 10).unwrap() as u8; + [c, ones, 0, 0] + } else if idx < 100 { + let tens = char::from_digit((idx / 10) as u32, 10).unwrap() as u8; + let ones = char::from_digit((idx % 10) as u32, 10).unwrap() as u8; + [c, tens, ones, 0] + } else { + let hundreds = char::from_digit((idx / 100) as u32, 10).unwrap() as u8; + let idx = idx % 100; + let tens = char::from_digit((idx / 10) as u32, 10).unwrap() as u8; + let ones = char::from_digit((idx % 10) as u32, 10).unwrap() as u8; + [c, hundreds, tens, ones] + }; + ChildAliasStr { alias } + } + + fn as_str(&self) -> &str { + let alias = if self.alias[1] == 0 { + return "i"; + } else if self.alias[2] == 0 { + &self.alias[..2] + } else if self.alias[3] == 0 { + &self.alias[..3] + } else { + &self.alias + }; + unsafe { std::str::from_utf8_unchecked(alias) } + } +} + +/// A table alias. We use `c` as the main table alias and `i`, `i1`, `i2`, +/// ... for child tables. The fact that we use these specific letters is +/// historical and doesn't have any meaning. +#[derive(Debug, Clone, Copy)] +pub enum Alias { + Main, + Child(ChildAliasStr), +} + +impl Alias { + fn as_str(&self) -> &str { + match self { + Alias::Main => "c", + Alias::Child(idx) => idx.as_str(), + } + } + + fn child(idx: u8) -> Self { + Alias::Child(ChildAliasStr::new(idx)) + } +} + +#[test] +fn alias() { + assert_eq!(Alias::Main.as_str(), "c"); + assert_eq!(Alias::Child(ChildAliasStr::new(0)).as_str(), "i"); + assert_eq!(Alias::Child(ChildAliasStr::new(1)).as_str(), "i1"); + assert_eq!(Alias::Child(ChildAliasStr::new(10)).as_str(), "i10"); + assert_eq!(Alias::Child(ChildAliasStr::new(100)).as_str(), "i100"); + assert_eq!(Alias::Child(ChildAliasStr::new(255)).as_str(), "i255"); +} + +#[derive(Debug, Clone, Copy)] +/// A wrapper around the `super::Table` struct that provides helper +/// functions for generating SQL queries +pub struct Table<'a> { + /// The metadata for this table + pub meta: &'a super::Table, + alias: Alias, +} + +impl<'a> Table<'a> { + pub(crate) fn new(meta: &'a super::Table) -> Self { + Self { + meta, + alias: Alias::Main, + } + } + + /// Change the alias for this table to be a child table. + pub fn child(mut self, idx: u8) -> Self { + self.alias = Alias::child(idx); + self + } + + /// Reference a column in this table and use the correct SQL type `ST` + fn bind(&self, name: &str) -> Option> { + self.column(name).map(|c| c.bind()) + } + + /// Reference a column without regard to the underlying SQL type. This + /// is useful if just the name of the column qualified with the table + /// name/alias is needed + pub fn column(&self, name: &str) -> Option> { + self.meta + .columns + .iter() + .chain(META_COLS.into_iter()) + .find(|c| &c.name == name) + .map(|c| Column::new(self.clone(), c)) + } + + pub fn name(&self) -> &str { + &self.meta.name + } + + pub fn column_for_field(&self, field: &str) -> Result, StoreError> { + self.meta + .column_for_field(field) + .map(|column| Column::new(*self, column)) + } + + pub fn primary_key(&self) -> Column<'a> { + Column::new(*self, self.meta.primary_key()) + } + + /// Return a filter expression that generates the SQL for `id = $id` + pub fn id_eq(&'a self, id: &'a Id) -> IdEq<'a> { + IdEq::new(*self, id) + } + + /// Return an expression that generates the SQL for `block_range @> + /// $block` or `block = $block` depending on whether the table is + /// mutable or not + pub fn at_block(&self, block: BlockNumber) -> AtBlock<'a> { + AtBlock::new(*self, block) + } + + /// The block column for this table for places where the just the + /// qualified name is needed + pub fn block_column(&self) -> BlockColumn<'a> { + BlockColumn::new(*self) + } + + /// An expression that is true if the entity has changed since `block` + pub fn changed_since(&self, block: BlockNumber) -> ChangedSince<'a> { + let column = self.block_column(); + ChangedSince { column, block } + } + + /// Return an expression that generates the SQL for `causality_region = + /// $cr` if the table uses causality regions + pub fn belongs_to_causality_region( + &'a self, + cr: CausalityRegion, + ) -> BelongsToCausalityRegion<'a> { + BelongsToCausalityRegion::new(*self, cr) + } + + /// Produce a list of the columns that should be selected for a query + /// based on `column_names`. The result needs to be used both to create + /// the actual select statement with `Self::select_cols` and to decode + /// query results with `FromOidRow`. + pub fn selected_columns( + &self, + column_names: &'a AttributeNames, + parent_type: Option, + ) -> Result, StoreError> { + let mut cols = Vec::new(); + if T::WITH_INTERNAL_KEYS { + cols.push(&*TYPENAME_COL); + } + + match column_names { + AttributeNames::All => cols.extend(self.meta.columns.iter()), + AttributeNames::Select(names) => { + let pk = self.meta.primary_key(); + cols.push(pk); + let mut names: Vec<_> = names.iter().filter(|name| *name != &*ID).collect(); + names.sort(); + for name in names { + let column = self.meta.column_for_field(&name)?; + cols.push(column); + } + } + }; + + if T::WITH_INTERNAL_KEYS { + match parent_type { + Some(IdType::String) => cols.push(&*PARENT_STRING_COL), + Some(IdType::Bytes) => cols.push(&*PARENT_BYTES_COL), + Some(IdType::Int8) => cols.push(&*PARENT_INT_COL), + None => (), + } + } + + if T::WITH_SYSTEM_COLUMNS { + cols.push(&*VID_COL); + if self.meta.immutable { + cols.push(&*BLOCK_COL); + } else { + // TODO: We can't deserialize in4range + cols.push(&*BLOCK_RANGE_COL); + } + } + Ok(cols) + } + + /// Create a Diesel select statement that selects the columns in + /// `columns`. Use to generate a query via + /// `table.select_cols(columns).filter(...)`. For a full example, see + /// `Layout::find` + pub fn select_cols( + &'a self, + columns: &[&'a RelColumn], + ) -> BoxedSelectStatement<'a, Untyped, FromClause>, Pg> { + type SelectClause<'b> = DynamicSelectClause<'b, Pg, Table<'b>>; + + fn add_field<'b, ST: SingleValue + Send>( + select: &mut SelectClause<'b>, + table: &'b Table<'b>, + column: &'b RelColumn, + ) { + let name = &column.name; + + match (column.is_list(), column.is_nullable()) { + (true, true) => select.add_field(table.bind::>>(name).unwrap()), + (true, false) => select.add_field(table.bind::>(name).unwrap()), + (false, true) => select.add_field(table.bind::>(name).unwrap()), + (false, false) => select.add_field(table.bind::(name).unwrap()), + } + } + + fn add_enum_field<'b>( + select: &mut SelectClause<'b>, + table: &'b Table<'b>, + column: &'b RelColumn, + ) { + let name = format!("{}.{}::text", table.alias.as_str(), &column.name); + + match (column.is_list(), column.is_nullable()) { + (true, true) => select.add_field(sql::>>(&name)), + (true, false) => select.add_field(sql::>(&name)), + (false, true) => select.add_field(sql::>(&name)), + (false, false) => select.add_field(sql::(&name)), + } + } + + let mut selection = DynamicSelectClause::new(); + for column in columns { + if column.name == TYPENAME_COL.name { + selection.add_field(sql::(&format!( + "'{}' as __typename", + self.meta.object.typename() + ))); + continue; + } + match column.column_type { + ColumnType::Boolean => add_field::(&mut selection, self, column), + ColumnType::BigDecimal => add_field::(&mut selection, self, column), + ColumnType::BigInt => add_field::(&mut selection, self, column), + ColumnType::Bytes => add_field::(&mut selection, self, column), + ColumnType::Int => add_field::(&mut selection, self, column), + ColumnType::Int8 => add_field::(&mut selection, self, column), + ColumnType::Timestamp => add_field::(&mut selection, self, column), + ColumnType::String => add_field::(&mut selection, self, column), + ColumnType::TSVector(_) => add_field::(&mut selection, self, column), + ColumnType::Enum(_) => add_enum_field(&mut selection, self, column), + }; + } + >>::select(*self, selection).into_boxed() + } +} + +/// Generate the SQL to use a table in the `from` clause, complete with +/// giving the table an alias +#[derive(Debug, Clone, Copy)] +pub struct FromTable<'a>(Table<'a>); + +impl<'a, DB> QueryFragment for FromTable<'a> +where + DB: Backend, +{ + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + + out.push_identifier(self.0.meta.nsp.as_str())?; + out.push_sql("."); + out.push_identifier(&self.0.meta.name)?; + out.push_sql(" as "); + out.push_sql(self.0.alias.as_str()); + Ok(()) + } +} + +impl std::fmt::Display for Table<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} as {}", self.meta.name, self.alias.as_str()) + } +} + +impl std::fmt::Display for FromTable<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl<'a> QuerySource for Table<'a> { + type FromClause = FromTable<'a>; + type DefaultSelection = DummyExpression; + + fn from_clause(&self) -> FromTable<'a> { + FromTable(*self) + } + + fn default_selection(&self) -> Self::DefaultSelection { + DummyExpression::new() + } +} + +impl<'a> AsQuery for Table<'a> +where + SelectStatement>: Query, +{ + type SqlType = expression_types::NotSelectable; + type Query = SelectStatement>; + + fn as_query(self) -> Self::Query { + SelectStatement::simple(self) + } +} + +impl<'a> diesel::Table for Table<'a> +where + Self: QuerySource + AsQuery, +{ + type PrimaryKey = DummyExpression; + type AllColumns = DummyExpression; + + fn primary_key(&self) -> Self::PrimaryKey { + DummyExpression::new() + } + + fn all_columns() -> Self::AllColumns { + DummyExpression::new() + } +} + +impl<'a, DB> QueryFragment for Table<'a> +where + DB: Backend, +{ + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + + out.push_sql(self.alias.as_str()); + Ok(()) + } +} + +impl<'a> QueryId for Table<'a> { + type QueryId = (); + const HAS_STATIC_QUERY_ID: bool = false; +} + +/// Generated by `Table.id_eq` +pub struct IdEq<'a> { + table: Table<'a>, + id: &'a Id, +} + +impl<'a> IdEq<'a> { + fn new(table: Table<'a>, id: &'a Id) -> Self { + IdEq { table, id } + } +} + +impl Expression for IdEq<'_> { + type SqlType = Bool; +} + +impl<'a> QueryFragment for IdEq<'a> { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + self.table.walk_ast(out.reborrow())?; + out.push_sql(".id = "); + match self.id { + Id::String(s) => out.push_bind_param::(s.as_str())?, + Id::Bytes(b) => out.push_bind_param::(b)?, + Id::Int8(i) => out.push_bind_param::(i)?, + } + Ok(()) + } +} + +impl ValidGrouping<()> for IdEq<'_> { + type IsAggregate = is_aggregate::No; +} + +impl<'a> AppearsOnTable> for IdEq<'a> {} + +/// Generated by `Table.block_column` +#[derive(Debug, Clone, Copy)] +pub struct BlockColumn<'a> { + table: Table<'a>, +} + +impl<'a> BlockColumn<'a> { + fn new(table: Table<'a>) -> Self { + BlockColumn { table } + } + + fn immutable(&self) -> bool { + self.table.meta.immutable + } + + pub fn name(&self) -> &str { + if self.immutable() { + BLOCK_COLUMN + } else { + BLOCK_RANGE_COLUMN + } + } +} + +impl std::fmt::Display for BlockColumn<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}.{}", self.table.alias.as_str(), self.name()) + } +} + +impl QueryFragment for BlockColumn<'_> { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + self.table.walk_ast(out.reborrow())?; + out.push_sql("."); + out.push_sql(self.name()); + Ok(()) + } +} + +/// Generated by `Table.at_block` +#[derive(Debug, Clone, Copy)] +pub struct AtBlock<'a> { + column: BlockColumn<'a>, + block: BlockNumber, + filters_by_id: bool, +} + +impl<'a> AtBlock<'a> { + fn new(table: Table<'a>, block: BlockNumber) -> Self { + let column = BlockColumn::new(table); + AtBlock { + column, + block, + filters_by_id: false, + } + } + + pub fn filters_by_id(mut self, by_id: bool) -> Self { + self.filters_by_id = by_id; + self + } +} + +impl Expression for AtBlock<'_> { + type SqlType = Bool; +} + +impl<'a> QueryFragment for AtBlock<'a> { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + + if self.column.immutable() { + if self.block == BLOCK_NUMBER_MAX { + // `self.block <= BLOCK_NUMBER_MAX` is always true + out.push_sql("true"); + } else { + self.column.walk_ast(out.reborrow())?; + out.push_sql(" <= "); + out.push_bind_param::(&self.block)?; + } + } else { + // Table is mutable and has a block_range column + self.column.walk_ast(out.reborrow())?; + out.push_sql(" @> "); + out.push_bind_param::(&self.block)?; + + let should_use_brin = + !self.filters_by_id || ENV_VARS.store.use_brin_for_all_query_types; + if self.column.table.meta.is_account_like + && self.block < BLOCK_NUMBER_MAX + && should_use_brin + { + // When block is BLOCK_NUMBER_MAX, these checks would be wrong; we + // don't worry about adding the equivalent in that case since + // we generally only see BLOCK_NUMBER_MAX here for metadata + // queries where block ranges don't matter anyway. + // + // We also don't need to add these if the query already filters by ID, + // because the ideal index is the GiST index on id and block_range. + out.push_sql(" and coalesce(upper("); + self.column.walk_ast(out.reborrow())?; + out.push_sql("), 2147483647) > "); + out.push_bind_param::(&self.block)?; + out.push_sql(" and lower("); + self.column.walk_ast(out.reborrow())?; + out.push_sql(") <= "); + out.push_bind_param::(&self.block)?; + } + } + + Ok(()) + } +} + +impl ValidGrouping<()> for AtBlock<'_> { + type IsAggregate = is_aggregate::No; +} + +impl<'a> AppearsOnTable> for AtBlock<'a> {} + +/// Generated by `Table.changed_since` +#[derive(Debug)] +pub struct ChangedSince<'a> { + column: BlockColumn<'a>, + block: BlockNumber, +} + +impl std::fmt::Display for ChangedSince<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} >= {}", self.column, self.block) + } +} + +impl Expression for ChangedSince<'_> { + type SqlType = Bool; +} + +impl QueryFragment for ChangedSince<'_> { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + if self.column.table.meta.immutable { + self.column.walk_ast(out.reborrow())?; + out.push_sql(" >= "); + out.push_bind_param::(&self.block) + } else { + out.push_sql("lower("); + self.column.walk_ast(out.reborrow())?; + out.push_sql(") >= "); + out.push_bind_param::(&self.block) + } + } +} + +/// Generated by `Table.belongs_to_causality_region` +pub struct BelongsToCausalityRegion<'a> { + table: Table<'a>, + cr: CausalityRegion, +} + +impl<'a> BelongsToCausalityRegion<'a> { + fn new(table: Table<'a>, cr: CausalityRegion) -> Self { + BelongsToCausalityRegion { table, cr } + } +} + +impl Expression for BelongsToCausalityRegion<'_> { + type SqlType = Bool; +} + +impl<'a> QueryFragment for BelongsToCausalityRegion<'a> { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + + if self.table.meta.has_causality_region { + self.table.walk_ast(out.reborrow())?; + out.push_sql(".causality_region"); + out.push_sql(" = "); + out.push_bind_param::(&self.cr)?; + } else { + out.push_sql("true"); + } + Ok(()) + } +} + +impl ValidGrouping<()> for BelongsToCausalityRegion<'_> { + type IsAggregate = is_aggregate::No; +} + +impl<'a> AppearsOnTable> for BelongsToCausalityRegion<'a> {} + +/// A specific column in a specific table +#[derive(Debug, Clone, Copy)] +pub struct Column<'a> { + table: Table<'a>, + column: &'a super::Column, +} + +impl<'a> Column<'a> { + fn new(table: Table<'a>, column: &'a super::Column) -> Self { + Column { table, column } + } + + /// Bind this column to a specific SQL type for use in contexts where + /// Diesel requires that + pub fn bind(&self) -> BoundColumn<'a, ST> { + BoundColumn::new(self.table, self.column) + } + + pub fn name(&self) -> &'a str { + &self.column.name + } +} + +impl std::fmt::Display for Column<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}.{}", self.table.alias.as_str(), self.column.name) + } +} + +impl<'a, DB> QueryFragment for Column<'a> +where + DB: Backend, +{ + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { + out.unsafe_to_cache_prepared(); + self.table.walk_ast(out.reborrow())?; + out.push_sql("."); + out.push_identifier(&self.column.name)?; + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +/// A database table column bound to the SQL type for the column +pub struct BoundColumn<'a, ST> { + column: Column<'a>, + _sql_type: PhantomData, +} + +impl<'a, ST> BoundColumn<'a, ST> { + fn new(table: Table<'a>, column: &'a super::Column) -> Self { + let column = Column::new(table, column); + Self { + column, + _sql_type: PhantomData, + } + } +} + +impl<'a, ST> QueryId for BoundColumn<'a, ST> { + type QueryId = (); + const HAS_STATIC_QUERY_ID: bool = false; +} + +impl<'a, ST, QS> SelectableExpression for BoundColumn<'a, ST> where Self: Expression {} + +impl<'a, ST, QS> AppearsOnTable for BoundColumn<'a, ST> where Self: Expression {} + +impl<'a, ST> Expression for BoundColumn<'a, ST> +where + ST: TypedExpressionType, +{ + type SqlType = ST; +} + +impl<'a, ST> ValidGrouping<()> for BoundColumn<'a, ST> { + type IsAggregate = is_aggregate::No; +} + +impl<'a, ST, DB> QueryFragment for BoundColumn<'a, ST> +where + DB: Backend, +{ + fn walk_ast<'b>(&'b self, out: AstPass<'_, 'b, DB>) -> QueryResult<()> { + self.column.walk_ast(out) + } +} diff --git a/store/postgres/src/relational/value.rs b/store/postgres/src/relational/value.rs new file mode 100644 index 00000000000..fadcfdcfbca --- /dev/null +++ b/store/postgres/src/relational/value.rs @@ -0,0 +1,263 @@ +//! Helpers to use diesel dynamic schema to retrieve values from Postgres + +use std::num::NonZeroU32; + +use diesel::sql_types::{Array, BigInt, Binary, Bool, Integer, Numeric, Text, Timestamptz}; +use diesel::{deserialize::FromSql, pg::Pg}; +use diesel_dynamic_schema::dynamic_value::{Any, DynamicRow}; + +use graph::{ + components::store::StoreError, + data::{ + store::{ + scalar::{BigDecimal, Bytes, Timestamp}, + Entity, QueryObject, + }, + value::{Object, Word}, + }, + prelude::r, + schema::InputSchema, +}; + +use super::ColumnType; +use crate::relational::Column; + +/// Represent values of the database types we care about as a single value. +/// The deserialization of these values is completely governed by the oid we +/// get from Postgres; in a second step, these values need to be transformed +/// into our internal values using the underlying `ColumnType`. Diesel's API +/// doesn't let us do that in one go, so we do a first transformation into +/// `OidValue` and then use `FromOidValue` to transform guided by the +/// `ColumnType` +#[derive(Debug)] +pub enum OidValue { + String(String), + StringArray(Vec), + Bytes(Bytes), + BytesArray(Vec), + Bool(bool), + BoolArray(Vec), + Int(i32), + Ints(Vec), + Int8(i64), + Int8Array(Vec), + BigDecimal(BigDecimal), + BigDecimalArray(Vec), + Timestamp(Timestamp), + TimestampArray(Vec), + Null, +} + +impl FromSql for OidValue { + fn from_sql(value: diesel::pg::PgValue) -> diesel::deserialize::Result { + const VARCHAR_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1043) }; + const VARCHAR_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1015) }; + const TEXT_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(25) }; + const TEXT_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1009) }; + const BYTEA_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(17) }; + const BYTEA_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1001) }; + const BOOL_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(16) }; + const BOOL_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1000) }; + const INTEGER_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(23) }; + const INTEGER_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1007) }; + const INT8_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(20) }; + const INT8_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1016) }; + const NUMERIC_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1700) }; + const NUMERIC_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1231) }; + const TIMESTAMPTZ_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1184) }; + const TIMESTAMPTZ_ARY_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1185) }; + + match value.get_oid() { + VARCHAR_OID | TEXT_OID => { + >::from_sql(value).map(OidValue::String) + } + VARCHAR_ARY_OID | TEXT_ARY_OID => { + as FromSql, Pg>>::from_sql(value) + .map(OidValue::StringArray) + } + BYTEA_OID => >::from_sql(value).map(OidValue::Bytes), + BYTEA_ARY_OID => as FromSql, Pg>>::from_sql(value) + .map(OidValue::BytesArray), + BOOL_OID => >::from_sql(value).map(OidValue::Bool), + BOOL_ARY_OID => { + as FromSql, Pg>>::from_sql(value).map(OidValue::BoolArray) + } + INTEGER_OID => >::from_sql(value).map(OidValue::Int), + INTEGER_ARY_OID => { + as FromSql, Pg>>::from_sql(value).map(OidValue::Ints) + } + INT8_OID => >::from_sql(value).map(OidValue::Int8), + INT8_ARY_OID => { + as FromSql, Pg>>::from_sql(value).map(OidValue::Int8Array) + } + NUMERIC_OID => { + >::from_sql(value).map(OidValue::BigDecimal) + } + NUMERIC_ARY_OID => as FromSql, Pg>>::from_sql(value) + .map(OidValue::BigDecimalArray), + TIMESTAMPTZ_OID => { + >::from_sql(value).map(OidValue::Timestamp) + } + TIMESTAMPTZ_ARY_OID => { + as FromSql, Pg>>::from_sql(value) + .map(OidValue::TimestampArray) + } + e => Err(format!("Unknown type: {e}").into()), + } + } + + fn from_nullable_sql(bytes: Option) -> diesel::deserialize::Result { + match bytes { + Some(bytes) => Self::from_sql(bytes), + None => Ok(OidValue::Null), + } + } +} + +pub trait FromOidValue: Sized { + fn from_oid_value(value: OidValue, column_type: &ColumnType) -> Result; +} + +impl FromOidValue for r::Value { + fn from_oid_value(value: OidValue, _: &ColumnType) -> Result { + fn as_list(values: Vec, f: F) -> r::Value + where + F: Fn(T) -> r::Value, + { + r::Value::List(values.into_iter().map(f).collect()) + } + + use OidValue as O; + let value = match value { + O::String(s) => Self::String(s), + O::StringArray(s) => as_list(s, Self::String), + O::Bytes(b) => Self::String(b.to_string()), + O::BytesArray(b) => as_list(b, |b| Self::String(b.to_string())), + O::Bool(b) => Self::Boolean(b), + O::BoolArray(b) => as_list(b, Self::Boolean), + O::Int(i) => Self::Int(i as i64), + O::Ints(i) => as_list(i, |i| Self::Int(i as i64)), + O::Int8(i) => Self::String(i.to_string()), + O::Int8Array(i) => as_list(i, |i| Self::String(i.to_string())), + O::BigDecimal(b) => Self::String(b.to_string()), + O::BigDecimalArray(b) => as_list(b, |b| Self::String(b.to_string())), + O::Timestamp(t) => Self::Timestamp(t), + O::TimestampArray(t) => as_list(t, Self::Timestamp), + O::Null => Self::Null, + }; + Ok(value) + } +} + +impl FromOidValue for graph::prelude::Value { + fn from_oid_value(value: OidValue, column_type: &ColumnType) -> Result { + fn as_list(values: Vec, f: F) -> graph::prelude::Value + where + F: Fn(T) -> graph::prelude::Value, + { + graph::prelude::Value::List(values.into_iter().map(f).collect()) + } + + fn as_list_err(values: Vec, f: F) -> Result + where + F: Fn(T) -> Result, + { + values + .into_iter() + .map(f) + .collect::>() + .map(graph::prelude::Value::List) + } + + use OidValue as O; + let value = match value { + O::String(s) => Self::String(s), + O::StringArray(s) => as_list(s, Self::String), + O::Bytes(b) => Self::Bytes(b), + O::BytesArray(b) => as_list(b, Self::Bytes), + O::Bool(b) => Self::Bool(b), + O::BoolArray(b) => as_list(b, Self::Bool), + O::Int(i) => Self::Int(i), + O::Ints(i) => as_list(i, Self::Int), + O::Int8(i) => Self::Int8(i), + O::Int8Array(i) => as_list(i, Self::Int8), + O::BigDecimal(b) => match column_type { + ColumnType::BigDecimal => Self::BigDecimal(b), + ColumnType::BigInt => Self::BigInt(b.to_bigint()?), + _ => unreachable!("only BigInt and BigDecimal are stored as numeric"), + }, + O::BigDecimalArray(b) => match column_type { + ColumnType::BigDecimal => as_list(b, Self::BigDecimal), + ColumnType::BigInt => as_list_err(b, |b| { + b.to_bigint().map(Self::BigInt).map_err(StoreError::from) + })?, + _ => unreachable!("only BigInt and BigDecimal are stored as numeric[]"), + }, + O::Timestamp(t) => Self::Timestamp(t), + O::TimestampArray(t) => as_list(t, Self::Timestamp), + O::Null => Self::Null, + }; + Ok(value) + } +} + +pub type OidRow = DynamicRow; + +pub trait FromOidRow: Sized { + // Should the columns for `__typename` and `g$parent_id` be selected + const WITH_INTERNAL_KEYS: bool; + // Should the system columns for block/block_range and vid be selected + const WITH_SYSTEM_COLUMNS: bool = false; + + fn from_oid_row( + row: DynamicRow, + schema: &InputSchema, + columns: &[&Column], + ) -> Result; +} + +impl FromOidRow for Entity { + const WITH_INTERNAL_KEYS: bool = false; + + fn from_oid_row( + row: DynamicRow, + schema: &InputSchema, + columns: &[&Column], + ) -> Result { + let x = row + .into_iter() + .zip(columns) + .filter(|(value, _)| !matches!(value, OidValue::Null)) + .map(|(value, column)| { + graph::prelude::Value::from_oid_value(value, &column.column_type) + .map(|value| (Word::from(column.field.clone()), value)) + }); + schema.try_make_entity(x).map_err(StoreError::from) + } +} + +impl FromOidRow for QueryObject { + const WITH_INTERNAL_KEYS: bool = true; + + fn from_oid_row( + row: DynamicRow, + _schema: &InputSchema, + columns: &[&Column], + ) -> Result { + let pairs = row + .into_iter() + .zip(columns) + .filter(|(value, _)| !matches!(value, OidValue::Null)) + .map(|(value, column)| -> Result<_, StoreError> { + let name = &column.name; + let value = r::Value::from_oid_value(value, &column.column_type)?; + Ok((Word::from(name.clone()), value)) + }) + .collect::, _>>()?; + let entity = Object::from_iter(pairs); + Ok(QueryObject { + entity, + parent: None, + }) + } +} diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index 4626ce0479e..3af425b6b86 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -22,7 +22,7 @@ use graph::prelude::{ EntityLink, EntityOrder, EntityOrderByChild, EntityOrderByChildInfo, EntityRange, EntityWindow, ParentLink, QueryExecutionError, StoreError, Value, ENV_VARS, }; -use graph::schema::{EntityKey, EntityType, FulltextAlgorithm, FulltextConfig, InputSchema}; +use graph::schema::{EntityType, FulltextAlgorithm, FulltextConfig, InputSchema}; use graph::{components::store::AttributeNames, data::store::scalar}; use inflector::Inflector; use itertools::Itertools; @@ -56,7 +56,7 @@ const SORT_KEY_COLUMN: &str = "sort_key$"; /// The name of the parent_id attribute that we inject into queries. Users /// outside of this module should access the parent id through the /// `QueryObject` struct -const PARENT_ID: &str = "g$parent_id"; +pub(crate) const PARENT_ID: &str = "g$parent_id"; /// Describes at what level a `SELECT` statement is used. enum SelectStatementLevel { @@ -112,14 +112,6 @@ trait ForeignKeyClauses { /// The name of the column fn name(&self) -> &str; - /// Generate a clause `{name()} = $id` using the right types to bind `$id` - /// into `out` - fn eq<'b>(&self, id: &'b Id, out: &mut AstPass<'_, 'b, Pg>) -> QueryResult<()> { - out.push_sql(self.name()); - out.push_sql(" = "); - id.push_bind_param(out) - } - /// Generate a clause /// `exists (select 1 from unnest($ids) as p(g$id) where id = p.g$id)` /// using the right types to bind `$ids` into `out` @@ -1915,62 +1907,6 @@ impl<'a> QueryFragment for Filter<'a> { } } -/// A query that finds an entity by key. Used during indexing. -/// See also `FindManyQuery`. -#[derive(Debug, Clone)] -pub struct FindQuery<'a> { - table: &'a Table, - key: &'a EntityKey, - br_column: BlockRangeColumn<'a>, -} - -impl<'a> FindQuery<'a> { - pub fn new(table: &'a Table, key: &'a EntityKey, block: BlockNumber) -> Self { - let br_column = BlockRangeColumn::new(table, "e.", block); - Self { - table, - key, - br_column, - } - } -} - -impl<'a> QueryFragment for FindQuery<'a> { - fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { - out.unsafe_to_cache_prepared(); - - // Generate - // select '..' as entity, to_jsonb(e.*) as data - // from schema.table e where id = $1 - out.push_sql("select "); - out.push_bind_param::(self.table.object.as_str())?; - out.push_sql(" as entity, to_jsonb(e.*) as data\n"); - out.push_sql(" from "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" e\n where "); - self.table.primary_key().eq(&self.key.entity_id, &mut out)?; - out.push_sql(" and "); - if self.table.has_causality_region { - out.push_sql("causality_region = "); - out.push_bind_param::(&self.key.causality_region)?; - out.push_sql(" and "); - } - self.br_column.contains(&mut out, true) - } -} - -impl<'a> QueryId for FindQuery<'a> { - type QueryId = (); - - const HAS_STATIC_QUERY_ID: bool = false; -} - -impl<'a> Query for FindQuery<'a> { - type SqlType = Untyped; -} - -impl<'a, Conn> RunQueryDsl for FindQuery<'a> {} - /// Builds a query over a given set of [`Table`]s in an attempt to find updated /// and/or newly inserted entities at a given block number; i.e. such that the /// block range's lower bound is equal to said block number. From d3ff34d73cc9a97b1fde381ac42cc604ddce7bb8 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 24 Apr 2024 17:11:51 -0700 Subject: [PATCH 2/7] store: More finegrained Diesel helpers --- store/postgres/src/block_range.rs | 8 - store/postgres/src/relational/dsl.rs | 20 + store/postgres/src/relational/query_tests.rs | 5 +- store/postgres/src/relational_queries.rs | 1224 ++++++++---------- 4 files changed, 574 insertions(+), 683 deletions(-) diff --git a/store/postgres/src/block_range.rs b/store/postgres/src/block_range.rs index 1d81eac5e81..8286f4a4243 100644 --- a/store/postgres/src/block_range.rs +++ b/store/postgres/src/block_range.rs @@ -280,14 +280,6 @@ impl<'a> BlockRangeColumn<'a> { } } - /// Output the name of the block range column without the table prefix - pub(crate) fn bare_name(&self, out: &mut AstPass) { - match self { - BlockRangeColumn::Mutable { .. } => out.push_sql(BLOCK_RANGE_COLUMN), - BlockRangeColumn::Immutable { .. } => out.push_sql(BLOCK_COLUMN), - } - } - /// Output an expression that matches all rows that have been changed /// after `block` (inclusive) pub(crate) fn changed_since<'b>(&'b self, out: &mut AstPass<'_, 'b, Pg>) -> QueryResult<()> { diff --git a/store/postgres/src/relational/dsl.rs b/store/postgres/src/relational/dsl.rs index 67c9cc781b1..b11b0858a5d 100644 --- a/store/postgres/src/relational/dsl.rs +++ b/store/postgres/src/relational/dsl.rs @@ -693,6 +693,26 @@ impl<'a> Column<'a> { pub fn name(&self) -> &'a str { &self.column.name } + + pub(crate) fn is_list(&self) -> bool { + self.column.is_list() + } + + pub(crate) fn is_primary_key(&self) -> bool { + self.column.is_primary_key() + } + + pub(crate) fn is_fulltext(&self) -> bool { + self.column.is_fulltext() + } + + pub(crate) fn column_type(&self) -> &'a ColumnType { + &self.column.column_type + } + + pub(crate) fn use_prefix_comparison(&self) -> bool { + self.column.use_prefix_comparison + } } impl std::fmt::Display for Column<'_> { diff --git a/store/postgres/src/relational/query_tests.rs b/store/postgres/src/relational/query_tests.rs index 06a98db4353..7a3286227f6 100644 --- a/store/postgres/src/relational/query_tests.rs +++ b/store/postgres/src/relational/query_tests.rs @@ -49,8 +49,9 @@ fn filter_contains(filter: EntityFilter, sql: &str) { let layout = test_layout(SCHEMA); let table = layout .table_for_entity(&layout.input_schema.entity_type("Thing").unwrap()) - .unwrap(); - let filter = Filter::main(&layout, table.as_ref(), &filter, Default::default()).unwrap(); + .unwrap() + .dsl_table(); + let filter = Filter::main(&layout, table, &filter, Default::default()).unwrap(); let query = debug_query::(&filter); assert!( query.to_string().contains(sql), diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index 3af425b6b86..742ecefb1f5 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -11,6 +11,7 @@ use diesel::query_dsl::RunQueryDsl; use diesel::result::{Error as DieselError, QueryResult}; use diesel::sql_types::Untyped; use diesel::sql_types::{Array, BigInt, Binary, Bool, Int8, Integer, Jsonb, Text, Timestamptz}; +use diesel::QuerySource as _; use graph::components::store::write::{EntityWrite, RowGroup, WriteChunk}; use graph::components::store::{Child as StoreChild, DerivedEntityQuery}; use graph::data::store::{Id, IdType, NULL}; @@ -33,8 +34,9 @@ use std::iter::FromIterator; use std::str::FromStr; use std::string::ToString; +use crate::relational::dsl::AtBlock; use crate::relational::{ - Column, ColumnType, Layout, SqlName, Table, BYTE_ARRAY_PREFIX_SIZE, PRIMARY_KEY_COLUMN, + dsl, Column, ColumnType, Layout, SqlName, Table, BYTE_ARRAY_PREFIX_SIZE, PRIMARY_KEY_COLUMN, STRING_PREFIX_SIZE, }; use crate::{ @@ -101,26 +103,14 @@ macro_rules! constraint_violation { }} } -/// Conveniences for handling foreign keys depending on whether we are using -/// `IdType::Bytes` or `IdType::String` as the primary key -/// -/// This trait adds some capabilities to `Column` that are very specific to -/// how we generate SQL queries. Using a method like `bind_ids` from this -/// trait on a given column means "send these values to the database in a form -/// that can later be used for comparisons with that column" -trait ForeignKeyClauses { - /// The name of the column - fn name(&self) -> &str; - - /// Generate a clause - /// `exists (select 1 from unnest($ids) as p(g$id) where id = p.g$id)` - /// using the right types to bind `$ids` into `out` - fn is_in<'b>(&self, ids: &'b IdList, out: &mut AstPass<'_, 'b, Pg>) -> QueryResult<()> { - out.push_sql("exists (select 1 from unnest("); - ids.push_bind_param(out)?; - out.push_sql(") as p(g$id) where id = p.g$id)"); - Ok(()) - } +/// Generate a clause +/// `exists (select 1 from unnest($ids) as p(g$id) where id = p.g$id)` +/// using the right types to bind `$ids` into `out` +fn id_is_in<'b>(ids: &'b IdList, out: &mut AstPass<'_, 'b, Pg>) -> QueryResult<()> { + out.push_sql("exists (select 1 from unnest("); + ids.push_bind_param(out)?; + out.push_sql(") as p(g$id) where id = p.g$id)"); + Ok(()) } /// This trait is here to deal with the fact that we can't implement `ToSql` @@ -155,12 +145,6 @@ impl PushBindParam for IdList { } } -impl ForeignKeyClauses for Column { - fn name(&self) -> &str { - self.name.as_str() - } -} - pub trait FromEntityData: Sized { /// Whether to include the internal keys `__typename` and `g$parent_id`. const WITH_INTERNAL_KEYS: bool; @@ -898,7 +882,7 @@ enum PrefixType { } impl PrefixType { - fn new(column: &QualColumn<'_>) -> QueryResult { + fn new(column: &dsl::Column<'_>) -> QueryResult { match column.column_type() { ColumnType::String => Ok(PrefixType::String), ColumnType::Bytes => Ok(PrefixType::Bytes), @@ -915,7 +899,7 @@ impl PrefixType { /// for the column fn push_column_prefix<'b>( self, - column: &'b QualColumn<'_>, + column: &'b dsl::Column<'b>, out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { match self { @@ -971,14 +955,14 @@ fn is_large_string(s: &String) -> Result { pub struct PrefixComparison<'a> { op: Comparison, kind: PrefixType, - column: QualColumn<'a>, + column: dsl::Column<'a>, value: QueryValue<'a>, } impl<'a> PrefixComparison<'a> { fn new( op: Comparison, - column: QualColumn<'a>, + column: dsl::Column<'a>, column_type: &'a ColumnType, text: &'a Value, ) -> Result { @@ -1134,23 +1118,21 @@ impl<'a> QueryFragment for PrefixComparison<'a> { /// filtered with `child_filter`` #[derive(Debug)] pub struct QueryChild<'a> { - parent_column: &'a Column, - child_table: &'a Table, - child_column: &'a Column, + parent_column: dsl::Column<'a>, + child_from: dsl::FromTable<'a>, + child_column: dsl::Column<'a>, child_filter: Filter<'a>, derived: bool, - br_column: BlockRangeColumn<'a>, + at_block: dsl::AtBlock<'a>, } impl<'a> QueryChild<'a> { fn new( layout: &'a Layout, - parent_table: &'a Table, + parent_table: dsl::Table<'a>, child: &'a StoreChild, block: BlockNumber, ) -> Result { - const CHILD_PREFIX: &str = "i."; - let StoreChild { attr, entity_type, @@ -1158,7 +1140,7 @@ impl<'a> QueryChild<'a> { derived, } = child; let derived = *derived; - let child_table = layout.table_for_entity(entity_type)?; + let child_table = layout.table_for_entity(entity_type)?.dsl_table().child(0); let (parent_column, child_column) = if derived { // If the parent is derived, the child column is picked based on // the provided attribute and the parent column is the primary @@ -1176,16 +1158,16 @@ impl<'a> QueryChild<'a> { child_table.primary_key(), ) }; - let br_column = BlockRangeColumn::new(child_table, CHILD_PREFIX, block); + let at_block = child_table.at_block(block).filters_by_id(!derived); let child_filter = Filter::new(layout, child_table, filter, block, ColumnQual::Child)?; - + let child_from = child_table.from_clause(); Ok(Self { parent_column, - child_table, + child_from, child_column, child_filter, derived, - br_column, + at_block, }) } } @@ -1196,68 +1178,52 @@ impl<'a> QueryFragment for QueryChild<'a> { let QueryChild { parent_column, - child_table, + child_from, child_column, child_filter, derived, - br_column, + at_block, } = self; let derived = *derived; - let child_prefix = "i."; - let parent_prefix = "c."; - out.push_sql("exists (select 1 from "); - out.push_sql(child_table.qualified_name.as_str()); - out.push_sql(" as i"); + child_from.walk_ast(out.reborrow())?; out.push_sql(" where "); - let mut is_type_c_or_d = false; - // Join tables if derived { if child_column.is_list() { // Type A: c.id = any(i.{parent_field}) - out.push_sql(parent_prefix); - out.push_identifier(parent_column.name.as_str())?; + parent_column.walk_ast(out.reborrow())?; out.push_sql(" = any("); - out.push_sql(child_prefix); - out.push_identifier(child_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; out.push_sql(")"); } else { // Type B: c.id = i.{parent_field} - out.push_sql(parent_prefix); - out.push_identifier(parent_column.name.as_str())?; + parent_column.walk_ast(out.reborrow())?; out.push_sql(" = "); - out.push_sql(child_prefix); - out.push_identifier(child_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; } } else { - is_type_c_or_d = true; - if parent_column.is_list() { // Type C: i.id = any(c.child_ids) - out.push_sql(child_prefix); - out.push_identifier(child_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; out.push_sql(" = any("); - out.push_sql(parent_prefix); - out.push_identifier(parent_column.name.as_str())?; + parent_column.walk_ast(out.reborrow())?; out.push_sql(")"); } else { // Type D: i.id = c.child_id - out.push_sql(child_prefix); - out.push_identifier(child_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; out.push_sql(" = "); - out.push_sql(parent_prefix); - out.push_identifier(parent_column.name.as_str())?; + parent_column.walk_ast(out.reborrow())?; } } out.push_sql(" and "); // Match by block - br_column.contains(&mut out, is_type_c_or_d)?; + at_block.walk_ast(out.reborrow())?; out.push_sql(" and "); @@ -1278,13 +1244,6 @@ enum ColumnQual { } impl ColumnQual { - fn with<'a>(&self, column: &'a Column) -> QualColumn<'a> { - match self { - ColumnQual::Main => QualColumn::Main(column), - ColumnQual::Child => QualColumn::Child(column), - } - } - /// Return `true` if we allow a nested child filter. That's allowed as /// long as we are filtering the main table fn allow_child(&self) -> bool { @@ -1293,55 +1252,6 @@ impl ColumnQual { ColumnQual::Child => false, } } - - fn prefix(&self) -> &'static str { - match self { - ColumnQual::Main => "c.", - ColumnQual::Child => "i.", - } - } -} - -/// A qualified column name. This is either `c.{column}` or `i.{column}` -#[derive(Debug)] -pub enum QualColumn<'a> { - Main(&'a Column), - Child(&'a Column), -} -impl QualColumn<'_> { - fn column_type(&self) -> &ColumnType { - &self.column().column_type - } - - fn prefix(&self) -> &str { - match self { - QualColumn::Main(_) => "c.", - QualColumn::Child(_) => "i.", - } - } - - fn column(&self) -> &Column { - match self { - QualColumn::Main(column) => column, - QualColumn::Child(column) => column, - } - } -} - -impl std::fmt::Display for QualColumn<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - QualColumn::Main(column) => write!(f, "{}", column.name), - QualColumn::Child(column) => write!(f, "{}", column.name), - } - } -} - -impl QueryFragment for QualColumn<'_> { - fn walk_ast<'a>(&self, mut out: AstPass<'_, 'a, Pg>) -> QueryResult<()> { - out.push_sql(self.prefix()); - out.push_identifier(self.column().name.as_str()) - } } /// The equivalent of `EntityFilter` with columns resolved and various @@ -1354,29 +1264,29 @@ pub enum Filter<'a> { And(Vec>), Or(Vec>), PrefixCmp(PrefixComparison<'a>), - Cmp(QualColumn<'a>, Comparison, QueryValue<'a>), - In(QualColumn<'a>, Vec>), - NotIn(QualColumn<'a>, Vec>), + Cmp(dsl::Column<'a>, Comparison, QueryValue<'a>), + In(dsl::Column<'a>, Vec>), + NotIn(dsl::Column<'a>, Vec>), Contains { - column: QualColumn<'a>, + column: dsl::Column<'a>, op: ContainsOp, pattern: QueryValue<'a>, }, StartsOrEndsWith { - column: QualColumn<'a>, + column: dsl::Column<'a>, op: &'static str, pattern: String, }, - ChangeBlockGte(BlockRangeColumn<'a>), + ChangeBlockGte(dsl::ChangedSince<'a>), Child(Box>), /// The value is never null for fulltext queries - Fulltext(QualColumn<'a>, QueryValue<'a>), + Fulltext(dsl::Column<'a>, QueryValue<'a>), } impl<'a> Filter<'a> { pub fn main( layout: &'a Layout, - table: &'a Table, + table: dsl::Table<'a>, filter: &'a EntityFilter, block: BlockNumber, ) -> Result { @@ -1385,32 +1295,30 @@ impl<'a> Filter<'a> { fn new( layout: &'a Layout, - table: &'a Table, + table: dsl::Table<'a>, filter: &'a EntityFilter, block: BlockNumber, qual: ColumnQual, ) -> Result { fn column_and_value<'v>( - prefix: ColumnQual, - table: &'v Table, + table: dsl::Table<'v>, attr: &String, value: &'v Value, - ) -> Result<(QualColumn<'v>, QueryValue<'v>), StoreError> { + ) -> Result<(dsl::Column<'v>, QueryValue<'v>), StoreError> { + let column = table.column_for_field(attr)?; + let value = QueryValue::new(value, column.column_type())?; let column = table.column_for_field(attr)?; - let value = QueryValue::new(value, &column.column_type)?; - let column = prefix.with(table.column_for_field(attr)?); Ok((column, value)) } fn starts_or_ends_with<'s>( - qual: ColumnQual, - table: &'s Table, + table: dsl::Table<'s>, attr: &String, value: &Value, op: &'static str, starts_with: bool, ) -> Result, StoreError> { - let column = qual.with(table.column_for_field(attr)?); + let column = table.column_for_field(attr)?; match value { Value::String(s) => { @@ -1443,8 +1351,7 @@ impl<'a> Filter<'a> { } fn cmp<'s>( - qual: ColumnQual, - table: &'s Table, + table: dsl::Table<'s>, attr: &String, op: Comparison, value: &'s Value, @@ -1453,27 +1360,24 @@ impl<'a> Filter<'a> { op.suitable(value)?; - if column.use_prefix_comparison && !value.is_null() { - let column_type = &column.column_type; - let column = qual.with(column); + if column.use_prefix_comparison() && !value.is_null() { + let column_type = column.column_type(); PrefixComparison::new(op, column, column_type, value) .map(|pc| Filter::PrefixCmp(pc)) } else { - let value = QueryValue::new(value, &column.column_type)?; - let column = qual.with(column); + let value = QueryValue::new(value, column.column_type())?; Ok(Filter::Cmp(column, op, value)) } } fn contains<'s>( - qual: ColumnQual, - table: &'s Table, + table: dsl::Table<'s>, attr: &String, op: ContainsOp, value: &'s Value, ) -> Result, StoreError> { let column = table.column_for_field(attr)?; - let pattern = QueryValue::new(value, &column.column_type)?; + let pattern = QueryValue::new(value, column.column_type())?; let pattern = match &pattern.value { SqlValue::String(s) => { if s.starts_with('%') || s.ends_with('%') { @@ -1508,7 +1412,6 @@ impl<'a> Filter<'a> { | SqlValue::Bytes(_) | SqlValue::Binary(_) => pattern, }; - let column = qual.with(column); Ok(Filter::Contains { column, op, @@ -1533,57 +1436,49 @@ impl<'a> Filter<'a> { .map(|f| F::new(layout, table, f, block, qual)) .collect::>()?, )), - Equal(attr, value) => cmp(qual, table, attr, C::Equal, value), - Not(attr, value) => cmp(qual, table, attr, C::NotEqual, value), - GreaterThan(attr, value) => cmp(qual, table, attr, C::Greater, value), - LessThan(attr, value) => cmp(qual, table, attr, C::Less, value), - GreaterOrEqual(attr, value) => cmp(qual, table, attr, C::GreaterOrEqual, value), - LessOrEqual(attr, value) => cmp(qual, table, attr, C::LessOrEqual, value), + Equal(attr, value) => cmp(table, attr, C::Equal, value), + Not(attr, value) => cmp(table, attr, C::NotEqual, value), + GreaterThan(attr, value) => cmp(table, attr, C::Greater, value), + LessThan(attr, value) => cmp(table, attr, C::Less, value), + GreaterOrEqual(attr, value) => cmp(table, attr, C::GreaterOrEqual, value), + LessOrEqual(attr, value) => cmp(table, attr, C::LessOrEqual, value), In(attr, values) => { let column = table.column_for_field(attr.as_str())?; - let values = QueryValue::many(values, &column.column_type)?; - let column = qual.with(column); + let values = QueryValue::many(values, column.column_type())?; Ok(F::In(column, values)) } NotIn(attr, values) => { let column = table.column_for_field(attr.as_str())?; - let values = QueryValue::many(values, &column.column_type)?; - let column = qual.with(column); + let values = QueryValue::many(values, &column.column_type())?; Ok(F::NotIn(column, values)) } - Contains(attr, value) => contains(qual, table, attr, K::Like, value), - ContainsNoCase(attr, value) => contains(qual, table, attr, K::ILike, value), - NotContains(attr, value) => contains(qual, table, attr, K::NotLike, value), - NotContainsNoCase(attr, value) => contains(qual, table, attr, K::NotILike, value), + Contains(attr, value) => contains(table, attr, K::Like, value), + ContainsNoCase(attr, value) => contains(table, attr, K::ILike, value), + NotContains(attr, value) => contains(table, attr, K::NotLike, value), + NotContainsNoCase(attr, value) => contains(table, attr, K::NotILike, value), - StartsWith(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " like ", true) - } + StartsWith(attr, value) => starts_or_ends_with(table, attr, value, " like ", true), StartsWithNoCase(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " ilike ", true) + starts_or_ends_with(table, attr, value, " ilike ", true) } NotStartsWith(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " not like ", true) + starts_or_ends_with(table, attr, value, " not like ", true) } NotStartsWithNoCase(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " not ilike ", true) + starts_or_ends_with(table, attr, value, " not ilike ", true) } - EndsWith(attr, value) => starts_or_ends_with(qual, table, attr, value, " like ", false), + EndsWith(attr, value) => starts_or_ends_with(table, attr, value, " like ", false), EndsWithNoCase(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " ilike ", false) + starts_or_ends_with(table, attr, value, " ilike ", false) } NotEndsWith(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " not like ", false) + starts_or_ends_with(table, attr, value, " not like ", false) } NotEndsWithNoCase(attr, value) => { - starts_or_ends_with(qual, table, attr, value, " not ilike ", false) + starts_or_ends_with(table, attr, value, " not ilike ", false) } - ChangeBlockGte(num) => Ok(F::ChangeBlockGte(BlockRangeColumn::new( - table, - qual.prefix(), - *num, - ))), + ChangeBlockGte(num) => Ok(F::ChangeBlockGte(table.changed_since(*num))), Child(child) => { if !qual.allow_child() { return Err(StoreError::ChildFilterNestingNotSupportedError( @@ -1595,7 +1490,7 @@ impl<'a> Filter<'a> { Ok(F::Child(Box::new(child))) } Fulltext(attr, value) => { - let (column, value) = column_and_value(qual, table, attr, value)?; + let (column, value) = column_and_value(table, attr, value)?; if value.is_null() { return Err(StoreError::UnsupportedFilter( "fulltext".to_owned(), @@ -1629,7 +1524,7 @@ impl<'a> Filter<'a> { } fn cmp<'b>( - column: &'b QualColumn<'b>, + column: &'b dsl::Column<'b>, qv: &'b QueryValue<'b>, op: Comparison, mut out: AstPass<'_, 'b, Pg>, @@ -1652,7 +1547,7 @@ impl<'a> Filter<'a> { } fn fulltext<'b>( - column: &'b QualColumn, + column: &'b dsl::Column<'b>, qv: &'b QueryValue, mut out: AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { @@ -1663,7 +1558,7 @@ impl<'a> Filter<'a> { } fn contains<'b>( - column: &'b QualColumn, + column: &'b dsl::Column<'b>, op: &'b ContainsOp, qv: &'b QueryValue, mut out: AstPass<'_, 'b, Pg>, @@ -1728,7 +1623,7 @@ impl<'a> Filter<'a> { } fn in_array<'b>( - column: &'b QualColumn, + column: &'b dsl::Column<'b>, values: &'b [QueryValue], negated: bool, mut out: AstPass<'_, 'b, Pg>, @@ -1772,8 +1667,8 @@ impl<'a> Filter<'a> { } if have_non_nulls { - if column.column().use_prefix_comparison - && PrefixType::new(column).is_ok() + if column.use_prefix_comparison() + && PrefixType::new(&column).is_ok() && values.iter().all(|v| match &v.value { SqlValue::Text(s) => s.len() < STRING_PREFIX_SIZE, SqlValue::String(s) => s.len() < STRING_PREFIX_SIZE, @@ -1788,7 +1683,7 @@ impl<'a> Filter<'a> { // query optimizer // See PrefixComparison for a more detailed discussion of what // is happening here - PrefixType::new(column)?.push_column_prefix(column, &mut out.reborrow())?; + PrefixType::new(&column)?.push_column_prefix(&column, &mut out.reborrow())?; } else { column.walk_ast(out.reborrow())?; } @@ -1860,12 +1755,12 @@ impl<'a> fmt::Display for Filter<'a> { } => { write!(f, "{column} {op} '{pattern}'") } - ChangeBlockGte(b) => write!(f, "block >= {}", b.block()), + ChangeBlockGte(b) => write!(f, "{}", b), Child(child /* a, et, cf, _ */) => write!( f, "join on {} with {}({})", child.child_column.name(), - child.child_table.name, + child.child_from, child.child_filter ), } @@ -1900,7 +1795,7 @@ impl<'a> QueryFragment for Filter<'a> { out.push_sql(op); out.push_bind_param::(pattern)?; } - ChangeBlockGte(br_column) => br_column.changed_since(&mut out)?, + ChangeBlockGte(changed_since) => changed_since.walk_ast(out.reborrow())?, Child(child) => child.walk_ast(out)?, } Ok(()) @@ -2067,9 +1962,7 @@ impl<'a> QueryFragment for FindManyQuery<'a> { out.push_sql(" from "); out.push_sql(table.qualified_name.as_str()); out.push_sql(" e\n where "); - table - .primary_key() - .is_in(&self.ids_for_type[&(table.object.clone(), *cr)], &mut out)?; + id_is_in(&self.ids_for_type[&(table.object.clone(), *cr)], &mut out)?; out.push_sql(" and "); if table.has_causality_region { out.push_sql("causality_region = "); @@ -2470,7 +2363,7 @@ impl ParentIds { /// corresponding table and column #[derive(Debug, Clone)] enum TableLink<'a> { - Direct(&'a Column, ChildMultiplicity), + Direct(dsl::Column<'a>, ChildMultiplicity), /// The `Table` is the parent table Parent(&'a Table, ParentIds), } @@ -2478,7 +2371,7 @@ enum TableLink<'a> { impl<'a> TableLink<'a> { fn new( layout: &'a Layout, - child_table: &'a Table, + child_table: dsl::Table<'a>, link: EntityLink, ) -> Result { match link { @@ -2542,7 +2435,8 @@ impl<'a> ParentLimit<'a> { #[derive(Debug)] pub struct FilterWindow<'a> { /// The table from which we take entities - table: &'a Table, + table: dsl::Table<'a>, + from_table: dsl::FromTable<'a>, /// The overall filter for the entire query query_filter: Option>, /// The parent ids we are interested in. The type in the database @@ -2554,7 +2448,7 @@ pub struct FilterWindow<'a> { /// How to filter by a set of parents link: TableLink<'a>, column_names: AttributeNames, - br_column: BlockRangeColumn<'a>, + at_block: AtBlock<'a>, } impl<'a> FilterWindow<'a> { @@ -2570,7 +2464,7 @@ impl<'a> FilterWindow<'a> { link, column_names, } = window; - let table = layout.table_for_entity(&child_type)?.as_ref(); + let table = layout.table_for_entity(&child_type)?.as_ref().dsl_table(); // Confidence check: ensure that all selected column names exist in the table if let AttributeNames::Select(ref selected_field_names) = column_names { @@ -2583,20 +2477,23 @@ impl<'a> FilterWindow<'a> { .map(|filter| Filter::main(layout, table, filter, block)) .transpose()?; let link = TableLink::new(layout, table, link)?; - let br_column = BlockRangeColumn::new(table, "c.", block); + let at_block = table + .at_block(block) + .filters_by_id(matches!(link, TableLink::Parent(_, _))); Ok(FilterWindow { table, + from_table: table.from_clause(), query_filter, ids, link, column_names, - br_column, + at_block, }) } fn parent_type(&self) -> QueryResult { match &self.link { - TableLink::Direct(column, _) => column.column_type.id_type(), + TableLink::Direct(column, _) => column.column_type().id_type(), TableLink::Parent(parent_table, _) => parent_table.primary_key().column_type.id_type(), } } @@ -2611,7 +2508,7 @@ impl<'a> FilterWindow<'a> { fn children_type_a<'b>( &'b self, - column: &Column, + column: &'b dsl::Column<'b>, is_outer: bool, limit: &'b ParentLimit<'_>, out: &mut AstPass<'_, 'b, Pg>, @@ -2634,12 +2531,12 @@ impl<'a> FilterWindow<'a> { out.push_sql(") as p(id) cross join lateral (select "); write_column_names(&self.column_names, self.table, None, out)?; out.push_sql(" from "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where "); - self.br_column.contains(out, false)?; + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where "); + self.at_block.walk_ast(out.reborrow())?; limit.filter(is_outer, out); - out.push_sql(" and p.id = any(c."); - out.push_identifier(column.name.as_str())?; + out.push_sql(" and p.id = any("); + column.walk_ast(out.reborrow())?; out.push_sql(")"); self.and_filter(out)?; limit.restrict(is_outer, out)?; @@ -2649,7 +2546,7 @@ impl<'a> FilterWindow<'a> { fn child_type_a<'b>( &'b self, - column: &Column, + column: &'b dsl::Column<'b>, is_outer: bool, limit: &'b ParentLimit<'_>, out: &mut AstPass<'_, 'b, Pg>, @@ -2670,16 +2567,16 @@ impl<'a> FilterWindow<'a> { out.push_sql("\n/* child_type_a */ from unnest("); self.ids.push_bind_param(out)?; out.push_sql(") as p(id), "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where "); - self.br_column.contains(out, false)?; + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where "); + self.at_block.walk_ast(out.reborrow())?; limit.filter(is_outer, out); - out.push_sql(" and c."); - out.push_identifier(column.name.as_str())?; + out.push_sql(" and "); + column.walk_ast(out.reborrow())?; out.push_sql(" @> array[p.id]"); if self.ids.len() < ENV_VARS.store.typea_batch_size { - out.push_sql(" and c."); - out.push_identifier(column.name.as_str())?; + out.push_sql(" and "); + column.walk_ast(out.reborrow())?; out.push_sql(" && "); self.ids.push_bind_param(out)?; } @@ -2690,7 +2587,7 @@ impl<'a> FilterWindow<'a> { fn children_type_b<'b>( &'b self, - column: &Column, + column: &'b dsl::Column<'b>, is_outer: bool, limit: &'b ParentLimit<'_>, out: &mut AstPass<'_, 'b, Pg>, @@ -2713,12 +2610,12 @@ impl<'a> FilterWindow<'a> { out.push_sql(") as p(id) cross join lateral (select "); write_column_names(&self.column_names, self.table, None, out)?; out.push_sql(" from "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where "); - self.br_column.contains(out, false)?; + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where "); + self.at_block.walk_ast(out.reborrow())?; limit.filter(is_outer, out); - out.push_sql(" and p.id = c."); - out.push_identifier(column.name.as_str())?; + out.push_sql(" and p.id = "); + column.walk_ast(out.reborrow())?; self.and_filter(out)?; limit.restrict(is_outer, out)?; out.push_sql(") c"); @@ -2727,7 +2624,7 @@ impl<'a> FilterWindow<'a> { fn child_type_b<'b>( &'b self, - column: &Column, + column: &'b dsl::Column<'b>, is_outer: bool, limit: &'b ParentLimit<'_>, out: &mut AstPass<'_, 'b, Pg>, @@ -2743,12 +2640,12 @@ impl<'a> FilterWindow<'a> { out.push_sql("\n/* child_type_b */ from unnest("); self.ids.push_bind_param(out)?; out.push_sql(") as p(id), "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where "); - self.br_column.contains(out, false)?; + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where "); + self.at_block.walk_ast(out.reborrow())?; limit.filter(is_outer, out); - out.push_sql(" and p.id = c."); - out.push_identifier(column.name.as_str())?; + out.push_sql(" and p.id = "); + column.walk_ast(out.reborrow())?; self.and_filter(out)?; limit.single_limit(is_outer, self.ids.len(), out); Ok(()) @@ -2797,9 +2694,9 @@ impl<'a> FilterWindow<'a> { out.push_sql(" cross join lateral (select "); write_column_names(&self.column_names, self.table, None, out)?; out.push_sql(" from "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where "); - self.br_column.contains(out, true)?; + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where "); + self.at_block.walk_ast(out.reborrow())?; limit.filter(is_outer, out); out.push_sql(" and c.id = any(p.child_ids)"); self.and_filter(out)?; @@ -2815,8 +2712,8 @@ impl<'a> FilterWindow<'a> { out.push_sql("from unnest(array[]::text[]) as p(id) cross join (select "); write_column_names(&self.column_names, self.table, None, out)?; out.push_sql(" from "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where false) c"); + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where false) c"); } Ok(()) } @@ -2839,9 +2736,9 @@ impl<'a> FilterWindow<'a> { out.push_sql("), unnest("); child_ids.push_bind_param(out)?; out.push_sql(")) as p(id, child_id), "); - out.push_sql(self.table.qualified_name.as_str()); - out.push_sql(" c where "); - self.br_column.contains(out, true)?; + self.from_table.walk_ast(out.reborrow())?; + out.push_sql(" where "); + self.at_block.walk_ast(out.reborrow())?; limit.filter(is_outer, out); // Include a constraint on the child IDs as a set if the size of the set @@ -2902,7 +2799,7 @@ impl<'a> FilterWindow<'a> { mut out: AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { out.push_sql("select '"); - out.push_sql(self.table.object.as_str()); + out.push_sql(self.table.meta.object.as_str()); out.push_sql("' as entity, c.id, c.vid, p.id::text as "); out.push_sql(&*PARENT_ID); limit @@ -2921,10 +2818,11 @@ impl<'a> FilterWindow<'a> { #[derive(Debug)] pub struct WholeTable<'a> { - table: &'a Table, + table: dsl::Table<'a>, + from_table: dsl::FromTable<'a>, filter: Option>, column_names: AttributeNames, - br_column: BlockRangeColumn<'a>, + at_block: AtBlock<'a>, } impl<'a> WholeTable<'a> { @@ -2935,16 +2833,25 @@ impl<'a> WholeTable<'a> { column_names: AttributeNames, block: BlockNumber, ) -> Result { - let table = layout.table_for_entity(entity_type).map(|rc| rc.as_ref())?; + let table = layout + .table_for_entity(entity_type) + .map(|rc| rc.as_ref())? + .dsl_table(); let filter = entity_filter .map(|filter| Filter::main(layout, table, filter, block)) .transpose()?; - let br_column = BlockRangeColumn::new(table, "c.", block); + + let filters_by_id = { + matches!(filter.as_ref(), Some(Filter::Cmp(column, Comparison::Equal, _)) if column.is_primary_key()) + }; + + let at_block = table.at_block(block).filters_by_id(filters_by_id); Ok(WholeTable { table, + from_table: table.from_clause(), filter, column_names, - br_column, + at_block, }) } } @@ -2966,11 +2873,15 @@ impl<'a> fmt::Display for FilterCollection<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), std::fmt::Error> { fn fmt_table( f: &mut fmt::Formatter, - table: &Table, + table: dsl::Table<'_>, attrs: &AttributeNames, filter: &Option, ) -> Result<(), std::fmt::Error> { - write!(f, "{}[", table.qualified_name.as_str().replace("\\\"", ""))?; + write!( + f, + "{}[", + table.meta.qualified_name.as_str().replace("\\\"", "") + )?; match attrs { AttributeNames::All => write!(f, "*")?, AttributeNames::Select(cols) => write!(f, "{}", cols.iter().join(","))?, @@ -2985,13 +2896,14 @@ impl<'a> fmt::Display for FilterCollection<'a> { fn fmt_window(f: &mut fmt::Formatter, w: &FilterWindow) -> Result<(), std::fmt::Error> { let FilterWindow { table, + from_table: _, query_filter, ids, link, column_names, - br_column: _, + at_block: _, } = w; - fmt_table(f, table, column_names, query_filter)?; + fmt_table(f, *table, column_names, query_filter)?; if !ids.is_empty() { use ChildMultiplicity::*; @@ -3085,7 +2997,7 @@ impl<'a> FilterCollection<'a> { } } - fn first_table(&self) -> Option<&Table> { + fn first_table(&self) -> Option> { match self { FilterCollection::All(entities) => entities.first().map(|wh| wh.table), FilterCollection::SingleWindow(window) => Some(window.table), @@ -3103,10 +3015,10 @@ impl<'a> FilterCollection<'a> { fn all_mutable(&self) -> bool { match self { - FilterCollection::All(entities) => entities.iter().all(|wh| !wh.table.immutable), - FilterCollection::SingleWindow(window) => !window.table.immutable, + FilterCollection::All(entities) => entities.iter().all(|wh| !wh.table.meta.immutable), + FilterCollection::SingleWindow(window) => !window.table.meta.immutable, FilterCollection::MultiWindow(windows, _) => { - windows.iter().all(|window| !window.table.immutable) + windows.iter().all(|window| !window.table.meta.immutable) } } } @@ -3134,33 +3046,33 @@ impl<'a> FilterCollection<'a> { #[derive(Debug, Clone)] pub struct ChildKeyDetails<'a> { /// Column in the parent table that stores the connection between the parent and the child - pub parent_join_column: &'a Column, + pub parent_join_column: dsl::Column<'a>, /// Table representing the child entity - pub child_table: &'a Table, + pub child_table: dsl::Table<'a>, + pub child_from: dsl::FromTable<'a>, /// Column in the child table that stores the connection between the child and the parent - pub child_join_column: &'a Column, + pub child_join_column: dsl::Column<'a>, + pub child_at_block: dsl::AtBlock<'a>, /// Column of the child table that sorting is done on - pub sort_by_column: &'a Column, - /// Prefix for the child table - pub prefix: String, + pub sort_by_column: dsl::Column<'a>, /// Either `asc` or `desc` pub direction: &'static str, } #[derive(Debug, Clone)] pub struct ChildKeyAndIdSharedDetails<'a> { - /// Table representing the parent entity - pub parent_table: &'a Table, /// Column in the parent table that stores the connection between the parent and the child - pub parent_join_column: &'a Column, + pub parent_join_column: dsl::Column<'a>, /// Table representing the child entity - pub child_table: &'a Table, + pub child_table: dsl::Table<'a>, + pub child_from: dsl::FromTable<'a>, /// Column in the child table that stores the connection between the child and the parent - pub child_join_column: &'a Column, + pub child_join_column: dsl::Column<'a>, + pub child_pk: dsl::Column<'a>, + pub child_br: dsl::BlockColumn<'a>, + pub child_at_block: dsl::AtBlock<'a>, /// Column of the child table that sorting is done on - pub sort_by_column: &'a Column, - /// Prefix for the child table - pub prefix: String, + pub sort_by_column: dsl::Column<'a>, /// Either `asc` or `desc` pub direction: &'static str, } @@ -3168,26 +3080,41 @@ pub struct ChildKeyAndIdSharedDetails<'a> { #[allow(unused)] #[derive(Debug, Clone)] pub struct ChildIdDetails<'a> { - /// Table representing the parent entity - pub parent_table: &'a Table, /// Column in the parent table that stores the connection between the parent and the child - pub parent_join_column: &'a Column, + pub parent_join_column: dsl::Column<'a>, /// Table representing the child entity - pub child_table: &'a Table, + pub child_table: dsl::Table<'a>, + pub child_from: dsl::FromTable<'a>, /// Column in the child table that stores the connection between the child and the parent - pub child_join_column: &'a Column, - /// Prefix for the child table - pub prefix: String, + pub child_join_column: dsl::Column<'a>, + pub child_pk: dsl::Column<'a>, + pub child_br: dsl::BlockColumn<'a>, + pub child_at_block: dsl::AtBlock<'a>, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UseBlockColumn { + Yes, + No, +} +impl UseBlockColumn { + fn block_column<'a>(&self, table: dsl::Table<'a>) -> Option> { + match self { + UseBlockColumn::Yes => Some(table.block_column()), + UseBlockColumn::No => None, + } + } } #[derive(Debug, Clone)] pub enum ChildKey<'a> { Single(ChildKeyDetails<'a>), - Many(Vec>), - IdAsc(ChildIdDetails<'a>, Option>), - IdDesc(ChildIdDetails<'a>, Option>), - ManyIdAsc(Vec>, Option>), - ManyIdDesc(Vec>, Option>), + /// First column is the primary key of the parent + Many(dsl::Column<'a>, Vec>), + IdAsc(ChildIdDetails<'a>, UseBlockColumn), + IdDesc(ChildIdDetails<'a>, UseBlockColumn), + ManyIdAsc(Vec>, UseBlockColumn), + ManyIdDesc(Vec>, UseBlockColumn), } /// Convenience to pass the name of the column to order by around. If `name` @@ -3196,12 +3123,12 @@ pub enum ChildKey<'a> { pub enum SortKey<'a> { None, /// Order by `id asc` - IdAsc(Option>), + IdAsc(Option>), /// Order by `id desc` - IdDesc(Option>), + IdDesc(Option>), /// Order by some other column; `column` will never be `id` Key { - column: &'a Column, + column: dsl::Column<'a>, value: Option<&'a str>, direction: &'static str, }, @@ -3215,10 +3142,10 @@ impl<'a> fmt::Display for SortKey<'a> { match self { SortKey::None => write!(f, "none"), SortKey::IdAsc(Option::None) => write!(f, "{}", PRIMARY_KEY_COLUMN), - SortKey::IdAsc(Some(br)) => write!(f, "{}, {}", PRIMARY_KEY_COLUMN, br.column_name()), + SortKey::IdAsc(Some(br)) => write!(f, "{}, {}", PRIMARY_KEY_COLUMN, br), SortKey::IdDesc(Option::None) => write!(f, "{} desc", PRIMARY_KEY_COLUMN), SortKey::IdDesc(Some(br)) => { - write!(f, "{} desc, {} desc", PRIMARY_KEY_COLUMN, br.column_name()) + write!(f, "{} desc, {} desc", PRIMARY_KEY_COLUMN, br) } SortKey::Key { column, @@ -3227,104 +3154,77 @@ impl<'a> fmt::Display for SortKey<'a> { } => write!( f, "{} {}, {} {}", - column.name.as_str(), - direction, - PRIMARY_KEY_COLUMN, - direction + column, direction, PRIMARY_KEY_COLUMN, direction ), SortKey::ChildKey(child) => match child { ChildKey::Single(details) => write!( f, - "{}.{} {}, {}.{} {}", - details.child_table.name.as_str(), - details.sort_by_column.name.as_str(), + "{} {}, {} {}", + details.sort_by_column, details.direction, - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN, + details.child_table.primary_key(), details.direction ), - ChildKey::Many(details) => details.iter().try_for_each(|details| { + ChildKey::Many(_, details) => details.iter().try_for_each(|details| { write!( f, - "{}.{} {}, {}.{} {}", - details.child_table.name.as_str(), - details.sort_by_column.name.as_str(), + "{} {}, {} {}", + details.sort_by_column, details.direction, - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN, + details.child_table.primary_key(), details.direction ) }), - ChildKey::ManyIdAsc(details, Option::None) => { + ChildKey::ManyIdAsc(details, UseBlockColumn::No) => details + .iter() + .try_for_each(|details| write!(f, "{}", details.child_table.primary_key())), + ChildKey::ManyIdAsc(details, UseBlockColumn::Yes) => { details.iter().try_for_each(|details| { write!( f, - "{}.{}", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN + "{}, {}", + details.child_table.primary_key(), + details.child_table.block_column() ) }) } - ChildKey::ManyIdAsc(details, Some(br)) => details.iter().try_for_each(|details| { - write!( - f, - "{}.{}, {}.{}", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN, - details.child_table.name.as_str(), - br.column_name() - ) - }), - ChildKey::ManyIdDesc(details, Option::None) => { + ChildKey::ManyIdDesc(details, UseBlockColumn::No) => { + details.iter().try_for_each(|details| { + write!(f, "{} desc", details.child_table.primary_key(),) + }) + } + ChildKey::ManyIdDesc(details, UseBlockColumn::Yes) => { details.iter().try_for_each(|details| { write!( f, - "{}.{} desc", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN + "{} desc, {} desc", + details.child_table.primary_key(), + details.child_br ) }) } - ChildKey::ManyIdDesc(details, Some(br)) => details.iter().try_for_each(|details| { + + ChildKey::IdAsc(details, UseBlockColumn::No) => { + write!(f, "{}", details.child_table.primary_key()) + } + ChildKey::IdAsc(details, UseBlockColumn::Yes) => { write!( f, - "{}.{} desc, {}.{} desc", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN, - details.child_table.name.as_str(), - br.column_name() + "{}, {}", + details.child_table.primary_key(), + details.child_br ) - }), - - ChildKey::IdAsc(details, Option::None) => write!( - f, - "{}.{}", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN - ), - ChildKey::IdAsc(details, Some(br)) => write!( - f, - "{}.{}, {}.{}", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN, - details.child_table.name.as_str(), - br.column_name() - ), - ChildKey::IdDesc(details, Option::None) => write!( - f, - "{}.{} desc", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN - ), - ChildKey::IdDesc(details, Some(br)) => { + } + ChildKey::IdDesc(details, UseBlockColumn::No) => { + write!(f, "{} desc", details.child_table.primary_key(),) + } + ChildKey::IdDesc(details, UseBlockColumn::Yes) => { write!( f, - "{}.{} desc, {}.{} desc", - details.child_table.name.as_str(), - PRIMARY_KEY_COLUMN, - details.child_table.name.as_str(), - br.column_name() + "{} desc, {} desc", + details.child_table.primary_key(), + details.child_br ) } }, @@ -3340,11 +3240,11 @@ impl<'a> SortKey<'a> { order: EntityOrder, collection: &'a FilterCollection, filter: Option<&'a EntityFilter>, - block: BlockNumber, layout: &'a Layout, + block: BlockNumber, ) -> Result { fn sort_key_from_value<'a>( - column: &'a Column, + column: dsl::Column<'a>, value: &'a Value, direction: &'static str, ) -> Result, QueryExecutionError> { @@ -3358,11 +3258,11 @@ impl<'a> SortKey<'a> { } fn with_key<'a>( - table: &'a Table, + table: dsl::Table<'a>, attribute: String, filter: Option<&'a EntityFilter>, direction: &'static str, - br_column: Option>, + use_block_column: UseBlockColumn, ) -> Result, QueryExecutionError> { let column = table.column_for_field(&attribute)?; if column.is_fulltext() { @@ -3379,9 +3279,10 @@ impl<'a> SortKey<'a> { _ => unreachable!(), } } else if column.is_primary_key() { + let block_column = use_block_column.block_column(table); match direction { - ASC => Ok(SortKey::IdAsc(br_column)), - DESC => Ok(SortKey::IdDesc(br_column)), + ASC => Ok(SortKey::IdAsc(block_column)), + DESC => Ok(SortKey::IdDesc(block_column)), _ => unreachable!("direction is 'asc' or 'desc'"), } } else { @@ -3394,14 +3295,16 @@ impl<'a> SortKey<'a> { } fn with_child_object_key<'a>( - parent_table: &'a Table, - child_table: &'a Table, + block: BlockNumber, + parent_table: dsl::Table<'a>, + child_table: dsl::Table<'a>, join_attribute: String, derived: bool, attribute: String, - br_column: Option>, + use_block_column: UseBlockColumn, direction: &'static str, ) -> Result, QueryExecutionError> { + let child_table = child_table.child(1); let sort_by_column = child_table.column_for_field(&attribute)?; if sort_by_column.is_fulltext() { Err(QueryExecutionError::NotSupported( @@ -3415,7 +3318,7 @@ impl<'a> SortKey<'a> { graph::constraint_violation!( "Column for a join attribute `{}` of `{}` table not found", join_attribute, - child_table.name.as_str() + child_table.name() ) })?, ), @@ -3426,7 +3329,7 @@ impl<'a> SortKey<'a> { graph::constraint_violation!( "Column for a join attribute `{}` of `{}` table not found", join_attribute, - parent_table.name.as_str() + parent_table.name() ) })?, child_table.primary_key(), @@ -3434,38 +3337,50 @@ impl<'a> SortKey<'a> { }; if sort_by_column.is_primary_key() { + let child_from = child_table.from_clause(); + let child_pk = child_table.primary_key(); + let child_br = child_table.block_column(); + let child_at_block = child_table.at_block(block); return match direction { ASC => Ok(SortKey::ChildKey(ChildKey::IdAsc( ChildIdDetails { - parent_table, child_table, + child_from, parent_join_column: parent_column, child_join_column: child_column, - prefix: "cc".to_string(), + child_pk, + child_br, + child_at_block, }, - br_column, + use_block_column, ))), DESC => Ok(SortKey::ChildKey(ChildKey::IdDesc( ChildIdDetails { - parent_table, child_table, + child_from, parent_join_column: parent_column, child_join_column: child_column, - prefix: "cc".to_string(), + child_pk, + child_br, + child_at_block, }, - br_column, + use_block_column, ))), _ => unreachable!("direction is 'asc' or 'desc'"), }; } + let child_table = child_table.child(1); + let child_at_block = child_table.at_block(block); + let child_from = child_table.from_clause(); Ok(SortKey::ChildKey(ChildKey::Single(ChildKeyDetails { - child_table, + child_table: child_table.child(1), + child_from, parent_join_column: parent_column, child_join_column: child_column, + child_at_block, // Sort by this column sort_by_column, - prefix: "cc".to_string(), direction, }))) } @@ -3473,16 +3388,21 @@ impl<'a> SortKey<'a> { fn build_children_vec<'a>( layout: &'a Layout, - parent_table: &'a Table, + block: BlockNumber, + parent_table: dsl::Table<'a>, entity_types: Vec, child: EntityOrderByChildInfo, direction: &'static str, ) -> Result>, QueryExecutionError> { + assert!(entity_types.len() < 255); return entity_types .iter() .enumerate() .map(|(i, entity_type)| { - let child_table = layout.table_for_entity(entity_type)?; + let child_table = layout + .table_for_entity(entity_type)? + .dsl_table() + .child((i + 1) as u8); let sort_by_column = child_table.column_for_field(&child.sort_by_attribute)?; if sort_by_column.is_fulltext() { Err(QueryExecutionError::NotSupported( @@ -3498,7 +3418,7 @@ impl<'a> SortKey<'a> { graph::constraint_violation!( "Column for a join attribute `{}` of `{}` table not found", child.join_attribute, - child_table.name.as_str() + child_table.name() ) })?, ), @@ -3509,19 +3429,25 @@ impl<'a> SortKey<'a> { graph::constraint_violation!( "Column for a join attribute `{}` of `{}` table not found", child.join_attribute, - parent_table.name.as_str() + parent_table.name() ) })?, child_table.primary_key(), ), }; + let child_pk = child_table.primary_key(); + let child_br = child_table.block_column(); + let child_at_block = child_table.at_block(block); + let child_from = child_table.from_clause(); Ok(ChildKeyAndIdSharedDetails { - parent_table, child_table, + child_from, parent_join_column: parent_column, child_join_column: child_column, - prefix: format!("cc{}", i), + child_pk, + child_br, + child_at_block, sort_by_column, direction, }) @@ -3532,79 +3458,98 @@ impl<'a> SortKey<'a> { fn with_child_interface_key<'a>( layout: &'a Layout, - parent_table: &'a Table, + block: BlockNumber, + parent_table: dsl::Table<'a>, child: EntityOrderByChildInfo, entity_types: Vec, - br_column: Option>, + use_block_column: UseBlockColumn, direction: &'static str, ) -> Result, QueryExecutionError> { - if let Some(first_entity) = entity_types.first() { - let child_table = layout.table_for_entity(first_entity)?; - let sort_by_column = child_table.column_for_field(&child.sort_by_attribute)?; + if entity_types.is_empty() { + return Err(QueryExecutionError::ConstraintViolation( + "Cannot order by child interface with no implementing entity types".to_string(), + )); + } - if sort_by_column.is_fulltext() { - Err(QueryExecutionError::NotSupported( - "Sorting by fulltext fields".to_string(), - )) - } else if sort_by_column.is_primary_key() { - if direction == ASC { - Ok(SortKey::ChildKey(ChildKey::ManyIdAsc( - build_children_vec( - layout, - parent_table, - entity_types, - child, - direction, - )? - .iter() - .map(|details| ChildIdDetails { - parent_table: details.parent_table, - child_table: details.child_table, - parent_join_column: details.parent_join_column, - child_join_column: details.child_join_column, - prefix: details.prefix.clone(), - }) - .collect(), - br_column, - ))) - } else { - Ok(SortKey::ChildKey(ChildKey::ManyIdDesc( - build_children_vec( - layout, - parent_table, - entity_types, - child, - direction, - )? - .iter() - .map(|details| ChildIdDetails { - parent_table: details.parent_table, - child_table: details.child_table, - parent_join_column: details.parent_join_column, - child_join_column: details.child_join_column, - prefix: details.prefix.clone(), - }) - .collect(), - br_column, - ))) - } + let first_entity = entity_types.first().unwrap(); + let child_table = layout.table_for_entity(first_entity)?; + let sort_by_column = child_table.column_for_field(&child.sort_by_attribute)?; + + if sort_by_column.is_fulltext() { + Err(QueryExecutionError::NotSupported( + "Sorting by fulltext fields".to_string(), + )) + } else if sort_by_column.is_primary_key() { + if direction == ASC { + Ok(SortKey::ChildKey(ChildKey::ManyIdAsc( + build_children_vec( + layout, + block, + parent_table, + entity_types, + child, + direction, + )? + .iter() + .map(|details| ChildIdDetails { + child_table: details.child_table, + child_from: details.child_from, + parent_join_column: details.parent_join_column, + child_join_column: details.child_join_column, + child_pk: details.child_pk, + child_br: details.child_br, + child_at_block: details.child_at_block, + }) + .collect(), + use_block_column, + ))) } else { - Ok(SortKey::ChildKey(ChildKey::Many( - build_children_vec(layout, parent_table, entity_types, child, direction)? - .iter() - .map(|details| ChildKeyDetails { - parent_join_column: details.parent_join_column, - child_table: details.child_table, - child_join_column: details.child_join_column, - sort_by_column: details.sort_by_column, - prefix: details.prefix.clone(), - direction: details.direction, - }) - .collect(), + Ok(SortKey::ChildKey(ChildKey::ManyIdDesc( + build_children_vec( + layout, + block, + parent_table, + entity_types, + child, + direction, + )? + .iter() + .map(|details| ChildIdDetails { + child_table: details.child_table, + child_from: details.child_from, + parent_join_column: details.parent_join_column, + child_join_column: details.child_join_column, + child_pk: details.child_pk, + child_br: details.child_br, + child_at_block: details.child_at_block, + }) + .collect(), + use_block_column, ))) } } else { - Ok(SortKey::ChildKey(ChildKey::Many(vec![]))) + Ok(SortKey::ChildKey(ChildKey::Many( + parent_table.primary_key(), + build_children_vec( + layout, + block, + parent_table, + entity_types, + child, + direction, + )? + .iter() + .map(|details| ChildKeyDetails { + parent_join_column: details.parent_join_column, + child_table: details.child_table, + child_from: details.child_from, + child_join_column: details.child_join_column, + child_at_block: details.child_at_block, + sort_by_column: details.sort_by_column, + direction: details.direction, + }) + .collect(), + ))) } } @@ -3616,52 +3561,68 @@ impl<'a> SortKey<'a> { .first_table() .expect("an entity query always contains at least one entity type/table"); - let br_column = if collection.all_mutable() && ENV_VARS.store.order_by_block_range { - Some(BlockRangeColumn::new(table, "c.", block)) + let use_block_column = if collection.all_mutable() && ENV_VARS.store.order_by_block_range { + UseBlockColumn::Yes } else { - None + UseBlockColumn::No }; match order { - EntityOrder::Ascending(attr, _) => with_key(table, attr, filter, ASC, br_column), - EntityOrder::Descending(attr, _) => with_key(table, attr, filter, DESC, br_column), - EntityOrder::Default => Ok(SortKey::IdAsc(br_column)), + EntityOrder::Ascending(attr, _) => with_key(table, attr, filter, ASC, use_block_column), + EntityOrder::Descending(attr, _) => { + with_key(table, attr, filter, DESC, use_block_column) + } + EntityOrder::Default => Ok(SortKey::IdAsc(use_block_column.block_column(table))), EntityOrder::Unordered => Ok(SortKey::None), EntityOrder::ChildAscending(kind) => match kind { EntityOrderByChild::Object(child, entity_type) => with_child_object_key( + block, table, - layout.table_for_entity(&entity_type)?, + layout.table_for_entity(&entity_type)?.dsl_table(), child.join_attribute, child.derived, child.sort_by_attribute, - br_column, + use_block_column, + ASC, + ), + EntityOrderByChild::Interface(child, entity_types) => with_child_interface_key( + layout, + block, + table, + child, + entity_types, + use_block_column, ASC, ), - EntityOrderByChild::Interface(child, entity_types) => { - with_child_interface_key(layout, table, child, entity_types, br_column, ASC) - } }, EntityOrder::ChildDescending(kind) => match kind { EntityOrderByChild::Object(child, entity_type) => with_child_object_key( + block, table, - layout.table_for_entity(&entity_type)?, + layout.table_for_entity(&entity_type)?.dsl_table(), child.join_attribute, child.derived, child.sort_by_attribute, - br_column, + use_block_column, + DESC, + ), + EntityOrderByChild::Interface(child, entity_types) => with_child_interface_key( + layout, + block, + table, + child, + entity_types, + use_block_column, DESC, ), - EntityOrderByChild::Interface(child, entity_types) => { - with_child_interface_key(layout, table, child, entity_types, br_column, DESC) - } }, } } /// Generate selecting the sort key if it is needed - fn select( - &self, - out: &mut AstPass, + fn select<'b>( + &'b self, + out: &mut AstPass<'_, 'b, Pg>, select_statement_level: SelectStatementLevel, ) -> QueryResult<()> { match self { @@ -3672,7 +3633,7 @@ impl<'a> SortKey<'a> { match select_statement_level { SelectStatementLevel::InnerStatement => { - br_column.name(out); + br_column.walk_ast(out.reborrow())?; out.push_sql(" as "); out.push_sql(SORT_KEY_COLUMN); } @@ -3691,8 +3652,8 @@ impl<'a> SortKey<'a> { match select_statement_level { SelectStatementLevel::InnerStatement => { - out.push_sql(", c."); - out.push_identifier(column.name.as_str())?; + out.push_sql(", "); + column.walk_ast(out.reborrow())?; out.push_sql(" as "); out.push_sql(SORT_KEY_COLUMN); } @@ -3712,9 +3673,7 @@ impl<'a> SortKey<'a> { match select_statement_level { SelectStatementLevel::InnerStatement => { out.push_sql(", "); - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - out.push_identifier(child.sort_by_column.name.as_str())?; + child.sort_by_column.walk_ast(out.reborrow())?; } SelectStatementLevel::OuterStatement => { out.push_sql(", "); @@ -3722,36 +3681,31 @@ impl<'a> SortKey<'a> { } } } - ChildKey::Many(children) => { + ChildKey::Many(_, children) => { for child in children.iter() { if child.sort_by_column.is_primary_key() { return Err(constraint_violation!("SortKey::Key never uses 'id'")); } out.push_sql(", "); - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - out.push_identifier(child.sort_by_column.name.as_str())?; + child.sort_by_column.walk_ast(out.reborrow())?; } } - ChildKey::ManyIdAsc(children, br_column) - | ChildKey::ManyIdDesc(children, br_column) => { + ChildKey::ManyIdAsc(children, UseBlockColumn::Yes) + | ChildKey::ManyIdDesc(children, UseBlockColumn::Yes) => { for child in children.iter() { - if let Some(br_column) = br_column { - out.push_sql(", "); - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - br_column.name(out); - } - } - } - ChildKey::IdAsc(child, br_column) | ChildKey::IdDesc(child, br_column) => { - if let Some(br_column) = br_column { out.push_sql(", "); - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - br_column.name(out); + child.child_br.walk_ast(out.reborrow())?; } } + ChildKey::ManyIdAsc(_, UseBlockColumn::No) + | ChildKey::ManyIdDesc(_, UseBlockColumn::No) => { /* nothing to do */ } + ChildKey::IdAsc(child, UseBlockColumn::Yes) + | ChildKey::IdDesc(child, UseBlockColumn::Yes) => { + out.push_sql(", "); + child.child_br.walk_ast(out.reborrow())?; + } + ChildKey::IdAsc(_, UseBlockColumn::No) + | ChildKey::IdDesc(_, UseBlockColumn::No) => { /* nothing to do */ } } if let SelectStatementLevel::InnerStatement = select_statement_level { @@ -3781,7 +3735,7 @@ impl<'a> SortKey<'a> { out.push_sql(SORT_KEY_COLUMN); } else { out.push_sql(", "); - br_column.bare_name(out); + out.push_sql(br_column.name()); } } Ok(()) @@ -3796,7 +3750,7 @@ impl<'a> SortKey<'a> { out.push_sql(SORT_KEY_COLUMN); } else { out.push_sql(", "); - br_column.bare_name(out); + out.push_sql(br_column.name()); } out.push_sql(" desc"); } @@ -3808,74 +3762,47 @@ impl<'a> SortKey<'a> { direction, } => { out.push_sql("order by "); - SortKey::sort_expr( - column, - value, - direction, - None, - None, - use_sort_key_alias, - out, - ) + SortKey::sort_expr(column, value, direction, None, use_sort_key_alias, out) } SortKey::ChildKey(child) => { out.push_sql("order by "); match child { ChildKey::Single(child) => SortKey::sort_expr( - child.sort_by_column, + &child.sort_by_column, &None, child.direction, - Some(&child.prefix), Some("c"), use_sort_key_alias, out, ), - ChildKey::Many(children) => { - let columns: Vec<(&Column, &str)> = children - .iter() - .map(|child| (child.sort_by_column, child.prefix.as_str())) - .collect(); - SortKey::multi_sort_expr( - columns, - children.first().unwrap().direction, - Some("c"), - out, - ) - } + ChildKey::Many(parent_pk, children) => SortKey::multi_sort_expr( + parent_pk, + children, + children.first().unwrap().direction, + out, + ), - ChildKey::ManyIdAsc(children, br_column) => { - let prefixes: Vec<&str> = - children.iter().map(|child| child.prefix.as_str()).collect(); - SortKey::multi_sort_id_expr(prefixes, ASC, br_column, out) + ChildKey::ManyIdAsc(children, use_block_column) => { + SortKey::multi_sort_id_expr(children, ASC, *use_block_column, out) } - ChildKey::ManyIdDesc(children, br_column) => { - let prefixes: Vec<&str> = - children.iter().map(|child| child.prefix.as_str()).collect(); - SortKey::multi_sort_id_expr(prefixes, DESC, br_column, out) + ChildKey::ManyIdDesc(children, use_block_column) => { + SortKey::multi_sort_id_expr(children, DESC, *use_block_column, out) } - ChildKey::IdAsc(child, br_column) => { - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - out.push_identifier(PRIMARY_KEY_COLUMN)?; - if let Some(br_column) = br_column { + ChildKey::IdAsc(child, use_block_column) => { + child.child_pk.walk_ast(out.reborrow())?; + if UseBlockColumn::Yes == *use_block_column { out.push_sql(", "); - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - br_column.bare_name(out); + child.child_br.walk_ast(out.reborrow())?; } Ok(()) } - ChildKey::IdDesc(child, br_column) => { - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - out.push_identifier(PRIMARY_KEY_COLUMN)?; + ChildKey::IdDesc(child, use_block_column) => { + child.child_pk.walk_ast(out.reborrow())?; out.push_sql(" desc"); - if let Some(br_column) = br_column { + if UseBlockColumn::Yes == *use_block_column { out.push_sql(", "); - out.push_sql(child.prefix.as_str()); - out.push_sql("."); - br_column.bare_name(out); + child.child_br.walk_ast(out.reborrow())?; out.push_sql(" desc"); } Ok(()) @@ -3919,15 +3846,7 @@ impl<'a> SortKey<'a> { direction, } => { order_by_parent_id(out); - SortKey::sort_expr( - column, - value, - direction, - None, - None, - use_sort_key_alias, - out, - ) + SortKey::sort_expr(column, value, direction, None, use_sort_key_alias, out) } SortKey::ChildKey(_) => Err(diesel::result::Error::QueryBuilderError( "SortKey::ChildKey cannot be used for parent ordering (yet)".into(), @@ -3938,10 +3857,9 @@ impl<'a> SortKey<'a> { /// Generate /// [name direction,] id fn sort_expr<'b>( - column: &Column, + column: &'b dsl::Column<'b>, value: &'b Option<&str>, direction: &str, - column_prefix: Option<&str>, rest_prefix: Option<&str>, use_sort_key_alias: bool, out: &mut AstPass<'_, 'b, Pg>, @@ -3960,7 +3878,7 @@ impl<'a> SortKey<'a> { } } - match &column.column_type { + match column.column_type() { ColumnType::TSVector(config) => { let algorithm = match config.algorithm { FulltextAlgorithm::Rank => "ts_rank(", @@ -3970,9 +3888,7 @@ impl<'a> SortKey<'a> { if use_sort_key_alias { out.push_sql(SORT_KEY_COLUMN); } else { - let name = column.name.as_str(); - push_prefix(column_prefix, out); - out.push_identifier(name)?; + column.walk_ast(out.reborrow())?; } out.push_sql(", to_tsquery("); @@ -3984,9 +3900,7 @@ impl<'a> SortKey<'a> { if use_sort_key_alias { out.push_sql(SORT_KEY_COLUMN); } else { - let name = column.name.as_str(); - push_prefix(column_prefix, out); - out.push_identifier(name)?; + column.walk_ast(out.reborrow())?; } } } @@ -4004,21 +3918,22 @@ impl<'a> SortKey<'a> { /// Generate /// [COALESCE(name1, name2) direction,] id1, id2 - fn multi_sort_expr( - columns: Vec<(&Column, &str)>, + fn multi_sort_expr<'b>( + parent_pk: &'b dsl::Column<'b>, + children: &'b [ChildKeyDetails<'b>], direction: &str, - rest_prefix: Option<&str>, - out: &mut AstPass, + out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { - for (column, _) in columns.iter() { - if column.is_primary_key() { + for child in children { + let sort_by = &child.sort_by_column; + if sort_by.is_primary_key() { // This shouldn't happen since we'd use SortKey::ManyIdAsc/ManyDesc return Err(constraint_violation!( "multi_sort_expr called with primary key column" )); } - match column.column_type { + match sort_by.column_type() { ColumnType::TSVector(_) => { return Err(constraint_violation!("TSVector is not supported")); } @@ -4026,31 +3941,25 @@ impl<'a> SortKey<'a> { } } - fn push_prefix(prefix: Option<&str>, out: &mut AstPass) { - if let Some(prefix) = prefix { - out.push_sql(prefix); - out.push_sql("."); - } - } - out.push_sql("coalesce("); - for (i, (column, prefix)) in columns.iter().enumerate() { - if i != 0 { + let mut first = true; + for child in children { + if first { + first = false; + } else { out.push_sql(", "); } - let name = column.name.as_str(); - push_prefix(Some(prefix), out); - out.push_identifier(name)?; + child.sort_by_column.walk_ast(out.reborrow())?; } out.push_sql(") "); out.push_sql(direction); out.push_sql(", "); - push_prefix(rest_prefix, out); - out.push_identifier(PRIMARY_KEY_COLUMN)?; + + parent_pk.walk_ast(out.reborrow())?; out.push_sql(" "); out.push_sql(direction); Ok(()) @@ -4058,41 +3967,38 @@ impl<'a> SortKey<'a> { /// Generate /// COALESCE(id1, id2) direction, [COALESCE(br_column1, br_column2) direction] - fn multi_sort_id_expr( - prefixes: Vec<&str>, + fn multi_sort_id_expr<'b>( + children: &'b [ChildIdDetails<'b>], direction: &str, - br_column: &Option, - out: &mut AstPass, + use_block_column: UseBlockColumn, + out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { - fn push_prefix(prefix: Option<&str>, out: &mut AstPass) { - if let Some(prefix) = prefix { - out.push_sql(prefix); - out.push_sql("."); - } - } - out.push_sql("coalesce("); - for (i, prefix) in prefixes.iter().enumerate() { - if i != 0 { + let mut first = true; + for child in children { + if first { + first = false; + } else { out.push_sql(", "); } - push_prefix(Some(prefix), out); - out.push_identifier(PRIMARY_KEY_COLUMN)?; + child.child_join_column.walk_ast(out.reborrow())?; } out.push_sql(") "); out.push_sql(direction); - if let Some(br_column) = br_column { + if UseBlockColumn::Yes == use_block_column { out.push_sql(", coalesce("); - for (i, prefix) in prefixes.iter().enumerate() { - if i != 0 { + let mut first = true; + for child in children { + if first { + first = false; + } else { out.push_sql(", "); } - push_prefix(Some(prefix), out); - br_column.bare_name(out); + child.child_br.walk_ast(out.reborrow())?; } out.push_sql(") "); out.push_sql(direction); @@ -4101,58 +4007,39 @@ impl<'a> SortKey<'a> { Ok(()) } - fn add_child<'b>( - &self, - block: &'b BlockNumber, - out: &mut AstPass<'_, 'b, Pg>, - ) -> QueryResult<()> { + fn add_child<'b>(&'b self, out: &mut AstPass<'_, 'b, Pg>) -> QueryResult<()> { fn add<'b>( - block: &'b BlockNumber, - child_table: &Table, - child_column: &Column, - parent_column: &Column, - prefix: &str, + child_from: &'b dsl::FromTable<'b>, + child_column: &'b dsl::Column<'b>, + child_at_block: &'b dsl::AtBlock<'b>, + parent_column: &'b dsl::Column<'b>, out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { out.push_sql(" left join "); - out.push_sql(child_table.qualified_name.as_str()); - out.push_sql(" as "); - out.push_sql(prefix); + child_from.walk_ast(out.reborrow())?; out.push_sql(" on ("); if child_column.is_list() { // Type C: p.id = any(c.child_ids) - out.push_sql("c."); - out.push_identifier(parent_column.name.as_str())?; + parent_column.walk_ast(out.reborrow())?; out.push_sql(" = any("); - out.push_sql(prefix); - out.push_sql("."); - out.push_identifier(child_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; out.push_sql(")"); } else if parent_column.is_list() { // Type A: c.id = any(p.{parent_field}) - out.push_sql(prefix); - out.push_sql("."); - out.push_identifier(child_column.name.as_str())?; - out.push_sql(" = any(c."); - out.push_identifier(parent_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; + out.push_sql(" = any("); + parent_column.walk_ast(out.reborrow())?; out.push_sql(")"); } else { // Type B: c.id = p.{parent_field} - out.push_sql(prefix); - out.push_sql("."); - out.push_identifier(child_column.name.as_str())?; + child_column.walk_ast(out.reborrow())?; out.push_sql(" = "); - out.push_sql("c."); - out.push_identifier(parent_column.name.as_str())?; + parent_column.walk_ast(out.reborrow())?; } out.push_sql(" and "); - out.push_sql(prefix); - out.push_sql("."); - out.push_identifier(BLOCK_RANGE_COLUMN)?; - out.push_sql(" @> "); - out.push_bind_param::(block)?; + child_at_block.walk_ast(out.reborrow())?; out.push_sql(") "); Ok(()) @@ -4162,22 +4049,20 @@ impl<'a> SortKey<'a> { SortKey::ChildKey(nested) => match nested { ChildKey::Single(child) => { add( - block, - child.child_table, - child.child_join_column, - child.parent_join_column, - &child.prefix, + &child.child_from, + &child.child_join_column, + &child.child_at_block, + &child.parent_join_column, out, )?; } - ChildKey::Many(children) => { + ChildKey::Many(_, children) => { for child in children.iter() { add( - block, - child.child_table, - child.child_join_column, - child.parent_join_column, - &child.prefix, + &child.child_from, + &child.child_join_column, + &child.child_at_block, + &child.parent_join_column, out, )?; } @@ -4185,22 +4070,20 @@ impl<'a> SortKey<'a> { ChildKey::ManyIdAsc(children, _) | ChildKey::ManyIdDesc(children, _) => { for child in children.iter() { add( - block, - child.child_table, - child.child_join_column, - child.parent_join_column, - &child.prefix, + &child.child_from, + &child.child_join_column, + &child.child_at_block, + &child.parent_join_column, out, )?; } } ChildKey::IdAsc(child, _) | ChildKey::IdDesc(child, _) => { add( - block, - child.child_table, - child.child_join_column, - child.parent_join_column, - &child.prefix, + &child.child_from, + &child.child_join_column, + &child.child_at_block, + &child.parent_join_column, out, )?; } @@ -4285,7 +4168,7 @@ impl<'a> FilterQuery<'a> { query_id: Option, site: &'a Site, ) -> Result { - let sort_key = SortKey::new(order, collection, filter, block, layout)?; + let sort_key = SortKey::new(order, collection, filter, layout, block)?; let range = FilterRange(range); let limit = ParentLimit { sort_key, range }; @@ -4310,18 +4193,13 @@ impl<'a> FilterQuery<'a> { out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { out.push_sql("\n from "); - out.push_sql(wh.table.qualified_name.as_str()); - out.push_sql(" c"); + wh.from_table.walk_ast(out.reborrow())?; - self.limit.sort_key.add_child(&self.block, out)?; + self.limit.sort_key.add_child(out)?; out.push_sql("\n where "); - let filters_by_id = { - matches!(wh.filter.as_ref(), Some(Filter::Cmp(column, Comparison::Equal, _)) if column.column().is_primary_key()) - }; - - wh.br_column.contains(out, filters_by_id)?; + wh.at_block.walk_ast(out.reborrow())?; if let Some(filter) = &wh.filter { out.push_sql(" and "); filter.walk_ast(out.reborrow())?; @@ -4330,9 +4208,9 @@ impl<'a> FilterQuery<'a> { Ok(()) } - fn select_entity_and_data(table: &Table, out: &mut AstPass) { + fn select_entity_and_data(table: dsl::Table<'_>, out: &mut AstPass) { out.push_sql("select '"); - out.push_sql(table.object.as_str()); + out.push_sql(table.meta.object.as_str()); out.push_sql("' as entity, to_jsonb(c.*) as data"); } @@ -4429,7 +4307,7 @@ impl<'a> FilterQuery<'a> { // c.vid, // c.${sort_key} out.push_sql("select '"); - out.push_sql(wh.table.object.as_str()); + out.push_sql(wh.table.meta.object.as_str()); out.push_sql("' as entity, c.id, c.vid"); self.limit .sort_key @@ -4454,11 +4332,11 @@ impl<'a> FilterQuery<'a> { .sort_key .select(out, SelectStatementLevel::OuterStatement)?; out.push_sql("\n from "); - out.push_sql(wh.table.qualified_name.as_str()); - out.push_sql(" c,"); + wh.from_table.walk_ast(out.reborrow())?; + out.push_sql(" ,"); out.push_sql(" matches m"); out.push_sql("\n where c.vid = m.vid and m.entity = "); - out.push_bind_param::(wh.table.object.as_str())?; + out.push_bind_param::(wh.table.meta.object.as_str())?; } out.push_sql("\n "); self.limit.sort_key.order_by(out, true)?; @@ -4527,8 +4405,8 @@ impl<'a> FilterQuery<'a> { .iter() .unique_by(|window| { ( - &window.table.qualified_name, - &window.table.object, + &window.table.meta.qualified_name, + &window.table.meta.object, &window.column_names, ) }) @@ -4542,9 +4420,9 @@ impl<'a> FilterQuery<'a> { jsonb_build_object(&window.column_names, "c", window.table, out)?; out.push_sql("|| jsonb_build_object('g$parent_id', m.g$parent_id) as data"); out.push_sql("\n from "); - out.push_sql(window.table.qualified_name.as_str()); - out.push_sql(" c, matches m\n where c.vid = m.vid and m.entity = '"); - out.push_sql(window.table.object.as_str()); + window.from_table.walk_ast(out.reborrow())?; + out.push_sql(", matches m\n where c.vid = m.vid and m.entity = '"); + out.push_sql(window.table.meta.object.as_str()); out.push_sql("'"); } out.push_sql("\n "); @@ -4654,7 +4532,7 @@ impl<'a> QueryFragment for ClampRangeQuery<'a> { self.br_column.clamp(&mut out)?; out.push_sql("\n where "); - self.table.primary_key().is_in(&self.entity_ids, &mut out)?; + id_is_in(&self.entity_ids, &mut out)?; out.push_sql(" and ("); self.br_column.latest(&mut out); out.push_sql(")"); @@ -4979,7 +4857,7 @@ pub struct CopyVid { fn write_column_names( column_names: &AttributeNames, - table: &Table, + table: dsl::Table<'_>, prefix: Option<&str>, out: &mut AstPass, ) -> QueryResult<()> { @@ -5008,7 +4886,7 @@ fn write_column_names( fn jsonb_build_object( column_names: &AttributeNames, table_identifier: &str, - table: &Table, + table: dsl::Table<'_>, out: &mut AstPass, ) -> QueryResult<()> { match column_names { @@ -5043,11 +4921,11 @@ fn jsonb_build_object( /// names, yielding valid SQL names for the given table. fn iter_column_names<'a, 'b>( attribute_names: &'a BTreeSet, - table: &'b Table, + table: dsl::Table<'b>, include_block_range_column: bool, ) -> impl Iterator { let extra = if include_block_range_column { - if table.immutable { + if table.meta.immutable { [BLOCK_COLUMN].iter() } else { [BLOCK_RANGE_COLUMN].iter() @@ -5063,7 +4941,7 @@ fn iter_column_names<'a, 'b>( // Unwrapping: We have already checked that all attribute names exist in table table.column_for_field(attribute_name).unwrap() }) - .map(|column| column.name.as_str()) + .map(|column| column.name()) .chain(BASE_SQL_COLUMNS.iter().copied()) .chain(extra) .sorted() From 2f7706395f3391e44220fbb95192a9d3da5874d5 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Apr 2024 11:08:51 -0700 Subject: [PATCH 3/7] store: Introduce an enum for sort direction --- store/postgres/src/relational_queries.rs | 102 ++++++++++++++--------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index 742ecefb1f5..2df54e154e4 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -3056,7 +3056,7 @@ pub struct ChildKeyDetails<'a> { /// Column of the child table that sorting is done on pub sort_by_column: dsl::Column<'a>, /// Either `asc` or `desc` - pub direction: &'static str, + pub direction: SortDirection, } #[derive(Debug, Clone)] @@ -3074,7 +3074,7 @@ pub struct ChildKeyAndIdSharedDetails<'a> { /// Column of the child table that sorting is done on pub sort_by_column: dsl::Column<'a>, /// Either `asc` or `desc` - pub direction: &'static str, + pub direction: SortDirection, } #[allow(unused)] @@ -3130,7 +3130,7 @@ pub enum SortKey<'a> { Key { column: dsl::Column<'a>, value: Option<&'a str>, - direction: &'static str, + direction: SortDirection, }, /// Order by some other column; `column` will never be `id` ChildKey(ChildKey<'a>), @@ -3232,8 +3232,26 @@ impl<'a> fmt::Display for SortKey<'a> { } } -const ASC: &str = "asc"; -const DESC: &str = "desc"; +#[derive(Debug, Clone, Copy)] +pub enum SortDirection { + Asc, + Desc, +} + +impl SortDirection { + fn as_str(&self) -> &'static str { + match self { + SortDirection::Asc => "asc", + SortDirection::Desc => "desc", + } + } +} + +impl std::fmt::Display for SortDirection { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} impl<'a> SortKey<'a> { fn new( @@ -3246,7 +3264,7 @@ impl<'a> SortKey<'a> { fn sort_key_from_value<'a>( column: dsl::Column<'a>, value: &'a Value, - direction: &'static str, + direction: SortDirection, ) -> Result, QueryExecutionError> { let sort_value = value.as_str(); @@ -3261,7 +3279,7 @@ impl<'a> SortKey<'a> { table: dsl::Table<'a>, attribute: String, filter: Option<&'a EntityFilter>, - direction: &'static str, + direction: SortDirection, use_block_column: UseBlockColumn, ) -> Result, QueryExecutionError> { let column = table.column_for_field(&attribute)?; @@ -3280,10 +3298,10 @@ impl<'a> SortKey<'a> { } } else if column.is_primary_key() { let block_column = use_block_column.block_column(table); + use SortDirection::*; match direction { - ASC => Ok(SortKey::IdAsc(block_column)), - DESC => Ok(SortKey::IdDesc(block_column)), - _ => unreachable!("direction is 'asc' or 'desc'"), + Asc => Ok(SortKey::IdAsc(block_column)), + Desc => Ok(SortKey::IdDesc(block_column)), } } else { Ok(SortKey::Key { @@ -3302,7 +3320,7 @@ impl<'a> SortKey<'a> { derived: bool, attribute: String, use_block_column: UseBlockColumn, - direction: &'static str, + direction: SortDirection, ) -> Result, QueryExecutionError> { let child_table = child_table.child(1); let sort_by_column = child_table.column_for_field(&attribute)?; @@ -3341,8 +3359,9 @@ impl<'a> SortKey<'a> { let child_pk = child_table.primary_key(); let child_br = child_table.block_column(); let child_at_block = child_table.at_block(block); + use SortDirection::*; return match direction { - ASC => Ok(SortKey::ChildKey(ChildKey::IdAsc( + Asc => Ok(SortKey::ChildKey(ChildKey::IdAsc( ChildIdDetails { child_table, child_from, @@ -3354,7 +3373,7 @@ impl<'a> SortKey<'a> { }, use_block_column, ))), - DESC => Ok(SortKey::ChildKey(ChildKey::IdDesc( + Desc => Ok(SortKey::ChildKey(ChildKey::IdDesc( ChildIdDetails { child_table, child_from, @@ -3366,7 +3385,6 @@ impl<'a> SortKey<'a> { }, use_block_column, ))), - _ => unreachable!("direction is 'asc' or 'desc'"), }; } @@ -3392,7 +3410,7 @@ impl<'a> SortKey<'a> { parent_table: dsl::Table<'a>, entity_types: Vec, child: EntityOrderByChildInfo, - direction: &'static str, + direction: SortDirection, ) -> Result>, QueryExecutionError> { assert!(entity_types.len() < 255); return entity_types @@ -3463,7 +3481,7 @@ impl<'a> SortKey<'a> { child: EntityOrderByChildInfo, entity_types: Vec, use_block_column: UseBlockColumn, - direction: &'static str, + direction: SortDirection, ) -> Result, QueryExecutionError> { if entity_types.is_empty() { return Err(QueryExecutionError::ConstraintViolation( @@ -3480,8 +3498,9 @@ impl<'a> SortKey<'a> { "Sorting by fulltext fields".to_string(), )) } else if sort_by_column.is_primary_key() { - if direction == ASC { - Ok(SortKey::ChildKey(ChildKey::ManyIdAsc( + use SortDirection::*; + match direction { + Asc => Ok(SortKey::ChildKey(ChildKey::ManyIdAsc( build_children_vec( layout, block, @@ -3502,9 +3521,8 @@ impl<'a> SortKey<'a> { }) .collect(), use_block_column, - ))) - } else { - Ok(SortKey::ChildKey(ChildKey::ManyIdDesc( + ))), + Desc => Ok(SortKey::ChildKey(ChildKey::ManyIdDesc( build_children_vec( layout, block, @@ -3525,7 +3543,7 @@ impl<'a> SortKey<'a> { }) .collect(), use_block_column, - ))) + ))), } } else { Ok(SortKey::ChildKey(ChildKey::Many( @@ -3567,10 +3585,11 @@ impl<'a> SortKey<'a> { UseBlockColumn::No }; + use SortDirection::*; match order { - EntityOrder::Ascending(attr, _) => with_key(table, attr, filter, ASC, use_block_column), + EntityOrder::Ascending(attr, _) => with_key(table, attr, filter, Asc, use_block_column), EntityOrder::Descending(attr, _) => { - with_key(table, attr, filter, DESC, use_block_column) + with_key(table, attr, filter, Desc, use_block_column) } EntityOrder::Default => Ok(SortKey::IdAsc(use_block_column.block_column(table))), EntityOrder::Unordered => Ok(SortKey::None), @@ -3583,7 +3602,7 @@ impl<'a> SortKey<'a> { child.derived, child.sort_by_attribute, use_block_column, - ASC, + Asc, ), EntityOrderByChild::Interface(child, entity_types) => with_child_interface_key( layout, @@ -3592,7 +3611,7 @@ impl<'a> SortKey<'a> { child, entity_types, use_block_column, - ASC, + Asc, ), }, EntityOrder::ChildDescending(kind) => match kind { @@ -3604,7 +3623,7 @@ impl<'a> SortKey<'a> { child.derived, child.sort_by_attribute, use_block_column, - DESC, + Desc, ), EntityOrderByChild::Interface(child, entity_types) => with_child_interface_key( layout, @@ -3613,7 +3632,7 @@ impl<'a> SortKey<'a> { child, entity_types, use_block_column, - DESC, + Desc, ), }, } @@ -3724,6 +3743,7 @@ impl<'a> SortKey<'a> { out: &mut AstPass<'_, 'b, Pg>, use_sort_key_alias: bool, ) -> QueryResult<()> { + use SortDirection::*; match self { SortKey::None => Ok(()), SortKey::IdAsc(br_column) => { @@ -3770,7 +3790,7 @@ impl<'a> SortKey<'a> { ChildKey::Single(child) => SortKey::sort_expr( &child.sort_by_column, &None, - child.direction, + &child.direction, Some("c"), use_sort_key_alias, out, @@ -3778,15 +3798,15 @@ impl<'a> SortKey<'a> { ChildKey::Many(parent_pk, children) => SortKey::multi_sort_expr( parent_pk, children, - children.first().unwrap().direction, + &children.first().unwrap().direction, out, ), ChildKey::ManyIdAsc(children, use_block_column) => { - SortKey::multi_sort_id_expr(children, ASC, *use_block_column, out) + SortKey::multi_sort_id_expr(children, Asc, *use_block_column, out) } ChildKey::ManyIdDesc(children, use_block_column) => { - SortKey::multi_sort_id_expr(children, DESC, *use_block_column, out) + SortKey::multi_sort_id_expr(children, Desc, *use_block_column, out) } ChildKey::IdAsc(child, use_block_column) => { @@ -3859,7 +3879,7 @@ impl<'a> SortKey<'a> { fn sort_expr<'b>( column: &'b dsl::Column<'b>, value: &'b Option<&str>, - direction: &str, + direction: &'b SortDirection, rest_prefix: Option<&str>, use_sort_key_alias: bool, out: &mut AstPass<'_, 'b, Pg>, @@ -3905,14 +3925,14 @@ impl<'a> SortKey<'a> { } } out.push_sql(" "); - out.push_sql(direction); + out.push_sql(direction.as_str()); out.push_sql(", "); if !use_sort_key_alias { push_prefix(rest_prefix, out); } out.push_identifier(PRIMARY_KEY_COLUMN)?; out.push_sql(" "); - out.push_sql(direction); + out.push_sql(direction.as_str()); Ok(()) } @@ -3921,7 +3941,7 @@ impl<'a> SortKey<'a> { fn multi_sort_expr<'b>( parent_pk: &'b dsl::Column<'b>, children: &'b [ChildKeyDetails<'b>], - direction: &str, + direction: &'b SortDirection, out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { for child in children { @@ -3956,12 +3976,12 @@ impl<'a> SortKey<'a> { out.push_sql(") "); - out.push_sql(direction); + out.push_sql(direction.as_str()); out.push_sql(", "); parent_pk.walk_ast(out.reborrow())?; out.push_sql(" "); - out.push_sql(direction); + out.push_sql(direction.as_str()); Ok(()) } @@ -3969,7 +3989,7 @@ impl<'a> SortKey<'a> { /// COALESCE(id1, id2) direction, [COALESCE(br_column1, br_column2) direction] fn multi_sort_id_expr<'b>( children: &'b [ChildIdDetails<'b>], - direction: &str, + direction: SortDirection, use_block_column: UseBlockColumn, out: &mut AstPass<'_, 'b, Pg>, ) -> QueryResult<()> { @@ -3986,7 +4006,7 @@ impl<'a> SortKey<'a> { } out.push_sql(") "); - out.push_sql(direction); + out.push_sql(direction.as_str()); if UseBlockColumn::Yes == use_block_column { out.push_sql(", coalesce("); @@ -4001,7 +4021,7 @@ impl<'a> SortKey<'a> { child.child_br.walk_ast(out.reborrow())?; } out.push_sql(") "); - out.push_sql(direction); + out.push_sql(direction.as_str()); } Ok(()) From 4f2b3f8fea0098fbe4723cc2aa8d6e1709e3b0ca Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Apr 2024 11:26:42 -0700 Subject: [PATCH 4/7] store: Combine SortKey::IdAsc and SortKey::IdDesc into one variant --- store/postgres/src/relational_queries.rs | 93 +++++++++--------------- 1 file changed, 35 insertions(+), 58 deletions(-) diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index 2df54e154e4..f9672f02d14 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -3122,10 +3122,8 @@ pub enum ChildKey<'a> { #[derive(Debug, Clone)] pub enum SortKey<'a> { None, - /// Order by `id asc` - IdAsc(Option>), - /// Order by `id desc` - IdDesc(Option>), + /// Order by `id , [block ]` + Id(SortDirection, Option>), /// Order by some other column; `column` will never be `id` Key { column: dsl::Column<'a>, @@ -3141,11 +3139,12 @@ impl<'a> fmt::Display for SortKey<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SortKey::None => write!(f, "none"), - SortKey::IdAsc(Option::None) => write!(f, "{}", PRIMARY_KEY_COLUMN), - SortKey::IdAsc(Some(br)) => write!(f, "{}, {}", PRIMARY_KEY_COLUMN, br), - SortKey::IdDesc(Option::None) => write!(f, "{} desc", PRIMARY_KEY_COLUMN), - SortKey::IdDesc(Some(br)) => { - write!(f, "{} desc, {} desc", PRIMARY_KEY_COLUMN, br) + SortKey::Id(direction, br) => { + write!(f, "{}{}", PRIMARY_KEY_COLUMN, direction)?; + if let Some(br) = br { + write!(f, ", {} {}", PRIMARY_KEY_COLUMN, br)?; + } + Ok(()) } SortKey::Key { column, @@ -3153,13 +3152,13 @@ impl<'a> fmt::Display for SortKey<'a> { direction, } => write!( f, - "{} {}, {} {}", + "{}{}, {}{}", column, direction, PRIMARY_KEY_COLUMN, direction ), SortKey::ChildKey(child) => match child { ChildKey::Single(details) => write!( f, - "{} {}, {} {}", + "{}{}, {}{}", details.sort_by_column, details.direction, details.child_table.primary_key(), @@ -3168,7 +3167,7 @@ impl<'a> fmt::Display for SortKey<'a> { ChildKey::Many(_, details) => details.iter().try_for_each(|details| { write!( f, - "{} {}, {} {}", + "{}{}, {}{}", details.sort_by_column, details.direction, details.child_table.primary_key(), @@ -3239,17 +3238,20 @@ pub enum SortDirection { } impl SortDirection { - fn as_str(&self) -> &'static str { + /// Generate either `""` or `" desc"`; convenient for SQL generation + /// without needing an additional space to separate it from preceding + /// text + fn as_sql(&self) -> &'static str { match self { - SortDirection::Asc => "asc", - SortDirection::Desc => "desc", + SortDirection::Asc => "", + SortDirection::Desc => " desc", } } } impl std::fmt::Display for SortDirection { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.as_str()) + write!(f, "{}", self.as_sql()) } } @@ -3298,11 +3300,7 @@ impl<'a> SortKey<'a> { } } else if column.is_primary_key() { let block_column = use_block_column.block_column(table); - use SortDirection::*; - match direction { - Asc => Ok(SortKey::IdAsc(block_column)), - Desc => Ok(SortKey::IdDesc(block_column)), - } + Ok(SortKey::Id(direction, block_column)) } else { Ok(SortKey::Key { column, @@ -3591,7 +3589,7 @@ impl<'a> SortKey<'a> { EntityOrder::Descending(attr, _) => { with_key(table, attr, filter, Desc, use_block_column) } - EntityOrder::Default => Ok(SortKey::IdAsc(use_block_column.block_column(table))), + EntityOrder::Default => Ok(SortKey::Id(Asc, use_block_column.block_column(table))), EntityOrder::Unordered => Ok(SortKey::None), EntityOrder::ChildAscending(kind) => match kind { EntityOrderByChild::Object(child, entity_type) => with_child_object_key( @@ -3646,7 +3644,7 @@ impl<'a> SortKey<'a> { ) -> QueryResult<()> { match self { SortKey::None => {} - SortKey::IdAsc(br_column) | SortKey::IdDesc(br_column) => { + SortKey::Id(_, br_column) => { if let Some(br_column) = br_column { out.push_sql(", "); @@ -3746,24 +3744,10 @@ impl<'a> SortKey<'a> { use SortDirection::*; match self { SortKey::None => Ok(()), - SortKey::IdAsc(br_column) => { - out.push_sql("order by "); - out.push_identifier(PRIMARY_KEY_COLUMN)?; - if let Some(br_column) = br_column { - if use_sort_key_alias { - out.push_sql(", "); - out.push_sql(SORT_KEY_COLUMN); - } else { - out.push_sql(", "); - out.push_sql(br_column.name()); - } - } - Ok(()) - } - SortKey::IdDesc(br_column) => { + SortKey::Id(direction, br_column) => { out.push_sql("order by "); out.push_identifier(PRIMARY_KEY_COLUMN)?; - out.push_sql(" desc"); + out.push_sql(direction.as_sql()); if let Some(br_column) = br_column { if use_sort_key_alias { out.push_sql(", "); @@ -3772,7 +3756,7 @@ impl<'a> SortKey<'a> { out.push_sql(", "); out.push_sql(br_column.name()); } - out.push_sql(" desc"); + out.push_sql(direction.as_sql()); } Ok(()) } @@ -3850,14 +3834,10 @@ impl<'a> SortKey<'a> { match self { SortKey::None => Ok(()), - SortKey::IdAsc(_) => { - order_by_parent_id(out); - out.push_identifier(PRIMARY_KEY_COLUMN) - } - SortKey::IdDesc(_) => { + SortKey::Id(direction, _) => { order_by_parent_id(out); out.push_identifier(PRIMARY_KEY_COLUMN)?; - out.push_sql(" desc"); + out.push_sql(direction.as_sql()); Ok(()) } SortKey::Key { @@ -3924,15 +3904,13 @@ impl<'a> SortKey<'a> { } } } - out.push_sql(" "); - out.push_sql(direction.as_str()); + out.push_sql(direction.as_sql()); out.push_sql(", "); if !use_sort_key_alias { push_prefix(rest_prefix, out); } out.push_identifier(PRIMARY_KEY_COLUMN)?; - out.push_sql(" "); - out.push_sql(direction.as_str()); + out.push_sql(direction.as_sql()); Ok(()) } @@ -3974,14 +3952,13 @@ impl<'a> SortKey<'a> { child.sort_by_column.walk_ast(out.reborrow())?; } - out.push_sql(") "); + out.push_sql(")"); - out.push_sql(direction.as_str()); + out.push_sql(direction.as_sql()); out.push_sql(", "); parent_pk.walk_ast(out.reborrow())?; - out.push_sql(" "); - out.push_sql(direction.as_str()); + out.push_sql(direction.as_sql()); Ok(()) } @@ -4004,9 +3981,9 @@ impl<'a> SortKey<'a> { child.child_join_column.walk_ast(out.reborrow())?; } - out.push_sql(") "); + out.push_sql(")"); - out.push_sql(direction.as_str()); + out.push_sql(direction.as_sql()); if UseBlockColumn::Yes == use_block_column { out.push_sql(", coalesce("); @@ -4020,8 +3997,8 @@ impl<'a> SortKey<'a> { child.child_br.walk_ast(out.reborrow())?; } - out.push_sql(") "); - out.push_sql(direction.as_str()); + out.push_sql(")"); + out.push_sql(direction.as_sql()); } Ok(()) From 78480174b9fb6d855c5e29937114675f6fe313f3 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Apr 2024 11:33:25 -0700 Subject: [PATCH 5/7] store: Combine ChildKey::IdAsc and ChildKey::IdDesc --- store/postgres/src/relational_queries.rs | 85 +++++++----------------- 1 file changed, 25 insertions(+), 60 deletions(-) diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index f9672f02d14..055a62e096f 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -3111,8 +3111,7 @@ pub enum ChildKey<'a> { Single(ChildKeyDetails<'a>), /// First column is the primary key of the parent Many(dsl::Column<'a>, Vec>), - IdAsc(ChildIdDetails<'a>, UseBlockColumn), - IdDesc(ChildIdDetails<'a>, UseBlockColumn), + Id(SortDirection, ChildIdDetails<'a>, UseBlockColumn), ManyIdAsc(Vec>, UseBlockColumn), ManyIdDesc(Vec>, UseBlockColumn), } @@ -3204,24 +3203,13 @@ impl<'a> fmt::Display for SortKey<'a> { }) } - ChildKey::IdAsc(details, UseBlockColumn::No) => { - write!(f, "{}", details.child_table.primary_key()) + ChildKey::Id(direction, details, UseBlockColumn::No) => { + write!(f, "{}{}", details.child_table.primary_key(), direction) } - ChildKey::IdAsc(details, UseBlockColumn::Yes) => { + ChildKey::Id(direction, details, UseBlockColumn::Yes) => { write!( f, - "{}, {}", - details.child_table.primary_key(), - details.child_br - ) - } - ChildKey::IdDesc(details, UseBlockColumn::No) => { - write!(f, "{} desc", details.child_table.primary_key(),) - } - ChildKey::IdDesc(details, UseBlockColumn::Yes) => { - write!( - f, - "{} desc, {} desc", + "{}{direction}, {}{direction}", details.child_table.primary_key(), details.child_br ) @@ -3357,33 +3345,20 @@ impl<'a> SortKey<'a> { let child_pk = child_table.primary_key(); let child_br = child_table.block_column(); let child_at_block = child_table.at_block(block); - use SortDirection::*; - return match direction { - Asc => Ok(SortKey::ChildKey(ChildKey::IdAsc( - ChildIdDetails { - child_table, - child_from, - parent_join_column: parent_column, - child_join_column: child_column, - child_pk, - child_br, - child_at_block, - }, - use_block_column, - ))), - Desc => Ok(SortKey::ChildKey(ChildKey::IdDesc( - ChildIdDetails { - child_table, - child_from, - parent_join_column: parent_column, - child_join_column: child_column, - child_pk, - child_br, - child_at_block, - }, - use_block_column, - ))), - }; + + return Ok(SortKey::ChildKey(ChildKey::Id( + direction, + ChildIdDetails { + child_table, + child_from, + parent_join_column: parent_column, + child_join_column: child_column, + child_pk, + child_br, + child_at_block, + }, + use_block_column, + ))); } let child_table = child_table.child(1); @@ -3716,13 +3691,11 @@ impl<'a> SortKey<'a> { } ChildKey::ManyIdAsc(_, UseBlockColumn::No) | ChildKey::ManyIdDesc(_, UseBlockColumn::No) => { /* nothing to do */ } - ChildKey::IdAsc(child, UseBlockColumn::Yes) - | ChildKey::IdDesc(child, UseBlockColumn::Yes) => { + ChildKey::Id(_, child, UseBlockColumn::Yes) => { out.push_sql(", "); child.child_br.walk_ast(out.reborrow())?; } - ChildKey::IdAsc(_, UseBlockColumn::No) - | ChildKey::IdDesc(_, UseBlockColumn::No) => { /* nothing to do */ } + ChildKey::Id(_, _, UseBlockColumn::No) => { /* nothing to do */ } } if let SelectStatementLevel::InnerStatement = select_statement_level { @@ -3793,21 +3766,13 @@ impl<'a> SortKey<'a> { SortKey::multi_sort_id_expr(children, Desc, *use_block_column, out) } - ChildKey::IdAsc(child, use_block_column) => { - child.child_pk.walk_ast(out.reborrow())?; - if UseBlockColumn::Yes == *use_block_column { - out.push_sql(", "); - child.child_br.walk_ast(out.reborrow())?; - } - Ok(()) - } - ChildKey::IdDesc(child, use_block_column) => { + ChildKey::Id(direction, child, use_block_column) => { child.child_pk.walk_ast(out.reborrow())?; - out.push_sql(" desc"); + out.push_sql(direction.as_sql()); if UseBlockColumn::Yes == *use_block_column { out.push_sql(", "); child.child_br.walk_ast(out.reborrow())?; - out.push_sql(" desc"); + out.push_sql(direction.as_sql()); } Ok(()) } @@ -4075,7 +4040,7 @@ impl<'a> SortKey<'a> { )?; } } - ChildKey::IdAsc(child, _) | ChildKey::IdDesc(child, _) => { + ChildKey::Id(_, child, _) => { add( &child.child_from, &child.child_join_column, From 989c98081378e29122a9af3abe089dc21d673e3d Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Apr 2024 12:47:36 -0700 Subject: [PATCH 6/7] store: Combine SortKey::ManyIdAsc and SortKey::ManyIdDesc --- store/postgres/src/relational_queries.rs | 113 +++++++---------------- 1 file changed, 34 insertions(+), 79 deletions(-) diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index 055a62e096f..56ad1aafacb 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -3112,8 +3112,7 @@ pub enum ChildKey<'a> { /// First column is the primary key of the parent Many(dsl::Column<'a>, Vec>), Id(SortDirection, ChildIdDetails<'a>, UseBlockColumn), - ManyIdAsc(Vec>, UseBlockColumn), - ManyIdDesc(Vec>, UseBlockColumn), + ManyId(SortDirection, Vec>, UseBlockColumn), } /// Convenience to pass the name of the column to order by around. If `name` @@ -3174,35 +3173,21 @@ impl<'a> fmt::Display for SortKey<'a> { ) }), - ChildKey::ManyIdAsc(details, UseBlockColumn::No) => details - .iter() - .try_for_each(|details| write!(f, "{}", details.child_table.primary_key())), - ChildKey::ManyIdAsc(details, UseBlockColumn::Yes) => { - details.iter().try_for_each(|details| { - write!( - f, - "{}, {}", - details.child_table.primary_key(), - details.child_table.block_column() - ) - }) - } - ChildKey::ManyIdDesc(details, UseBlockColumn::No) => { + ChildKey::ManyId(direction, details, UseBlockColumn::No) => { details.iter().try_for_each(|details| { - write!(f, "{} desc", details.child_table.primary_key(),) + write!(f, "{}{direction}", details.child_table.primary_key()) }) } - ChildKey::ManyIdDesc(details, UseBlockColumn::Yes) => { + ChildKey::ManyId(direction, details, UseBlockColumn::Yes) => { details.iter().try_for_each(|details| { write!( f, - "{} desc, {} desc", + "{}{direction}, {}{direction}", details.child_table.primary_key(), - details.child_br + details.child_table.block_column() ) }) } - ChildKey::Id(direction, details, UseBlockColumn::No) => { write!(f, "{}{}", details.child_table.primary_key(), direction) } @@ -3471,53 +3456,29 @@ impl<'a> SortKey<'a> { "Sorting by fulltext fields".to_string(), )) } else if sort_by_column.is_primary_key() { - use SortDirection::*; - match direction { - Asc => Ok(SortKey::ChildKey(ChildKey::ManyIdAsc( - build_children_vec( - layout, - block, - parent_table, - entity_types, - child, - direction, - )? - .iter() - .map(|details| ChildIdDetails { - child_table: details.child_table, - child_from: details.child_from, - parent_join_column: details.parent_join_column, - child_join_column: details.child_join_column, - child_pk: details.child_pk, - child_br: details.child_br, - child_at_block: details.child_at_block, - }) - .collect(), - use_block_column, - ))), - Desc => Ok(SortKey::ChildKey(ChildKey::ManyIdDesc( - build_children_vec( - layout, - block, - parent_table, - entity_types, - child, - direction, - )? - .iter() - .map(|details| ChildIdDetails { - child_table: details.child_table, - child_from: details.child_from, - parent_join_column: details.parent_join_column, - child_join_column: details.child_join_column, - child_pk: details.child_pk, - child_br: details.child_br, - child_at_block: details.child_at_block, - }) - .collect(), - use_block_column, - ))), - } + Ok(SortKey::ChildKey(ChildKey::ManyId( + direction, + build_children_vec( + layout, + block, + parent_table, + entity_types, + child, + direction, + )? + .iter() + .map(|details| ChildIdDetails { + child_table: details.child_table, + child_from: details.child_from, + parent_join_column: details.parent_join_column, + child_join_column: details.child_join_column, + child_pk: details.child_pk, + child_br: details.child_br, + child_at_block: details.child_at_block, + }) + .collect(), + use_block_column, + ))) } else { Ok(SortKey::ChildKey(ChildKey::Many( parent_table.primary_key(), @@ -3682,15 +3643,13 @@ impl<'a> SortKey<'a> { child.sort_by_column.walk_ast(out.reborrow())?; } } - ChildKey::ManyIdAsc(children, UseBlockColumn::Yes) - | ChildKey::ManyIdDesc(children, UseBlockColumn::Yes) => { + ChildKey::ManyId(_, children, UseBlockColumn::Yes) => { for child in children.iter() { out.push_sql(", "); child.child_br.walk_ast(out.reborrow())?; } } - ChildKey::ManyIdAsc(_, UseBlockColumn::No) - | ChildKey::ManyIdDesc(_, UseBlockColumn::No) => { /* nothing to do */ } + ChildKey::ManyId(_, _, UseBlockColumn::No) => { /* nothing to do */ } ChildKey::Id(_, child, UseBlockColumn::Yes) => { out.push_sql(", "); child.child_br.walk_ast(out.reborrow())?; @@ -3714,7 +3673,6 @@ impl<'a> SortKey<'a> { out: &mut AstPass<'_, 'b, Pg>, use_sort_key_alias: bool, ) -> QueryResult<()> { - use SortDirection::*; match self { SortKey::None => Ok(()), SortKey::Id(direction, br_column) => { @@ -3759,11 +3717,8 @@ impl<'a> SortKey<'a> { out, ), - ChildKey::ManyIdAsc(children, use_block_column) => { - SortKey::multi_sort_id_expr(children, Asc, *use_block_column, out) - } - ChildKey::ManyIdDesc(children, use_block_column) => { - SortKey::multi_sort_id_expr(children, Desc, *use_block_column, out) + ChildKey::ManyId(direction, children, use_block_column) => { + SortKey::multi_sort_id_expr(children, *direction, *use_block_column, out) } ChildKey::Id(direction, child, use_block_column) => { @@ -4029,7 +3984,7 @@ impl<'a> SortKey<'a> { )?; } } - ChildKey::ManyIdAsc(children, _) | ChildKey::ManyIdDesc(children, _) => { + ChildKey::ManyId(_, children, _) => { for child in children.iter() { add( &child.child_from, From 605c6d249ac2cc1fb0535c53812f35e1179f3d35 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Apr 2024 15:08:23 -0700 Subject: [PATCH 7/7] store: Remove unused methods on BlockRangeColumn --- store/postgres/src/block_range.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/store/postgres/src/block_range.rs b/store/postgres/src/block_range.rs index 8286f4a4243..f05c4e73869 100644 --- a/store/postgres/src/block_range.rs +++ b/store/postgres/src/block_range.rs @@ -165,13 +165,6 @@ impl<'a> BlockRangeColumn<'a> { } } } - - pub fn block(&self) -> BlockNumber { - match self { - BlockRangeColumn::Mutable { block, .. } => *block, - BlockRangeColumn::Immutable { block, .. } => *block, - } - } } impl<'a> BlockRangeColumn<'a> { @@ -227,13 +220,6 @@ impl<'a> BlockRangeColumn<'a> { } } - pub fn column_name(&self) -> &str { - match self { - BlockRangeColumn::Mutable { .. } => BLOCK_RANGE_COLUMN, - BlockRangeColumn::Immutable { .. } => BLOCK_COLUMN, - } - } - /// Output the qualified name of the block range column pub fn name(&self, out: &mut AstPass) { match self {