Skip to content

Commit

Permalink
feat: [lab3] support table alias in join clause (#14)
Browse files Browse the repository at this point in the history
目前代码不支持 `JOIN` 子句中使用表别名,涉及多表连接时不方便书写 SQL 语句。考虑扩充 SQL 解析的文法,在 `join_list`
支持形如 `table_name table_alias` 或 `table_name AS table_alias`
的语法,同时重构文法中对表别名的处理方式,提升代码复用性。

修改后,连接操作的 SQL 可以使用别名简化,例如
```sql
SELECT * FROM join_table_1 a
INNER JOIN join_table_2 b ON b.id = a.id
INNER JOIN join_table_3 c ON c.id = b.id
WHERE a.name = 'a';
```
同时也更方便进行 self-join,例如
```sql
SELECT * FROM join_table_1 a
INNER JOIN join_table_1 b ON b.id = a.id + 1;
```
  • Loading branch information
hotwords123 authored May 6, 2024
1 parent a43bd5f commit 1cfb3b5
Show file tree
Hide file tree
Showing 9 changed files with 635 additions and 718 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class IndexScanPhysicalOperator : public PhysicalOperator
return PhysicalOperatorType::INDEX_SCAN;
}

void set_table_alias(const std::string &alias) {
table_alias_ = alias;
}

