QUERY EXECUTION
QUERY EXECUTION
再开始这个实验之前首先要搞懂几个类的定义和方法
Catalog.h
Catalog
用来维护一个数据库的元信息,包括表的信息,列的信息,索引的信息,用户和许可权等。在catalog.h
文件中主要包含一下几点信息:
std::unordered_map<table_oid_t, std::unique_ptr<TableInfo>> tables_; //用来维持一个table_id 到 TableInfo 之间的映射
/** 用来表示table的名字到table_id的映射 */
std::unordered_map<std::string, table_oid_t> table_names_;
/** 创建一个table_id */
std::atomic<table_oid_t> next_table_oid_{0};
/** 用来维持一个index_id 到 indexinfo的映射**/
std::unordered_map<index_oid_t, std::unique_ptr<IndexInfo>> indexes_;
/** Map table name -> index names -> index identifiers. */
std::unordered_map<std::string, std::unordered_map<std::string, index_oid_t>> index_names_;
std::atomic<index_oid_t> next_index_oid_{0};
其中TableInfo
类用来维持一个表的基本信息:
Schema schema_; //一个表的schema
const std::string name_; //一个表的名字
std::unique_ptr<TableHeap> table_; //一个表的迭代器
const table_oid_t oid_; //一个表的id
IndexInfo
类用来维持一个索引的基本信息:
Schema key_schema_; //一个key对应的schema
std::string name_; //一个索引的名字
std::unique_ptr<Index> index_; // 一个指向索引的指针
index_oid_t index_oid_; //索引的id
std::string table_name_; //拥有这个索引的表名
const size_t key_size_; //索引的大小
在Catalog
类中我们通过:
TableInfo *CreateTable(); //创建一个表
TableInfo *GetTable(); //获取一个表
IndexInfo *CreateIndex(); //创建一个索引
IndexInfo *GetIndex(); //获取索引
Column.h
用来表示一个表格的一个列的信息。
std::string column_name_; //列的名字
TypeId column_type_; //该列所存储的值的类型
uint32_t fixed_length_;
uint32_t variable_length_{0};
uint32_t column_offset_{0}; //column在一个tuple中的偏移量
const AbstractExpression *expr_; //用来创建这个列的表达式
Schema.h
schema.h
类用来存储一个表格中所有的列。
/** All the columns in the schema, inlined and uninlined. */
std::vector<Column> columns_; // 一个shcema对应的全部列
/** @return all the columns in the schema */
const std::vector<Column> &GetColumns() const { return columns_; }
const Column &GetColumn(const uint32_t col_idx) const { return columns_[col_idx]; }
AbstractExpression.h
AbstractExpression.h
类表达式类是一个虚基类,用来提供各个类的接口。
Value Evaluate
用来返回一个tuple
对应schema
的value
Value EvaluateJoin
返回一个join
过后的value
Value EvaluateAggregate(const std::vector<Value> &group_bys, const std::vector<Value> &aggregates)
返回一个按照group_bys
分组聚合之后的value
表达式类,用来存储一个表达式树中的节点。这个类派生出了一下几个子类:
ComparisonExpression
: 用于比较,实例化用作一个predicate_
谓词或者是一个having
ColumnValueExpression
: 列元素的的表达值,Evalute
函数用来返回当前tuple
的哪一个value
用来对应要求的coloumn
.AggregateValueExpression
:EvaluateAggregate
函数根据是操作时group_by
操作还是aggrate
操作,返回对应操作的value
。
AbstractExecutor
当SQL语句拆分成一个执行数,每个节点的功能承担者。是所有执行算子的虚基类,主要包含一下两个方法:
void Init()
用来初始化一个算子bool Next(Tuple *tuple, RID *rid)
因为我们采用的是火山模型一次只能返回一个tuple
。Schema *GetOutputSchema()
用来表明该节点执行需要输出哪些列
ExecutorContext
用来维护一个Executor
J节点执行所需要的上下文信息。主要包含一下信息:
/** 当前执行当前executor的事务 */
Transaction *transaction_;
/** 当前executor执行的database的catalog */
Catalog *catalog_;
/** 相关的bufferpoolmanager */
BufferPoolManager *bpm_;
/** 和当前事务有关的事务管理器 */
TransactionManager *txn_mgr_;
/** 执行executor所需要的锁管理器 */
LockManager *lock_mgr_;
AbstractPlanNode
所有执行的PlanNode
的虚基类,用来维护每一个存储节点有关的信息。主要包含一下信息:
const Schema *output_schema_; //当前节点输出的schema
/** 当前节点的子节点 */
std::vector<const AbstractPlanNode *> children_;
比如一个seq_plan
包含:
const AbstractExpression *predicate_
: 扫描满足条件的谓词table_oid_t table_oid
_, 用来扫描的表格的table_id
Value
最小的存储单位,用来表示每一个tuple
对应的列的存储数据。
Tuple
元组,代表一个表里面的一行,存储了一行的value
, 每一个tuple
的长度由表格对应的schema
决定,也就是有多少个列tuple
就有多长。每个tuple
根据其所在的page
和对应的slot
都有一个rid
用来标识每一个tuple
。
// 就是将数据二进制化或者反过来,用于存储
void SerializeTo(char *storage) const;
void DeserializeFrom(const char *storage);
inline RID GetRid(); //获取一个tuple的rid
// 返回数据指针
inline char *GetData();//获取一个tuple的全部数据
// 返回tuple的长度(bits)
inline uint32_t GetLength();
// 返回指定colum_idx位置的值
Value GetValue(const Schema *schema, uint32_t column_idx)
// 根据schema , key_schema,构建当前tuple的一个Key主要用来插入到hash table中
Tuple KeyFromTuple(const Schema &schema, const Schema &key_schema, const std::vector<uint32_t> &key_attrs);
TableHeap
代表一个磁盘中存储的一张表的本身
page_id_t first_page_id_{}; //用来存储当前表的第一个page的pageid
/*一个事务,用来插入一个tuple*/
bool InsertTuple(const Tuple &tuple, RID *rid, Transaction *txn);
bool MarkDelete(const RID &rid, Transaction *txn); // 根据rid删除一个tuple
bool UpdateTuple(const Tuple &tuple, const RID &rid, Transaction *txn); //更新对应的tuple
void ApplyDelete(const RID &rid, Transaction *txn); // 执行删除操作
void RollbackDelete(const RID &rid, Transaction *txn); //txn回滚,回滚删除操作
TableIterator Begin(Transaction *txn); //当前table第一个tuple的位置,通过Page_id迭代查找第一个tuple的位置
TableIterator End(); //最后一个位置的下一个位置
Table_page
在底层的tuple存储到page
里面的存储方式采用sloted_page
的存储方式,分为一个slot_array
里面存储每一个tuple
的指针用来指向tuple
的位置,slot_array
按照从前往后的次序依次递增,tuple
从后往前依次存储,当两者相遇的时候说明一个page
存满了。
/**
** Slotted page format:*
** ---------------------------------------------------------*
** | HEADER | ... FREE SPACE ... | ... INSERTED TUPLES ... |*
** ---------------------------------------------------------*
** ^*
** free space pointer*
** Header format (size in bytes)😗
** ----------------------------------------------------------------------------*
** | PageId (4)| LSN (4)| PrevPageId (4)| NextPageId (4)| FreeSpacePointer(4) |*
** ----------------------------------------------------------------------------*
** ----------------------------------------------------------------*
** | TupleCount (4) | Tuple_1 offset (4) | Tuple_1 size (4) | ... |*
** ----------------------------------------------------------------*
*/
一个table_page
用来控制一个table
所在page
的tuple
的插入和删除操作,因为一个page
可能存不下一个table
我们采用双向链表的形式记录下一个和上一个用来存储table
的page_id
TableIterator
TableHeap
的迭代器,指向一个Table
的每一个tuple,通过这个可以遍历一张表里面的tuple
每个Executors的实现
-
SEQUENTIAL SCAN 循序扫描,根据
TableIterator
扫描对应的元组,利用plan_
中的predicate
来判断每一个元组是否符合读取的条件,注意要根据out_put_shcema
来获取需要的coloumn
, 在获取指定列对应的值,最后向上返回读到的新的元组 -
INSERT 插入元组, 插入操作分为两种,第一种是
RawInsert
也就是当前插入算子没有子节点,直接插入到对应的表中,我们获取插入表格的schema
和需要插入的value
来构建tuple
并插入到对应的表格中,当插入一个tuple的时候我们还需要在上一个实验中建立的可扩展哈希表中也插入对应的tuple
.第二种是需要我们从子节点读取对应的tuple
然后再执行操作。 -
UPDATE/DELETE 和INSERT操作类似,执行UPDATE操作的时候需要我们同时更新哈希表中的索引,执行delete操作的时候需要删除索引
-
NESTED LOOP JOIN 采用两层
while
循环来执行tuple
的连接操作,利用EvaluteJoin
来判断左右两个tuple
是否满足连接操作。现在Init阶段就执行完全部操作,将链接后的tuple存入一个vector
中,为了方便后续每一次Next
返回一个tuple
, 每调用一次Next
就返回一个vector
中的一个tuple
。 -
HASH JOIN 哈希join我们先利用
left_tuple
创建一个哈希表,当right_tuple
找到对应的哈希表的位置时就将两个tuple
join -
AGGREGATION 主要实现了4种聚合操作,分别是
COUNT
,SUM
,MIN
,MAX
, 同时支持GROUP BY
, 和HAVING
. 为了实现火山模型,我们先再INIT
阶段构建aggragatekey
和aggragatevalue
,并存入对应的哈希表中,再next
阶段遍历哈希表里面的每一个元素,利用如果有having谓词的话利用having
来判断是否aggragatekey
和aggratatevalue
是否满足条件,满足后获取聚合后对应列的值并将其存入一个数组中,最后构建一个tuple并返回。 -
LIMIT 直接设定一个计数器即可实现
-
DISTINCT 和HASH JOIN类似,再同一个哈希表里面的就不返回。