在ParquetReader的构造函数中,会打开文件句柄,加载元数据,并初始化。Scan函数负责实际的扫描过程,处理行组,应用过滤器,读取数据到DataChunk中。首先,在元数据加载部分,parquet文件的元数据通常位于文件末尾,包含schema、行组信息、统计信息等。LoadMetadata函数通过读取文件末尾的元数据部分,解析出FileMetaData结构。
DeriveLogicalType函数根据Parquet的SchemaElement中的类型信息(如Type、converted_type、logicalType)转换为DuckDB的LogicalType。例如,Parquet的INT32可能对应DuckDB的INTEGER,而带有converted_type为DATE的INT32会被转换为DATE类型。这里还处理了时间戳、UUID等复杂类型的转换。
创建列读取器的过程在CreateReaderRecursive中完成,这个函数递归地遍历SchemaElement树,根据每个元素的类型和子元素创建相应的ColumnReader。例如,遇到STRUCT类型时,会创建StructColumnReader,并为每个子列创建对应的读取器,对于重复的字段(如LIST或MAP),会使用ListColumnReader来处理嵌套结构。
初始化时,InitializeSchema调用CreateReader创建根读取器,通常是StructColumnReader,因为它对应Parquet文件的根结构。然后根据读取到的列信息填充columns向量,记录每个列的名称和类型。
扫描数据的过程由Scan函数处理。ParquetReaderScanState用于跟踪扫描的状态,包括当前处理的行组、文件句柄、过滤器等。PrepareRowGroupBuffer函数准备当前行组的数据,可能应用统计信息进行过滤,跳过不需要读取的行组。然后通过ColumnReader的Read方法将数据读取到DataChunk中,应用过滤器(如果有的话),最终将结果返回。
过滤器处理部分,ApplyFilter函数根据过滤条件对读取的数据进行过滤。例如,等值比较、范围比较、IS NULL等条件会被应用到数据上,减少需要处理的数据量,提升查询性能。