Skip to content
Snippets Groups Projects
Commit dc83f9e7 authored by Denis Smirnov's avatar Denis Smirnov
Browse files

feat: implement IR

At the moment IR (logical plan) is far from final release. Current
commit fixes some point on a way to the final API and full test
coverage.
parent e10c70ce
No related branches found
No related tags found
1 merge request!1414sbroad import
Showing
with 809 additions and 95 deletions
......@@ -8,11 +8,12 @@ edition = "2018"
[dependencies]
decimal = "2.1.0"
tarantool = "0.4.2"
sqlparser = "0.11.0"
fasthash = "0.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
fasthash = "0.4.0"
sqlparser = "0.11.0"
tarantool = "0.4.2"
traversal = "0.1.2"
yaml-rust = "0.4.1"
[dev-dependencies]
......
......@@ -3,6 +3,8 @@ use std::fmt;
const BUCKET_ID_ERROR: &str = "field doesn't contains sharding key value";
const DUPLICATE_COLUMN_ERROR: &str = "duplicate column";
const INVALID_PLAN_ERROR: &str = "invalid plan";
const INVALID_RELATION_ERROR: &str = "invalid relation";
const INVALID_SHARDING_KEY_ERROR: &str = "invalid sharding key";
const SERIALIZATION_ERROR: &str = "serialization";
const SIMPLE_QUERY_ERROR: &str = "query doesn't simple";
......@@ -15,6 +17,8 @@ pub enum QueryPlannerError {
BucketIdError,
DuplicateColumn,
InvalidShardingKey,
InvalidPlan,
InvalidRelation,
Serialization,
SimpleQueryError,
SimpleUnionQueryError,
......@@ -27,6 +31,8 @@ impl fmt::Display for QueryPlannerError {
let p = match self {
QueryPlannerError::BucketIdError => BUCKET_ID_ERROR,
QueryPlannerError::DuplicateColumn => DUPLICATE_COLUMN_ERROR,
QueryPlannerError::InvalidPlan => INVALID_PLAN_ERROR,
QueryPlannerError::InvalidRelation => INVALID_RELATION_ERROR,
QueryPlannerError::InvalidShardingKey => INVALID_SHARDING_KEY_ERROR,
QueryPlannerError::Serialization => SERIALIZATION_ERROR,
QueryPlannerError::SimpleQueryError => SIMPLE_QUERY_ERROR,
......
pub mod expression;
pub mod operator;
pub mod relation;
pub mod value;
use crate::errors::QueryPlannerError;
use expression::Expression;
use operator::Relational;
use relation::Table;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Node {
Expression(Expression),
Relational(Relational),
}
pub fn push_and_get_idx<T>(v: &mut Vec<T>, item: T) -> usize {
let idx = v.len();
v.push(item);
idx
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Plan {
nodes: Vec<Node>,
relations: Option<HashMap<String, Table>>,
slices: Option<Vec<Vec<usize>>>,
top: Option<usize>,
}
#[allow(dead_code)]
impl Plan {
pub fn add_rel(&mut self, table: Table) {
match &mut self.relations {
None => {
let mut map = HashMap::new();
map.insert(String::from(table.name()), table);
self.relations = Some(map);
}
Some(relations) => {
relations.entry(String::from(table.name())).or_insert(table);
}
}
}
pub fn empty() -> Self {
Plan {
nodes: Vec::new(),
relations: None,
slices: None,
top: None,
}
}
pub fn from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
let plan: Plan = match serde_yaml::from_str(s) {
Ok(p) => p,
Err(_) => return Err(QueryPlannerError::Serialization),
};
plan.check()?;
Ok(plan)
}
pub fn check(&self) -> Result<(), QueryPlannerError> {
if self.top.is_none() {
return Err(QueryPlannerError::InvalidPlan);
} else if self.nodes.get(self.top.unwrap()).is_none() {
return Err(QueryPlannerError::ValueOutOfRange);
}
//TODO: additional consistency checks
Ok(())
}
}
#[derive(Debug)]
pub struct BranchIterator<'n> {
node: &'n Node,
step: RefCell<usize>,
plan: &'n Plan,
}
#[allow(dead_code)]
impl<'n> BranchIterator<'n> {
pub fn new(node: &'n Node, plan: &'n Plan) -> Self {
BranchIterator {
node,
step: RefCell::new(0),
plan,
}
}
}
impl<'n> Iterator for BranchIterator<'n> {
type Item = &'n Node;
fn next(&mut self) -> Option<Self::Item> {
match self.node {
Node::Expression(expr) => match expr {
Expression::Bool { left, right, .. } => {
let current_step = *self.step.borrow();
if current_step == 0 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*left);
} else if current_step == 1 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*right);
}
None
}
Expression::Constant { .. } | Expression::Reference { .. } => None,
Expression::Row { list } => {
let current_step = *self.step.borrow();
if let Some(node) = list.get(current_step) {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*node);
}
None
}
},
Node::Relational(rel) => match rel {
Relational::InnerJoin {
left,
right,
condition,
..
} => {
let current_step = *self.step.borrow();
if current_step == 0 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*left);
} else if current_step == 1 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*right);
} else if current_step == 2 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*condition);
}
None
}
Relational::ScanRelation { .. } => None,
Relational::ScanSubQuery { child, .. }
| Relational::Motion { child, .. }
| Relational::Selection { child, .. }
| Relational::Projection { child, .. } => {
let current_step = *self.step.borrow();
if current_step == 0 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*child);
}
None
}
Relational::UnionAll { left, right, .. } => {
let current_step = *self.step.borrow();
if current_step == 0 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*left);
} else if current_step == 1 {
*self.step.borrow_mut() += 1;
return self.plan.nodes.get(*right);
}
None
}
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::fs;
use std::path::Path;
#[test]
fn plan_no_top() {
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("plan_no_top.yaml");
let s = fs::read_to_string(path).unwrap();
assert_eq!(
QueryPlannerError::InvalidPlan,
Plan::from_yaml(&s).unwrap_err()
);
}
#[test]
fn plan_oor_top() {
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("plan_oor_top.yaml");
let s = fs::read_to_string(path).unwrap();
assert_eq!(
QueryPlannerError::ValueOutOfRange,
Plan::from_yaml(&s).unwrap_err()
);
}
//TODO: add relation test
}
use super::operator;
use super::value::Value;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Branch {
Both,
Left,
Right,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Expression {
// a > 42
// b in (select c from ...)
Bool {
left: usize,
op: operator::Bool,
right: usize,
},
// 42
Constant {
value: Value,
},
// &0 (left) as b
Reference {
alias: String,
branch: Branch,
/// expression position in the input row
position: usize,
},
// (a, b, 1)
Row {
list: Vec<usize>,
},
}
#[allow(dead_code)]
impl Expression {
pub fn new_const(value: Value) -> Self {
Expression::Constant { value }
}
pub fn new_ref(alias: String, branch: Branch, position: usize) -> Self {
Expression::Reference {
alias,
branch,
position,
}
}
pub fn new_row(list: Vec<usize>) -> Self {
Expression::Row { list }
}
pub fn new_bool(left: usize, op: operator::Bool, right: usize) -> Self {
Expression::Bool { left, op, right }
}
}
use super::expression::{Branch, Expression};
use super::relation::Table;
use super::{push_and_get_idx, Node, Plan};
use crate::errors::QueryPlannerError;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Distribution {
Random,
Replicated,
Segment { key: Vec<usize> },
Single,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Bool {
And,
Eq,
EqAll,
Gt,
GtEq,
Lt,
LtEq,
NotEq,
Or,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Relational {
InnerJoin {
distribution: Distribution,
condition: usize,
left: usize,
output: usize,
right: usize,
},
Motion {
child: usize,
distribution: Distribution,
output: usize,
},
Projection {
child: usize,
distribution: Distribution,
output: usize,
},
ScanRelation {
distribution: Distribution,
output: usize,
relation: String,
},
ScanSubQuery {
child: usize,
distribution: Distribution,
output: usize,
},
Selection {
child: usize,
distribution: Distribution,
filter: usize,
output: usize,
},
UnionAll {
distribution: Distribution,
left: usize,
right: usize,
output: usize,
},
}
#[allow(dead_code)]
impl Relational {
pub fn output(&self) -> usize {
match self {
Relational::InnerJoin { output, .. }
| Relational::Motion { output, .. }
| Relational::Projection { output, .. }
| Relational::ScanRelation { output, .. }
| Relational::ScanSubQuery { output, .. }
| Relational::Selection { output, .. }
| Relational::UnionAll { output, .. } => *output,
}
}
pub fn distribution(&self) -> Distribution {
match self {
Relational::InnerJoin {
ref distribution, ..
}
| Relational::Motion {
ref distribution, ..
}
| Relational::Projection {
ref distribution, ..
}
| Relational::ScanRelation {
ref distribution, ..
}
| Relational::ScanSubQuery {
ref distribution, ..
}
| Relational::Selection {
ref distribution, ..
}
| Relational::UnionAll {
ref distribution, ..
} => match distribution {
Distribution::Random => Distribution::Random,
Distribution::Replicated => Distribution::Replicated,
Distribution::Segment { ref key } => Distribution::Segment { key: key.clone() },
Distribution::Single => Distribution::Single,
},
}
}
pub fn from_table(table_name: &str, plan: &mut Plan) -> Result<Self, QueryPlannerError> {
let nodes = &mut plan.nodes;
if let Some(relations) = &plan.relations {
if let Some(rel) = relations.get(table_name) {
match rel {
Table::Segment {
ref columns,
key,
name: _,
} => {
let refs = columns
.iter()
.enumerate()
.map(|(pos, col)| {
let r =
Expression::new_ref(String::from(&col.name), Branch::Left, pos);
push_and_get_idx(nodes, Node::Expression(r))
})
.collect();
return Ok(Relational::ScanRelation {
distribution: Distribution::Segment { key: key.clone() },
output: push_and_get_idx(
nodes,
Node::Expression(Expression::new_row(refs)),
),
relation: String::from(table_name),
});
}
//TODO: implement virtual tables as well
_ => return Err(QueryPlannerError::InvalidRelation),
}
}
}
Err(QueryPlannerError::InvalidRelation)
}
// TODO: replace output with column name list
pub fn new_proj(
plan: &mut Plan,
child: usize,
output: Vec<usize>,
) -> Result<Self, QueryPlannerError> {
let nodes = &mut plan.nodes;
let get_node = |pos: usize| -> Result<&Node, QueryPlannerError> {
match nodes.get(pos) {
None => Err(QueryPlannerError::ValueOutOfRange),
Some(node) => Ok(node),
}
};
match get_node(child)? {
Node::Expression(_) => Err(QueryPlannerError::InvalidPlan),
Node::Relational(rel) => {
if let Node::Expression(Expression::Row { ref list }) = get_node(rel.output())? {
if list.len() < output.len() {
Err(QueryPlannerError::InvalidPlan)
} else {
Ok(Relational::Projection {
child,
distribution: rel.distribution(),
output: push_and_get_idx(
nodes,
Node::Expression(Expression::new_row(output)),
),
})
}
} else {
Err(QueryPlannerError::InvalidPlan)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::relation::*;
use pretty_assertions::assert_eq;
use std::fs;
use std::path::Path;
#[test]
fn scan_rel() {
let mut plan = Plan::empty();
let t = Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
Column::new("b", Type::Number),
Column::new("c", Type::String),
Column::new("d", Type::String),
],
&["b", "a"],
)
.unwrap();
plan.add_rel(t);
let scan = Relational::from_table("t", &mut plan).unwrap();
assert_eq!(
Relational::ScanRelation {
distribution: Distribution::Segment { key: vec![1, 0] },
output: 4,
relation: String::from("t"),
},
scan
);
assert_eq!(5, push_and_get_idx(&mut plan.nodes, Node::Relational(scan)));
}
#[test]
fn scan_rel_serialized() {
let mut plan = Plan::empty();
let t = Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
Column::new("b", Type::Number),
Column::new("c", Type::String),
Column::new("d", Type::String),
],
&["b", "a"],
)
.unwrap();
plan.add_rel(t);
let scan = Relational::from_table("t", &mut plan).unwrap();
plan.nodes.push(Node::Relational(scan));
plan.top = Some(5);
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("operator")
.join("scan_rel.yaml");
let s = fs::read_to_string(path).unwrap();
assert_eq!(plan, Plan::from_yaml(&s).unwrap());
}
#[test]
fn projection() {
let mut plan = Plan::empty();
let t = Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
Column::new("b", Type::Number),
Column::new("c", Type::String),
Column::new("d", Type::String),
],
&["b", "a"],
)
.unwrap();
plan.add_rel(t);
let scan = Relational::from_table("t", &mut plan).unwrap();
let scan_idx = push_and_get_idx(&mut plan.nodes, Node::Relational(scan));
let proj = Relational::new_proj(&mut plan, scan_idx, vec![3, 4]).unwrap();
}
}
use super::value::Value;
use crate::errors::QueryPlannerError;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
......@@ -11,13 +12,13 @@ pub enum Type {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Column {
name: String,
type_name: Type,
pub name: String,
pub type_name: Type,
}
#[allow(dead_code)]
impl Column {
fn new(n: &str, t: Type) -> Self {
pub fn new(n: &str, t: Type) -> Self {
Column {
name: n.into(),
type_name: t,
......@@ -26,15 +27,36 @@ impl Column {
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct TableShard {
name: String,
columns: Vec<Column>,
sharding_key: Vec<usize>,
pub enum Table {
Segment {
columns: Vec<Column>,
key: Vec<usize>,
name: String,
},
Virtual {
columns: Vec<Column>,
data: Vec<Vec<Value>>,
name: String,
},
VirtualSegment {
columns: Vec<Column>,
data: HashMap<String, Vec<Vec<Value>>>,
key: Vec<usize>,
name: String,
},
}
#[allow(dead_code)]
impl TableShard {
fn new(n: &str, c: Vec<Column>, k: &[&str]) -> Result<Self, QueryPlannerError> {
impl Table {
pub fn name(&self) -> &str {
match self {
Table::Segment { name, .. }
| Table::Virtual { name, .. }
| Table::VirtualSegment { name, .. } => name,
}
}
pub fn new_seg(n: &str, c: Vec<Column>, k: &[&str]) -> Result<Self, QueryPlannerError> {
let mut pos_map: HashMap<&str, usize> = HashMap::new();
let cols = &c;
let no_duplicates = cols
......@@ -56,36 +78,42 @@ impl TableShard {
.collect();
let positions = res_positions?;
Ok(TableShard {
Ok(Table::Segment {
name: n.into(),
columns: c,
sharding_key: positions,
key: positions,
})
}
fn from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
let ts: TableShard = match serde_yaml::from_str(s) {
pub fn seg_from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
let ts: Table = match serde_yaml::from_str(s) {
Ok(t) => t,
Err(_) => return Err(QueryPlannerError::Serialization),
};
let mut uniq_cols: HashSet<&str> = HashSet::new();
let cols = &ts.columns;
if let Table::Segment { columns, key, .. } = &ts {
let mut uniq_cols: HashSet<&str> = HashSet::new();
let cols = columns;
let no_duplicates = cols.iter().all(|col| uniq_cols.insert(&col.name));
let no_duplicates = cols.iter().all(|col| uniq_cols.insert(&col.name));
if !no_duplicates {
return Err(QueryPlannerError::DuplicateColumn);
}
if !no_duplicates {
return Err(QueryPlannerError::DuplicateColumn);
}
let keys = &ts.sharding_key;
let in_range = keys.iter().all(|pos| *pos < cols.len());
let keys = key;
let in_range = keys.iter().all(|pos| *pos < cols.len());
if !in_range {
return Err(QueryPlannerError::ValueOutOfRange);
}
if !in_range {
return Err(QueryPlannerError::ValueOutOfRange);
}
Ok(ts)
Ok(ts)
} else {
Err(QueryPlannerError::Serialization)
}
}
//TODO: constructors for Virtual and VirtualSegment
}
#[cfg(test)]
......@@ -107,8 +135,8 @@ mod tests {
}
#[test]
fn table() {
let t = TableShard::new(
fn table_seg() {
let t = Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
......@@ -119,15 +147,23 @@ mod tests {
&["b", "a"],
)
.unwrap();
assert_eq!(2, t.sharding_key.len());
assert_eq!(0, t.sharding_key[1]);
assert_eq!(1, t.sharding_key[0]);
if let Table::Segment { key, .. } = &t {
assert_eq!(2, key.len());
assert_eq!(0, key[1]);
assert_eq!(1, key[0]);
}
}
#[test]
fn table_seg_name() {
let t = Table::new_seg("t", vec![Column::new("a", Type::Boolean)], &["a"]).unwrap();
assert_eq!("t", t.name());
}
#[test]
fn table_duplicate_columns() {
fn table_seg_duplicate_columns() {
assert_eq!(
TableShard::new(
Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
......@@ -143,9 +179,9 @@ mod tests {
}
#[test]
fn table_wrong_sharding_key() {
fn table_seg_wrong_key() {
assert_eq!(
TableShard::new(
Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
......@@ -161,8 +197,8 @@ mod tests {
}
#[test]
fn table_serialized() {
let t = TableShard::new(
fn table_seg_serialized() {
let t = Table::new_seg(
"t",
vec![
Column::new("a", Type::Boolean),
......@@ -178,66 +214,66 @@ mod tests {
.join("artifactory")
.join("ir")
.join("relation")
.join("table_serialized.yaml");
.join("table_seg_serialized.yaml");
let s = fs::read_to_string(path).unwrap();
let t_yaml = TableShard::from_yaml(&s).unwrap();
let t_yaml = Table::seg_from_yaml(&s).unwrap();
assert_eq!(t, t_yaml);
}
#[test]
fn table_serialized_duplicate_columns() {
fn table_seg_serialized_duplicate_columns() {
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("relation")
.join("table_serialized_duplicate_columns.yaml");
.join("table_seg_serialized_duplicate_columns.yaml");
let s = fs::read_to_string(path).unwrap();
assert_eq!(
TableShard::from_yaml(&s).unwrap_err(),
Table::seg_from_yaml(&s).unwrap_err(),
QueryPlannerError::DuplicateColumn
);
}
#[test]
fn table_serialized_out_of_range_sharding_key() {
fn table_seg_serialized_out_of_range_key() {
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("relation")
.join("table_serialized_out_of_range_sharding_key.yaml");
.join("table_seg_serialized_out_of_range_key.yaml");
let s = fs::read_to_string(path).unwrap();
assert_eq!(
TableShard::from_yaml(&s).unwrap_err(),
Table::seg_from_yaml(&s).unwrap_err(),
QueryPlannerError::ValueOutOfRange
);
}
#[test]
fn table_serialized_no_sharding_key() {
fn table_seg_serialized_no_key() {
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("relation")
.join("table_serialized_no_sharding_key.yaml");
.join("table_seg_serialized_no_key.yaml");
let s = fs::read_to_string(path).unwrap();
let t = TableShard::from_yaml(&s);
let t = Table::seg_from_yaml(&s);
assert_eq!(t.unwrap_err(), QueryPlannerError::Serialization);
}
#[test]
fn table_serialized_no_columns() {
fn table_seg_serialized_no_columns() {
let path = Path::new("")
.join("tests")
.join("artifactory")
.join("ir")
.join("relation")
.join("table_serialized_no_columns.yaml");
.join("table_seg_serialized_no_columns.yaml");
let s = fs::read_to_string(path).unwrap();
assert_eq!(
TableShard::from_yaml(&s).unwrap_err(),
Table::seg_from_yaml(&s).unwrap_err(),
QueryPlannerError::Serialization
);
}
......
......@@ -20,7 +20,7 @@ impl From<bool> for Trivalent {
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Value {
Boolean(bool),
Null,
......@@ -41,16 +41,16 @@ impl fmt::Display for Value {
#[allow(dead_code)]
impl Value {
fn number_from_str(f: &str) -> Self {
pub fn number_from_str(f: &str) -> Self {
let d = d128::from_str(&f.to_string()).unwrap();
Value::Number(d)
}
fn string_from_str(f: &str) -> Self {
pub fn string_from_str(f: &str) -> Self {
Value::String(String::from(f))
}
fn eq(&self, other: &Value) -> Trivalent {
pub fn eq(&self, other: &Value) -> Trivalent {
match &*self {
Value::Boolean(s) => match other {
Value::Boolean(o) => (s == o).into(),
......@@ -87,6 +87,7 @@ impl From<Trivalent> for Value {
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
// Helpers
......
---
nodes:
- Expression:
Reference:
alias: a
branch: Left
position: 0
- Expression:
Reference:
alias: b
branch: Left
position: 1
- Expression:
Reference:
alias: c
branch: Left
position: 2
- Expression:
Reference:
alias: d
branch: Left
position: 3
- Expression:
Row:
list:
- 0
- 1
- 2
- 3
- Relational:
ScanRelation:
distribution:
Segment:
key:
- 1
- 0
output: 4
relation: t
relations:
t:
Segment:
columns:
- name: a
type_name: Boolean
- name: b
type_name: Number
- name: c
type_name: String
- name: d
type_name: String
key:
- 1
- 0
name: t
slices: ~
top: 5
---
nodes:
- Expression:
Reference:
alias: a
branch: Left
position: 0
- Expression:
Row:
list:
- 0
- Relational:
ScanRelation:
distribution:
Segment:
key:
- 0
output: 1
relation: t
relations:
t:
Segment:
columns:
- name: a
type_name: Boolean
key:
- 0
name: t
slices: ~
top: ~
---
nodes:
- Expression:
Reference:
alias: a
branch: Left
position: 0
- Expression:
Row:
list:
- 0
- Relational:
ScanRelation:
distribution:
Segment:
key:
- 0
output: 1
relation: t
relations:
t:
Segment:
columns:
- name: a
type_name: Boolean
key:
- 0
name: t
slices: ~
top: 42
Segment:
columns:
- name: a
type_name: Boolean
- name: b
type_name: Number
- name: c
type_name: String
- name: d
type_name: String
name: t
key:
- 0
- 3
Segment:
columns:
- name: a
type_name: Boolean
- name: b
type_name: String
- name: a
type_name: String
name: t
key:
- 0
Segment:
columns: ~
name: t
key:
- 0
Segment:
columns:
- name: a
type_name: Boolean
name: t
key: ~
Segment:
columns:
- name: a
type_name: Boolean
name: t
key:
- 10
columns:
- name: a
type_name: Boolean
- name: b
type_name: Number
- name: c
type_name: String
- name: d
type_name: String
name: t
sharding_key:
- 0
- 3
columns:
- name: a
type_name: Boolean
- name: b
type_name: String
- name: a
type_name: String
name: t
sharding_key:
- 0
columns: ~
name: t
sharding_key:
- 0
columns:
- name: a
type_name: Boolean
name: t
sharding_key: ~
columns:
- name: a
type_name: Boolean
name: t
sharding_key:
- 10
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment