Skip to content

Commit c6e93e8

Browse files
committed
store: Support aliasing tables
1 parent 751cba2 commit c6e93e8

File tree

1 file changed

+109
-8
lines changed
  • store/postgres/src/relational

1 file changed

+109
-8
lines changed

store/postgres/src/relational/dsl.rs

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,102 @@ impl ValidGrouping<()> for DummyExpression {
7676
type IsAggregate = is_aggregate::No;
7777
}
7878

79+
/// A fixed size string for the table alias. We want to make sure that
80+
/// converting these to `&str` doesn't allocate and that they are small
81+
/// enough that the `Table` struct is only 16 bytes and can be `Copy`
82+
#[derive(Debug, Clone, Copy)]
83+
pub struct ChildAliasStr {
84+
alias: [u8; 4],
85+
}
86+
87+
impl ChildAliasStr {
88+
fn new(idx: u8) -> Self {
89+
let c = 'i' as u8;
90+
let alias = if idx == 0 {
91+
[c, 0, 0, 0]
92+
} else if idx < 10 {
93+
let ones = char::from_digit(idx as u32, 10).unwrap() as u8;
94+
[c, ones, 0, 0]
95+
} else if idx < 100 {
96+
let tens = char::from_digit((idx / 10) as u32, 10).unwrap() as u8;
97+
let ones = char::from_digit((idx % 10) as u32, 10).unwrap() as u8;
98+
[c, tens, ones, 0]
99+
} else {
100+
let hundreds = char::from_digit((idx / 100) as u32, 10).unwrap() as u8;
101+
let idx = idx % 100;
102+
let tens = char::from_digit((idx / 10) as u32, 10).unwrap() as u8;
103+
let ones = char::from_digit((idx % 10) as u32, 10).unwrap() as u8;
104+
[c, hundreds, tens, ones]
105+
};
106+
ChildAliasStr { alias }
107+
}
108+
109+
fn as_str(&self) -> &str {
110+
let alias = if self.alias[1] == 0 {
111+
return "i";
112+
} else if self.alias[2] == 0 {
113+
&self.alias[..2]
114+
} else if self.alias[3] == 0 {
115+
&self.alias[..3]
116+
} else {
117+
&self.alias
118+
};
119+
unsafe { std::str::from_utf8_unchecked(alias) }
120+
}
121+
}
122+
123+
/// A table alias. We use `c` as the main table alias and `i`, `i1`, `i2`,
124+
/// ... for child tables. The fact that we use these specific letters is
125+
/// historical and doesn't have any meaning.
126+
#[derive(Debug, Clone, Copy)]
127+
pub enum Alias {
128+
Main,
129+
Child(ChildAliasStr),
130+
}
131+
132+
impl Alias {
133+
fn as_str(&self) -> &str {
134+
match self {
135+
Alias::Main => "c",
136+
Alias::Child(idx) => idx.as_str(),
137+
}
138+
}
139+
140+
fn child(idx: u8) -> Self {
141+
Alias::Child(ChildAliasStr::new(idx))
142+
}
143+
}
144+
145+
#[test]
146+
fn alias() {
147+
assert_eq!(Alias::Main.as_str(), "c");
148+
assert_eq!(Alias::Child(ChildAliasStr::new(0)).as_str(), "i");
149+
assert_eq!(Alias::Child(ChildAliasStr::new(1)).as_str(), "i1");
150+
assert_eq!(Alias::Child(ChildAliasStr::new(10)).as_str(), "i10");
151+
assert_eq!(Alias::Child(ChildAliasStr::new(100)).as_str(), "i100");
152+
assert_eq!(Alias::Child(ChildAliasStr::new(255)).as_str(), "i255");
153+
}
154+
79155
#[derive(Debug, Clone, Copy)]
80156
/// A wrapper around the `super::Table` struct that provides helper
81157
/// functions for generating SQL queries
82158
pub struct Table<'a> {
83159
/// The metadata for this table
84160
meta: &'a super::Table,
161+
alias: Alias,
85162
}
86163

87164
impl<'a> Table<'a> {
88165
pub(crate) fn new(meta: &'a super::Table) -> Self {
89-
Self { meta }
166+
Self {
167+
meta,
168+
alias: Alias::Main,
169+
}
170+
}
171+
172+
pub fn child(mut self, idx: u8) -> Self {
173+
self.alias = Alias::child(idx);
174+
self
90175
}
91176

92177
/// Reference a column in this table and use the correct SQL type `ST`
@@ -201,7 +286,7 @@ impl<'a> Table<'a> {
201286
table: &'b Table<'b>,
202287
column: &'b RelColumn,
203288
) {
204-
let name = format!("{}.{}::text", table.meta.qualified_name, &column.name);
289+
let name = format!("{}.{}::text", table.alias.as_str(), &column.name);
205290

206291
match (column.is_list(), column.is_nullable()) {
207292
(true, true) => select.add_field(sql::<Nullable<Array<Text>>>(&name)),
@@ -237,12 +322,30 @@ impl<'a> Table<'a> {
237322
}
238323
}
239324

325+
pub struct FromTable<'a>(Table<'a>);
326+
327+
impl<'a, DB> QueryFragment<DB> for FromTable<'a>
328+
where
329+
DB: Backend,
330+
{
331+
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
332+
out.unsafe_to_cache_prepared();
333+
334+
out.push_identifier(self.0.meta.nsp.as_str())?;
335+
out.push_sql(".");
336+
out.push_identifier(&self.0.meta.name)?;
337+
out.push_sql(" as ");
338+
out.push_sql(self.0.alias.as_str());
339+
Ok(())
340+
}
341+
}
342+
240343
impl<'a> QuerySource for Table<'a> {
241-
type FromClause = Self;
344+
type FromClause = FromTable<'a>;
242345
type DefaultSelection = DummyExpression;
243346

244-
fn from_clause(&self) -> Self {
245-
self.clone()
347+
fn from_clause(&self) -> FromTable<'a> {
348+
FromTable(*self)
246349
}
247350

248351
fn default_selection(&self) -> Self::DefaultSelection {
@@ -285,9 +388,7 @@ where
285388
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
286389
out.unsafe_to_cache_prepared();
287390

288-
out.push_identifier(self.meta.nsp.as_str())?;
289-
out.push_sql(".");
290-
out.push_identifier(&self.meta.name)?;
391+
out.push_sql(self.alias.as_str());
291392
Ok(())
292393
}
293394
}

0 commit comments

Comments
 (0)