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对应schemavalue
  • 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

用来维护一个ExecutorJ节点执行所需要的上下文信息。主要包含一下信息:

/** 当前执行当前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所在pagetuple的插入和删除操作,因为一个page可能存不下一个table我们采用双向链表的形式记录下一个和上一个用来存储tablepage_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找到对应的哈希表的位置时就将两个tuplejoin

  • AGGREGATION 主要实现了4种聚合操作,分别是COUNT, SUM, MIN, MAX, 同时支持GROUP BY, 和HAVING. 为了实现火山模型,我们先再INIT阶段构建aggragatekeyaggragatevalue,并存入对应的哈希表中,再next阶段遍历哈希表里面的每一个元素,利用如果有having谓词的话利用having来判断是否aggragatekeyaggratatevalue是否满足条件,满足后获取聚合后对应列的值并将其存入一个数组中,最后构建一个tuple并返回。

  • LIMIT 直接设定一个计数器即可实现

  • DISTINCT 和HASH JOIN类似,再同一个哈希表里面的就不返回。

posted on 2022-12-01 23:13  翔鸽  阅读(42)  评论(0编辑  收藏  举报