RC open(Trx *trx) override;
RC next() override;
RC close() override;
Expand All @@ -54,6 +58,7 @@ class IndexScanPhysicalOperator : public PhysicalOperator
private:
RC filter(RowTuple &tuple, bool &result);
Table *table_ = nullptr;
std::string table_alias_;
Index *index_ = nullptr;
IndexScanner *index_scanner_ = nullptr;
RecordFileHandler *record_handler_ = nullptr;
Expand Down
9 changes: 5 additions & 4 deletions src/server/include/query_engine/structor/tuple/row_tuple.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class RowTuple : public Tuple
void set_schema(const Table *table, const std::string &table_alias, const std::vector<FieldMeta> *fields)
{
table_ = table;
table_alias_ = table_alias;
this->species_.reserve(fields->size());
for (const FieldMeta &field : *fields) {
species_.push_back(new FieldExpr(table, &field));
Expand Down Expand Up @@ -81,15 +82,14 @@ class RowTuple : public Tuple
{
const char *table_name = spec.table_name();
const char *field_name = spec.field_name();
if (0 != strcmp(table_name, table_->name())) {
if (0 != strcmp(table_name, table_->name()) || spec.alias() != table_alias_) {
return RC::NOTFOUND;
}

for (size_t i = 0; i < species_.size(); ++i) {
const FieldExpr *field_expr = species_[i];
const Field &field = field_expr->field();
if (0 == strcmp(table_name, field.table_name()) &&
0 == strcmp(field_name, field.field_name())) {
if (0 == strcmp(field_name, field.field_name())) {
return cell_at(i, cell);
}
}
Expand All @@ -110,6 +110,7 @@ class RowTuple : public Tuple
Record *record_ = nullptr;
common::Bitmap bitmap_;
const Table *table_ = nullptr;
std::string table_alias_;
std::vector<FieldExpr *> species_;
bool order_set_ = false;
};
};
149 changes: 64 additions & 85 deletions src/server/query_engine/analyzer/statement/select_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,7 @@ static void wildcard_fields(
Table *table,
int table_count,
std::vector<Expression*> &projects,
std::unordered_map<Table *, std::string> &alias_map) {

std::string alias = table->name();
if (alias_map.contains(table)) {
alias = alias_map[table];
}
const std::string &alias) {

if (table_count == 1) {
wildcard_fields_without_table_name(table, projects, alias);
Expand All @@ -63,8 +58,9 @@ static void wildcard_fields(
}

RC _process_attribute_expression(Db *db, const Expression *expr, const char *table_name, const char* field_name,
std::vector<Table *> &tables, std::vector<Expression *> &projects,std::unordered_map<Table *, std::string> alias_map,
std::unordered_map<std::string, Table *> &table_map){
const std::vector<Table *> &tables /*in*/,
std::vector<Expression *> &projects /*out*/,
const std::vector<std::string> &table_alias /*in*/) {
int table_count = static_cast<int>(tables.size());
/**
* There should be four possible states for attribute_expression with '*'
Expand All @@ -76,75 +72,63 @@ RC _process_attribute_expression(Db *db, const Expression *expr, const char *tab
* This method is complex, you can try to optimize it and submit PR if you like.
*/

if (common::is_blank(table_name) &&
0 == strcmp(field_name, "*")) { // select *
for (Table *table : tables) {
wildcard_fields(table, table_count, projects, alias_map);
}
// Table name is not null
} else if (!common::is_blank(table_name)) {
if (0 == strcmp(table_name, "*")) {
if (0 != strcmp(field_name, "*")) {
// *.attr is unsupported, because you can't guarantee there is
// corresponding 'attr' in each table
LOG_WARN("invalid field name while table is *. attr=%s", field_name);
return RC::SCHEMA_FIELD_MISSING;
}
// *.* is supported
for (Table *table : tables) {
wildcard_fields(table, table_count, projects, alias_map);
}
} else {
// If table_name is not '*', just get fieldExpressions.
auto iter = table_map.find(table_name);
if (iter == table_map.end()) {
LOG_WARN("no such table in from list: %s", table_name);
return RC::SCHEMA_FIELD_MISSING;
}

Table *table = iter->second;
if (0 == strcmp(field_name, "*")) { // select table.*
std::string alias = table->name();
if (alias_map.contains(table)) {
alias = alias_map[table];
}
wildcard_fields_with_table_name(table, projects, alias);

} else { // table.attr
const FieldMeta *field_meta = table->table_meta().field(field_name);
if (nullptr == field_meta) {
LOG_WARN("no such field. field=%s.%s.%s", db->name(), table->name(), field_name);
return RC::SCHEMA_FIELD_MISSING;
}
auto *field_expr = new FieldExpr(table, field_meta);
std::string alias = table->name();
if (alias_map.find(table) != alias_map.end()) {
alias = alias_map[table];
}
field_expr->set_field_table_alias(alias);
field_expr->set_alias(expr->alias().empty() ? expr->name() : expr->alias());
projects.push_back(field_expr);
bool table_is_blank = common::is_blank(table_name);
bool table_is_wildcard = 0 == strcmp(table_name, "*");
bool field_is_wildcard = 0 == strcmp(field_name, "*");

if (field_is_wildcard) {
if (table_is_blank || table_is_wildcard) {
// select * or select *.*
for (int i = 0; i < table_count; i++) {
wildcard_fields(tables[i], table_count, projects, table_alias[i]);
}
return RC::SUCCESS;
}
} else {
if (table_is_wildcard) {
// select *.attr is unsupported, because you can't guarantee there is
// corresponding 'attr' in each table
LOG_WARN("invalid field name while table is *. attr=%s", field_name);
return RC::SCHEMA_FIELD_MISSING;
}
}

// select attr or select table.(attr|*), all for a certain table
Table *table = nullptr;
std::string alias;

if (table_is_blank) {
// If table_name is null, there should be only one table as default table, otherwise it
// will be ambiguous
if (tables.size() != 1) {
if (table_count != 1) {
LOG_WARN("invalid. I do not know the attr's table. attr=%s", field_name);
return RC::SCHEMA_FIELD_MISSING;
}
Table *table = tables[0];

table = tables.front();
alias = table_alias.front();
} else {
auto iter = std::find(table_alias.begin(), table_alias.end(), table_name);
if (iter == table_alias.end()) {
LOG_WARN("no such table in from list: %s", table_name);
return RC::SCHEMA_FIELD_MISSING;
}

table = tables[iter - table_alias.begin()];
alias = *iter;
}

if (field_is_wildcard) {
// select table.*
wildcard_fields_with_table_name(table, projects, alias);
} else {
// select a single field
const FieldMeta *field_meta = table->table_meta().field(field_name);
if (nullptr == field_meta) {
LOG_WARN("no such field. field=%s.%s", table->name(), field_name);
return RC::SCHEMA_FIELD_MISSING;
}
auto *field_expr = new FieldExpr(table, field_meta);
std::string alias = table->name();
if (alias_map.contains(table)) {
alias = alias_map[table];
}
field_expr->set_field_table_alias(alias);
field_expr->set_alias(expr->alias().empty() ? expr->name() : expr->alias());
projects.push_back(field_expr);
Expand All @@ -162,39 +146,31 @@ RC SelectStmt::analyze_tables_and_projects(
std::vector<Expression *> &projects) {

// 1. Collect tables in `from` statement
std::unordered_map<Table *, std::string> alias_map;
for (size_t i = 0; i < select_sql.relations.size(); i++) {
const char *table_name = select_sql.relations[i].relation_name.c_str();
const char *alias_name = select_sql.relations[i].alias.c_str();
if (nullptr == table_name) {
LOG_WARN("invalid argument. relation name is null. index=%d", i);
return RC::INVALID_ARGUMENT;
const string &table_name = select_sql.relations[i].relation_name;
string alias_name = select_sql.relations[i].alias;

// If alias is empty, use table_name as alias
if (alias_name.empty()) {
alias_name = table_name;
}

// check if alias duplicate
if (!select_sql.relations[i].alias.empty()) {
if (0 != table_map.count(std::string(alias_name))) {
return RC::SQL_SYNTAX;
}
if (0 != table_map.count(alias_name)) {
return RC::SQL_SYNTAX;
}

// Find relevant table object from memory(opened_tables)
Table *table = db->find_table(table_name);
Table *table = db->find_table(table_name.c_str());
if (nullptr == table) {
LOG_WARN("no such table. db=%s, table_name=%s", db->name(), table_name);
LOG_WARN("no such table. db=%s, table_name=%s", db->name(), table_name.c_str());
return RC::SCHEMA_TABLE_NOT_EXIST;
}

tables.push_back(table);

table_map.insert(std::pair<std::string, Table *>(table_name, table));
if (!select_sql.relations[i].alias.empty()) {
table_map.insert(std::pair<std::string, Table *>(alias_name, table));
alias_map.insert(std::pair<Table *, std::string>(table, alias_name));
table_alias.emplace_back(alias_name);
} else {
table_alias.emplace_back(table_name);
}
table_map.insert(std::pair<std::string, Table *>(alias_name, table));
table_alias.emplace_back(alias_name);
}

// 2. Collect info in `select` statement
Expand All @@ -206,8 +182,11 @@ RC SelectStmt::analyze_tables_and_projects(
const auto *rel_attr_expr = dynamic_cast<const RelAttrExpr *>(expr);
const RelAttrSqlNode relation_attr = rel_attr_expr->rel_attr_sql_node();
// special logic for attribute expression to deal with '*'
_process_attribute_expression(db, expr, relation_attr.relation_name.c_str(), relation_attr.attribute_name.c_str(),
tables, projects, alias_map, table_map);
RC rc = _process_attribute_expression(db, expr, relation_attr.relation_name.c_str(), relation_attr.attribute_name.c_str(),
tables, projects, table_alias);
if (rc != RC::SUCCESS) {
return rc;
}

} else {
// normal expression analysis
Expand Down Expand Up @@ -369,4 +348,4 @@ RC SelectStmt::create(Db *db, const SelectSqlNode &select_sql, Stmt *&stmt)
select_stmt->join_filter_stmts_ = join_filter_stmts;
stmt = select_stmt;
return RC::SUCCESS;
}
}
2 changes: 2 additions & 0 deletions src/server/query_engine/executor/execution_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,15 @@ RC send_result(SessionRequest *request, bool &need_disconnect, const size_t &min
Value value;
rc = tuple->cell_at(i, value);
if(rc != RC::SUCCESS){
LOG_WARN("failed to get value from tuple. rc=%s", strrc(rc));
sql_result->close();
return rc;
}

std::string value_str;
rc = value_to_string(value, value_str);
if(rc != RC::SUCCESS){
LOG_WARN("failed to convert value to string. rc=%s", strrc(rc));
sql_result->close();
return rc;
}
Expand Down
Loading

0 comments on commit 1cfb3b5

Please sign in to comment.