diff --git a/pkg/frontend/mysql_cmd_executor.go b/pkg/frontend/mysql_cmd_executor.go index 1cfdcb4c9558a..12f3aa97c2245 100644 --- a/pkg/frontend/mysql_cmd_executor.go +++ b/pkg/frontend/mysql_cmd_executor.go @@ -999,6 +999,13 @@ func doShowVariables(ses *Session, execCtx *ExecCtx, sv *tree.ShowVariables) err var err error useGlobal := sv.Global + if useGlobal { + bh := ses.GetBackgroundExec(execCtx.reqCtx) + defer bh.Close() + if err = ses.refreshGlobalSysVars(execCtx.reqCtx, bh); err != nil { + return err + } + } col1 := new(MysqlColumn) col1.SetColumnType(defines.MYSQL_TYPE_VARCHAR) diff --git a/pkg/frontend/mysql_cmd_executor_test.go b/pkg/frontend/mysql_cmd_executor_test.go index 5bf570a4edba0..22e86402bddba 100644 --- a/pkg/frontend/mysql_cmd_executor_test.go +++ b/pkg/frontend/mysql_cmd_executor_test.go @@ -796,6 +796,100 @@ func Test_handleShowVariables(t *testing.T) { }) } +func TestShowGlobalVariablesRefreshesGlobalSysVarCache(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := defines.AttachAccountId(context.Background(), sysAccountID) + ses := newSes(nil, ctrl) + ses.SetMysqlResultSet(&MysqlResultSet{}) + ses.gSysVars.Set("long_query_time", float64(10)) + require.NoError(t, ses.SetSessionSysVar(ctx, "interactive_timeout", int64(30100))) + require.NoError(t, ses.SetSessionSysVar(ctx, "enable_remap_hint", "on")) + require.True(t, ses.rewriteEnabled.Load()) + + bh := &backgroundExecTest{} + bh.init() + sql := getSqlForGetSystemVariablesWithAccount(sysAccountID) + bh.sql2result[sql] = newMrsForGlobalSystemVariables([][]interface{}{ + {"long_query_time", "1.1"}, + }) + + bhStub := gostub.StubFunc(&NewBackgroundExec, bh) + defer bhStub.Reset() + + stmt, err := parsers.ParseOne(ctx, dialect.MYSQL, "show global variables like 'long_query_time'", 1) + require.NoError(t, err) + showVars, ok := stmt.(*tree.ShowVariables) + require.True(t, ok) + + ec := newTestExecCtx(ctx, ctrl) + require.NoError(t, doShowVariables(ses, ec, showVars)) + + mrs := ses.GetMysqlResultSet() + require.Equal(t, uint64(1), mrs.GetRowCount()) + row, err := mrs.GetRow(ctx, 0) + require.NoError(t, err) + require.Equal(t, "long_query_time", row[0]) + require.Equal(t, float64(1.1), row[1]) + require.Contains(t, bh.executedSQLs, sql) + + sessionTimeout, err := ses.GetSessionSysVar("interactive_timeout") + require.NoError(t, err) + require.Equal(t, int64(30100), sessionTimeout) + enableRemapHint, err := ses.GetSessionSysVar("enable_remap_hint") + require.NoError(t, err) + require.Equal(t, int8(1), enableRemapHint) + require.True(t, ses.rewriteEnabled.Load()) +} + +func TestShowGlobalVariablesReturnsRefreshError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := defines.AttachAccountId(context.Background(), sysAccountID) + ses := newSes(nil, ctrl) + ses.SetMysqlResultSet(&MysqlResultSet{}) + + bh := &backgroundExecTest{} + bh.init() + sql := getSqlForGetSystemVariablesWithAccount(sysAccountID) + bh.sql2err[sql] = moerr.NewInternalErrorNoCtx("refresh global sysvars failed") + + bhStub := gostub.StubFunc(&NewBackgroundExec, bh) + defer bhStub.Reset() + + stmt, err := parsers.ParseOne(ctx, dialect.MYSQL, "show global variables like 'long_query_time'", 1) + require.NoError(t, err) + showVars, ok := stmt.(*tree.ShowVariables) + require.True(t, ok) + + err = doShowVariables(ses, newTestExecCtx(ctx, ctrl), showVars) + require.Error(t, err) + require.Contains(t, err.Error(), "refresh global sysvars failed") + require.Contains(t, bh.executedSQLs, sql) +} + +func newMrsForGlobalSystemVariables(rows [][]interface{}) *MysqlResultSet { + mrs := &MysqlResultSet{} + + col1 := &MysqlColumn{} + col1.SetName("variable_name") + col1.SetColumnType(defines.MYSQL_TYPE_VARCHAR) + mrs.AddColumn(col1) + + col2 := &MysqlColumn{} + col2.SetName("variable_value") + col2.SetColumnType(defines.MYSQL_TYPE_VARCHAR) + mrs.AddColumn(col2) + + for _, row := range rows { + mrs.AddRow(row) + } + + return mrs +} + func Test_GetColumns(t *testing.T) { convey.Convey("GetColumns succ", t, func() { //cw := &ComputationWrapperImpl{exec: &compile.Exec{}} diff --git a/pkg/frontend/session.go b/pkg/frontend/session.go index c0c3aa7b28d63..4ef51e4721485 100644 --- a/pkg/frontend/session.go +++ b/pkg/frontend/session.go @@ -1719,6 +1719,17 @@ func (ses *Session) getGlobalSysVars(ctx context.Context, bh BackgroundExec) (gS return } +func (ses *Session) refreshGlobalSysVars(ctx context.Context, bh BackgroundExec) (err error) { + var sv *SystemVariables + if sv, err = GSysVarsMgr.Get(ses.GetTenantInfo().TenantID, ses, ctx, bh); err != nil { + return + } + ses.mu.Lock() + defer ses.mu.Unlock() + ses.gSysVars = sv + return +} + func (ses *Session) GetPrivilege() *privilege { ses.mu.Lock() defer ses.mu.Unlock() diff --git a/pkg/pb/pipeline/pipeline.pb.go b/pkg/pb/pipeline/pipeline.pb.go index ee362b93ea899..48e05b9c05617 100644 --- a/pkg/pb/pipeline/pipeline.pb.go +++ b/pkg/pb/pipeline/pipeline.pb.go @@ -119,7 +119,7 @@ func (x SampleFunc_SampleType) String() string { } func (SampleFunc_SampleType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{39, 0} + return fileDescriptor_7ac67a7adf3df9c7, []int{40, 0} } type SessionLoggerInfo_LogLevel int32 @@ -156,7 +156,7 @@ func (x SessionLoggerInfo_LogLevel) String() string { } func (SessionLoggerInfo_LogLevel) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{48, 0} + return fileDescriptor_7ac67a7adf3df9c7, []int{49, 0} } type Pipeline_PipelineType int32 @@ -187,7 +187,7 @@ func (x Pipeline_PipelineType) String() string { } func (Pipeline_PipelineType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{49, 0} + return fileDescriptor_7ac67a7adf3df9c7, []int{50, 0} } type Message struct { @@ -3110,29 +3110,109 @@ func (m *FileOffset) GetOffset() []int64 { return nil } +type ParquetRowGroupShard struct { + FileIndex int32 `protobuf:"varint,1,opt,name=file_index,json=fileIndex,proto3" json:"file_index,omitempty"` + RowGroupStart int32 `protobuf:"varint,2,opt,name=row_group_start,json=rowGroupStart,proto3" json:"row_group_start,omitempty"` + RowGroupEnd int32 `protobuf:"varint,3,opt,name=row_group_end,json=rowGroupEnd,proto3" json:"row_group_end,omitempty"` + NumRows int64 `protobuf:"varint,4,opt,name=num_rows,json=numRows,proto3" json:"num_rows,omitempty"` + Bytes int64 `protobuf:"varint,5,opt,name=bytes,proto3" json:"bytes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ParquetRowGroupShard) Reset() { *m = ParquetRowGroupShard{} } +func (m *ParquetRowGroupShard) String() string { return proto.CompactTextString(m) } +func (*ParquetRowGroupShard) ProtoMessage() {} +func (*ParquetRowGroupShard) Descriptor() ([]byte, []int) { + return fileDescriptor_7ac67a7adf3df9c7, []int{32} +} +func (m *ParquetRowGroupShard) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ParquetRowGroupShard) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ParquetRowGroupShard.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ParquetRowGroupShard) XXX_Merge(src proto.Message) { + xxx_messageInfo_ParquetRowGroupShard.Merge(m, src) +} +func (m *ParquetRowGroupShard) XXX_Size() int { + return m.ProtoSize() +} +func (m *ParquetRowGroupShard) XXX_DiscardUnknown() { + xxx_messageInfo_ParquetRowGroupShard.DiscardUnknown(m) +} + +var xxx_messageInfo_ParquetRowGroupShard proto.InternalMessageInfo + +func (m *ParquetRowGroupShard) GetFileIndex() int32 { + if m != nil { + return m.FileIndex + } + return 0 +} + +func (m *ParquetRowGroupShard) GetRowGroupStart() int32 { + if m != nil { + return m.RowGroupStart + } + return 0 +} + +func (m *ParquetRowGroupShard) GetRowGroupEnd() int32 { + if m != nil { + return m.RowGroupEnd + } + return 0 +} + +func (m *ParquetRowGroupShard) GetNumRows() int64 { + if m != nil { + return m.NumRows + } + return 0 +} + +func (m *ParquetRowGroupShard) GetBytes() int64 { + if m != nil { + return m.Bytes + } + return 0 +} + type ExternalScan struct { - Attrs []plan.ExternAttr `protobuf:"bytes,1,rep,name=attrs,proto3" json:"attrs"` - FileSize []int64 `protobuf:"varint,2,rep,packed,name=file_size,json=fileSize,proto3" json:"file_size,omitempty"` - FileOffsetTotal []*FileOffset `protobuf:"bytes,3,rep,name=file_offset_total,json=fileOffsetTotal,proto3" json:"file_offset_total,omitempty"` - Cols []*plan.ColDef `protobuf:"bytes,4,rep,name=cols,proto3" json:"cols,omitempty"` - CreateSql string `protobuf:"bytes,5,opt,name=create_sql,json=createSql,proto3" json:"create_sql,omitempty"` - FileList []string `protobuf:"bytes,6,rep,name=file_list,json=fileList,proto3" json:"file_list,omitempty"` - OriginCols []*plan.ColDef `protobuf:"bytes,7,rep,name=origin_cols,json=originCols,proto3" json:"origin_cols,omitempty"` - Filter *plan.Expr `protobuf:"bytes,8,opt,name=filter,proto3" json:"filter,omitempty"` - StrictSqlMode bool `protobuf:"varint,9,opt,name=strict_sql_mode,json=strictSqlMode,proto3" json:"strict_sql_mode,omitempty"` - ColumnListLen int32 `protobuf:"varint,10,opt,name=column_list_len,json=columnListLen,proto3" json:"column_list_len,omitempty"` - ParallelLoad bool `protobuf:"varint,11,opt,name=parallel_load,json=parallelLoad,proto3" json:"parallel_load,omitempty"` - LoadEmptyNumericAsZero bool `protobuf:"varint,12,opt,name=load_empty_numeric_as_zero,json=loadEmptyNumericAsZero,proto3" json:"load_empty_numeric_as_zero,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Attrs []plan.ExternAttr `protobuf:"bytes,1,rep,name=attrs,proto3" json:"attrs"` + FileSize []int64 `protobuf:"varint,2,rep,packed,name=file_size,json=fileSize,proto3" json:"file_size,omitempty"` + FileOffsetTotal []*FileOffset `protobuf:"bytes,3,rep,name=file_offset_total,json=fileOffsetTotal,proto3" json:"file_offset_total,omitempty"` + Cols []*plan.ColDef `protobuf:"bytes,4,rep,name=cols,proto3" json:"cols,omitempty"` + CreateSql string `protobuf:"bytes,5,opt,name=create_sql,json=createSql,proto3" json:"create_sql,omitempty"` + FileList []string `protobuf:"bytes,6,rep,name=file_list,json=fileList,proto3" json:"file_list,omitempty"` + OriginCols []*plan.ColDef `protobuf:"bytes,7,rep,name=origin_cols,json=originCols,proto3" json:"origin_cols,omitempty"` + Filter *plan.Expr `protobuf:"bytes,8,opt,name=filter,proto3" json:"filter,omitempty"` + StrictSqlMode bool `protobuf:"varint,9,opt,name=strict_sql_mode,json=strictSqlMode,proto3" json:"strict_sql_mode,omitempty"` + ColumnListLen int32 `protobuf:"varint,10,opt,name=column_list_len,json=columnListLen,proto3" json:"column_list_len,omitempty"` + ParallelLoad bool `protobuf:"varint,11,opt,name=parallel_load,json=parallelLoad,proto3" json:"parallel_load,omitempty"` + LoadEmptyNumericAsZero bool `protobuf:"varint,12,opt,name=load_empty_numeric_as_zero,json=loadEmptyNumericAsZero,proto3" json:"load_empty_numeric_as_zero,omitempty"` + ParquetRowGroupShards []*ParquetRowGroupShard `protobuf:"bytes,13,rep,name=parquet_row_group_shards,json=parquetRowGroupShards,proto3" json:"parquet_row_group_shards,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ExternalScan) Reset() { *m = ExternalScan{} } func (m *ExternalScan) String() string { return proto.CompactTextString(m) } func (*ExternalScan) ProtoMessage() {} func (*ExternalScan) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{32} + return fileDescriptor_7ac67a7adf3df9c7, []int{33} } func (m *ExternalScan) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3245,6 +3325,13 @@ func (m *ExternalScan) GetLoadEmptyNumericAsZero() bool { return false } +func (m *ExternalScan) GetParquetRowGroupShards() []*ParquetRowGroupShard { + if m != nil { + return m.ParquetRowGroupShards + } + return nil +} + type StreamScan struct { TblDef *plan.TableDef `protobuf:"bytes,1,opt,name=tbl_def,json=tblDef,proto3" json:"tbl_def,omitempty"` Offset int64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` @@ -3258,7 +3345,7 @@ func (m *StreamScan) Reset() { *m = StreamScan{} } func (m *StreamScan) String() string { return proto.CompactTextString(m) } func (*StreamScan) ProtoMessage() {} func (*StreamScan) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{33} + return fileDescriptor_7ac67a7adf3df9c7, []int{34} } func (m *StreamScan) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3320,7 +3407,7 @@ func (m *TableScan) Reset() { *m = TableScan{} } func (m *TableScan) String() string { return proto.CompactTextString(m) } func (*TableScan) ProtoMessage() {} func (*TableScan) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{34} + return fileDescriptor_7ac67a7adf3df9c7, []int{35} } func (m *TableScan) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3374,7 +3461,7 @@ func (m *ValueScan) Reset() { *m = ValueScan{} } func (m *ValueScan) String() string { return proto.CompactTextString(m) } func (*ValueScan) ProtoMessage() {} func (*ValueScan) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{35} + return fileDescriptor_7ac67a7adf3df9c7, []int{36} } func (m *ValueScan) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3420,7 +3507,7 @@ func (m *UnionAll) Reset() { *m = UnionAll{} } func (m *UnionAll) String() string { return proto.CompactTextString(m) } func (*UnionAll) ProtoMessage() {} func (*UnionAll) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{36} + return fileDescriptor_7ac67a7adf3df9c7, []int{37} } func (m *UnionAll) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3477,7 +3564,7 @@ func (m *HashBuild) Reset() { *m = HashBuild{} } func (m *HashBuild) String() string { return proto.CompactTextString(m) } func (*HashBuild) ProtoMessage() {} func (*HashBuild) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{37} + return fileDescriptor_7ac67a7adf3df9c7, []int{38} } func (m *HashBuild) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3643,7 +3730,7 @@ func (m *Indexbuild) Reset() { *m = Indexbuild{} } func (m *Indexbuild) String() string { return proto.CompactTextString(m) } func (*Indexbuild) ProtoMessage() {} func (*Indexbuild) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{38} + return fileDescriptor_7ac67a7adf3df9c7, []int{39} } func (m *Indexbuild) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3693,7 +3780,7 @@ func (m *SampleFunc) Reset() { *m = SampleFunc{} } func (m *SampleFunc) String() string { return proto.CompactTextString(m) } func (*SampleFunc) ProtoMessage() {} func (*SampleFunc) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{39} + return fileDescriptor_7ac67a7adf3df9c7, []int{40} } func (m *SampleFunc) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3812,7 +3899,7 @@ func (m *Instruction) Reset() { *m = Instruction{} } func (m *Instruction) String() string { return proto.CompactTextString(m) } func (*Instruction) ProtoMessage() {} func (*Instruction) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{40} + return fileDescriptor_7ac67a7adf3df9c7, []int{41} } func (m *Instruction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4188,7 +4275,7 @@ func (m *AnalysisList) Reset() { *m = AnalysisList{} } func (m *AnalysisList) String() string { return proto.CompactTextString(m) } func (*AnalysisList) ProtoMessage() {} func (*AnalysisList) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{41} + return fileDescriptor_7ac67a7adf3df9c7, []int{42} } func (m *AnalysisList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4248,7 +4335,7 @@ func (m *Source) Reset() { *m = Source{} } func (m *Source) String() string { return proto.CompactTextString(m) } func (*Source) ProtoMessage() {} func (*Source) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{42} + return fileDescriptor_7ac67a7adf3df9c7, []int{43} } func (m *Source) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4391,7 +4478,7 @@ func (m *NodeInfo) Reset() { *m = NodeInfo{} } func (m *NodeInfo) String() string { return proto.CompactTextString(m) } func (*NodeInfo) ProtoMessage() {} func (*NodeInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{43} + return fileDescriptor_7ac67a7adf3df9c7, []int{44} } func (m *NodeInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4477,7 +4564,7 @@ func (m *ProcessLimitation) Reset() { *m = ProcessLimitation{} } func (m *ProcessLimitation) String() string { return proto.CompactTextString(m) } func (*ProcessLimitation) ProtoMessage() {} func (*ProcessLimitation) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{44} + return fileDescriptor_7ac67a7adf3df9c7, []int{45} } func (m *ProcessLimitation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4555,7 +4642,7 @@ func (m *PrepareParamInfo) Reset() { *m = PrepareParamInfo{} } func (m *PrepareParamInfo) String() string { return proto.CompactTextString(m) } func (*PrepareParamInfo) ProtoMessage() {} func (*PrepareParamInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{45} + return fileDescriptor_7ac67a7adf3df9c7, []int{46} } func (m *PrepareParamInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4631,7 +4718,7 @@ func (m *ProcessInfo) Reset() { *m = ProcessInfo{} } func (m *ProcessInfo) String() string { return proto.CompactTextString(m) } func (*ProcessInfo) ProtoMessage() {} func (*ProcessInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{46} + return fileDescriptor_7ac67a7adf3df9c7, []int{47} } func (m *ProcessInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4742,7 +4829,7 @@ func (m *SessionInfo) Reset() { *m = SessionInfo{} } func (m *SessionInfo) String() string { return proto.CompactTextString(m) } func (*SessionInfo) ProtoMessage() {} func (*SessionInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{47} + return fileDescriptor_7ac67a7adf3df9c7, []int{48} } func (m *SessionInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4848,7 +4935,7 @@ func (m *SessionLoggerInfo) Reset() { *m = SessionLoggerInfo{} } func (m *SessionLoggerInfo) String() string { return proto.CompactTextString(m) } func (*SessionLoggerInfo) ProtoMessage() {} func (*SessionLoggerInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{48} + return fileDescriptor_7ac67a7adf3df9c7, []int{49} } func (m *SessionLoggerInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4929,7 +5016,7 @@ func (m *Pipeline) Reset() { *m = Pipeline{} } func (m *Pipeline) String() string { return proto.CompactTextString(m) } func (*Pipeline) ProtoMessage() {} func (*Pipeline) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{49} + return fileDescriptor_7ac67a7adf3df9c7, []int{50} } func (m *Pipeline) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5068,7 +5155,7 @@ func (m *WrapNode) Reset() { *m = WrapNode{} } func (m *WrapNode) String() string { return proto.CompactTextString(m) } func (*WrapNode) ProtoMessage() {} func (*WrapNode) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{50} + return fileDescriptor_7ac67a7adf3df9c7, []int{51} } func (m *WrapNode) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5124,7 +5211,7 @@ func (m *UuidToRegIdx) Reset() { *m = UuidToRegIdx{} } func (m *UuidToRegIdx) String() string { return proto.CompactTextString(m) } func (*UuidToRegIdx) ProtoMessage() {} func (*UuidToRegIdx) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{51} + return fileDescriptor_7ac67a7adf3df9c7, []int{52} } func (m *UuidToRegIdx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5188,7 +5275,7 @@ func (m *Apply) Reset() { *m = Apply{} } func (m *Apply) String() string { return proto.CompactTextString(m) } func (*Apply) ProtoMessage() {} func (*Apply) Descriptor() ([]byte, []int) { - return fileDescriptor_7ac67a7adf3df9c7, []int{52} + return fileDescriptor_7ac67a7adf3df9c7, []int{53} } func (m *Apply) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5286,6 +5373,7 @@ func init() { proto.RegisterType((*TableFunction)(nil), "pipeline.TableFunction") proto.RegisterType((*ExternalName2ColIndex)(nil), "pipeline.ExternalName2ColIndex") proto.RegisterType((*FileOffset)(nil), "pipeline.file_offset") + proto.RegisterType((*ParquetRowGroupShard)(nil), "pipeline.parquet_row_group_shard") proto.RegisterType((*ExternalScan)(nil), "pipeline.ExternalScan") proto.RegisterType((*StreamScan)(nil), "pipeline.StreamScan") proto.RegisterType((*TableScan)(nil), "pipeline.TableScan") @@ -5312,384 +5400,391 @@ func init() { func init() { proto.RegisterFile("pipeline.proto", fileDescriptor_7ac67a7adf3df9c7) } var fileDescriptor_7ac67a7adf3df9c7 = []byte{ - // 6027 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7b, 0x4d, 0x6f, 0x1c, 0x49, - 0x72, 0xe8, 0xf4, 0x77, 0x75, 0xf4, 0x07, 0x9b, 0x29, 0x51, 0xea, 0x91, 0x66, 0x24, 0xaa, 0x56, - 0xd2, 0x70, 0x35, 0x12, 0x25, 0x71, 0x56, 0xbb, 0xb3, 0xdf, 0x4b, 0x91, 0xd2, 0x0e, 0x77, 0x48, - 0x8a, 0xaf, 0x48, 0xbd, 0xc1, 0x9b, 0xc3, 0x2b, 0x14, 0xab, 0xb2, 0x9b, 0x35, 0xac, 0xae, 0x2c, - 0xd5, 0x87, 0x44, 0xea, 0xf4, 0x80, 0xe7, 0x93, 0xff, 0x80, 0x01, 0xfb, 0xb2, 0x30, 0x0c, 0x18, - 0x6b, 0xd8, 0x58, 0x03, 0x3e, 0xfa, 0xe0, 0xeb, 0x1e, 0x7d, 0xf2, 0xd1, 0x30, 0xd6, 0x47, 0x03, - 0x3e, 0xd9, 0xc6, 0x5e, 0x6c, 0x18, 0x11, 0x99, 0x59, 0x55, 0xdd, 0x6c, 0x6a, 0x46, 0x63, 0x63, - 0x2f, 0xde, 0x53, 0x67, 0x46, 0x44, 0x66, 0x65, 0x47, 0x46, 0xc6, 0x67, 0x26, 0xf4, 0x23, 0x3f, - 0xe2, 0x81, 0x1f, 0xf2, 0xd5, 0x28, 0x16, 0xa9, 0x60, 0x86, 0xee, 0x5f, 0xb9, 0x37, 0xf6, 0xd3, - 0xa3, 0xec, 0x70, 0xd5, 0x15, 0x93, 0xfb, 0x63, 0x31, 0x16, 0xf7, 0x89, 0xe0, 0x30, 0x1b, 0x51, - 0x8f, 0x3a, 0xd4, 0x92, 0x03, 0xaf, 0x40, 0x20, 0xdc, 0x63, 0xdd, 0x8e, 0x02, 0x27, 0x54, 0xed, - 0x85, 0xd4, 0x9f, 0xf0, 0x24, 0x75, 0x26, 0x91, 0x02, 0xb4, 0xd3, 0x13, 0x85, 0x33, 0xff, 0xa8, - 0x0a, 0xad, 0x1d, 0x9e, 0x24, 0xce, 0x98, 0x33, 0x13, 0x6a, 0x89, 0xef, 0x0d, 0x2b, 0xcb, 0x95, - 0x95, 0xfe, 0xda, 0x60, 0x35, 0x5f, 0xd6, 0x7e, 0xea, 0xa4, 0x59, 0x62, 0x21, 0x12, 0x69, 0xdc, - 0x89, 0x37, 0xac, 0xce, 0xd2, 0xec, 0xf0, 0xf4, 0x48, 0x78, 0x16, 0x22, 0xd9, 0x00, 0x6a, 0x3c, - 0x8e, 0x87, 0xb5, 0xe5, 0xca, 0x4a, 0xd7, 0xc2, 0x26, 0x63, 0x50, 0xf7, 0x9c, 0xd4, 0x19, 0xd6, - 0x09, 0x44, 0x6d, 0x76, 0x13, 0xfa, 0x51, 0x2c, 0x5c, 0xdb, 0x0f, 0x47, 0xc2, 0x26, 0x6c, 0x83, - 0xb0, 0x5d, 0x84, 0x6e, 0x85, 0x23, 0xb1, 0x89, 0x54, 0x43, 0x68, 0x39, 0xa1, 0x13, 0x9c, 0x26, - 0x7c, 0xd8, 0x24, 0xb4, 0xee, 0xb2, 0x3e, 0x54, 0x7d, 0x6f, 0xd8, 0x5a, 0xae, 0xac, 0xd4, 0xad, - 0xaa, 0xef, 0xe1, 0x37, 0xb2, 0xcc, 0xf7, 0x86, 0x86, 0xfc, 0x06, 0xb6, 0x99, 0x09, 0xdd, 0x90, - 0x73, 0x6f, 0x57, 0xa4, 0x16, 0x8f, 0x82, 0xd3, 0x61, 0x7b, 0xb9, 0xb2, 0x62, 0x58, 0x53, 0x30, - 0x76, 0x05, 0x0c, 0x8f, 0x1f, 0x66, 0xe3, 0x9d, 0x64, 0x3c, 0x84, 0xe5, 0xca, 0x4a, 0xdb, 0xca, - 0xfb, 0xe6, 0x73, 0x68, 0x6f, 0x88, 0x30, 0xe4, 0x6e, 0x2a, 0x62, 0x76, 0x1d, 0x3a, 0xfa, 0xef, - 0xda, 0x8a, 0x4d, 0x0d, 0x0b, 0x34, 0x68, 0xcb, 0x63, 0x1f, 0xc0, 0x82, 0xab, 0xa9, 0x6d, 0x3f, - 0xf4, 0xf8, 0x09, 0xf1, 0xa9, 0x61, 0xf5, 0x73, 0xf0, 0x16, 0x42, 0xcd, 0x3f, 0xac, 0x41, 0x6b, - 0xff, 0x28, 0x1b, 0x8d, 0x02, 0xce, 0x6e, 0x42, 0x4f, 0x35, 0x37, 0x44, 0xb0, 0xe5, 0x9d, 0xa8, - 0x79, 0xa7, 0x81, 0x6c, 0x19, 0x3a, 0x0a, 0x70, 0x70, 0x1a, 0x71, 0x35, 0x6d, 0x19, 0x34, 0x3d, - 0xcf, 0x8e, 0x1f, 0x12, 0xfb, 0x6b, 0xd6, 0x34, 0x70, 0x86, 0xca, 0x39, 0xa1, 0x1d, 0x99, 0xa6, - 0x72, 0xe8, 0x6b, 0xeb, 0x81, 0xff, 0x92, 0x5b, 0x7c, 0xbc, 0x11, 0xa6, 0xb4, 0x2f, 0x0d, 0xab, - 0x0c, 0x62, 0x6b, 0xb0, 0x94, 0xc8, 0x21, 0x76, 0xec, 0x84, 0x63, 0x9e, 0xd8, 0x99, 0x1f, 0xa6, - 0xdf, 0xfe, 0xd6, 0xb0, 0xb9, 0x5c, 0x5b, 0xa9, 0x5b, 0x17, 0x14, 0xd2, 0x22, 0xdc, 0x73, 0x42, - 0xb1, 0x07, 0x70, 0x71, 0x66, 0x8c, 0x1c, 0xd2, 0x5a, 0xae, 0xad, 0xd4, 0x2c, 0x36, 0x35, 0x64, - 0x8b, 0x46, 0x3c, 0x81, 0xc5, 0x38, 0x0b, 0x51, 0x7a, 0x9f, 0xfa, 0x41, 0xca, 0xe3, 0xfd, 0x88, - 0xbb, 0xb4, 0xbf, 0x9d, 0xb5, 0xcb, 0xab, 0x24, 0xe0, 0xd6, 0x2c, 0xda, 0x3a, 0x3b, 0x82, 0xdd, - 0xcd, 0x99, 0xf7, 0xe4, 0x24, 0x8a, 0x49, 0x08, 0x3a, 0x6b, 0x20, 0x27, 0x40, 0x88, 0x55, 0x46, - 0x9b, 0xbf, 0xa9, 0x82, 0xb1, 0xe9, 0x27, 0x91, 0x93, 0xba, 0x47, 0xec, 0x32, 0xb4, 0x46, 0x59, - 0xe8, 0x16, 0xfb, 0xdd, 0xc4, 0xee, 0x96, 0xc7, 0x7e, 0x00, 0x0b, 0x81, 0x70, 0x9d, 0xc0, 0xce, - 0xb7, 0x76, 0x58, 0x5d, 0xae, 0xad, 0x74, 0xd6, 0x2e, 0x14, 0x67, 0x22, 0x17, 0x1d, 0xab, 0x4f, - 0xb4, 0x85, 0x28, 0xfd, 0x10, 0x06, 0x31, 0x9f, 0x88, 0x94, 0x97, 0x86, 0xd7, 0x68, 0x38, 0x2b, - 0x86, 0x7f, 0x16, 0x3b, 0xd1, 0xae, 0xf0, 0xb8, 0xb5, 0x20, 0x69, 0x8b, 0xe1, 0x0f, 0x4b, 0xdc, - 0xe7, 0x63, 0xdb, 0xf7, 0x4e, 0x6c, 0xfa, 0xc0, 0xb0, 0xbe, 0x5c, 0x5b, 0x69, 0x14, 0xac, 0xe4, - 0xe3, 0x2d, 0xef, 0x64, 0x1b, 0x31, 0xec, 0x23, 0xb8, 0x34, 0x3b, 0x44, 0xce, 0x3a, 0x6c, 0xd0, - 0x98, 0x0b, 0x53, 0x63, 0x2c, 0x42, 0xb1, 0x1b, 0xd0, 0xd5, 0x83, 0x52, 0x14, 0xbb, 0xa6, 0x14, - 0x84, 0xa4, 0x24, 0x76, 0x97, 0xa1, 0xe5, 0x27, 0x76, 0xe2, 0x87, 0xc7, 0x74, 0x14, 0x0d, 0xab, - 0xe9, 0x27, 0xfb, 0x7e, 0x78, 0xcc, 0xde, 0x05, 0x23, 0xe6, 0xae, 0xc4, 0x18, 0x84, 0x69, 0xc5, - 0xdc, 0x25, 0xd4, 0x65, 0xc0, 0xa6, 0xed, 0xa6, 0x5c, 0x1d, 0xc8, 0x66, 0xcc, 0xdd, 0x8d, 0x94, - 0x9b, 0x09, 0x34, 0x76, 0x78, 0x3c, 0xe6, 0x78, 0x26, 0x71, 0xe0, 0xbe, 0xeb, 0x84, 0xc4, 0x77, - 0xc3, 0xca, 0xfb, 0xa8, 0x11, 0x22, 0x27, 0x4e, 0x7d, 0x27, 0xa0, 0x63, 0x60, 0x58, 0xba, 0xcb, - 0xae, 0x42, 0x3b, 0x49, 0x9d, 0x38, 0xc5, 0x7f, 0x47, 0xe2, 0xdf, 0xb0, 0x0c, 0x02, 0xe0, 0x09, - 0xba, 0x0c, 0x2d, 0x1e, 0x7a, 0x84, 0xaa, 0xcb, 0x9d, 0xe4, 0xa1, 0xb7, 0xe5, 0x9d, 0x98, 0x7f, - 0x55, 0x81, 0xde, 0x4e, 0x16, 0xa4, 0xfe, 0x7a, 0x3c, 0xce, 0xf8, 0x24, 0x4c, 0x51, 0x93, 0x6c, - 0xfa, 0x49, 0xaa, 0xbe, 0x4c, 0x6d, 0xb6, 0x02, 0xed, 0x9f, 0xc6, 0x22, 0x8b, 0x48, 0x82, 0xe4, - 0x4e, 0x97, 0x25, 0xa8, 0x40, 0xa2, 0xb4, 0x3d, 0x8b, 0x3d, 0x1e, 0x3f, 0x3e, 0x25, 0xda, 0xda, - 0x19, 0xda, 0x32, 0x9a, 0xbd, 0x07, 0xed, 0x7d, 0x1e, 0x39, 0xb1, 0x83, 0x22, 0x50, 0x27, 0xf5, - 0x53, 0x00, 0xf0, 0xbf, 0x12, 0xf1, 0x96, 0xa7, 0x0e, 0xa1, 0xee, 0x9a, 0x63, 0x68, 0xaf, 0x8f, - 0xc7, 0x31, 0x1f, 0x3b, 0x29, 0xa9, 0x42, 0x11, 0xd1, 0x72, 0x6b, 0x56, 0x55, 0x44, 0xa4, 0x6e, - 0xf1, 0x0f, 0x48, 0xfe, 0x50, 0x9b, 0x5d, 0x83, 0x3a, 0x9f, 0xbf, 0x1e, 0x82, 0xb3, 0x4b, 0xd0, - 0x74, 0x45, 0x38, 0xf2, 0xc7, 0x4a, 0x49, 0xab, 0x9e, 0xf9, 0x67, 0x35, 0x68, 0xd0, 0x9f, 0x43, - 0xf6, 0xa2, 0xe2, 0xb4, 0xf9, 0x4b, 0x27, 0xd0, 0xbb, 0x82, 0x80, 0x27, 0x2f, 0x9d, 0x80, 0x2d, - 0x43, 0x03, 0xa7, 0x49, 0xe6, 0xf0, 0x46, 0x22, 0xd8, 0x6d, 0x68, 0xa0, 0x10, 0x25, 0xd3, 0x2b, - 0x40, 0x21, 0x7a, 0x5c, 0xff, 0xd5, 0xdf, 0x5f, 0x7f, 0xc7, 0x92, 0x68, 0xf6, 0x01, 0xd4, 0x9d, - 0xf1, 0x38, 0x21, 0x59, 0x9e, 0x3a, 0x4e, 0xf9, 0xff, 0xb5, 0x88, 0x80, 0x3d, 0x82, 0xb6, 0xdc, - 0x37, 0xa4, 0x6e, 0x10, 0xf5, 0xe5, 0x92, 0x41, 0x2a, 0x6f, 0xa9, 0x55, 0x50, 0x22, 0xc7, 0xfd, - 0x44, 0x1d, 0x78, 0x92, 0x68, 0xc3, 0x2a, 0x00, 0x68, 0x31, 0xa2, 0x98, 0xaf, 0x07, 0x81, 0x70, - 0xf7, 0xfd, 0xd7, 0x5c, 0xd9, 0x97, 0x29, 0x18, 0xbb, 0x0d, 0xfd, 0x3d, 0x29, 0x72, 0x16, 0x4f, - 0xb2, 0x20, 0x4d, 0x94, 0xcd, 0x99, 0x81, 0xb2, 0x55, 0x60, 0x53, 0x90, 0x03, 0xfa, 0xfb, 0xed, - 0xe5, 0xda, 0x4a, 0xcf, 0x9a, 0x83, 0x61, 0xdf, 0x80, 0xde, 0x18, 0x39, 0xed, 0x87, 0x63, 0x7b, - 0x14, 0x38, 0x68, 0x8e, 0x6a, 0x68, 0xae, 0x34, 0xf0, 0x69, 0xe0, 0x8c, 0x49, 0xc8, 0x23, 0x3f, - 0x08, 0xec, 0x09, 0x9f, 0x0c, 0x3b, 0xb4, 0xe5, 0x06, 0x01, 0x76, 0xf8, 0xc4, 0xfc, 0x97, 0x2a, - 0x34, 0xb7, 0xc2, 0x84, 0xc7, 0x29, 0x1e, 0x21, 0x67, 0x34, 0xe2, 0x6e, 0xca, 0xa5, 0xea, 0xaa, - 0x5b, 0x79, 0x1f, 0x59, 0x70, 0x20, 0x3e, 0x8b, 0xfd, 0x94, 0xef, 0x7f, 0xa4, 0x84, 0xa4, 0x00, - 0xb0, 0x3b, 0xb0, 0xe8, 0x78, 0x9e, 0xad, 0xa9, 0xed, 0x58, 0xbc, 0x4a, 0xe8, 0x38, 0x19, 0xd6, - 0x82, 0xe3, 0x79, 0xeb, 0x0a, 0x6e, 0x89, 0x57, 0x09, 0xbb, 0x01, 0xb5, 0x98, 0x8f, 0x48, 0x64, - 0x3a, 0x6b, 0x0b, 0x72, 0x4b, 0x9f, 0x1d, 0x7e, 0xc1, 0xdd, 0xd4, 0xe2, 0x23, 0x0b, 0x71, 0xec, - 0x22, 0x34, 0x9c, 0x34, 0x8d, 0xe5, 0x16, 0xb5, 0x2d, 0xd9, 0x61, 0xab, 0x70, 0x81, 0x8e, 0x6d, - 0xea, 0x8b, 0xd0, 0x4e, 0x9d, 0xc3, 0x00, 0x6d, 0x6a, 0xa2, 0xcc, 0xc7, 0x62, 0x8e, 0x3a, 0x40, - 0xcc, 0x96, 0x97, 0xa0, 0xc1, 0x99, 0xa5, 0x0f, 0x9d, 0x09, 0x4f, 0xc8, 0x7a, 0xb4, 0xad, 0x0b, - 0xd3, 0x23, 0x76, 0x11, 0x85, 0xfc, 0x2c, 0xc6, 0xe0, 0xc1, 0x37, 0xe8, 0x0c, 0x75, 0x73, 0x20, - 0xea, 0x85, 0x25, 0x68, 0xfa, 0x89, 0xcd, 0x43, 0x4f, 0xe9, 0xa2, 0x86, 0x9f, 0x3c, 0x09, 0x3d, - 0xf6, 0x21, 0xb4, 0xe5, 0x57, 0x3c, 0x3e, 0x22, 0xb7, 0xa0, 0xb3, 0xd6, 0x57, 0x12, 0x8b, 0xe0, - 0x4d, 0x3e, 0xb2, 0x8c, 0x54, 0xb5, 0xcc, 0xff, 0x5f, 0x81, 0x0e, 0x09, 0xd8, 0xf3, 0xc8, 0xc3, - 0xf3, 0xf8, 0x0d, 0xe8, 0x4d, 0x73, 0x4f, 0x6e, 0x40, 0xd7, 0x29, 0xb3, 0xee, 0x12, 0x34, 0xd7, - 0x5d, 0x5c, 0x05, 0xed, 0x40, 0xcf, 0x52, 0x3d, 0xf6, 0x1d, 0x58, 0xc8, 0x68, 0x1a, 0xdb, 0x4d, - 0x4f, 0xec, 0x00, 0xcf, 0xb1, 0x3c, 0x31, 0x8a, 0xbd, 0xf2, 0x1b, 0x1b, 0xe9, 0x89, 0xd5, 0xcb, - 0x74, 0x73, 0xdb, 0x4f, 0x52, 0xf3, 0x7d, 0x68, 0xac, 0xc7, 0xb1, 0x73, 0x4a, 0x1c, 0xc7, 0xc6, - 0xb0, 0x42, 0xaa, 0x5d, 0x76, 0x4c, 0x17, 0x6a, 0x3b, 0x4e, 0xc4, 0x6e, 0x41, 0x75, 0x12, 0x11, - 0xa6, 0xb3, 0xb6, 0x54, 0x3a, 0x2e, 0x4e, 0xb4, 0xba, 0x13, 0x3d, 0x09, 0xd3, 0xf8, 0xd4, 0xaa, - 0x4e, 0xa2, 0x2b, 0x8f, 0xa0, 0xa5, 0xba, 0xe8, 0xce, 0x1d, 0xf3, 0x53, 0xfa, 0x0f, 0x6d, 0x0b, - 0x9b, 0xf8, 0x81, 0x97, 0x4e, 0x90, 0x69, 0x3f, 0x44, 0x76, 0xbe, 0x57, 0xfd, 0xb8, 0x62, 0xfe, - 0x6b, 0x1d, 0x8c, 0x4d, 0x1e, 0x70, 0xfa, 0x27, 0x26, 0x74, 0xcb, 0xc2, 0xa2, 0xb9, 0x30, 0x25, - 0x40, 0x26, 0x74, 0xa5, 0xb1, 0xa1, 0x51, 0x5c, 0x49, 0xe3, 0x14, 0x0c, 0xb5, 0xe0, 0xd6, 0xe3, - 0xcc, 0x3d, 0xe6, 0x29, 0x89, 0x61, 0xcf, 0xd2, 0x5d, 0xc4, 0xec, 0x2a, 0x4c, 0x5d, 0x62, 0x54, - 0x97, 0xbd, 0x07, 0x10, 0x8b, 0x57, 0xb6, 0x2f, 0x35, 0xbe, 0x54, 0x9e, 0x46, 0x2c, 0x5e, 0x6d, - 0xa1, 0xce, 0xff, 0xad, 0x48, 0xdf, 0x77, 0x60, 0x58, 0x92, 0x3e, 0xf4, 0xfb, 0x6c, 0x3f, 0xb4, - 0x0f, 0xd1, 0xad, 0x50, 0x82, 0x58, 0xcc, 0x49, 0x6e, 0xe1, 0x56, 0xf8, 0x98, 0x7c, 0x0e, 0x75, - 0xa6, 0xda, 0x6f, 0x38, 0x53, 0x73, 0x8f, 0x28, 0xcc, 0x3f, 0xa2, 0x8f, 0x01, 0xf6, 0xf9, 0x78, - 0xc2, 0xc3, 0x74, 0xc7, 0x89, 0x86, 0x1d, 0xda, 0x78, 0xb3, 0xd8, 0x78, 0xbd, 0x5b, 0xab, 0x05, - 0x91, 0x94, 0x82, 0xd2, 0x28, 0x74, 0x04, 0x5c, 0x27, 0xb4, 0xd3, 0x38, 0x0b, 0x5d, 0x27, 0xe5, - 0xc3, 0x2e, 0x7d, 0xaa, 0xe3, 0x3a, 0xe1, 0x81, 0x02, 0x95, 0xce, 0x51, 0xaf, 0x7c, 0x8e, 0x6e, - 0xc3, 0x42, 0x14, 0xfb, 0x13, 0x27, 0x3e, 0xb5, 0x8f, 0xf9, 0x29, 0x6d, 0x46, 0x5f, 0x3a, 0xb8, - 0x0a, 0xfc, 0x29, 0x3f, 0xdd, 0xf2, 0x4e, 0xae, 0xfc, 0x10, 0x16, 0x66, 0x16, 0xf0, 0x56, 0x72, - 0xf7, 0xf3, 0x1a, 0xb4, 0xf7, 0x62, 0xae, 0x74, 0xdf, 0x75, 0xe8, 0x24, 0xee, 0x11, 0x9f, 0x38, - 0xb4, 0x4b, 0x6a, 0x06, 0x90, 0x20, 0xdc, 0x9c, 0xe9, 0xd3, 0x5d, 0x7d, 0xf3, 0xe9, 0xc6, 0x75, - 0x48, 0x87, 0x02, 0x0f, 0x13, 0x36, 0x0b, 0x95, 0x56, 0x2f, 0xab, 0xb4, 0x65, 0xe8, 0x1e, 0x39, - 0x89, 0xed, 0x64, 0xa9, 0xb0, 0x5d, 0x11, 0x90, 0xd0, 0x19, 0x16, 0x1c, 0x39, 0xc9, 0x7a, 0x96, - 0x8a, 0x0d, 0x11, 0xb0, 0xf7, 0x01, 0x5c, 0x11, 0xd8, 0x62, 0x34, 0x4a, 0x78, 0xaa, 0xbc, 0xa9, - 0xb6, 0x2b, 0x82, 0x67, 0x04, 0x40, 0xa9, 0xe4, 0x49, 0xea, 0x4f, 0x1c, 0xb5, 0xa5, 0xb6, 0x2b, - 0xb2, 0x30, 0x25, 0x13, 0x54, 0xb3, 0x16, 0x73, 0x94, 0x25, 0x5e, 0x6d, 0x20, 0x82, 0x3d, 0x80, - 0xbe, 0x2b, 0x26, 0x91, 0x1d, 0x21, 0x67, 0xc9, 0xb8, 0x1b, 0x67, 0x5c, 0xdb, 0x2e, 0x52, 0xec, - 0x1d, 0x73, 0xe9, 0x6d, 0xac, 0xc1, 0x82, 0x1b, 0x64, 0x49, 0xca, 0x63, 0xfb, 0x50, 0x0d, 0x39, - 0xeb, 0x0d, 0xf7, 0x14, 0x89, 0xf2, 0x50, 0x4c, 0xe8, 0xf9, 0x89, 0x2d, 0x02, 0xcf, 0x96, 0xea, - 0x46, 0xc9, 0x59, 0xc7, 0x4f, 0x9e, 0x05, 0x9e, 0x52, 0x78, 0x92, 0x26, 0xe4, 0xaf, 0x34, 0x4d, - 0x47, 0xd3, 0xec, 0xf2, 0x57, 0x92, 0xc6, 0xfc, 0xbb, 0x2a, 0xb4, 0xf6, 0x44, 0x92, 0x6e, 0x4e, - 0x02, 0x2d, 0xe2, 0x95, 0xb7, 0x15, 0xf1, 0xea, 0x7c, 0x11, 0x9f, 0x23, 0x64, 0xb5, 0x39, 0x42, - 0xc6, 0x56, 0x60, 0x50, 0xa6, 0x23, 0xe1, 0x90, 0x3e, 0x57, 0xbf, 0x20, 0x24, 0x01, 0xb9, 0x8a, - 0x4e, 0x82, 0xed, 0x49, 0x9d, 0x24, 0x37, 0xd2, 0xf0, 0x13, 0xa5, 0x8f, 0x24, 0xd2, 0x27, 0x59, - 0x53, 0x1e, 0x84, 0xe1, 0x27, 0x4a, 0xf6, 0xbe, 0x0b, 0xef, 0xe6, 0x23, 0xed, 0x57, 0x7e, 0x7a, - 0x24, 0xb2, 0xd4, 0x1e, 0x51, 0x30, 0x92, 0x28, 0x17, 0xf9, 0x92, 0x9e, 0xe9, 0x33, 0x89, 0x96, - 0xa1, 0x0a, 0x39, 0x34, 0xa3, 0x2c, 0x08, 0xec, 0x94, 0x9f, 0xa4, 0x6a, 0x2b, 0x87, 0x92, 0x37, - 0x8a, 0x6f, 0x4f, 0xb3, 0x20, 0x38, 0xe0, 0x27, 0x29, 0x2a, 0x7f, 0x63, 0xa4, 0x3a, 0xe6, 0x1f, - 0xd4, 0x01, 0xb6, 0x85, 0x7b, 0x7c, 0xe0, 0xc4, 0x63, 0x9e, 0xa2, 0xe3, 0xad, 0x35, 0x9a, 0xd2, - 0xb8, 0xad, 0x54, 0xea, 0x31, 0xb6, 0x06, 0x97, 0xf4, 0xff, 0x47, 0x39, 0xc4, 0x20, 0x40, 0xaa, - 0x24, 0x75, 0xa0, 0x98, 0xc2, 0xca, 0xa0, 0x93, 0xf4, 0x11, 0xfb, 0xb8, 0xe0, 0x2d, 0x8e, 0x49, - 0x4f, 0x23, 0xe2, 0xed, 0x3c, 0x07, 0xae, 0x57, 0x0c, 0x3f, 0x38, 0x8d, 0xd8, 0x03, 0x58, 0x8a, - 0xf9, 0x28, 0xe6, 0xc9, 0x91, 0x9d, 0x26, 0xe5, 0x8f, 0x49, 0xff, 0x7b, 0x51, 0x21, 0x0f, 0x92, - 0xfc, 0x5b, 0x0f, 0x60, 0x49, 0x72, 0x6a, 0x76, 0x79, 0x52, 0x7f, 0x2f, 0x4a, 0x64, 0x79, 0x75, - 0xef, 0x03, 0x25, 0x3d, 0xa4, 0x4e, 0xd6, 0xde, 0x5c, 0x40, 0xcc, 0x38, 0x0c, 0x38, 0x3a, 0x3a, - 0x1b, 0x47, 0x18, 0x50, 0x6e, 0xf2, 0x91, 0x62, 0x7e, 0x01, 0x60, 0x26, 0xd4, 0x77, 0x84, 0xc7, - 0x89, 0xd5, 0xfd, 0xb5, 0xfe, 0x2a, 0xa5, 0x4f, 0x90, 0x93, 0x08, 0xb5, 0x08, 0xc7, 0x3e, 0x00, - 0x9a, 0x4e, 0x8a, 0xdf, 0xd9, 0xb3, 0x62, 0x20, 0x92, 0x64, 0xf0, 0x01, 0x2c, 0x15, 0x2b, 0xb1, - 0x9d, 0xd4, 0x4e, 0x8f, 0x38, 0xa9, 0x43, 0x79, 0x5c, 0x16, 0xf3, 0x45, 0xad, 0xa7, 0x07, 0x47, - 0x1c, 0x55, 0xe3, 0x0a, 0xb4, 0xc4, 0xe1, 0x17, 0x36, 0x1e, 0x84, 0xce, 0xfc, 0x83, 0xd0, 0x14, - 0x87, 0x5f, 0x58, 0x7c, 0xc4, 0xbe, 0x5d, 0x36, 0x25, 0x33, 0xac, 0xe9, 0x12, 0x6b, 0x2e, 0xe6, - 0xf8, 0x12, 0x77, 0xcc, 0x8f, 0xa1, 0x89, 0x7f, 0xe7, 0x59, 0xc4, 0x56, 0xa1, 0x95, 0x92, 0x78, - 0x24, 0xca, 0xf4, 0x5f, 0x2c, 0x2c, 0x40, 0x21, 0x3b, 0x96, 0x26, 0x32, 0x2d, 0x58, 0xc8, 0xd5, - 0xe9, 0xf3, 0xd0, 0x7f, 0x91, 0x71, 0xf6, 0x63, 0x58, 0x8c, 0x62, 0xae, 0xc4, 0xde, 0xce, 0x8e, - 0xd1, 0x3d, 0x51, 0x27, 0xf8, 0xa2, 0x92, 0xd2, 0x7c, 0xc4, 0x31, 0x4a, 0x68, 0x3f, 0x9a, 0xea, - 0x9b, 0x9f, 0xc3, 0xe5, 0x9c, 0x62, 0x9f, 0xbb, 0x22, 0xf4, 0x9c, 0xf8, 0x94, 0x2c, 0xdf, 0xcc, - 0xdc, 0xc9, 0xdb, 0xcc, 0xbd, 0x4f, 0x73, 0xff, 0x71, 0x0d, 0xfa, 0xcf, 0xc2, 0xcd, 0x2c, 0x0a, - 0x7c, 0xb4, 0x46, 0x9f, 0x4a, 0x63, 0x21, 0x95, 0x74, 0xa5, 0xac, 0xa4, 0x57, 0x60, 0xa0, 0xbe, - 0x82, 0x7c, 0x94, 0x0a, 0x56, 0x25, 0x69, 0x24, 0x7c, 0x43, 0x04, 0x52, 0xbb, 0xfe, 0x10, 0x96, - 0x32, 0xfa, 0xe7, 0x92, 0xf2, 0x88, 0xbb, 0xc7, 0xf6, 0x39, 0x11, 0x14, 0x93, 0x84, 0x38, 0x14, - 0xc9, 0x48, 0x6d, 0x5e, 0x87, 0x4e, 0x31, 0x5c, 0x5b, 0x0a, 0xc8, 0x09, 0x69, 0x25, 0x22, 0xb4, - 0x3d, 0xbd, 0x64, 0xe5, 0xa7, 0xa0, 0x8d, 0xe9, 0x8b, 0xe2, 0x9f, 0xa0, 0xda, 0xfa, 0x3f, 0xb0, - 0x38, 0x45, 0x49, 0xab, 0x68, 0xd2, 0x2a, 0xee, 0x15, 0xdb, 0x38, 0xfd, 0xf7, 0xcb, 0x5d, 0x5c, - 0x8f, 0xb4, 0xe9, 0x0b, 0x62, 0x1a, 0xaa, 0x55, 0xd9, 0x38, 0x14, 0x31, 0x57, 0x07, 0x04, 0x55, - 0x19, 0xf5, 0xaf, 0xec, 0xc2, 0xc5, 0x79, 0xb3, 0xcc, 0x31, 0xcc, 0xcb, 0x65, 0xc3, 0x3c, 0x13, - 0xfd, 0x15, 0x46, 0xfa, 0x4f, 0x2b, 0xd0, 0x79, 0x9a, 0xbd, 0x7e, 0x7d, 0x2a, 0x15, 0x1e, 0xeb, - 0x42, 0x65, 0x97, 0x66, 0xa9, 0x5a, 0x95, 0x5d, 0xf4, 0x87, 0xf7, 0x8e, 0x51, 0xf9, 0xd2, 0x24, - 0x6d, 0x4b, 0xf5, 0x30, 0x6e, 0xdc, 0x3b, 0x3e, 0x78, 0x83, 0xda, 0x91, 0x68, 0x0c, 0x78, 0x1e, - 0x67, 0x7e, 0x80, 0xfe, 0x9d, 0xd2, 0x30, 0x79, 0x1f, 0x23, 0xb1, 0xad, 0x91, 0x94, 0x97, 0xa7, - 0xb1, 0x98, 0x48, 0x89, 0x56, 0x7a, 0x7d, 0x0e, 0xc6, 0xfc, 0x4d, 0x1d, 0x8c, 0x4f, 0x9c, 0xe4, - 0xe8, 0x67, 0xc2, 0x0f, 0xd9, 0x03, 0x68, 0x7f, 0x21, 0xfc, 0x50, 0xa6, 0x40, 0x64, 0x72, 0xf4, - 0x82, 0x5c, 0xc4, 0xae, 0xf0, 0xf8, 0x2a, 0xd2, 0xe0, 0x6a, 0x2c, 0xe3, 0x0b, 0xd5, 0x52, 0xe6, - 0x30, 0xf6, 0xc7, 0x47, 0xa9, 0x8d, 0x40, 0x65, 0xb7, 0x3a, 0x7e, 0x62, 0x21, 0x8c, 0x66, 0x7d, - 0x0f, 0xd0, 0x33, 0x38, 0xb2, 0x45, 0x68, 0x47, 0xc7, 0x2a, 0xbc, 0x32, 0x10, 0xf2, 0x2c, 0xdc, - 0x3b, 0x46, 0xbd, 0xe6, 0x27, 0xb6, 0x4a, 0xb4, 0xd0, 0xdf, 0x99, 0x8a, 0x52, 0x6f, 0x42, 0x1f, - 0xfd, 0xb1, 0xe4, 0xd8, 0x8f, 0xec, 0x28, 0x16, 0x87, 0xfa, 0xbf, 0xa0, 0x97, 0xb6, 0x7f, 0xec, - 0x47, 0x7b, 0x08, 0x23, 0x37, 0x48, 0xa5, 0x6f, 0x50, 0xb8, 0xa4, 0xbf, 0x01, 0x0a, 0x84, 0x6c, - 0xa1, 0x1c, 0x4d, 0x20, 0x63, 0x8c, 0x16, 0x89, 0x5e, 0x2b, 0xe6, 0x01, 0x06, 0x13, 0x88, 0x42, - 0xb1, 0x27, 0x94, 0x21, 0x51, 0xae, 0x90, 0xa8, 0x6f, 0x02, 0x04, 0x7c, 0x84, 0x07, 0x28, 0xf4, - 0x64, 0x38, 0x3b, 0x93, 0x0b, 0x41, 0xec, 0x06, 0x22, 0xd9, 0x87, 0xd0, 0x91, 0x5c, 0x90, 0xb4, - 0x70, 0x86, 0x16, 0x08, 0x2d, 0x89, 0xef, 0x40, 0x27, 0x14, 0xa1, 0xcd, 0x5f, 0x10, 0xb5, 0xd2, - 0x89, 0x53, 0x13, 0x87, 0x22, 0x7c, 0xf2, 0x02, 0x89, 0xd9, 0x7d, 0xb5, 0x06, 0x99, 0x51, 0xe8, - 0x9e, 0x93, 0x51, 0xa0, 0x95, 0xc8, 0xd8, 0xfa, 0xa1, 0x5e, 0x89, 0x1c, 0xd1, 0x3b, 0x67, 0x84, - 0x5c, 0x8f, 0x1c, 0xb2, 0x0c, 0x5d, 0xda, 0xf7, 0x89, 0x13, 0xd9, 0xa9, 0x33, 0x56, 0x7e, 0x2b, - 0x20, 0x6c, 0xc7, 0x89, 0x0e, 0x9c, 0x31, 0xb3, 0xe0, 0x5d, 0x95, 0x6d, 0x54, 0x16, 0xde, 0x3e, - 0x44, 0x89, 0x93, 0x5c, 0x5b, 0xd0, 0x19, 0x89, 0xf9, 0x79, 0xca, 0x4b, 0x53, 0x79, 0x4a, 0x92, - 0x54, 0x8a, 0xe2, 0xfe, 0xa4, 0x0a, 0xc6, 0xb6, 0x10, 0xd1, 0xd7, 0x14, 0xbd, 0xf2, 0x96, 0x56, - 0xcf, 0xdf, 0xd2, 0xda, 0xf4, 0x96, 0xce, 0xb0, 0xbe, 0xfe, 0xd5, 0x59, 0xdf, 0x78, 0x6b, 0xd6, - 0x37, 0xbf, 0x06, 0xeb, 0x5b, 0xb3, 0xac, 0x37, 0x5b, 0xd0, 0xd8, 0xe7, 0xe9, 0xb3, 0xc8, 0xfc, - 0xa5, 0x01, 0xed, 0x4d, 0xee, 0x65, 0x92, 0x61, 0xe5, 0xbf, 0x5f, 0x39, 0xff, 0xef, 0x57, 0xa7, - 0xff, 0x3e, 0x1a, 0x79, 0x2d, 0xd1, 0x73, 0xd4, 0xbb, 0xa1, 0x05, 0x1a, 0x45, 0xbf, 0x90, 0x67, - 0x95, 0xa1, 0x9a, 0x62, 0x53, 0x2e, 0xce, 0x6f, 0x96, 0x8d, 0xc6, 0xd7, 0x92, 0x8d, 0x19, 0xad, - 0x70, 0x26, 0x77, 0xf5, 0xa5, 0x5c, 0x9b, 0xd5, 0x08, 0xc6, 0x19, 0x8d, 0xb0, 0x0d, 0x17, 0xa6, - 0x4c, 0x8d, 0x23, 0x33, 0x14, 0x6d, 0x12, 0xbd, 0xf7, 0x4a, 0xa2, 0x57, 0x32, 0x0c, 0x32, 0x6f, - 0x61, 0x2d, 0x8a, 0x59, 0x10, 0xaa, 0x29, 0x0f, 0xb7, 0x86, 0x2c, 0x28, 0x79, 0xdb, 0xb2, 0xc0, - 0xd2, 0x25, 0xe8, 0x86, 0x08, 0x48, 0xc1, 0x7f, 0x0c, 0x0b, 0x05, 0x95, 0x94, 0x91, 0xce, 0x39, - 0x32, 0xd2, 0xd3, 0x03, 0xa5, 0x98, 0xfc, 0x36, 0xb4, 0xc0, 0x3d, 0xb8, 0xa0, 0xd3, 0x31, 0xca, - 0xf1, 0xa2, 0x1d, 0xec, 0x93, 0x04, 0x0d, 0x54, 0x06, 0x86, 0x7c, 0x2e, 0xda, 0xa2, 0xef, 0xc3, - 0xc5, 0x12, 0x39, 0x5a, 0xea, 0xb2, 0x36, 0x28, 0xcb, 0xca, 0x62, 0x3e, 0x16, 0xbb, 0xdb, 0x32, - 0x47, 0xdb, 0xf1, 0x78, 0xa0, 0x3f, 0x34, 0x1c, 0xc8, 0x00, 0xd1, 0xe3, 0x81, 0xaa, 0x02, 0xed, - 0xc0, 0x4d, 0x8c, 0xc3, 0xc8, 0x1f, 0x71, 0xa2, 0x34, 0x8b, 0xb9, 0x1d, 0x05, 0x8e, 0xcb, 0x8f, - 0x44, 0xe0, 0xf1, 0xb8, 0x58, 0xdc, 0x22, 0x2d, 0xee, 0xba, 0x08, 0x3c, 0x74, 0x49, 0x24, 0xe5, - 0x5e, 0x41, 0xa8, 0xd7, 0xba, 0x0e, 0xd7, 0xce, 0x4c, 0x87, 0x86, 0xa3, 0x98, 0x88, 0xd1, 0x44, - 0xef, 0x4e, 0x4f, 0x84, 0x24, 0x7a, 0x8a, 0x87, 0xb0, 0x24, 0xf7, 0x4e, 0x0a, 0xf7, 0x31, 0xe7, - 0x91, 0x1d, 0x38, 0x49, 0x3a, 0xbc, 0x20, 0x6d, 0x2b, 0x21, 0x49, 0x80, 0x3f, 0xe5, 0x3c, 0xda, - 0x76, 0xe4, 0x57, 0xe5, 0x10, 0x15, 0x23, 0xd1, 0x98, 0x29, 0xde, 0x5e, 0x94, 0x5f, 0x25, 0x2a, - 0x19, 0x28, 0xe1, 0xe0, 0x12, 0x93, 0x7f, 0x00, 0x57, 0xa7, 0xa6, 0x98, 0x38, 0xf1, 0x71, 0x11, - 0x34, 0x0c, 0x97, 0x88, 0x6f, 0x97, 0x4b, 0xe3, 0x77, 0x88, 0x40, 0xce, 0x60, 0xfe, 0x73, 0x03, - 0xfa, 0x64, 0x87, 0x7f, 0xa7, 0x36, 0x7e, 0xa7, 0x36, 0xfe, 0x07, 0xa8, 0x0d, 0xf3, 0xff, 0x55, - 0xa0, 0xb5, 0x17, 0x0b, 0x2f, 0x73, 0xd3, 0xaf, 0x29, 0xe9, 0xd3, 0x12, 0x54, 0xfb, 0x32, 0x09, - 0xaa, 0x9f, 0x31, 0xd7, 0xbf, 0xa8, 0x40, 0x5b, 0x2d, 0x61, 0x7b, 0xed, 0x6b, 0x2e, 0xa2, 0xa8, - 0x60, 0x55, 0xe6, 0x56, 0xb0, 0xbe, 0x74, 0x15, 0x28, 0x58, 0x2f, 0x65, 0x75, 0x5e, 0x44, 0xd2, - 0xa7, 0x6a, 0x48, 0xc1, 0x92, 0xd0, 0x67, 0x11, 0xee, 0x9d, 0xf9, 0x0a, 0xda, 0x14, 0x95, 0x92, - 0x66, 0xb8, 0x04, 0xcd, 0x98, 0x4a, 0x34, 0x6a, 0xa1, 0xaa, 0xf7, 0xe6, 0x73, 0x5a, 0xfd, 0x7a, - 0xae, 0xdf, 0x5f, 0x56, 0xa0, 0x47, 0x29, 0x82, 0xa7, 0x59, 0x28, 0x4f, 0xc2, 0xfc, 0x18, 0x76, - 0x19, 0xea, 0x31, 0x46, 0xf2, 0xf2, 0x33, 0x5d, 0xf9, 0x99, 0x0d, 0x11, 0x6c, 0xf2, 0x91, 0x45, - 0x18, 0x64, 0x95, 0x13, 0x8f, 0x93, 0x79, 0xc5, 0x3e, 0x84, 0xe3, 0xbf, 0x8a, 0x9c, 0xd8, 0x99, - 0x24, 0xba, 0xd8, 0x27, 0x7b, 0x8c, 0x41, 0x9d, 0xce, 0x9b, 0x64, 0x0b, 0xb5, 0x55, 0x88, 0x98, - 0xf8, 0xe1, 0x38, 0x57, 0x1e, 0x06, 0xd5, 0x78, 0xc7, 0x01, 0x37, 0xd7, 0x61, 0xe9, 0xc9, 0x49, - 0xca, 0xe3, 0xd0, 0xa1, 0x43, 0xb9, 0x86, 0x12, 0x47, 0x11, 0xbd, 0x9e, 0xa9, 0x52, 0x9a, 0xe9, - 0x22, 0x34, 0xca, 0xb7, 0x22, 0x64, 0xc7, 0xbc, 0x05, 0x9d, 0x91, 0x1f, 0x70, 0x95, 0x15, 0xc5, - 0xa5, 0xa9, 0xfc, 0x68, 0x85, 0xee, 0x05, 0xa8, 0x9e, 0xf9, 0x1f, 0x35, 0xe8, 0xea, 0x4f, 0x51, - 0x1d, 0xf8, 0x6e, 0x99, 0x37, 0x9d, 0xb5, 0x81, 0xfe, 0x93, 0x48, 0xb2, 0x9e, 0xa6, 0xb1, 0x8e, - 0x0e, 0x25, 0xcf, 0xae, 0x42, 0x9b, 0xbe, 0x92, 0xf8, 0xaf, 0x39, 0x31, 0xae, 0x66, 0x19, 0x08, - 0xa0, 0x82, 0xde, 0x3a, 0x2c, 0x96, 0x96, 0x60, 0xa7, 0x22, 0x75, 0x02, 0xc5, 0xbb, 0x52, 0x89, - 0xa4, 0x44, 0x62, 0x2d, 0x60, 0x47, 0xa6, 0x6d, 0x0f, 0x90, 0x1a, 0xf7, 0x24, 0x8f, 0xf3, 0xcf, - 0xec, 0x09, 0x62, 0x28, 0xf9, 0x1b, 0x73, 0x3c, 0xe2, 0xc9, 0x8b, 0x40, 0x71, 0xb8, 0x2d, 0x21, - 0xfb, 0x2f, 0x82, 0x7c, 0x81, 0x24, 0x40, 0x4d, 0xda, 0x6e, 0x5a, 0x20, 0x89, 0xfe, 0x3d, 0xe8, - 0x88, 0xd8, 0x1f, 0xfb, 0xa1, 0x4c, 0x26, 0xb4, 0xe6, 0x7c, 0x04, 0x24, 0x01, 0xa5, 0x16, 0x4c, - 0x68, 0x4a, 0xa1, 0x9c, 0x93, 0x10, 0x56, 0x18, 0x76, 0x1b, 0x16, 0x92, 0x34, 0xf6, 0xdd, 0x14, - 0x97, 0x63, 0x4f, 0x84, 0xa7, 0x8b, 0xf1, 0x3d, 0x09, 0xde, 0x7f, 0x11, 0x50, 0x02, 0xec, 0x36, - 0x2c, 0xb8, 0x22, 0xc8, 0x26, 0x21, 0xad, 0xcc, 0x0e, 0x78, 0x48, 0xda, 0xb8, 0x61, 0xf5, 0x24, - 0x18, 0xd7, 0xb7, 0xcd, 0x43, 0x55, 0x6c, 0x73, 0x82, 0x00, 0x0f, 0xb6, 0x70, 0x3c, 0x95, 0x02, - 0xee, 0x6a, 0xe0, 0xb6, 0x70, 0x3c, 0xf6, 0x3d, 0xb8, 0x82, 0x38, 0x9b, 0x4f, 0xa2, 0xf4, 0xd4, - 0x0e, 0xb3, 0x09, 0x8f, 0x7d, 0xd7, 0x76, 0x12, 0xfb, 0x35, 0x8f, 0x85, 0xaa, 0x2a, 0x5c, 0x42, - 0x8a, 0x27, 0x48, 0xb0, 0x2b, 0xf1, 0xeb, 0xc9, 0xe7, 0x3c, 0x16, 0xa6, 0x0b, 0xb0, 0x9f, 0xc6, - 0xdc, 0x99, 0xd0, 0xee, 0x7f, 0x00, 0xad, 0xf4, 0x30, 0xa0, 0xfc, 0x7d, 0x65, 0x6e, 0xfe, 0xbe, - 0x99, 0x1e, 0x22, 0x5f, 0x4a, 0xf2, 0x54, 0xa5, 0x3c, 0xba, 0xea, 0xa1, 0x30, 0x06, 0xfe, 0xc4, - 0x4f, 0xd5, 0x3d, 0x19, 0xd9, 0x31, 0x0f, 0xa1, 0x4d, 0x33, 0xd0, 0x37, 0xf2, 0x8a, 0x75, 0xe5, - 0xcd, 0x15, 0xeb, 0x7b, 0xd0, 0x55, 0x3a, 0xe0, 0xbc, 0x12, 0x78, 0x47, 0xe2, 0xb1, 0x9d, 0x98, - 0x77, 0xa1, 0xfd, 0xbf, 0x9d, 0x20, 0x93, 0xdf, 0xb8, 0x0e, 0x1d, 0x2a, 0x09, 0xd9, 0x87, 0x81, - 0x70, 0x8f, 0x75, 0xa9, 0x82, 0x40, 0x8f, 0x11, 0x62, 0x02, 0x18, 0xcf, 0x43, 0x5f, 0x84, 0xeb, - 0x41, 0x60, 0xfe, 0x75, 0x13, 0xda, 0x9f, 0x38, 0xc9, 0x11, 0xa9, 0x0c, 0x66, 0x42, 0x8f, 0xea, - 0xf1, 0x94, 0x46, 0x98, 0x38, 0x91, 0xaa, 0xc9, 0x77, 0x10, 0x88, 0x54, 0x3b, 0x4e, 0x34, 0x93, - 0x65, 0xa8, 0xce, 0x64, 0x19, 0x6e, 0xc8, 0xeb, 0x51, 0xb2, 0x28, 0xc5, 0x75, 0x91, 0x97, 0x26, - 0x78, 0x2c, 0x41, 0xec, 0x2e, 0x30, 0x22, 0x71, 0x82, 0x40, 0x90, 0x69, 0x4f, 0x78, 0x90, 0xa8, - 0x84, 0xc4, 0x00, 0x31, 0xeb, 0x0a, 0xb1, 0xcf, 0xa5, 0x8c, 0x97, 0xec, 0x44, 0x63, 0xd6, 0x4e, - 0xdc, 0x01, 0x40, 0x0f, 0x88, 0xf2, 0x94, 0x33, 0x81, 0xa0, 0xcc, 0x06, 0x14, 0xd8, 0xaf, 0xe0, - 0x95, 0x7c, 0x00, 0x83, 0x9c, 0x22, 0xe6, 0x23, 0xdb, 0x0d, 0x53, 0xe5, 0x9a, 0xf4, 0x14, 0x95, - 0xc5, 0x47, 0x1b, 0x61, 0x3a, 0xeb, 0xbe, 0xb4, 0xcf, 0xb8, 0x2f, 0x3f, 0x85, 0x0b, 0x33, 0xca, - 0x3c, 0x89, 0xb8, 0xab, 0xca, 0xbe, 0x6f, 0x73, 0xd3, 0xe8, 0x5d, 0x30, 0x28, 0xf9, 0xef, 0x65, - 0x91, 0x92, 0xff, 0x96, 0x9f, 0x90, 0x9b, 0x79, 0x9e, 0x8b, 0xd4, 0xfd, 0xef, 0x72, 0x91, 0x7a, - 0x5f, 0xcd, 0x45, 0xea, 0x7f, 0x35, 0x17, 0x69, 0xc6, 0xa5, 0x58, 0x98, 0x8d, 0x44, 0xce, 0xf5, - 0xfb, 0x07, 0xe7, 0xfa, 0xfd, 0x5f, 0xe2, 0xb4, 0x2f, 0xbe, 0xd1, 0x69, 0xff, 0x0a, 0x51, 0x03, - 0xfb, 0x92, 0xa8, 0xc1, 0x7c, 0x0e, 0x40, 0xb6, 0x89, 0x96, 0x7c, 0xde, 0x9e, 0x57, 0xde, 0x76, - 0xcf, 0xcd, 0x7f, 0xaf, 0x00, 0xec, 0x3b, 0x93, 0x48, 0x9a, 0x6d, 0xf6, 0x13, 0xe8, 0x24, 0xd4, - 0x2b, 0x27, 0x6d, 0xae, 0x97, 0x2e, 0x53, 0xe6, 0xa4, 0xaa, 0x49, 0x09, 0x1c, 0x48, 0xf2, 0x36, - 0x89, 0xab, 0x9c, 0x21, 0xaf, 0x79, 0x35, 0x34, 0x01, 0x95, 0x1a, 0x6e, 0x41, 0x5f, 0x11, 0x44, - 0x3c, 0x76, 0x79, 0x28, 0x75, 0x58, 0xc5, 0xea, 0x49, 0xe8, 0x9e, 0x04, 0xb2, 0x87, 0x39, 0x99, - 0xd4, 0xd4, 0xc9, 0x9c, 0xc8, 0x43, 0x0d, 0xd9, 0x90, 0x04, 0xe6, 0x9a, 0xfe, 0x2b, 0xb4, 0x10, - 0x03, 0xea, 0xf8, 0xbd, 0xc1, 0x3b, 0xac, 0x03, 0x2d, 0x35, 0xeb, 0xa0, 0xc2, 0x7a, 0xd0, 0xa6, - 0x5b, 0x5a, 0x84, 0xab, 0x9a, 0x7f, 0xb3, 0x08, 0x9d, 0xad, 0x30, 0x49, 0xe3, 0x4c, 0x8a, 0x66, - 0x71, 0x19, 0xa9, 0x41, 0x97, 0x91, 0x54, 0xf9, 0x54, 0xfe, 0x0d, 0x2a, 0x9f, 0xde, 0x83, 0x96, - 0xba, 0xf6, 0xa6, 0x7c, 0xb9, 0xb9, 0x77, 0xe6, 0x34, 0x0d, 0x5b, 0x05, 0xc3, 0x53, 0xf7, 0xf1, - 0x54, 0x66, 0xaa, 0x74, 0x49, 0x4e, 0xdf, 0xd4, 0xb3, 0x72, 0x1a, 0x76, 0x03, 0x6a, 0xce, 0x78, - 0x4c, 0xda, 0x87, 0x6a, 0x2a, 0x9a, 0x94, 0x6e, 0x31, 0x59, 0x88, 0x63, 0xf7, 0xa1, 0x4d, 0x6a, - 0x91, 0x92, 0xb3, 0xcd, 0xd9, 0x39, 0x75, 0xe6, 0x57, 0x6a, 0x4a, 0x72, 0x03, 0xef, 0x43, 0x3b, - 0x10, 0x22, 0x92, 0x03, 0x5a, 0xb3, 0x03, 0x74, 0xbe, 0xce, 0x32, 0x02, 0x9d, 0xb9, 0xbb, 0x0d, - 0x4d, 0x74, 0x25, 0x44, 0xa4, 0x4c, 0x70, 0x69, 0x1d, 0x94, 0xb7, 0xb2, 0x1a, 0x09, 0xfe, 0xb0, - 0x35, 0x00, 0x29, 0xd7, 0x34, 0x73, 0x7b, 0x96, 0x1d, 0x79, 0x88, 0x8a, 0x87, 0x4f, 0x47, 0xab, - 0x8f, 0x61, 0x20, 0xc3, 0x91, 0xd2, 0x48, 0xd0, 0xe5, 0x42, 0x3d, 0x72, 0x3a, 0xc2, 0xb5, 0xfa, - 0xf1, 0x74, 0xc4, 0xfb, 0x21, 0xb4, 0x22, 0xe9, 0x8f, 0x93, 0xe6, 0xe8, 0xac, 0x2d, 0x16, 0x43, - 0x95, 0xa3, 0x6e, 0x69, 0x0a, 0xf6, 0x23, 0xe8, 0xcb, 0xb2, 0xd6, 0x48, 0x39, 0xa6, 0x94, 0x0b, - 0x9d, 0xba, 0x6e, 0x35, 0xe5, 0xb7, 0x5a, 0xbd, 0x74, 0xca, 0x8d, 0xfd, 0x3e, 0xf4, 0xb8, 0x72, - 0xdd, 0xec, 0xc4, 0x75, 0x42, 0xd2, 0x27, 0x9d, 0xb5, 0x4b, 0xc5, 0xf0, 0xb2, 0x67, 0x67, 0x75, - 0x79, 0xd9, 0xcf, 0x5b, 0x81, 0xa6, 0x2a, 0xb5, 0x0e, 0x68, 0x54, 0xe9, 0xd2, 0xb1, 0xcc, 0xdb, - 0x5b, 0x0a, 0x8f, 0x7c, 0x99, 0x52, 0xb1, 0xc7, 0xfc, 0x94, 0xd4, 0xca, 0x14, 0x5f, 0xa6, 0xcb, - 0x24, 0x53, 0xb5, 0x96, 0x4f, 0xf9, 0x29, 0xee, 0x47, 0x51, 0x89, 0x1a, 0xb2, 0xd9, 0xfd, 0xc8, - 0xcb, 0x50, 0x56, 0x3b, 0xaf, 0x40, 0xb1, 0x27, 0xd3, 0x95, 0x31, 0x59, 0x5c, 0xb8, 0x40, 0x43, - 0xdf, 0x9d, 0x33, 0x54, 0xd6, 0x18, 0xac, 0x85, 0x68, 0xa6, 0xc0, 0x76, 0x17, 0x0c, 0x11, 0x7b, - 0x54, 0x9a, 0xa7, 0x14, 0x08, 0xed, 0x09, 0x15, 0x04, 0xe5, 0x7d, 0x41, 0x52, 0x40, 0x2d, 0x21, - 0x3b, 0xe8, 0x74, 0x44, 0xb1, 0xf8, 0x82, 0xbb, 0xa9, 0x54, 0x7f, 0x4b, 0x67, 0x9d, 0x0e, 0x85, - 0x27, 0x0f, 0xf2, 0x26, 0xb4, 0x74, 0x11, 0xfa, 0xd2, 0x19, 0x4a, 0x8d, 0x62, 0x1f, 0xc1, 0xc2, - 0xb4, 0x52, 0x4c, 0x86, 0x97, 0xcf, 0x50, 0xf7, 0xa7, 0x74, 0x20, 0x5a, 0x6a, 0xe5, 0x49, 0x0d, - 0xcf, 0x16, 0x7f, 0x08, 0x81, 0xfe, 0xa8, 0xf2, 0xc1, 0xde, 0x3d, 0xeb, 0x8f, 0x2a, 0x7f, 0x6c, - 0x08, 0x2d, 0x3f, 0x79, 0xea, 0xc7, 0x49, 0x3a, 0xbc, 0xa2, 0x2d, 0x27, 0x75, 0xd1, 0x83, 0xf3, - 0x13, 0x34, 0x21, 0xc3, 0xab, 0xfa, 0x86, 0x29, 0x19, 0x94, 0x3b, 0xd0, 0x54, 0x05, 0xfa, 0xe5, - 0x33, 0x5a, 0x41, 0x5d, 0x6a, 0xb1, 0x14, 0x05, 0xfb, 0x26, 0xb4, 0xa8, 0x3a, 0x2b, 0xa2, 0xe1, - 0x8d, 0x59, 0x29, 0x92, 0x25, 0x52, 0xab, 0x19, 0xc8, 0x52, 0xe9, 0x87, 0xd0, 0xd2, 0x0e, 0x8c, - 0x39, 0x7b, 0x32, 0x94, 0x23, 0x63, 0x69, 0x0a, 0x76, 0x0b, 0x1a, 0x13, 0xd4, 0x85, 0xc3, 0x6f, - 0xcc, 0x9e, 0x72, 0xa9, 0x22, 0x25, 0x96, 0x3d, 0x82, 0x4e, 0x42, 0xbe, 0xab, 0x14, 0xff, 0x9b, - 0xba, 0xb2, 0x59, 0xdc, 0xb0, 0xd7, 0x8e, 0xad, 0x05, 0x49, 0xe1, 0xe4, 0xfe, 0x5f, 0xb8, 0x52, - 0x2e, 0x8b, 0xea, 0x9a, 0xa9, 0xba, 0x5b, 0x7e, 0x8b, 0x66, 0xb9, 0x31, 0x47, 0xc2, 0xa6, 0xab, - 0xab, 0xd6, 0xe5, 0xe8, 0x9c, 0xb2, 0xeb, 0xa3, 0xdc, 0xd2, 0xe0, 0xc1, 0x1e, 0xde, 0x3e, 0xb3, - 0xac, 0xdc, 0x56, 0x69, 0xfb, 0x43, 0x26, 0xee, 0x63, 0xe8, 0x8e, 0xb2, 0xd7, 0xaf, 0x4f, 0x95, - 0x8c, 0x0c, 0x3f, 0xa0, 0x71, 0xa5, 0x48, 0xa9, 0x54, 0xe4, 0xb3, 0x3a, 0xa3, 0x52, 0xc5, 0xef, - 0x32, 0xb4, 0xdc, 0xd0, 0x76, 0x3c, 0x2f, 0x1e, 0xae, 0xc8, 0x22, 0x9f, 0x1b, 0xae, 0x7b, 0x1e, - 0x55, 0x4b, 0x45, 0xc4, 0xe9, 0xd2, 0xab, 0xed, 0x7b, 0xc3, 0x6f, 0x4a, 0x9b, 0xa7, 0x41, 0x5b, - 0x1e, 0x5d, 0xbe, 0xd7, 0xe1, 0x85, 0xef, 0x0d, 0xef, 0xa8, 0xcb, 0xf7, 0x0a, 0xb4, 0xe5, 0xa1, - 0x2f, 0x3b, 0x71, 0x4e, 0x6c, 0x0d, 0x19, 0x7e, 0x28, 0xef, 0x2a, 0x4f, 0x9c, 0x93, 0x3d, 0x05, - 0xc2, 0xb3, 0x2d, 0xef, 0x61, 0x91, 0xc6, 0xbc, 0x3b, 0x7b, 0xb6, 0xf3, 0xa0, 0xdf, 0x6a, 0xfb, - 0x79, 0xfc, 0x4f, 0xfa, 0x80, 0xb4, 0xa0, 0x1d, 0xac, 0x0d, 0xef, 0x9d, 0xd5, 0x07, 0x2a, 0xa7, - 0x81, 0xfa, 0x40, 0xa7, 0x37, 0xd6, 0x00, 0xa4, 0xba, 0xa4, 0xcd, 0x5e, 0x9d, 0x1d, 0x93, 0x07, - 0x18, 0x96, 0xbc, 0x84, 0x44, 0x5b, 0xbd, 0x06, 0x40, 0x85, 0x52, 0x39, 0xe6, 0xfe, 0xec, 0x98, - 0x3c, 0x60, 0xb0, 0xda, 0x2f, 0xf3, 0xd8, 0xe1, 0x3e, 0xb4, 0x33, 0x0c, 0x0d, 0xd0, 0x39, 0x1f, - 0x3e, 0x98, 0x3d, 0x03, 0x3a, 0x6a, 0xb0, 0x8c, 0x4c, 0xb5, 0xf0, 0x23, 0x64, 0xf6, 0xc8, 0x03, - 0x1a, 0x3e, 0x9c, 0xfd, 0x48, 0x1e, 0x5a, 0x58, 0x64, 0x1d, 0x65, 0x94, 0xf1, 0x08, 0x3a, 0x92, - 0x69, 0x72, 0xd0, 0xda, 0xac, 0x8c, 0x14, 0x2e, 0x95, 0x25, 0xb9, 0x2b, 0x87, 0xdd, 0x82, 0x86, - 0x13, 0x45, 0xc1, 0xe9, 0xf0, 0xa3, 0xd9, 0x83, 0xb1, 0x8e, 0x60, 0x4b, 0x62, 0x51, 0x94, 0x26, - 0x59, 0x90, 0xfa, 0xfa, 0xde, 0xd0, 0xb7, 0x66, 0x45, 0xa9, 0x74, 0xad, 0xd2, 0xea, 0x4c, 0x4a, - 0x77, 0x2c, 0xef, 0x82, 0x11, 0x89, 0x24, 0xb5, 0xbd, 0x49, 0x30, 0x7c, 0x74, 0xc6, 0x82, 0xc9, - 0xfb, 0x32, 0x56, 0x2b, 0x52, 0x17, 0x8e, 0xa6, 0x6e, 0xcd, 0x7e, 0x7b, 0xe6, 0xd6, 0xec, 0x23, - 0xe8, 0xae, 0xd3, 0xa3, 0x12, 0x3f, 0x21, 0x5d, 0x79, 0x0b, 0xea, 0x79, 0x6a, 0x2a, 0x57, 0xc2, - 0x44, 0xf1, 0x9a, 0x6f, 0x85, 0x23, 0x61, 0x11, 0xda, 0xfc, 0xf3, 0x3a, 0x34, 0xf7, 0x45, 0x16, - 0xbb, 0xfc, 0xcb, 0x2f, 0x9c, 0xbd, 0xaf, 0x45, 0x22, 0x2c, 0x0a, 0xdc, 0x72, 0xf7, 0x09, 0x3d, - 0x5b, 0x9a, 0x6b, 0x17, 0x59, 0xaf, 0x8b, 0xd0, 0x90, 0xa1, 0xa1, 0xbc, 0xa8, 0x24, 0x3b, 0x74, - 0x1c, 0xb2, 0xe4, 0xc8, 0x13, 0xaf, 0x42, 0x3c, 0x0e, 0x0d, 0xba, 0xe7, 0x03, 0x1a, 0xb4, 0xe5, - 0x51, 0x38, 0xae, 0x09, 0xe8, 0xbc, 0x35, 0x65, 0x7c, 0xa0, 0x81, 0x74, 0xea, 0x74, 0x46, 0xad, - 0x75, 0x4e, 0x46, 0xed, 0x1a, 0xd4, 0x43, 0x7d, 0x41, 0x26, 0xc7, 0xd3, 0x93, 0x04, 0x82, 0xb3, - 0x3b, 0x90, 0xdf, 0x92, 0x53, 0xae, 0xcb, 0xf9, 0xb7, 0xe8, 0xd6, 0xa0, 0x9d, 0x3f, 0x43, 0x52, - 0xde, 0xca, 0xc5, 0xd5, 0xe2, 0x61, 0xd2, 0x81, 0x6e, 0x59, 0x05, 0xd9, 0x9c, 0x24, 0x9b, 0xac, - 0x4f, 0x10, 0x9f, 0x3a, 0x6f, 0x93, 0x64, 0xa3, 0xa2, 0x85, 0x4e, 0x30, 0xfa, 0x89, 0xed, 0x8a, - 0x30, 0x49, 0x55, 0xc2, 0xa1, 0xe5, 0x27, 0x1b, 0xd8, 0x65, 0xdf, 0x85, 0x5e, 0xcc, 0xdd, 0x97, - 0xf6, 0x24, 0x19, 0xcb, 0x4f, 0xf4, 0xca, 0xf7, 0x6e, 0x27, 0xc9, 0xf8, 0x13, 0xee, 0xa0, 0xf1, - 0x95, 0x11, 0x53, 0x07, 0x69, 0x77, 0x92, 0x31, 0xcd, 0x7a, 0x03, 0xba, 0x87, 0x81, 0x10, 0x13, - 0xad, 0x12, 0xfb, 0x94, 0x56, 0xeb, 0x10, 0x4c, 0xae, 0xc0, 0xfc, 0xfd, 0x0a, 0x18, 0xc8, 0x3b, - 0x94, 0x20, 0xc6, 0xa0, 0x3e, 0x71, 0xa3, 0x4c, 0xb9, 0xc9, 0xd4, 0x56, 0x0f, 0x9a, 0xa4, 0x6c, - 0xa8, 0x07, 0x4d, 0xb4, 0x73, 0x35, 0x99, 0x42, 0xc3, 0xb6, 0x7c, 0xfc, 0x70, 0x4a, 0xf9, 0x15, - 0x29, 0x0f, 0xba, 0xcb, 0x96, 0xa0, 0xe9, 0x86, 0x14, 0x03, 0xcb, 0xcb, 0x52, 0x0d, 0x37, 0xc4, - 0xd8, 0x57, 0x82, 0x8b, 0xf2, 0x7f, 0xc3, 0x0d, 0xb7, 0xbc, 0x13, 0xf3, 0x2f, 0x2a, 0xb0, 0xb8, - 0x17, 0x0b, 0x97, 0x27, 0xc9, 0x36, 0x9a, 0x68, 0x87, 0xfc, 0x34, 0x06, 0x75, 0xca, 0x8f, 0xc9, - 0x97, 0x04, 0xd4, 0x46, 0xc9, 0x95, 0x09, 0x8a, 0x3c, 0x18, 0xa9, 0x59, 0x6d, 0x82, 0x50, 0x2c, - 0x92, 0xa3, 0x69, 0x60, 0xad, 0x84, 0xa6, 0xcc, 0xda, 0x2d, 0xe8, 0x17, 0x37, 0x97, 0x68, 0x06, - 0xf5, 0xe0, 0x28, 0x87, 0xd2, 0x2c, 0xd7, 0xa1, 0x13, 0x13, 0x6f, 0xe5, 0x34, 0x0d, 0xa2, 0x01, - 0x09, 0xc2, 0x79, 0xcc, 0x23, 0x18, 0xec, 0xc5, 0x3c, 0x72, 0x62, 0x8e, 0xda, 0x7c, 0x42, 0x3c, - 0xbc, 0x04, 0xcd, 0x80, 0x87, 0xe3, 0xf4, 0x48, 0xad, 0x57, 0xf5, 0xf2, 0xc7, 0x66, 0xd5, 0xd2, - 0x63, 0x33, 0xe4, 0x65, 0xcc, 0x1d, 0xf5, 0x26, 0x8d, 0xda, 0x78, 0xb2, 0xc2, 0x2c, 0x50, 0x39, - 0x3b, 0xc3, 0x92, 0x1d, 0xf3, 0x17, 0x35, 0xe8, 0x28, 0xce, 0xd0, 0x57, 0xe4, 0xae, 0x54, 0xf2, - 0x5d, 0x19, 0x40, 0x2d, 0x79, 0x11, 0xa8, 0x6d, 0xc2, 0x26, 0xfb, 0x08, 0x6a, 0x81, 0x3f, 0x51, - 0xa1, 0xcc, 0xd5, 0x29, 0xdb, 0x30, 0xcd, 0x5f, 0x25, 0x38, 0x48, 0x8d, 0x0a, 0x29, 0x0b, 0xfd, - 0x13, 0x1b, 0x45, 0x54, 0xf1, 0x04, 0xf5, 0xf4, 0x09, 0x9e, 0x03, 0x64, 0xaa, 0xe3, 0xd2, 0xdd, - 0x24, 0x7d, 0xb8, 0x7b, 0x56, 0x5b, 0x41, 0xb6, 0x3c, 0xf6, 0x2d, 0x30, 0x92, 0xd0, 0x89, 0x92, - 0x23, 0x91, 0xe6, 0xc1, 0x4b, 0x7a, 0x12, 0xae, 0x6e, 0xec, 0x1e, 0x9c, 0x84, 0xfb, 0x0a, 0xa3, - 0x3e, 0x96, 0x53, 0xb2, 0x1f, 0x41, 0x37, 0xe1, 0x49, 0x22, 0x6f, 0x23, 0x8f, 0x84, 0x3a, 0xf4, - 0x4b, 0xe5, 0xb8, 0x84, 0xb0, 0xf8, 0xaf, 0xb5, 0x88, 0x27, 0x05, 0x88, 0x7d, 0x02, 0x7d, 0x3d, - 0x3e, 0x10, 0xe3, 0x71, 0x9e, 0x5c, 0xbc, 0x7a, 0x66, 0x86, 0x6d, 0x42, 0x97, 0xe6, 0xe9, 0x25, - 0x65, 0x04, 0xfb, 0x29, 0xf4, 0x23, 0xb9, 0x99, 0xb6, 0xca, 0x42, 0x4b, 0xe5, 0x71, 0x65, 0xca, - 0x95, 0x99, 0xda, 0xec, 0xe2, 0x86, 0x61, 0x01, 0x4f, 0xcc, 0x7f, 0xab, 0x40, 0xa7, 0xb4, 0x6a, - 0x7a, 0x02, 0x98, 0xf0, 0x58, 0x27, 0x9d, 0xb1, 0x8d, 0xb0, 0x23, 0xa1, 0xde, 0xc2, 0xb4, 0x2d, - 0x6a, 0x23, 0x2c, 0x16, 0xaa, 0x90, 0xd1, 0xb6, 0xa8, 0x8d, 0x0a, 0x53, 0x05, 0x9c, 0xf2, 0xb5, - 0x00, 0x6d, 0x4a, 0xdd, 0xea, 0x16, 0xc0, 0x2d, 0x8f, 0xde, 0x0a, 0x3a, 0xa9, 0x73, 0xe8, 0x24, - 0x3a, 0x47, 0x9e, 0xf7, 0xf1, 0x68, 0xbe, 0xe4, 0x31, 0xae, 0x45, 0xe9, 0x5a, 0xdd, 0xc5, 0xbd, - 0x26, 0x1d, 0xf6, 0x5a, 0x84, 0xf2, 0x92, 0x55, 0xd7, 0x32, 0x10, 0xf0, 0xb9, 0x08, 0x69, 0x98, - 0xda, 0x59, 0xe2, 0x67, 0xdb, 0xd2, 0x5d, 0xd4, 0x54, 0x2f, 0x32, 0x8e, 0xee, 0x9e, 0x47, 0xb7, - 0x6c, 0xda, 0x56, 0x8b, 0xfa, 0x5b, 0x9e, 0xf9, 0x4f, 0x15, 0x58, 0x3c, 0xc3, 0x6c, 0xf4, 0xae, - 0x90, 0xd1, 0xfa, 0xe2, 0x67, 0xd7, 0x6a, 0x62, 0x77, 0xcb, 0x23, 0x44, 0x3a, 0x21, 0x61, 0xaa, - 0x2a, 0x44, 0x3a, 0x41, 0x49, 0x5a, 0x82, 0x66, 0x7a, 0x42, 0xff, 0x56, 0x1e, 0x8c, 0x46, 0x7a, - 0x82, 0x7f, 0x73, 0x1d, 0xa3, 0xdd, 0xb1, 0x1d, 0xf0, 0x97, 0x3c, 0x20, 0x3e, 0xf4, 0xd7, 0x6e, - 0xbe, 0x61, 0x97, 0x57, 0xb7, 0xc5, 0x78, 0x1b, 0x69, 0x31, 0xfe, 0x95, 0x2d, 0xf3, 0x67, 0x60, - 0x68, 0x28, 0x6b, 0x43, 0x63, 0x93, 0x1f, 0x66, 0xe3, 0xc1, 0x3b, 0xcc, 0x80, 0x3a, 0x8e, 0x18, - 0x54, 0xb0, 0xf5, 0x99, 0x13, 0x87, 0x83, 0x2a, 0xa2, 0x9f, 0xc4, 0xb1, 0x88, 0x07, 0x35, 0x6c, - 0xee, 0x39, 0xa1, 0xef, 0x0e, 0xea, 0xd8, 0x7c, 0xea, 0xa4, 0x4e, 0x30, 0x68, 0x98, 0xbf, 0x6c, - 0x80, 0xb1, 0xa7, 0xbe, 0xce, 0x36, 0xa1, 0x97, 0xbf, 0xc2, 0x9c, 0x9f, 0x61, 0xd9, 0x9b, 0x6d, - 0x50, 0x86, 0xa5, 0x1b, 0x95, 0x7a, 0xb3, 0x6f, 0x39, 0xab, 0x67, 0xde, 0x72, 0xbe, 0x07, 0xb5, - 0x17, 0xf1, 0xe9, 0x74, 0xad, 0x69, 0x2f, 0x70, 0x42, 0x0b, 0xc1, 0xec, 0x21, 0x74, 0x70, 0xdf, - 0xed, 0x84, 0xcc, 0xbf, 0xca, 0x4a, 0x94, 0x5f, 0xcc, 0x12, 0xdc, 0x02, 0x24, 0x52, 0x2e, 0xc2, - 0x2a, 0x18, 0xee, 0x91, 0x1f, 0x78, 0x31, 0x0f, 0x55, 0x1d, 0x97, 0x9d, 0x5d, 0xb2, 0x95, 0xd3, - 0xb0, 0x9f, 0xd0, 0x45, 0x45, 0x9d, 0x55, 0x29, 0xca, 0x02, 0x53, 0x47, 0xb6, 0x94, 0x77, 0xb1, - 0x16, 0x4a, 0xe4, 0x64, 0x93, 0x8a, 0x1b, 0xf9, 0xad, 0xf2, 0x8d, 0x7c, 0xf9, 0x62, 0x8f, 0x4c, - 0x88, 0x91, 0xc7, 0x53, 0x68, 0x41, 0x6e, 0x2b, 0x6b, 0xdf, 0x9e, 0xf5, 0x24, 0xb5, 0xd5, 0x52, - 0x56, 0xff, 0x26, 0xf4, 0xd1, 0x8b, 0xb0, 0xa5, 0xf3, 0x81, 0xaa, 0x04, 0xd4, 0xbb, 0x9a, 0x2c, - 0x39, 0xda, 0x44, 0xf7, 0x03, 0x85, 0xf1, 0x16, 0xf4, 0xf5, 0x7f, 0x51, 0xd7, 0x2c, 0x3b, 0xaa, - 0x6c, 0xa0, 0xa0, 0xf2, 0x96, 0xe5, 0x2a, 0x5c, 0x70, 0x8f, 0x9c, 0x30, 0xe4, 0x81, 0x7d, 0x98, - 0x8d, 0x46, 0xda, 0x02, 0x74, 0x29, 0x99, 0xb7, 0xa8, 0x50, 0x8f, 0x09, 0x43, 0x06, 0xc5, 0x84, - 0x5e, 0xe8, 0x07, 0x32, 0x63, 0x4d, 0xd6, 0xae, 0x47, 0x94, 0x9d, 0xd0, 0x0f, 0x28, 0x65, 0x8d, - 0x36, 0xef, 0xc7, 0x30, 0xc8, 0x32, 0xdf, 0x4b, 0xec, 0x54, 0xe8, 0xc7, 0x8e, 0x2a, 0xef, 0x59, - 0xca, 0x38, 0x3c, 0xcf, 0x7c, 0xef, 0x40, 0xa8, 0xe7, 0x8e, 0x3d, 0xa2, 0xd7, 0x5d, 0xf3, 0xc7, - 0xd0, 0x2d, 0xcb, 0x0e, 0xca, 0x22, 0x85, 0x73, 0x83, 0x77, 0x18, 0x40, 0x73, 0x57, 0xc4, 0x13, - 0x27, 0x18, 0x54, 0xb0, 0x2d, 0xdf, 0xa9, 0x0c, 0xaa, 0xac, 0x0b, 0x86, 0x8e, 0x33, 0x06, 0x35, - 0xf3, 0xfb, 0x60, 0xe8, 0xd7, 0x9b, 0xf4, 0x6c, 0x4e, 0x78, 0x5c, 0x7a, 0x61, 0x52, 0x33, 0x19, - 0x08, 0x20, 0x0f, 0x4c, 0x3f, 0x5a, 0xae, 0x16, 0x8f, 0x96, 0xcd, 0xff, 0x05, 0xdd, 0xf2, 0xe2, - 0x74, 0x02, 0xad, 0x52, 0x24, 0xd0, 0xe6, 0x8c, 0xa2, 0xfa, 0x51, 0x2c, 0x26, 0x76, 0xc9, 0x65, - 0x30, 0x10, 0x80, 0x9f, 0x31, 0x7f, 0xaf, 0x02, 0x0d, 0xf2, 0xbb, 0xc9, 0xb4, 0x60, 0xa3, 0x38, - 0x3b, 0x0d, 0xab, 0x4d, 0x90, 0xff, 0xc2, 0xf5, 0xb1, 0xbc, 0x50, 0x52, 0x7f, 0x63, 0xa1, 0xe4, - 0xce, 0x0b, 0x68, 0xca, 0x77, 0xe2, 0x6c, 0x11, 0x7a, 0xcf, 0xc3, 0xe3, 0x50, 0xbc, 0x0a, 0x25, - 0x60, 0xf0, 0x0e, 0xbb, 0x00, 0x0b, 0x9a, 0xe9, 0xea, 0x41, 0xfa, 0xa0, 0xc2, 0x06, 0xd0, 0xa5, - 0x6d, 0xd5, 0x90, 0x2a, 0x7b, 0x0f, 0x86, 0xca, 0x38, 0x6c, 0x8a, 0x90, 0xef, 0x8a, 0xd4, 0x1f, - 0x9d, 0x6a, 0x6c, 0x8d, 0x2d, 0x40, 0x67, 0x3f, 0x15, 0xd1, 0x3e, 0x0f, 0x3d, 0x3f, 0x1c, 0x0f, - 0xea, 0x77, 0x9e, 0x42, 0x53, 0x3e, 0x5f, 0x2f, 0x7d, 0x52, 0x02, 0x06, 0xef, 0x20, 0xf5, 0x67, - 0x8e, 0x9f, 0xfa, 0xe1, 0x78, 0x97, 0x9f, 0xa4, 0x52, 0x29, 0x6d, 0x3b, 0x49, 0x3a, 0xa8, 0xb2, - 0x3e, 0x80, 0x9a, 0xf5, 0x49, 0xe8, 0x0d, 0x6a, 0x8f, 0x37, 0x7e, 0xf5, 0xeb, 0x6b, 0x95, 0xbf, - 0xfd, 0xf5, 0xb5, 0xca, 0x3f, 0xfc, 0xfa, 0xda, 0x3b, 0x3f, 0xff, 0xc7, 0x6b, 0x95, 0xcf, 0x1f, - 0x96, 0x1e, 0xe7, 0x4f, 0x9c, 0x34, 0xf6, 0x4f, 0x64, 0x05, 0x4e, 0x77, 0x42, 0x7e, 0x3f, 0x3a, - 0x1e, 0xdf, 0x8f, 0x0e, 0xef, 0x6b, 0x99, 0x3b, 0x6c, 0xd2, 0x9b, 0xfb, 0x8f, 0xfe, 0x33, 0x00, - 0x00, 0xff, 0xff, 0xd1, 0x64, 0xd1, 0xc5, 0xf2, 0x3f, 0x00, 0x00, + // 6139 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3c, 0x4d, 0x6f, 0xdc, 0x48, + 0x76, 0xd3, 0xdf, 0xec, 0xd7, 0x1f, 0x6a, 0xd1, 0x96, 0xdd, 0xe3, 0x99, 0xb1, 0x65, 0xae, 0xed, + 0xd1, 0x7a, 0x6c, 0xd9, 0xd6, 0xac, 0x77, 0x67, 0xbf, 0x57, 0x96, 0xec, 0x1d, 0xed, 0x48, 0xb2, + 0x42, 0xc9, 0x19, 0x64, 0x0e, 0x21, 0x28, 0xb2, 0xba, 0xc5, 0x11, 0x9b, 0x45, 0x93, 0x45, 0x5b, + 0xf2, 0x29, 0x40, 0x72, 0xca, 0x1f, 0x08, 0x90, 0x5c, 0x16, 0x41, 0x80, 0x60, 0x83, 0x04, 0x1b, + 0x20, 0xc8, 0x29, 0x87, 0x5c, 0xf7, 0x98, 0x53, 0x0e, 0x39, 0x04, 0xc1, 0xe6, 0x18, 0x20, 0xa7, + 0x24, 0xd8, 0x4b, 0x80, 0xe0, 0xbd, 0xaa, 0x22, 0xd9, 0xad, 0x96, 0x67, 0x3c, 0x09, 0xf6, 0x92, + 0x3d, 0xa9, 0xea, 0xbd, 0x57, 0xc5, 0xea, 0x57, 0xaf, 0xde, 0x67, 0x95, 0xa0, 0x1f, 0x07, 0x31, + 0x0b, 0x83, 0x88, 0xad, 0xc6, 0x09, 0x17, 0xdc, 0x34, 0x74, 0xff, 0xca, 0xdd, 0x71, 0x20, 0x8e, + 0xb2, 0xc3, 0x55, 0x8f, 0x4f, 0xee, 0x8d, 0xf9, 0x98, 0xdf, 0x23, 0x82, 0xc3, 0x6c, 0x44, 0x3d, + 0xea, 0x50, 0x4b, 0x0e, 0xbc, 0x02, 0x21, 0xf7, 0x8e, 0x75, 0x3b, 0x0e, 0xdd, 0x48, 0xb5, 0x17, + 0x44, 0x30, 0x61, 0xa9, 0x70, 0x27, 0xb1, 0x02, 0xb4, 0xc5, 0x89, 0xc2, 0x59, 0x7f, 0x52, 0x85, + 0xd6, 0x0e, 0x4b, 0x53, 0x77, 0xcc, 0x4c, 0x0b, 0x6a, 0x69, 0xe0, 0x0f, 0x2b, 0xcb, 0x95, 0x95, + 0xfe, 0xda, 0x60, 0x35, 0x5f, 0xd6, 0xbe, 0x70, 0x45, 0x96, 0xda, 0x88, 0x44, 0x1a, 0x6f, 0xe2, + 0x0f, 0xab, 0xb3, 0x34, 0x3b, 0x4c, 0x1c, 0x71, 0xdf, 0x46, 0xa4, 0x39, 0x80, 0x1a, 0x4b, 0x92, + 0x61, 0x6d, 0xb9, 0xb2, 0xd2, 0xb5, 0xb1, 0x69, 0x9a, 0x50, 0xf7, 0x5d, 0xe1, 0x0e, 0xeb, 0x04, + 0xa2, 0xb6, 0x79, 0x03, 0xfa, 0x71, 0xc2, 0x3d, 0x27, 0x88, 0x46, 0xdc, 0x21, 0x6c, 0x83, 0xb0, + 0x5d, 0x84, 0x6e, 0x45, 0x23, 0xbe, 0x89, 0x54, 0x43, 0x68, 0xb9, 0x91, 0x1b, 0x9e, 0xa6, 0x6c, + 0xd8, 0x24, 0xb4, 0xee, 0x9a, 0x7d, 0xa8, 0x06, 0xfe, 0xb0, 0xb5, 0x5c, 0x59, 0xa9, 0xdb, 0xd5, + 0xc0, 0xc7, 0x6f, 0x64, 0x59, 0xe0, 0x0f, 0x0d, 0xf9, 0x0d, 0x6c, 0x9b, 0x16, 0x74, 0x23, 0xc6, + 0xfc, 0x5d, 0x2e, 0x6c, 0x16, 0x87, 0xa7, 0xc3, 0xf6, 0x72, 0x65, 0xc5, 0xb0, 0xa7, 0x60, 0xe6, + 0x15, 0x30, 0x7c, 0x76, 0x98, 0x8d, 0x77, 0xd2, 0xf1, 0x10, 0x96, 0x2b, 0x2b, 0x6d, 0x3b, 0xef, + 0x5b, 0xcf, 0xa0, 0xbd, 0xc1, 0xa3, 0x88, 0x79, 0x82, 0x27, 0xe6, 0x35, 0xe8, 0xe8, 0x9f, 0xeb, + 0x28, 0x36, 0x35, 0x6c, 0xd0, 0xa0, 0x2d, 0xdf, 0x7c, 0x1f, 0x16, 0x3c, 0x4d, 0xed, 0x04, 0x91, + 0xcf, 0x4e, 0x88, 0x4f, 0x0d, 0xbb, 0x9f, 0x83, 0xb7, 0x10, 0x6a, 0xfd, 0x71, 0x0d, 0x5a, 0xfb, + 0x47, 0xd9, 0x68, 0x14, 0x32, 0xf3, 0x06, 0xf4, 0x54, 0x73, 0x83, 0x87, 0x5b, 0xfe, 0x89, 0x9a, + 0x77, 0x1a, 0x68, 0x2e, 0x43, 0x47, 0x01, 0x0e, 0x4e, 0x63, 0xa6, 0xa6, 0x2d, 0x83, 0xa6, 0xe7, + 0xd9, 0x09, 0x22, 0x62, 0x7f, 0xcd, 0x9e, 0x06, 0xce, 0x50, 0xb9, 0x27, 0xb4, 0x23, 0xd3, 0x54, + 0x2e, 0x7d, 0x6d, 0x3d, 0x0c, 0x5e, 0x30, 0x9b, 0x8d, 0x37, 0x22, 0x41, 0xfb, 0xd2, 0xb0, 0xcb, + 0x20, 0x73, 0x0d, 0x96, 0x52, 0x39, 0xc4, 0x49, 0xdc, 0x68, 0xcc, 0x52, 0x27, 0x0b, 0x22, 0xf1, + 0xcd, 0x6f, 0x0c, 0x9b, 0xcb, 0xb5, 0x95, 0xba, 0x7d, 0x41, 0x21, 0x6d, 0xc2, 0x3d, 0x23, 0x94, + 0x79, 0x1f, 0x2e, 0xce, 0x8c, 0x91, 0x43, 0x5a, 0xcb, 0xb5, 0x95, 0x9a, 0x6d, 0x4e, 0x0d, 0xd9, + 0xa2, 0x11, 0x8f, 0x61, 0x31, 0xc9, 0x22, 0x94, 0xde, 0x27, 0x41, 0x28, 0x58, 0xb2, 0x1f, 0x33, + 0x8f, 0xf6, 0xb7, 0xb3, 0x76, 0x79, 0x95, 0x04, 0xdc, 0x9e, 0x45, 0xdb, 0x67, 0x47, 0x98, 0x77, + 0x72, 0xe6, 0x3d, 0x3e, 0x89, 0x13, 0x12, 0x82, 0xce, 0x1a, 0xc8, 0x09, 0x10, 0x62, 0x97, 0xd1, + 0xd6, 0xaf, 0xaa, 0x60, 0x6c, 0x06, 0x69, 0xec, 0x0a, 0xef, 0xc8, 0xbc, 0x0c, 0xad, 0x51, 0x16, + 0x79, 0xc5, 0x7e, 0x37, 0xb1, 0xbb, 0xe5, 0x9b, 0xdf, 0x83, 0x85, 0x90, 0x7b, 0x6e, 0xe8, 0xe4, + 0x5b, 0x3b, 0xac, 0x2e, 0xd7, 0x56, 0x3a, 0x6b, 0x17, 0x8a, 0x33, 0x91, 0x8b, 0x8e, 0xdd, 0x27, + 0xda, 0x42, 0x94, 0xbe, 0x0f, 0x83, 0x84, 0x4d, 0xb8, 0x60, 0xa5, 0xe1, 0x35, 0x1a, 0x6e, 0x16, + 0xc3, 0x3f, 0x4d, 0xdc, 0x78, 0x97, 0xfb, 0xcc, 0x5e, 0x90, 0xb4, 0xc5, 0xf0, 0x07, 0x25, 0xee, + 0xb3, 0xb1, 0x13, 0xf8, 0x27, 0x0e, 0x7d, 0x60, 0x58, 0x5f, 0xae, 0xad, 0x34, 0x0a, 0x56, 0xb2, + 0xf1, 0x96, 0x7f, 0xb2, 0x8d, 0x18, 0xf3, 0x43, 0xb8, 0x34, 0x3b, 0x44, 0xce, 0x3a, 0x6c, 0xd0, + 0x98, 0x0b, 0x53, 0x63, 0x6c, 0x42, 0x99, 0xd7, 0xa1, 0xab, 0x07, 0x09, 0x14, 0xbb, 0xa6, 0x14, + 0x84, 0xb4, 0x24, 0x76, 0x97, 0xa1, 0x15, 0xa4, 0x4e, 0x1a, 0x44, 0xc7, 0x74, 0x14, 0x0d, 0xbb, + 0x19, 0xa4, 0xfb, 0x41, 0x74, 0x6c, 0xbe, 0x0d, 0x46, 0xc2, 0x3c, 0x89, 0x31, 0x08, 0xd3, 0x4a, + 0x98, 0x47, 0xa8, 0xcb, 0x80, 0x4d, 0xc7, 0x13, 0x4c, 0x1d, 0xc8, 0x66, 0xc2, 0xbc, 0x0d, 0xc1, + 0xac, 0x14, 0x1a, 0x3b, 0x2c, 0x19, 0x33, 0x3c, 0x93, 0x38, 0x70, 0xdf, 0x73, 0x23, 0xe2, 0xbb, + 0x61, 0xe7, 0x7d, 0xd4, 0x08, 0xb1, 0x9b, 0x88, 0xc0, 0x0d, 0xe9, 0x18, 0x18, 0xb6, 0xee, 0x9a, + 0xef, 0x40, 0x3b, 0x15, 0x6e, 0x22, 0xf0, 0xd7, 0x91, 0xf8, 0x37, 0x6c, 0x83, 0x00, 0x78, 0x82, + 0x2e, 0x43, 0x8b, 0x45, 0x3e, 0xa1, 0xea, 0x72, 0x27, 0x59, 0xe4, 0x6f, 0xf9, 0x27, 0xd6, 0xdf, + 0x54, 0xa0, 0xb7, 0x93, 0x85, 0x22, 0x58, 0x4f, 0xc6, 0x19, 0x9b, 0x44, 0x02, 0x35, 0xc9, 0x66, + 0x90, 0x0a, 0xf5, 0x65, 0x6a, 0x9b, 0x2b, 0xd0, 0xfe, 0x71, 0xc2, 0xb3, 0x98, 0x24, 0x48, 0xee, + 0x74, 0x59, 0x82, 0x0a, 0x24, 0x4a, 0xdb, 0xd3, 0xc4, 0x67, 0xc9, 0xa3, 0x53, 0xa2, 0xad, 0x9d, + 0xa1, 0x2d, 0xa3, 0xcd, 0x77, 0xa1, 0xbd, 0xcf, 0x62, 0x37, 0x71, 0x51, 0x04, 0xea, 0xa4, 0x7e, + 0x0a, 0x00, 0xfe, 0x56, 0x22, 0xde, 0xf2, 0xd5, 0x21, 0xd4, 0x5d, 0x6b, 0x0c, 0xed, 0xf5, 0xf1, + 0x38, 0x61, 0x63, 0x57, 0x90, 0x2a, 0xe4, 0x31, 0x2d, 0xb7, 0x66, 0x57, 0x79, 0x4c, 0xea, 0x16, + 0x7f, 0x80, 0xe4, 0x0f, 0xb5, 0xcd, 0xab, 0x50, 0x67, 0xf3, 0xd7, 0x43, 0x70, 0xf3, 0x12, 0x34, + 0x3d, 0x1e, 0x8d, 0x82, 0xb1, 0x52, 0xd2, 0xaa, 0x67, 0xfd, 0x45, 0x0d, 0x1a, 0xf4, 0xe3, 0x90, + 0xbd, 0xa8, 0x38, 0x1d, 0xf6, 0xc2, 0x0d, 0xf5, 0xae, 0x20, 0xe0, 0xf1, 0x0b, 0x37, 0x34, 0x97, + 0xa1, 0x81, 0xd3, 0xa4, 0x73, 0x78, 0x23, 0x11, 0xe6, 0x2d, 0x68, 0xa0, 0x10, 0xa5, 0xd3, 0x2b, + 0x40, 0x21, 0x7a, 0x54, 0xff, 0xc5, 0x3f, 0x5f, 0x7b, 0xcb, 0x96, 0x68, 0xf3, 0x7d, 0xa8, 0xbb, + 0xe3, 0x71, 0x4a, 0xb2, 0x3c, 0x75, 0x9c, 0xf2, 0xdf, 0x6b, 0x13, 0x81, 0xf9, 0x10, 0xda, 0x72, + 0xdf, 0x90, 0xba, 0x41, 0xd4, 0x97, 0x4b, 0x06, 0xa9, 0xbc, 0xa5, 0x76, 0x41, 0x89, 0x1c, 0x0f, + 0x52, 0x75, 0xe0, 0x49, 0xa2, 0x0d, 0xbb, 0x00, 0xa0, 0xc5, 0x88, 0x13, 0xb6, 0x1e, 0x86, 0xdc, + 0xdb, 0x0f, 0x5e, 0x31, 0x65, 0x5f, 0xa6, 0x60, 0xe6, 0x2d, 0xe8, 0xef, 0x49, 0x91, 0xb3, 0x59, + 0x9a, 0x85, 0x22, 0x55, 0x36, 0x67, 0x06, 0x6a, 0xae, 0x82, 0x39, 0x05, 0x39, 0xa0, 0x9f, 0xdf, + 0x5e, 0xae, 0xad, 0xf4, 0xec, 0x39, 0x18, 0xf3, 0x6b, 0xd0, 0x1b, 0x23, 0xa7, 0x83, 0x68, 0xec, + 0x8c, 0x42, 0x17, 0xcd, 0x51, 0x0d, 0xcd, 0x95, 0x06, 0x3e, 0x09, 0xdd, 0x31, 0x09, 0x79, 0x1c, + 0x84, 0xa1, 0x33, 0x61, 0x93, 0x61, 0x87, 0xb6, 0xdc, 0x20, 0xc0, 0x0e, 0x9b, 0x58, 0xff, 0x51, + 0x85, 0xe6, 0x56, 0x94, 0xb2, 0x44, 0xe0, 0x11, 0x72, 0x47, 0x23, 0xe6, 0x09, 0x26, 0x55, 0x57, + 0xdd, 0xce, 0xfb, 0xc8, 0x82, 0x03, 0xfe, 0x69, 0x12, 0x08, 0xb6, 0xff, 0xa1, 0x12, 0x92, 0x02, + 0x60, 0xde, 0x86, 0x45, 0xd7, 0xf7, 0x1d, 0x4d, 0xed, 0x24, 0xfc, 0x65, 0x4a, 0xc7, 0xc9, 0xb0, + 0x17, 0x5c, 0xdf, 0x5f, 0x57, 0x70, 0x9b, 0xbf, 0x4c, 0xcd, 0xeb, 0x50, 0x4b, 0xd8, 0x88, 0x44, + 0xa6, 0xb3, 0xb6, 0x20, 0xb7, 0xf4, 0xe9, 0xe1, 0xe7, 0xcc, 0x13, 0x36, 0x1b, 0xd9, 0x88, 0x33, + 0x2f, 0x42, 0xc3, 0x15, 0x22, 0x91, 0x5b, 0xd4, 0xb6, 0x65, 0xc7, 0x5c, 0x85, 0x0b, 0x74, 0x6c, + 0x45, 0xc0, 0x23, 0x47, 0xb8, 0x87, 0x21, 0xda, 0xd4, 0x54, 0x99, 0x8f, 0xc5, 0x1c, 0x75, 0x80, + 0x98, 0x2d, 0x3f, 0x45, 0x83, 0x33, 0x4b, 0x1f, 0xb9, 0x13, 0x96, 0x92, 0xf5, 0x68, 0xdb, 0x17, + 0xa6, 0x47, 0xec, 0x22, 0x0a, 0xf9, 0x59, 0x8c, 0xc1, 0x83, 0x6f, 0xd0, 0x19, 0xea, 0xe6, 0x40, + 0xd4, 0x0b, 0x4b, 0xd0, 0x0c, 0x52, 0x87, 0x45, 0xbe, 0xd2, 0x45, 0x8d, 0x20, 0x7d, 0x1c, 0xf9, + 0xe6, 0x07, 0xd0, 0x96, 0x5f, 0xf1, 0xd9, 0x88, 0xdc, 0x82, 0xce, 0x5a, 0x5f, 0x49, 0x2c, 0x82, + 0x37, 0xd9, 0xc8, 0x36, 0x84, 0x6a, 0x59, 0xbf, 0x5f, 0x81, 0x0e, 0x09, 0xd8, 0xb3, 0xd8, 0xc7, + 0xf3, 0xf8, 0x35, 0xe8, 0x4d, 0x73, 0x4f, 0x6e, 0x40, 0xd7, 0x2d, 0xb3, 0xee, 0x12, 0x34, 0xd7, + 0x3d, 0x5c, 0x05, 0xed, 0x40, 0xcf, 0x56, 0x3d, 0xf3, 0x5b, 0xb0, 0x90, 0xd1, 0x34, 0x8e, 0x27, + 0x4e, 0x9c, 0x10, 0xcf, 0xb1, 0x3c, 0x31, 0x8a, 0xbd, 0xf2, 0x1b, 0x1b, 0xe2, 0xc4, 0xee, 0x65, + 0xba, 0xb9, 0x1d, 0xa4, 0xc2, 0x7a, 0x0f, 0x1a, 0xeb, 0x49, 0xe2, 0x9e, 0x12, 0xc7, 0xb1, 0x31, + 0xac, 0x90, 0x6a, 0x97, 0x1d, 0xcb, 0x83, 0xda, 0x8e, 0x1b, 0x9b, 0x37, 0xa1, 0x3a, 0x89, 0x09, + 0xd3, 0x59, 0x5b, 0x2a, 0x1d, 0x17, 0x37, 0x5e, 0xdd, 0x89, 0x1f, 0x47, 0x22, 0x39, 0xb5, 0xab, + 0x93, 0xf8, 0xca, 0x43, 0x68, 0xa9, 0x2e, 0xba, 0x73, 0xc7, 0xec, 0x94, 0x7e, 0x43, 0xdb, 0xc6, + 0x26, 0x7e, 0xe0, 0x85, 0x1b, 0x66, 0xda, 0x0f, 0x91, 0x9d, 0xef, 0x54, 0x3f, 0xaa, 0x58, 0xff, + 0x59, 0x07, 0x63, 0x93, 0x85, 0x8c, 0x7e, 0x89, 0x05, 0xdd, 0xb2, 0xb0, 0x68, 0x2e, 0x4c, 0x09, + 0x90, 0x05, 0x5d, 0x69, 0x6c, 0x68, 0x14, 0x53, 0xd2, 0x38, 0x05, 0x43, 0x2d, 0xb8, 0xf5, 0x28, + 0xf3, 0x8e, 0x99, 0x20, 0x31, 0xec, 0xd9, 0xba, 0x8b, 0x98, 0x5d, 0x85, 0xa9, 0x4b, 0x8c, 0xea, + 0x9a, 0xef, 0x02, 0x24, 0xfc, 0xa5, 0x13, 0x48, 0x8d, 0x2f, 0x95, 0xa7, 0x91, 0xf0, 0x97, 0x5b, + 0xa8, 0xf3, 0x7f, 0x2d, 0xd2, 0xf7, 0x2d, 0x18, 0x96, 0xa4, 0x0f, 0xfd, 0x3e, 0x27, 0x88, 0x9c, + 0x43, 0x74, 0x2b, 0x94, 0x20, 0x16, 0x73, 0x92, 0x5b, 0xb8, 0x15, 0x3d, 0x22, 0x9f, 0x43, 0x9d, + 0xa9, 0xf6, 0x6b, 0xce, 0xd4, 0xdc, 0x23, 0x0a, 0xf3, 0x8f, 0xe8, 0x23, 0x80, 0x7d, 0x36, 0x9e, + 0xb0, 0x48, 0xec, 0xb8, 0xf1, 0xb0, 0x43, 0x1b, 0x6f, 0x15, 0x1b, 0xaf, 0x77, 0x6b, 0xb5, 0x20, + 0x92, 0x52, 0x50, 0x1a, 0x85, 0x8e, 0x80, 0xe7, 0x46, 0x8e, 0x48, 0xb2, 0xc8, 0x73, 0x05, 0x1b, + 0x76, 0xe9, 0x53, 0x1d, 0xcf, 0x8d, 0x0e, 0x14, 0xa8, 0x74, 0x8e, 0x7a, 0xe5, 0x73, 0x74, 0x0b, + 0x16, 0xe2, 0x24, 0x98, 0xb8, 0xc9, 0xa9, 0x73, 0xcc, 0x4e, 0x69, 0x33, 0xfa, 0xd2, 0xc1, 0x55, + 0xe0, 0x4f, 0xd8, 0xe9, 0x96, 0x7f, 0x72, 0xe5, 0xfb, 0xb0, 0x30, 0xb3, 0x80, 0x37, 0x92, 0xbb, + 0x9f, 0xd6, 0xa0, 0xbd, 0x97, 0x30, 0xa5, 0xfb, 0xae, 0x41, 0x27, 0xf5, 0x8e, 0xd8, 0xc4, 0xa5, + 0x5d, 0x52, 0x33, 0x80, 0x04, 0xe1, 0xe6, 0x4c, 0x9f, 0xee, 0xea, 0xeb, 0x4f, 0x37, 0xae, 0x43, + 0x3a, 0x14, 0x78, 0x98, 0xb0, 0x59, 0xa8, 0xb4, 0x7a, 0x59, 0xa5, 0x2d, 0x43, 0xf7, 0xc8, 0x4d, + 0x1d, 0x37, 0x13, 0xdc, 0xf1, 0x78, 0x48, 0x42, 0x67, 0xd8, 0x70, 0xe4, 0xa6, 0xeb, 0x99, 0xe0, + 0x1b, 0x3c, 0x34, 0xdf, 0x03, 0xf0, 0x78, 0xe8, 0xf0, 0xd1, 0x28, 0x65, 0x42, 0x79, 0x53, 0x6d, + 0x8f, 0x87, 0x4f, 0x09, 0x80, 0x52, 0xc9, 0x52, 0x11, 0x4c, 0x5c, 0xb5, 0xa5, 0x8e, 0xc7, 0xb3, + 0x48, 0x90, 0x09, 0xaa, 0xd9, 0x8b, 0x39, 0xca, 0xe6, 0x2f, 0x37, 0x10, 0x61, 0xde, 0x87, 0xbe, + 0xc7, 0x27, 0xb1, 0x13, 0x23, 0x67, 0xc9, 0xb8, 0x1b, 0x67, 0x5c, 0xdb, 0x2e, 0x52, 0xec, 0x1d, + 0x33, 0xe9, 0x6d, 0xac, 0xc1, 0x82, 0x17, 0x66, 0xa9, 0x60, 0x89, 0x73, 0xa8, 0x86, 0x9c, 0xf5, + 0x86, 0x7b, 0x8a, 0x44, 0x79, 0x28, 0x16, 0xf4, 0x82, 0xd4, 0xe1, 0xa1, 0xef, 0x48, 0x75, 0xa3, + 0xe4, 0xac, 0x13, 0xa4, 0x4f, 0x43, 0x5f, 0x29, 0x3c, 0x49, 0x13, 0xb1, 0x97, 0x9a, 0xa6, 0xa3, + 0x69, 0x76, 0xd9, 0x4b, 0x49, 0x63, 0xfd, 0x63, 0x15, 0x5a, 0x7b, 0x3c, 0x15, 0x9b, 0x93, 0x50, + 0x8b, 0x78, 0xe5, 0x4d, 0x45, 0xbc, 0x3a, 0x5f, 0xc4, 0xe7, 0x08, 0x59, 0x6d, 0x8e, 0x90, 0x99, + 0x2b, 0x30, 0x28, 0xd3, 0x91, 0x70, 0x48, 0x9f, 0xab, 0x5f, 0x10, 0x92, 0x80, 0xbc, 0x83, 0x4e, + 0x82, 0xe3, 0x4b, 0x9d, 0x24, 0x37, 0xd2, 0x08, 0x52, 0xa5, 0x8f, 0x24, 0x32, 0x20, 0x59, 0x53, + 0x1e, 0x84, 0x11, 0xa4, 0x4a, 0xf6, 0xbe, 0x0d, 0x6f, 0xe7, 0x23, 0x9d, 0x97, 0x81, 0x38, 0xe2, + 0x99, 0x70, 0x46, 0x14, 0x8c, 0xa4, 0xca, 0x45, 0xbe, 0xa4, 0x67, 0xfa, 0x54, 0xa2, 0x65, 0xa8, + 0x42, 0x0e, 0xcd, 0x28, 0x0b, 0x43, 0x47, 0xb0, 0x13, 0xa1, 0xb6, 0x72, 0x28, 0x79, 0xa3, 0xf8, + 0xf6, 0x24, 0x0b, 0xc3, 0x03, 0x76, 0x22, 0x50, 0xf9, 0x1b, 0x23, 0xd5, 0xb1, 0xfe, 0xa8, 0x0e, + 0xb0, 0xcd, 0xbd, 0xe3, 0x03, 0x37, 0x19, 0x33, 0x81, 0x8e, 0xb7, 0xd6, 0x68, 0x4a, 0xe3, 0xb6, + 0x84, 0xd4, 0x63, 0xe6, 0x1a, 0x5c, 0xd2, 0xbf, 0x1f, 0xe5, 0x10, 0x83, 0x00, 0xa9, 0x92, 0xd4, + 0x81, 0x32, 0x15, 0x56, 0x06, 0x9d, 0xa4, 0x8f, 0xcc, 0x8f, 0x0a, 0xde, 0xe2, 0x18, 0x71, 0x1a, + 0x13, 0x6f, 0xe7, 0x39, 0x70, 0xbd, 0x62, 0xf8, 0xc1, 0x69, 0x6c, 0xde, 0x87, 0xa5, 0x84, 0x8d, + 0x12, 0x96, 0x1e, 0x39, 0x22, 0x2d, 0x7f, 0x4c, 0xfa, 0xdf, 0x8b, 0x0a, 0x79, 0x90, 0xe6, 0xdf, + 0xba, 0x0f, 0x4b, 0x92, 0x53, 0xb3, 0xcb, 0x93, 0xfa, 0x7b, 0x51, 0x22, 0xcb, 0xab, 0x7b, 0x0f, + 0x28, 0xe9, 0x21, 0x75, 0xb2, 0xf6, 0xe6, 0x42, 0x62, 0xc6, 0x61, 0xc8, 0xd0, 0xd1, 0xd9, 0x38, + 0xc2, 0x80, 0x72, 0x93, 0x8d, 0x14, 0xf3, 0x0b, 0x80, 0x69, 0x41, 0x7d, 0x87, 0xfb, 0x8c, 0x58, + 0xdd, 0x5f, 0xeb, 0xaf, 0x52, 0xfa, 0x04, 0x39, 0x89, 0x50, 0x9b, 0x70, 0xe6, 0xfb, 0x40, 0xd3, + 0x49, 0xf1, 0x3b, 0x7b, 0x56, 0x0c, 0x44, 0x92, 0x0c, 0xde, 0x87, 0xa5, 0x62, 0x25, 0x8e, 0x2b, + 0x1c, 0x71, 0xc4, 0x48, 0x1d, 0xca, 0xe3, 0xb2, 0x98, 0x2f, 0x6a, 0x5d, 0x1c, 0x1c, 0x31, 0x54, + 0x8d, 0x2b, 0xd0, 0xe2, 0x87, 0x9f, 0x3b, 0x78, 0x10, 0x3a, 0xf3, 0x0f, 0x42, 0x93, 0x1f, 0x7e, + 0x6e, 0xb3, 0x91, 0xf9, 0xcd, 0xb2, 0x29, 0x99, 0x61, 0x4d, 0x97, 0x58, 0x73, 0x31, 0xc7, 0x97, + 0xb8, 0x63, 0x7d, 0x04, 0x4d, 0xfc, 0x39, 0x4f, 0x63, 0x73, 0x15, 0x5a, 0x82, 0xc4, 0x23, 0x55, + 0xa6, 0xff, 0x62, 0x61, 0x01, 0x0a, 0xd9, 0xb1, 0x35, 0x91, 0x65, 0xc3, 0x42, 0xae, 0x4e, 0x9f, + 0x45, 0xc1, 0xf3, 0x8c, 0x99, 0x3f, 0x84, 0xc5, 0x38, 0x61, 0x4a, 0xec, 0x9d, 0xec, 0x18, 0xdd, + 0x13, 0x75, 0x82, 0x2f, 0x2a, 0x29, 0xcd, 0x47, 0x1c, 0xa3, 0x84, 0xf6, 0xe3, 0xa9, 0xbe, 0xf5, + 0x19, 0x5c, 0xce, 0x29, 0xf6, 0x99, 0xc7, 0x23, 0xdf, 0x4d, 0x4e, 0xc9, 0xf2, 0xcd, 0xcc, 0x9d, + 0xbe, 0xc9, 0xdc, 0xfb, 0x34, 0xf7, 0x9f, 0xd6, 0xa0, 0xff, 0x34, 0xda, 0xcc, 0xe2, 0x30, 0x40, + 0x6b, 0xf4, 0x89, 0x34, 0x16, 0x52, 0x49, 0x57, 0xca, 0x4a, 0x7a, 0x05, 0x06, 0xea, 0x2b, 0xc8, + 0x47, 0xa9, 0x60, 0x55, 0x92, 0x46, 0xc2, 0x37, 0x78, 0x28, 0xb5, 0xeb, 0xf7, 0x61, 0x29, 0xa3, + 0x5f, 0x2e, 0x29, 0x8f, 0x98, 0x77, 0xec, 0x9c, 0x13, 0x41, 0x99, 0x92, 0x10, 0x87, 0x22, 0x19, + 0xa9, 0xcd, 0x6b, 0xd0, 0x29, 0x86, 0x6b, 0x4b, 0x01, 0x39, 0x21, 0xad, 0x84, 0x47, 0x8e, 0xaf, + 0x97, 0xac, 0xfc, 0x14, 0xb4, 0x31, 0x7d, 0x5e, 0xfc, 0x12, 0x54, 0x5b, 0xbf, 0x03, 0x8b, 0x53, + 0x94, 0xb4, 0x8a, 0x26, 0xad, 0xe2, 0x6e, 0xb1, 0x8d, 0xd3, 0x3f, 0xbf, 0xdc, 0xc5, 0xf5, 0x48, + 0x9b, 0xbe, 0xc0, 0xa7, 0xa1, 0x5a, 0x95, 0x8d, 0x23, 0x9e, 0x30, 0x75, 0x40, 0x50, 0x95, 0x51, + 0xff, 0xca, 0x2e, 0x5c, 0x9c, 0x37, 0xcb, 0x1c, 0xc3, 0xbc, 0x5c, 0x36, 0xcc, 0x33, 0xd1, 0x5f, + 0x61, 0xa4, 0xff, 0xbc, 0x02, 0x9d, 0x27, 0xd9, 0xab, 0x57, 0xa7, 0x52, 0xe1, 0x99, 0x5d, 0xa8, + 0xec, 0xd2, 0x2c, 0x55, 0xbb, 0xb2, 0x8b, 0xfe, 0xf0, 0xde, 0x31, 0x2a, 0x5f, 0x9a, 0xa4, 0x6d, + 0xab, 0x1e, 0xc6, 0x8d, 0x7b, 0xc7, 0x07, 0xaf, 0x51, 0x3b, 0x12, 0x8d, 0x01, 0xcf, 0xa3, 0x2c, + 0x08, 0xd1, 0xbf, 0x53, 0x1a, 0x26, 0xef, 0x63, 0x24, 0xb6, 0x35, 0x92, 0xf2, 0xf2, 0x24, 0xe1, + 0x13, 0x29, 0xd1, 0x4a, 0xaf, 0xcf, 0xc1, 0x58, 0xbf, 0xaa, 0x83, 0xf1, 0xb1, 0x9b, 0x1e, 0xfd, + 0x84, 0x07, 0x91, 0x79, 0x1f, 0xda, 0x9f, 0xf3, 0x20, 0x92, 0x29, 0x10, 0x99, 0x1c, 0xbd, 0x20, + 0x17, 0xb1, 0xcb, 0x7d, 0xb6, 0x8a, 0x34, 0xb8, 0x1a, 0xdb, 0xf8, 0x5c, 0xb5, 0x94, 0x39, 0x4c, + 0x82, 0xf1, 0x91, 0x70, 0x10, 0xa8, 0xec, 0x56, 0x27, 0x48, 0x6d, 0x84, 0xd1, 0xac, 0xef, 0x02, + 0x7a, 0x06, 0x47, 0x0e, 0x8f, 0x9c, 0xf8, 0x58, 0x85, 0x57, 0x06, 0x42, 0x9e, 0x46, 0x7b, 0xc7, + 0xa8, 0xd7, 0x82, 0xd4, 0x51, 0x89, 0x16, 0xfa, 0x39, 0x53, 0x51, 0xea, 0x0d, 0xe8, 0xa3, 0x3f, + 0x96, 0x1e, 0x07, 0xb1, 0x13, 0x27, 0xfc, 0x50, 0xff, 0x16, 0xf4, 0xd2, 0xf6, 0x8f, 0x83, 0x78, + 0x0f, 0x61, 0xe4, 0x06, 0xa9, 0xf4, 0x0d, 0x0a, 0x97, 0xf4, 0x37, 0x40, 0x81, 0x90, 0x2d, 0x94, + 0xa3, 0x09, 0x65, 0x8c, 0xd1, 0x22, 0xd1, 0x6b, 0x25, 0x2c, 0xc4, 0x60, 0x02, 0x51, 0x28, 0xf6, + 0x84, 0x32, 0x24, 0xca, 0xe3, 0x12, 0xf5, 0x75, 0x80, 0x90, 0x8d, 0xf0, 0x00, 0x45, 0xbe, 0x0c, + 0x67, 0x67, 0x72, 0x21, 0x88, 0xdd, 0x40, 0xa4, 0xf9, 0x01, 0x74, 0x24, 0x17, 0x24, 0x2d, 0x9c, + 0xa1, 0x05, 0x42, 0x4b, 0xe2, 0xdb, 0xd0, 0x89, 0x78, 0xe4, 0xb0, 0xe7, 0x44, 0xad, 0x74, 0xe2, + 0xd4, 0xc4, 0x11, 0x8f, 0x1e, 0x3f, 0x47, 0x62, 0xf3, 0x9e, 0x5a, 0x83, 0xcc, 0x28, 0x74, 0xcf, + 0xc9, 0x28, 0xd0, 0x4a, 0x64, 0x6c, 0xfd, 0x40, 0xaf, 0x44, 0x8e, 0xe8, 0x9d, 0x33, 0x42, 0xae, + 0x47, 0x0e, 0x59, 0x86, 0x2e, 0xed, 0xfb, 0xc4, 0x8d, 0x1d, 0xe1, 0x8e, 0x95, 0xdf, 0x0a, 0x08, + 0xdb, 0x71, 0xe3, 0x03, 0x77, 0x6c, 0xda, 0xf0, 0xb6, 0xca, 0x36, 0x2a, 0x0b, 0xef, 0x1c, 0xa2, + 0xc4, 0x49, 0xae, 0x2d, 0xe8, 0x8c, 0xc4, 0xfc, 0x3c, 0xe5, 0xa5, 0xa9, 0x3c, 0x25, 0x49, 0x2a, + 0x45, 0x71, 0x7f, 0x56, 0x05, 0x63, 0x9b, 0xf3, 0xf8, 0x2b, 0x8a, 0x5e, 0x79, 0x4b, 0xab, 0xe7, + 0x6f, 0x69, 0x6d, 0x7a, 0x4b, 0x67, 0x58, 0x5f, 0xff, 0xf2, 0xac, 0x6f, 0xbc, 0x31, 0xeb, 0x9b, + 0x5f, 0x81, 0xf5, 0xad, 0x59, 0xd6, 0x5b, 0x2d, 0x68, 0xec, 0x33, 0xf1, 0x34, 0xb6, 0x7e, 0x6e, + 0x40, 0x7b, 0x93, 0xf9, 0x99, 0x64, 0x58, 0xf9, 0xe7, 0x57, 0xce, 0xff, 0xf9, 0xd5, 0xe9, 0x9f, + 0x8f, 0x46, 0x5e, 0x4b, 0xf4, 0x1c, 0xf5, 0x6e, 0x68, 0x81, 0x46, 0xd1, 0x2f, 0xe4, 0x59, 0x65, + 0xa8, 0xa6, 0xd8, 0x94, 0x8b, 0xf3, 0xeb, 0x65, 0xa3, 0xf1, 0x95, 0x64, 0x63, 0x46, 0x2b, 0x9c, + 0xc9, 0x5d, 0x7d, 0x21, 0xd7, 0x66, 0x35, 0x82, 0x71, 0x46, 0x23, 0x6c, 0xc3, 0x85, 0x29, 0x53, + 0xe3, 0xca, 0x0c, 0x45, 0x9b, 0x44, 0xef, 0xdd, 0x92, 0xe8, 0x95, 0x0c, 0x83, 0xcc, 0x5b, 0xd8, + 0x8b, 0x7c, 0x16, 0x84, 0x6a, 0xca, 0xc7, 0xad, 0x21, 0x0b, 0x4a, 0xde, 0xb6, 0x2c, 0xb0, 0x74, + 0x09, 0xba, 0xc1, 0x43, 0x52, 0xf0, 0x1f, 0xc1, 0x42, 0x41, 0x25, 0x65, 0xa4, 0x73, 0x8e, 0x8c, + 0xf4, 0xf4, 0x40, 0x29, 0x26, 0xbf, 0x0e, 0x2d, 0x70, 0x17, 0x2e, 0xe8, 0x74, 0x8c, 0x72, 0xbc, + 0x68, 0x07, 0xfb, 0x24, 0x41, 0x03, 0x95, 0x81, 0x21, 0x9f, 0x8b, 0xb6, 0xe8, 0xbb, 0x70, 0xb1, + 0x44, 0x8e, 0x96, 0xba, 0xac, 0x0d, 0xca, 0xb2, 0xb2, 0x98, 0x8f, 0xc5, 0xee, 0xb6, 0xcc, 0xd1, + 0x76, 0x7c, 0x16, 0xea, 0x0f, 0x0d, 0x07, 0x32, 0x40, 0xf4, 0x59, 0xa8, 0xaa, 0x40, 0x3b, 0x70, + 0x03, 0xe3, 0x30, 0xf2, 0x47, 0xdc, 0x58, 0x64, 0x09, 0x73, 0xe2, 0xd0, 0xf5, 0xd8, 0x11, 0x0f, + 0x7d, 0x96, 0x14, 0x8b, 0x5b, 0xa4, 0xc5, 0x5d, 0xe3, 0xa1, 0x8f, 0x2e, 0x89, 0xa4, 0xdc, 0x2b, + 0x08, 0xf5, 0x5a, 0xd7, 0xe1, 0xea, 0x99, 0xe9, 0xd0, 0x70, 0x14, 0x13, 0x99, 0x34, 0xd1, 0xdb, + 0xd3, 0x13, 0x21, 0x89, 0x9e, 0xe2, 0x01, 0x2c, 0xc9, 0xbd, 0x93, 0xc2, 0x7d, 0xcc, 0x58, 0xec, + 0x84, 0x6e, 0x2a, 0x86, 0x17, 0xa4, 0x6d, 0x25, 0x24, 0x09, 0xf0, 0x27, 0x8c, 0xc5, 0xdb, 0xae, + 0xfc, 0xaa, 0x1c, 0xa2, 0x62, 0x24, 0x1a, 0x33, 0xc5, 0xdb, 0x8b, 0xf2, 0xab, 0x44, 0x25, 0x03, + 0x25, 0x1c, 0x5c, 0x62, 0xf2, 0xf7, 0xe0, 0x9d, 0xa9, 0x29, 0x26, 0x6e, 0x72, 0x5c, 0x04, 0x0d, + 0xc3, 0x25, 0xe2, 0xdb, 0xe5, 0xd2, 0xf8, 0x1d, 0x22, 0x90, 0x33, 0x58, 0xff, 0xde, 0x80, 0x3e, + 0xd9, 0xe1, 0xdf, 0xa8, 0x8d, 0xdf, 0xa8, 0x8d, 0xff, 0x07, 0x6a, 0xc3, 0xfa, 0xbd, 0x0a, 0xb4, + 0xf6, 0x12, 0xee, 0x67, 0x9e, 0xf8, 0x8a, 0x92, 0x3e, 0x2d, 0x41, 0xb5, 0x2f, 0x92, 0xa0, 0xfa, + 0x19, 0x73, 0xfd, 0xb3, 0x0a, 0xb4, 0xd5, 0x12, 0xb6, 0xd7, 0xbe, 0xe2, 0x22, 0x8a, 0x0a, 0x56, + 0x65, 0x6e, 0x05, 0xeb, 0x0b, 0x57, 0x81, 0x82, 0xf5, 0x42, 0x56, 0xe7, 0x79, 0x2c, 0x7d, 0xaa, + 0x86, 0x14, 0x2c, 0x09, 0x7d, 0x1a, 0xe3, 0xde, 0x59, 0x2f, 0xa1, 0x4d, 0x51, 0x29, 0x69, 0x86, + 0x4b, 0xd0, 0x4c, 0xa8, 0x44, 0xa3, 0x16, 0xaa, 0x7a, 0xaf, 0x3f, 0xa7, 0xd5, 0xaf, 0xe6, 0xfa, + 0xfd, 0x75, 0x05, 0x7a, 0x94, 0x22, 0x78, 0x92, 0x45, 0xf2, 0x24, 0xcc, 0x8f, 0x61, 0x97, 0xa1, + 0x9e, 0x60, 0x24, 0x2f, 0x3f, 0xd3, 0x95, 0x9f, 0xd9, 0xe0, 0xe1, 0x26, 0x1b, 0xd9, 0x84, 0x41, + 0x56, 0xb9, 0xc9, 0x38, 0x9d, 0x57, 0xec, 0x43, 0x38, 0xfe, 0xaa, 0xd8, 0x4d, 0xdc, 0x49, 0xaa, + 0x8b, 0x7d, 0xb2, 0x67, 0x9a, 0x50, 0xa7, 0xf3, 0x26, 0xd9, 0x42, 0x6d, 0x15, 0x22, 0xa6, 0x41, + 0x34, 0xce, 0x95, 0x87, 0x41, 0x35, 0xde, 0x71, 0xc8, 0xac, 0x75, 0x58, 0x7a, 0x7c, 0x22, 0x58, + 0x12, 0xb9, 0x74, 0x28, 0xd7, 0x50, 0xe2, 0x28, 0xa2, 0xd7, 0x33, 0x55, 0x4a, 0x33, 0x5d, 0x84, + 0x46, 0xf9, 0x56, 0x84, 0xec, 0x58, 0x37, 0xa1, 0x33, 0x0a, 0x42, 0xa6, 0xb2, 0xa2, 0xb8, 0x34, + 0x95, 0x1f, 0xad, 0xd0, 0xbd, 0x00, 0xd5, 0xb3, 0xfe, 0xb6, 0x02, 0x97, 0x63, 0x37, 0x79, 0x9e, + 0x31, 0x41, 0xb9, 0x51, 0x2a, 0x8a, 0x39, 0xe9, 0x91, 0x9b, 0xf8, 0x28, 0x9e, 0x34, 0x85, 0x9c, + 0x5d, 0x16, 0xea, 0xdb, 0x08, 0x91, 0x6b, 0xb9, 0x05, 0x0b, 0xa5, 0x11, 0xc2, 0x4d, 0x74, 0xc8, + 0xdf, 0x4b, 0xf8, 0x4b, 0xaa, 0x6d, 0xee, 0x23, 0x10, 0xc3, 0xb6, 0x82, 0x8e, 0x91, 0x4e, 0xa7, + 0x7a, 0xb7, 0xa6, 0x7a, 0x1c, 0xf9, 0x28, 0x9f, 0x51, 0x36, 0x91, 0xe9, 0x20, 0x79, 0x77, 0xa2, + 0x15, 0x65, 0x13, 0xca, 0x00, 0x5d, 0x84, 0xc6, 0xe1, 0xa9, 0x20, 0x9f, 0x18, 0xe1, 0xb2, 0x63, + 0xfd, 0x53, 0x1d, 0xba, 0x9a, 0x45, 0x54, 0xbf, 0xbe, 0x53, 0xde, 0xd3, 0xce, 0xda, 0x40, 0x6f, + 0x0e, 0x92, 0xac, 0x0b, 0x91, 0xe8, 0xa8, 0x56, 0xee, 0xf5, 0x3b, 0x40, 0x3f, 0xc4, 0x49, 0x83, + 0x57, 0x8c, 0x36, 0xbc, 0x66, 0x1b, 0x08, 0xa0, 0x42, 0xe4, 0x3a, 0x2c, 0x96, 0x58, 0xe7, 0x08, + 0x2e, 0xdc, 0x50, 0xed, 0x79, 0xa9, 0xb4, 0x53, 0x22, 0xb1, 0x17, 0xb0, 0x23, 0xd3, 0xcd, 0x07, + 0x48, 0x8d, 0xb2, 0x94, 0xe7, 0x27, 0xce, 0xc8, 0x12, 0x62, 0x28, 0x69, 0x9d, 0x30, 0x54, 0x4d, + 0xe9, 0xf3, 0x50, 0x49, 0x46, 0x5b, 0x42, 0xf6, 0x9f, 0x87, 0xf9, 0x02, 0x49, 0xf0, 0x9b, 0x24, + 0xa6, 0xb4, 0x40, 0x3a, 0xb2, 0x77, 0xa1, 0xc3, 0x93, 0x60, 0x1c, 0x44, 0x32, 0x09, 0xd2, 0x9a, + 0xf3, 0x11, 0x90, 0x04, 0x94, 0x12, 0xb1, 0xa0, 0x29, 0x0f, 0xd3, 0x9c, 0x44, 0xb6, 0xc2, 0xe0, + 0x66, 0xa6, 0x22, 0x09, 0x3c, 0x81, 0xcb, 0x71, 0x26, 0xdc, 0xd7, 0x97, 0x08, 0x7a, 0x12, 0xbc, + 0xff, 0x3c, 0xa4, 0xc4, 0xdd, 0x2d, 0x58, 0xf0, 0x78, 0x98, 0x4d, 0x22, 0x5a, 0x99, 0x13, 0xb2, + 0x88, 0xac, 0x48, 0xc3, 0xee, 0x49, 0x30, 0xae, 0x6f, 0x9b, 0x45, 0xaa, 0x48, 0xe8, 0x86, 0x21, + 0x2a, 0x24, 0xee, 0xfa, 0x2a, 0x75, 0xdd, 0xd5, 0xc0, 0x6d, 0xee, 0xfa, 0xe6, 0x77, 0xe0, 0x0a, + 0xe2, 0x1c, 0x36, 0x89, 0xc5, 0xa9, 0x13, 0x65, 0x13, 0x96, 0x04, 0x9e, 0xe3, 0xa6, 0xce, 0x2b, + 0x96, 0x70, 0x55, 0x0d, 0xb9, 0x84, 0x14, 0x8f, 0x91, 0x60, 0x57, 0xe2, 0xd7, 0xd3, 0xcf, 0x58, + 0xc2, 0xcd, 0xcf, 0x28, 0x79, 0x37, 0x4f, 0x6e, 0xb5, 0x25, 0xb9, 0x5e, 0xec, 0xd5, 0x39, 0x94, + 0x54, 0x2a, 0x42, 0x84, 0xad, 0x05, 0x96, 0xc6, 0x5b, 0x1e, 0xc0, 0xbe, 0x48, 0x98, 0x3b, 0x21, + 0xc9, 0x7a, 0x1f, 0x5a, 0xe2, 0x30, 0xa4, 0x9a, 0x46, 0x65, 0x6e, 0x4d, 0xa3, 0x29, 0x0e, 0x91, + 0xe7, 0xa5, 0x33, 0x56, 0x25, 0x51, 0x55, 0x3d, 0x94, 0xe0, 0x30, 0x98, 0x04, 0x42, 0xdd, 0x1d, + 0x92, 0x1d, 0xeb, 0x10, 0xda, 0x34, 0x03, 0x7d, 0x23, 0xaf, 0xe2, 0x57, 0x5e, 0x5f, 0xc5, 0xbf, + 0x0b, 0x5d, 0xa5, 0x17, 0xcf, 0xbb, 0x16, 0xd0, 0x91, 0x78, 0x6c, 0xa7, 0xd6, 0x1d, 0x68, 0xff, + 0xb6, 0x1b, 0x66, 0xf2, 0x1b, 0xd7, 0xa0, 0x43, 0x65, 0x32, 0xe7, 0x30, 0xe4, 0xde, 0xb1, 0x2e, + 0xdf, 0x10, 0xe8, 0x11, 0x42, 0x2c, 0x00, 0xe3, 0x59, 0x14, 0xf0, 0x68, 0x3d, 0x0c, 0xad, 0xbf, + 0x6b, 0x42, 0xfb, 0x63, 0x37, 0x3d, 0x22, 0x35, 0x8a, 0x47, 0x98, 0xee, 0x28, 0x50, 0x6a, 0x65, + 0xe2, 0xc6, 0xea, 0x9e, 0x42, 0x07, 0x81, 0x48, 0xb5, 0xe3, 0xc6, 0x33, 0x99, 0x97, 0xea, 0x4c, + 0xe6, 0xe5, 0xba, 0xbc, 0x32, 0x26, 0x0b, 0x75, 0x4c, 0x17, 0xbe, 0x69, 0x82, 0x47, 0x12, 0x64, + 0xde, 0x01, 0x93, 0x48, 0xdc, 0x30, 0xe4, 0xe4, 0xee, 0xa4, 0x2c, 0x4c, 0x55, 0x92, 0x66, 0x80, + 0x98, 0x75, 0x85, 0xd8, 0x67, 0xf2, 0xfc, 0x94, 0x6c, 0x67, 0x63, 0xd6, 0x76, 0xde, 0x06, 0x40, + 0xaf, 0x90, 0x72, 0xb7, 0x33, 0xc1, 0xb1, 0xcc, 0x90, 0x14, 0xd8, 0x2f, 0xe1, 0xa9, 0xbd, 0x0f, + 0x83, 0x9c, 0x22, 0x61, 0x23, 0xc7, 0x8b, 0x84, 0x72, 0xd7, 0x7a, 0x8a, 0xca, 0x66, 0xa3, 0x8d, + 0x48, 0xcc, 0xba, 0x74, 0xed, 0x33, 0x2e, 0xdd, 0x8f, 0xe1, 0xc2, 0x8c, 0x81, 0x4b, 0x63, 0xe6, + 0xa9, 0x52, 0xf8, 0x9b, 0xdc, 0xbe, 0x7a, 0x1b, 0x0c, 0x2a, 0x88, 0xf8, 0x59, 0xac, 0xce, 0x56, + 0x2b, 0x48, 0xc9, 0xf5, 0x3e, 0xcf, 0x6d, 0xec, 0xfe, 0x5f, 0xb9, 0x8d, 0xbd, 0x2f, 0xe7, 0x36, + 0xf6, 0xbf, 0x9c, 0xdb, 0x38, 0xe3, 0x66, 0x2d, 0xcc, 0x46, 0x67, 0xe7, 0xc6, 0x42, 0x83, 0x73, + 0x63, 0xa1, 0x2f, 0x08, 0x64, 0x16, 0x5f, 0x1b, 0xc8, 0x7c, 0x89, 0x48, 0xca, 0xfc, 0x82, 0x48, + 0xca, 0x7a, 0x06, 0x40, 0x36, 0x92, 0x96, 0x7c, 0xde, 0x9e, 0x57, 0xde, 0x74, 0xcf, 0xad, 0xff, + 0xae, 0x00, 0xec, 0xbb, 0x93, 0x58, 0xba, 0x32, 0xe6, 0x8f, 0xa0, 0x93, 0x52, 0xaf, 0x9c, 0xc8, + 0xba, 0x56, 0xba, 0x60, 0x9a, 0x93, 0xaa, 0x26, 0x25, 0xb5, 0x20, 0xcd, 0xdb, 0x24, 0xae, 0x72, + 0x86, 0xbc, 0x0e, 0xd8, 0xd0, 0x04, 0x64, 0x7c, 0x6f, 0x42, 0x5f, 0x11, 0xc4, 0x2c, 0xf1, 0x58, + 0x24, 0x75, 0x58, 0xc5, 0xee, 0x49, 0xe8, 0x9e, 0x04, 0x9a, 0x0f, 0x72, 0x32, 0x69, 0x05, 0xd2, + 0x39, 0xd1, 0x98, 0x1a, 0xb2, 0x21, 0x09, 0xac, 0x35, 0xfd, 0x53, 0x68, 0x21, 0x06, 0xd4, 0xf1, + 0x7b, 0x83, 0xb7, 0xcc, 0x0e, 0xb4, 0xd4, 0xac, 0x83, 0x8a, 0xd9, 0x83, 0x36, 0xdd, 0x5c, 0x23, + 0x5c, 0xd5, 0xfa, 0xfb, 0x45, 0xe8, 0x6c, 0x45, 0xa9, 0x48, 0x32, 0x29, 0x9a, 0xc5, 0x05, 0xad, + 0x06, 0x5d, 0xd0, 0x52, 0x25, 0x65, 0xf9, 0x33, 0xa8, 0xa4, 0x7c, 0x17, 0x5a, 0xea, 0x2a, 0xa0, + 0xf2, 0x6f, 0xe7, 0xde, 0x23, 0xd4, 0x34, 0xe6, 0x2a, 0x18, 0xbe, 0xba, 0xa3, 0xa8, 0xb2, 0x75, + 0xa5, 0x8b, 0x83, 0xfa, 0xf6, 0xa2, 0x9d, 0xd3, 0x98, 0xd7, 0xa1, 0xe6, 0x8e, 0xc7, 0xa4, 0x7d, + 0xa8, 0xce, 0xa4, 0x49, 0xc9, 0x98, 0xd8, 0x88, 0x33, 0xef, 0x41, 0x9b, 0xd4, 0x22, 0x25, 0xac, + 0x9b, 0xb3, 0x73, 0xea, 0x6c, 0xb8, 0xd4, 0x94, 0xe4, 0x1a, 0xdf, 0x83, 0x76, 0xc8, 0x79, 0x2c, + 0x07, 0xb4, 0x66, 0x07, 0xe8, 0x1c, 0xa6, 0x6d, 0x84, 0x3a, 0x9b, 0x79, 0x0b, 0x9a, 0xe8, 0xa6, + 0xf0, 0x58, 0x99, 0xf7, 0xd2, 0x3a, 0x28, 0x97, 0x67, 0x37, 0x52, 0xfc, 0x63, 0xae, 0x01, 0x48, + 0xb9, 0xa6, 0x99, 0xdb, 0xb3, 0xec, 0xc8, 0xc3, 0x76, 0x3c, 0x7c, 0x3a, 0x82, 0x7f, 0x04, 0x03, + 0x19, 0xa2, 0x95, 0x46, 0x82, 0x2e, 0xa1, 0xea, 0x91, 0xd3, 0x51, 0xbf, 0xdd, 0x4f, 0xa6, 0xb3, + 0x00, 0x1f, 0x40, 0x2b, 0x96, 0x31, 0x0a, 0x69, 0x8e, 0xce, 0xda, 0x62, 0x31, 0x54, 0x05, 0x2f, + 0xb6, 0xa6, 0x30, 0x7f, 0x00, 0x7d, 0x59, 0xea, 0x1b, 0x29, 0x67, 0x9d, 0xf2, 0xc3, 0x53, 0x57, + 0xd0, 0xa6, 0x7c, 0x79, 0xbb, 0x27, 0xa6, 0x5c, 0xfb, 0xef, 0x42, 0x8f, 0x29, 0xb7, 0xd0, 0x49, + 0x3d, 0x37, 0x22, 0x7d, 0xd2, 0x59, 0xbb, 0x54, 0x0c, 0x2f, 0x7b, 0x8d, 0x76, 0x97, 0x95, 0x7d, + 0xc8, 0x15, 0x68, 0xaa, 0xf2, 0xf3, 0x80, 0x46, 0x95, 0x2e, 0x62, 0xcb, 0x5a, 0x86, 0xad, 0xf0, + 0xc8, 0x97, 0x29, 0x15, 0x7b, 0xcc, 0x4e, 0x49, 0xad, 0x4c, 0xf1, 0x65, 0xba, 0x74, 0x34, 0x55, + 0x7f, 0xfa, 0x84, 0x9d, 0xe2, 0x7e, 0x14, 0xd5, 0xb9, 0xa1, 0x39, 0xbb, 0x1f, 0x79, 0x69, 0xce, + 0x6e, 0xe7, 0x55, 0x39, 0xf3, 0xf1, 0x74, 0xb5, 0x50, 0x16, 0x5c, 0x2e, 0xd0, 0xd0, 0xb7, 0xe7, + 0x0c, 0x95, 0x75, 0x17, 0x7b, 0x21, 0x9e, 0x29, 0x3a, 0xde, 0x01, 0x83, 0x27, 0x3e, 0x5d, 0x57, + 0xa0, 0xb4, 0x10, 0xed, 0x09, 0x15, 0x49, 0xe5, 0x1d, 0x4a, 0x52, 0x40, 0x2d, 0x2e, 0x3b, 0xe8, + 0x74, 0xc4, 0x09, 0xff, 0x9c, 0x79, 0x42, 0xaa, 0xbf, 0xa5, 0xb3, 0x4e, 0x87, 0xc2, 0x93, 0x77, + 0x7a, 0x03, 0x5a, 0xba, 0x30, 0x7f, 0xe9, 0x0c, 0xa5, 0x46, 0x99, 0x1f, 0xc2, 0xc2, 0xb4, 0x52, + 0x4c, 0x87, 0x97, 0xcf, 0x50, 0xf7, 0xa7, 0x74, 0x20, 0x5a, 0x6a, 0xe5, 0x49, 0x0d, 0xcf, 0x16, + 0xc4, 0x08, 0x81, 0xbe, 0xae, 0xf2, 0xc1, 0xde, 0x3e, 0xeb, 0xeb, 0x2a, 0x7f, 0x6c, 0x08, 0xad, + 0x20, 0x7d, 0x12, 0x24, 0xa9, 0x18, 0x5e, 0xd1, 0x96, 0x93, 0xba, 0xe8, 0xc1, 0x05, 0x29, 0x9a, + 0x90, 0xe1, 0x3b, 0xfa, 0xd6, 0x2d, 0x19, 0x94, 0xdb, 0xd0, 0x54, 0x97, 0x16, 0x96, 0xcf, 0x68, + 0x05, 0x75, 0xd1, 0xc7, 0x56, 0x14, 0xe6, 0xd7, 0xa1, 0x45, 0x15, 0x6b, 0x1e, 0x0f, 0xaf, 0xcf, + 0x4a, 0x91, 0x2c, 0x1b, 0xdb, 0xcd, 0x50, 0x96, 0x8f, 0x3f, 0x80, 0x96, 0x76, 0x60, 0xac, 0xd9, + 0x93, 0xa1, 0x1c, 0x19, 0x5b, 0x53, 0x98, 0x37, 0xa1, 0x31, 0x41, 0x5d, 0x38, 0xfc, 0xda, 0xec, + 0x29, 0x97, 0x2a, 0x52, 0x62, 0xcd, 0x87, 0xd0, 0x49, 0xc9, 0x77, 0x95, 0xe2, 0x7f, 0x43, 0x57, + 0x7b, 0x8b, 0x57, 0x07, 0xda, 0xb1, 0xb5, 0x21, 0x2d, 0x9c, 0xdc, 0xdf, 0x85, 0x2b, 0xe5, 0x52, + 0xb1, 0xae, 0x23, 0xab, 0xd8, 0xef, 0x26, 0xcd, 0x72, 0x7d, 0x8e, 0x84, 0x4d, 0x57, 0x9c, 0xed, + 0xcb, 0xf1, 0x39, 0xa5, 0xe8, 0x87, 0xb9, 0xa5, 0xc1, 0x83, 0x3d, 0xbc, 0x75, 0x66, 0x59, 0xb9, + 0xad, 0xd2, 0xf6, 0x87, 0x4c, 0xdc, 0x47, 0xd0, 0x1d, 0x65, 0xaf, 0x5e, 0x9d, 0x2a, 0x19, 0x19, + 0xbe, 0x4f, 0xe3, 0x4a, 0x51, 0x58, 0xa9, 0xf0, 0x69, 0x77, 0x46, 0xa5, 0x2a, 0xe8, 0x65, 0x68, + 0x79, 0x91, 0xe3, 0xfa, 0x7e, 0x32, 0x5c, 0x91, 0x85, 0x4f, 0x2f, 0x5a, 0xf7, 0x7d, 0xaa, 0x20, + 0xf3, 0x98, 0xd1, 0x45, 0x60, 0x27, 0xf0, 0x87, 0x5f, 0x97, 0x36, 0x4f, 0x83, 0xb6, 0x7c, 0x7a, + 0x90, 0xa0, 0x43, 0x97, 0xc0, 0x1f, 0xde, 0x56, 0x0f, 0x12, 0x14, 0x68, 0xcb, 0x47, 0x5f, 0x76, + 0xe2, 0x9e, 0x38, 0x1a, 0x32, 0xfc, 0x40, 0xc6, 0xb3, 0x13, 0xf7, 0x64, 0x4f, 0x81, 0xf0, 0x6c, + 0xcb, 0xbb, 0x69, 0xa4, 0x31, 0xef, 0xcc, 0x9e, 0xed, 0x3c, 0x11, 0x62, 0xb7, 0x83, 0x3c, 0x27, + 0x42, 0xfa, 0x80, 0xb4, 0xa0, 0x13, 0xae, 0x0d, 0xef, 0x9e, 0xd5, 0x07, 0x2a, 0xcf, 0x83, 0xfa, + 0x40, 0xa7, 0x7c, 0xd6, 0x00, 0xa4, 0xba, 0xa4, 0xcd, 0x5e, 0x9d, 0x1d, 0x93, 0x07, 0x18, 0xb6, + 0xbc, 0x98, 0x45, 0x5b, 0xbd, 0x06, 0x40, 0xc5, 0x63, 0x39, 0xe6, 0xde, 0xec, 0x98, 0x3c, 0x60, + 0xb0, 0xdb, 0x2f, 0xf2, 0xd8, 0xe1, 0x1e, 0xb4, 0x33, 0x0c, 0x0d, 0xd0, 0x39, 0x1f, 0xde, 0x9f, + 0x3d, 0x03, 0x3a, 0x6a, 0xb0, 0x8d, 0x4c, 0xb5, 0xf0, 0x23, 0x64, 0xf6, 0xc8, 0x03, 0x1a, 0x3e, + 0x98, 0xfd, 0x48, 0x1e, 0x5a, 0xd8, 0x64, 0x1d, 0x65, 0x94, 0xf1, 0x10, 0x3a, 0x92, 0x69, 0x72, + 0xd0, 0xda, 0xac, 0x8c, 0x14, 0x2e, 0x95, 0x2d, 0xb9, 0x2b, 0x87, 0xdd, 0x84, 0x86, 0x1b, 0xc7, + 0xe1, 0xe9, 0xf0, 0xc3, 0xd9, 0x83, 0xb1, 0x8e, 0x60, 0x5b, 0x62, 0x51, 0x94, 0x26, 0x59, 0x28, + 0x02, 0x7d, 0x97, 0xea, 0x1b, 0xb3, 0xa2, 0x54, 0xba, 0x6a, 0x6a, 0x77, 0x26, 0xa5, 0x7b, 0xa7, + 0x77, 0xc0, 0x88, 0x79, 0x2a, 0x1c, 0x7f, 0x12, 0x0e, 0x1f, 0x9e, 0xb1, 0x60, 0xf2, 0x0e, 0x91, + 0xdd, 0x8a, 0xd5, 0x25, 0xac, 0xa9, 0x9b, 0xc4, 0xdf, 0x9c, 0xb9, 0x49, 0xfc, 0x10, 0xba, 0xeb, + 0xf4, 0xd0, 0x26, 0x48, 0x49, 0x57, 0xde, 0x84, 0x7a, 0x9e, 0xae, 0xcb, 0x95, 0x30, 0x51, 0xbc, + 0x62, 0x5b, 0xd1, 0x88, 0xdb, 0x84, 0xb6, 0xfe, 0xb2, 0x0e, 0xcd, 0x7d, 0x9e, 0x25, 0x1e, 0xfb, + 0xe2, 0x4b, 0x78, 0xef, 0x69, 0x91, 0x88, 0x8a, 0xa2, 0xbf, 0xdc, 0x7d, 0x42, 0xcf, 0x96, 0x2b, + 0xdb, 0x45, 0x26, 0xf0, 0x22, 0x34, 0x64, 0x68, 0x28, 0x2f, 0x6f, 0xc9, 0x0e, 0x1d, 0x87, 0x2c, + 0x3d, 0xf2, 0xf9, 0xcb, 0x08, 0x8f, 0x43, 0x83, 0xee, 0x3e, 0x81, 0x06, 0x6d, 0xf9, 0x14, 0xea, + 0x6b, 0x02, 0x3a, 0x6f, 0x4d, 0x19, 0x1f, 0x68, 0x20, 0x9d, 0x3a, 0x9d, 0x65, 0x6c, 0x9d, 0x93, + 0x65, 0xbc, 0x0a, 0xf5, 0x48, 0x5f, 0x1a, 0xca, 0xf1, 0xf4, 0x4c, 0x83, 0xe0, 0xe6, 0x6d, 0xc8, + 0x6f, 0x0e, 0x2a, 0xd7, 0xe5, 0xfc, 0x9b, 0x85, 0x6b, 0xd0, 0xce, 0x9f, 0x66, 0x29, 0x6f, 0xe5, + 0xe2, 0x6a, 0xf1, 0x58, 0xeb, 0x40, 0xb7, 0xec, 0x82, 0x6c, 0x4e, 0xe2, 0x51, 0xd6, 0x6c, 0x88, + 0x4f, 0x9d, 0x37, 0x49, 0x3c, 0x52, 0x21, 0x47, 0x27, 0x5d, 0x83, 0xd4, 0xf1, 0x78, 0x94, 0x0a, + 0x95, 0xcc, 0x68, 0x05, 0xe9, 0x06, 0x76, 0xcd, 0x6f, 0x43, 0x2f, 0x61, 0xde, 0x0b, 0x67, 0x92, + 0x8e, 0xe5, 0x27, 0x7a, 0xe5, 0xbb, 0xc8, 0x93, 0x74, 0xfc, 0x31, 0x73, 0xd1, 0xf8, 0xca, 0x88, + 0xa9, 0x83, 0xb4, 0x3b, 0xe9, 0x98, 0x66, 0xbd, 0x0e, 0xdd, 0xc3, 0x90, 0xf3, 0x89, 0x56, 0x89, + 0x7d, 0x4a, 0x35, 0x76, 0x08, 0x26, 0x57, 0x60, 0xfd, 0x61, 0x05, 0x0c, 0xe4, 0x1d, 0x4a, 0x90, + 0x69, 0x42, 0x7d, 0xe2, 0xc5, 0x99, 0x72, 0x93, 0xa9, 0xad, 0x1e, 0x79, 0x49, 0xd9, 0x50, 0x8f, + 0xbc, 0x68, 0xe7, 0x6a, 0x32, 0xad, 0x88, 0x6d, 0xf9, 0x20, 0xe4, 0x94, 0x72, 0x37, 0x52, 0x1e, + 0x74, 0xd7, 0x5c, 0x82, 0xa6, 0x17, 0x51, 0x0c, 0x2c, 0x2f, 0x90, 0x35, 0xbc, 0x08, 0x63, 0x5f, + 0x09, 0x2e, 0xae, 0x44, 0x34, 0xbc, 0x68, 0xcb, 0x3f, 0xb1, 0xfe, 0xaa, 0x02, 0x8b, 0x7b, 0x09, + 0xf7, 0x58, 0x9a, 0x6e, 0xa3, 0x89, 0x76, 0xc9, 0x4f, 0x33, 0xa1, 0x4e, 0xb9, 0x37, 0xf9, 0xba, + 0x82, 0xda, 0x28, 0xb9, 0x32, 0x41, 0x91, 0x07, 0x23, 0x35, 0xbb, 0x4d, 0x10, 0x8a, 0x45, 0x72, + 0x34, 0x0d, 0xac, 0x95, 0xd0, 0x94, 0xb5, 0xbb, 0x09, 0xfd, 0xe2, 0x36, 0x57, 0x29, 0x91, 0x58, + 0x5c, 0x56, 0xa7, 0x59, 0xae, 0x41, 0x27, 0x21, 0xde, 0xca, 0x69, 0x64, 0x52, 0x11, 0x24, 0x08, + 0xe7, 0xb1, 0x8e, 0x60, 0xb0, 0x97, 0xb0, 0xd8, 0x4d, 0x18, 0x6a, 0xf3, 0x09, 0xf1, 0xf0, 0x12, + 0x34, 0x43, 0x16, 0x8d, 0xc5, 0x91, 0x5a, 0xaf, 0xea, 0xe5, 0x0f, 0xf0, 0xaa, 0xa5, 0x07, 0x78, + 0xc8, 0xcb, 0x84, 0xb9, 0xea, 0x9d, 0x1e, 0xb5, 0xf1, 0x64, 0x45, 0x59, 0xa8, 0xf2, 0x81, 0x86, + 0x2d, 0x3b, 0xd6, 0xcf, 0x6a, 0xd0, 0x51, 0x9c, 0xa1, 0xaf, 0xc8, 0x5d, 0xa9, 0xe4, 0xbb, 0x32, + 0x80, 0x5a, 0xfa, 0x3c, 0x54, 0xdb, 0x84, 0x4d, 0xf3, 0x43, 0xa8, 0x85, 0xc1, 0x44, 0x85, 0x32, + 0xef, 0x4c, 0xd9, 0x86, 0x69, 0xfe, 0x2a, 0xc1, 0x41, 0x6a, 0x54, 0x48, 0x59, 0x14, 0x9c, 0x38, + 0x28, 0xa2, 0x8a, 0x27, 0xa8, 0xa7, 0x4f, 0xf0, 0x1c, 0x20, 0x53, 0x5d, 0x8f, 0xee, 0x6b, 0xe9, + 0xc3, 0xdd, 0xb3, 0xdb, 0x0a, 0xb2, 0xe5, 0x9b, 0xdf, 0x00, 0x23, 0x8d, 0xdc, 0x38, 0x3d, 0xe2, + 0x22, 0x0f, 0x5e, 0xc4, 0x49, 0xb4, 0xba, 0xb1, 0x7b, 0x70, 0x12, 0xed, 0x2b, 0x8c, 0xfa, 0x58, + 0x4e, 0x69, 0xfe, 0x00, 0xba, 0x29, 0x4b, 0x53, 0x79, 0x43, 0x7b, 0xc4, 0xd5, 0xa1, 0x5f, 0x2a, + 0xc7, 0x25, 0x84, 0xc5, 0x5f, 0xad, 0x45, 0x3c, 0x2d, 0x40, 0xe6, 0xc7, 0xd0, 0xd7, 0xe3, 0x43, + 0x3e, 0x1e, 0xe7, 0x89, 0xcb, 0x77, 0xce, 0xcc, 0xb0, 0x4d, 0xe8, 0xd2, 0x3c, 0xbd, 0xb4, 0x8c, + 0x30, 0x7f, 0x0c, 0xfd, 0x58, 0x6e, 0xa6, 0xa3, 0x32, 0xf3, 0x52, 0x79, 0x5c, 0x99, 0x72, 0x65, + 0xa6, 0x36, 0xbb, 0xb8, 0x75, 0x59, 0xc0, 0x53, 0xeb, 0xbf, 0x2a, 0xd0, 0x29, 0xad, 0x9a, 0x9e, + 0x45, 0xa6, 0x2c, 0xd1, 0x89, 0x78, 0x6c, 0x23, 0xec, 0x88, 0xab, 0xf7, 0x41, 0x6d, 0x9b, 0xda, + 0x08, 0x4b, 0xb8, 0x2a, 0xee, 0xb4, 0x6d, 0x6a, 0xa3, 0xc2, 0x54, 0x01, 0xa7, 0x7c, 0x41, 0x41, + 0x9b, 0x52, 0xb7, 0xbb, 0x05, 0x70, 0xcb, 0xa7, 0xf7, 0x93, 0xae, 0x70, 0x0f, 0xdd, 0x54, 0xd7, + 0x0d, 0xf2, 0x3e, 0x1e, 0xcd, 0x17, 0x2c, 0xc1, 0xb5, 0x28, 0x5d, 0xab, 0xbb, 0xb8, 0xd7, 0xa4, + 0xc3, 0x5e, 0xf1, 0x48, 0x5e, 0x3c, 0xeb, 0xda, 0x06, 0x02, 0x3e, 0xe3, 0x11, 0x0d, 0x53, 0x3b, + 0x4b, 0xfc, 0x6c, 0xdb, 0xba, 0x8b, 0x9a, 0xea, 0x79, 0xc6, 0xd0, 0xdd, 0xf3, 0xe9, 0xe6, 0x51, + 0xdb, 0x6e, 0x51, 0x7f, 0xcb, 0xb7, 0xfe, 0xad, 0x02, 0x8b, 0x67, 0x98, 0x8d, 0xde, 0x15, 0x32, + 0x5a, 0x5f, 0x86, 0xed, 0xda, 0x4d, 0xec, 0x6e, 0xf9, 0x84, 0x10, 0x13, 0x12, 0xa6, 0xaa, 0x42, + 0x88, 0x09, 0x4a, 0xd2, 0x12, 0x34, 0xc5, 0x09, 0xfd, 0x5a, 0x79, 0x30, 0x1a, 0xe2, 0x04, 0x7f, + 0xe6, 0x3a, 0x46, 0xbb, 0x63, 0x27, 0x64, 0x2f, 0x58, 0x48, 0x7c, 0xe8, 0xaf, 0xdd, 0x78, 0xcd, + 0x2e, 0xaf, 0x6e, 0xf3, 0xf1, 0x36, 0xd2, 0x62, 0xfc, 0x2b, 0x5b, 0xd6, 0x4f, 0xc0, 0xd0, 0x50, + 0xb3, 0x0d, 0x8d, 0x4d, 0x76, 0x98, 0x8d, 0x07, 0x6f, 0x99, 0x06, 0xd4, 0x71, 0xc4, 0xa0, 0x82, + 0xad, 0x4f, 0xdd, 0x24, 0x1a, 0x54, 0x11, 0xfd, 0x38, 0x49, 0x78, 0x32, 0xa8, 0x61, 0x73, 0xcf, + 0x8d, 0x02, 0x6f, 0x50, 0xc7, 0xe6, 0x13, 0x57, 0xb8, 0xe1, 0xa0, 0x61, 0xfd, 0xbc, 0x01, 0xc6, + 0x9e, 0xfa, 0xba, 0xb9, 0x09, 0xbd, 0xfc, 0x65, 0xea, 0xfc, 0x0c, 0xcb, 0xde, 0x6c, 0x83, 0x32, + 0x2c, 0xdd, 0xb8, 0xd4, 0x9b, 0x7d, 0xdf, 0x5a, 0x3d, 0xf3, 0xbe, 0xf5, 0x5d, 0xa8, 0x3d, 0x4f, + 0x4e, 0xa7, 0xeb, 0x6f, 0x7b, 0xa1, 0x1b, 0xd9, 0x08, 0x36, 0x1f, 0x40, 0x07, 0xf7, 0xdd, 0x49, + 0xc9, 0xfc, 0xab, 0xac, 0x44, 0xf9, 0x15, 0x31, 0xc1, 0x6d, 0x40, 0x22, 0xe5, 0x22, 0xac, 0x82, + 0xe1, 0x1d, 0x05, 0xa1, 0x9f, 0xb0, 0x48, 0xd5, 0xb6, 0xcd, 0xb3, 0x4b, 0xb6, 0x73, 0x1a, 0xf3, + 0x47, 0x74, 0x79, 0x53, 0x67, 0x55, 0x8a, 0x92, 0xc3, 0xd4, 0x91, 0x2d, 0xe5, 0x5d, 0xec, 0x85, + 0x12, 0x39, 0xd9, 0xa4, 0xe2, 0x95, 0x42, 0xab, 0xfc, 0x4a, 0x41, 0xbe, 0x62, 0x24, 0x13, 0x62, + 0xe4, 0xf1, 0x14, 0x5a, 0x90, 0x5b, 0xca, 0xda, 0xb7, 0x67, 0x3d, 0x49, 0x6d, 0xb5, 0x94, 0xd5, + 0xbf, 0x01, 0x7d, 0xf4, 0x22, 0x1c, 0xe9, 0x7c, 0xa0, 0x2a, 0x01, 0xf5, 0xd6, 0x28, 0x4b, 0x8f, + 0x36, 0xd1, 0xfd, 0x40, 0x61, 0xbc, 0x09, 0x7d, 0xfd, 0x5b, 0xd4, 0xd5, 0xd3, 0x8e, 0x2a, 0x49, + 0x28, 0xa8, 0xbc, 0x79, 0xba, 0x0a, 0x17, 0xbc, 0x23, 0x37, 0x8a, 0x58, 0xe8, 0x1c, 0x66, 0xa3, + 0x91, 0xb6, 0x00, 0x5d, 0x4a, 0xe6, 0x2d, 0x2a, 0xd4, 0x23, 0xc2, 0x90, 0x41, 0xb1, 0xa0, 0x17, + 0x05, 0xa1, 0xcc, 0x58, 0x93, 0xb5, 0xeb, 0x11, 0x65, 0x27, 0x0a, 0x42, 0x4a, 0x59, 0xa3, 0xcd, + 0xfb, 0x21, 0x0c, 0xb2, 0x2c, 0xf0, 0x53, 0x47, 0x70, 0xfd, 0x00, 0x54, 0xe5, 0x3d, 0x4b, 0x19, + 0x87, 0x67, 0x59, 0xe0, 0x1f, 0x70, 0xf5, 0x04, 0xb4, 0x47, 0xf4, 0xba, 0x6b, 0xfd, 0x10, 0xba, + 0x65, 0xd9, 0x41, 0x59, 0xa4, 0x70, 0x6e, 0xf0, 0x96, 0x09, 0xd0, 0xdc, 0xe5, 0xc9, 0xc4, 0x0d, + 0x07, 0x15, 0x6c, 0xcb, 0xb7, 0x3b, 0x83, 0xaa, 0xd9, 0x05, 0x43, 0xc7, 0x19, 0x83, 0x9a, 0xf5, + 0x5d, 0x30, 0xf4, 0x8b, 0x56, 0x7a, 0x4a, 0xc8, 0x7d, 0x26, 0xbd, 0x30, 0xa9, 0x99, 0x0c, 0x04, + 0x90, 0x07, 0xa6, 0x1f, 0x72, 0x57, 0x8b, 0x87, 0xdc, 0xd6, 0x6f, 0x41, 0xb7, 0xbc, 0x38, 0x9d, + 0x40, 0xab, 0x14, 0x09, 0xb4, 0x39, 0xa3, 0xa8, 0x36, 0x95, 0xf0, 0x89, 0x53, 0x72, 0x19, 0x0c, + 0x04, 0xe0, 0x67, 0xac, 0x3f, 0xa8, 0x40, 0x83, 0xfc, 0x6e, 0x32, 0x2d, 0xd8, 0x28, 0xce, 0x4e, + 0xc3, 0x6e, 0x13, 0xe4, 0x7f, 0x71, 0xa5, 0x2e, 0x2f, 0x94, 0xd4, 0x5f, 0x5b, 0x28, 0xb9, 0xfd, + 0x1c, 0x9a, 0xf2, 0xed, 0xbc, 0xb9, 0x08, 0xbd, 0x67, 0xd1, 0x71, 0xc4, 0x5f, 0x46, 0x12, 0x30, + 0x78, 0xcb, 0xbc, 0x00, 0x0b, 0x9a, 0xe9, 0xea, 0x91, 0xfe, 0xa0, 0x62, 0x0e, 0xa0, 0x4b, 0xdb, + 0xaa, 0x21, 0x55, 0xf3, 0x5d, 0x18, 0x2a, 0xe3, 0xb0, 0xc9, 0x23, 0xb6, 0xcb, 0x45, 0x30, 0x3a, + 0xd5, 0xd8, 0x9a, 0xb9, 0x00, 0x9d, 0x7d, 0xc1, 0xe3, 0x7d, 0x16, 0xf9, 0x41, 0x34, 0x1e, 0xd4, + 0x6f, 0x3f, 0x81, 0xa6, 0x7c, 0xd2, 0x5f, 0xfa, 0xa4, 0x04, 0x0c, 0xde, 0x42, 0xea, 0x4f, 0xdd, + 0x40, 0x04, 0xd1, 0x78, 0x97, 0x9d, 0x08, 0xa9, 0x94, 0xb6, 0xdd, 0x54, 0x0c, 0xaa, 0x66, 0x1f, + 0x40, 0xcd, 0xfa, 0x38, 0xf2, 0x07, 0xb5, 0x47, 0x1b, 0xbf, 0xf8, 0xe5, 0xd5, 0xca, 0x3f, 0xfc, + 0xf2, 0x6a, 0xe5, 0x5f, 0x7e, 0x79, 0xf5, 0xad, 0x9f, 0xfe, 0xeb, 0xd5, 0xca, 0x67, 0x0f, 0x4a, + 0xff, 0xb0, 0x60, 0xe2, 0x8a, 0x24, 0x38, 0x91, 0xd5, 0x3d, 0xdd, 0x89, 0xd8, 0xbd, 0xf8, 0x78, + 0x7c, 0x2f, 0x3e, 0xbc, 0xa7, 0x65, 0xee, 0xb0, 0x49, 0xff, 0x87, 0xe0, 0xc3, 0xff, 0x09, 0x00, + 0x00, 0xff, 0xff, 0x7e, 0x8f, 0x86, 0x29, 0x06, 0x41, 0x00, 0x00, } func (m *Message) Marshal() (dAtA []byte, err error) { @@ -8773,6 +8868,58 @@ func (m *FileOffset) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ParquetRowGroupShard) Marshal() (dAtA []byte, err error) { + size := m.ProtoSize() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ParquetRowGroupShard) MarshalTo(dAtA []byte) (int, error) { + size := m.ProtoSize() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ParquetRowGroupShard) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Bytes != 0 { + i = encodeVarintPipeline(dAtA, i, uint64(m.Bytes)) + i-- + dAtA[i] = 0x28 + } + if m.NumRows != 0 { + i = encodeVarintPipeline(dAtA, i, uint64(m.NumRows)) + i-- + dAtA[i] = 0x20 + } + if m.RowGroupEnd != 0 { + i = encodeVarintPipeline(dAtA, i, uint64(m.RowGroupEnd)) + i-- + dAtA[i] = 0x18 + } + if m.RowGroupStart != 0 { + i = encodeVarintPipeline(dAtA, i, uint64(m.RowGroupStart)) + i-- + dAtA[i] = 0x10 + } + if m.FileIndex != 0 { + i = encodeVarintPipeline(dAtA, i, uint64(m.FileIndex)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *ExternalScan) Marshal() (dAtA []byte, err error) { size := m.ProtoSize() dAtA = make([]byte, size) @@ -8797,6 +8944,20 @@ func (m *ExternalScan) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.ParquetRowGroupShards) > 0 { + for iNdEx := len(m.ParquetRowGroupShards) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ParquetRowGroupShards[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintPipeline(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a + } + } if m.LoadEmptyNumericAsZero { i-- if m.LoadEmptyNumericAsZero { @@ -12320,6 +12481,33 @@ func (m *FileOffset) ProtoSize() (n int) { return n } +func (m *ParquetRowGroupShard) ProtoSize() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.FileIndex != 0 { + n += 1 + sovPipeline(uint64(m.FileIndex)) + } + if m.RowGroupStart != 0 { + n += 1 + sovPipeline(uint64(m.RowGroupStart)) + } + if m.RowGroupEnd != 0 { + n += 1 + sovPipeline(uint64(m.RowGroupEnd)) + } + if m.NumRows != 0 { + n += 1 + sovPipeline(uint64(m.NumRows)) + } + if m.Bytes != 0 { + n += 1 + sovPipeline(uint64(m.Bytes)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *ExternalScan) ProtoSize() (n int) { if m == nil { return 0 @@ -12383,6 +12571,12 @@ func (m *ExternalScan) ProtoSize() (n int) { if m.LoadEmptyNumericAsZero { n += 2 } + if len(m.ParquetRowGroupShards) > 0 { + for _, e := range m.ParquetRowGroupShards { + l = e.ProtoSize() + n += 1 + l + sovPipeline(uint64(l)) + } + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -22048,6 +22242,152 @@ func (m *FileOffset) Unmarshal(dAtA []byte) error { } return nil } +func (m *ParquetRowGroupShard) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: parquet_row_group_shard: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: parquet_row_group_shard: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FileIndex", wireType) + } + m.FileIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FileIndex |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RowGroupStart", wireType) + } + m.RowGroupStart = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RowGroupStart |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RowGroupEnd", wireType) + } + m.RowGroupEnd = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RowGroupEnd |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NumRows", wireType) + } + m.NumRows = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NumRows |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Bytes", wireType) + } + m.Bytes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Bytes |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipPipeline(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPipeline + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ExternalScan) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -22468,6 +22808,40 @@ func (m *ExternalScan) Unmarshal(dAtA []byte) error { } } m.LoadEmptyNumericAsZero = bool(v != 0) + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ParquetRowGroupShards", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPipeline + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthPipeline + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ParquetRowGroupShards = append(m.ParquetRowGroupShards, &ParquetRowGroupShard{}) + if err := m.ParquetRowGroupShards[len(m.ParquetRowGroupShards)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPipeline(dAtA[iNdEx:]) diff --git a/pkg/sql/colexec/external/external.go b/pkg/sql/colexec/external/external.go index 232692136a4f0..538f2238c06ef 100644 --- a/pkg/sql/colexec/external/external.go +++ b/pkg/sql/colexec/external/external.go @@ -191,14 +191,15 @@ func (external *External) Call(proc *process.Process) (vm.CallResult, error) { t1 := time.Now() analyzer := external.OpAnalyzer + param := external.Es defer func() { analyzer.AddScanTime(t1) + param.flushParquetProfile(analyzer) span.End() v2.TxnStatementExternalScanDurationHistogram.Observe(time.Since(t).Seconds()) }() result := vm.NewCallResult() - param := external.Es if param.Fileparam.End { result.Status = vm.ExecStop return result, nil @@ -262,6 +263,7 @@ func (external *External) Call(proc *process.Process) (vm.CallResult, error) { result.Batch = external.ctr.buf if external.ctr.buf != nil { external.ctr.maxAllocSize = max(external.ctr.maxAllocSize, external.ctr.buf.Size()) + param.addParquetProfile(process.ParquetProfileStats{PeakBatchBytes: int64(external.ctr.buf.Size())}) result.Batch.ShuffleIDX = int32(param.Idx) } diff --git a/pkg/sql/colexec/external/parquet.go b/pkg/sql/colexec/external/parquet.go index 89d469b4a0f28..99bb50f7adfe4 100644 --- a/pkg/sql/colexec/external/parquet.go +++ b/pkg/sql/colexec/external/parquet.go @@ -56,6 +56,9 @@ func newParquetHandler(param *ExternalParam) (*ParquetHandler, error) { if err != nil { return nil, err } + if err := h.initRowGroupSelection(param); err != nil { + return nil, err + } // Empty file handling (0 rows): only check column count, skip column name and type checks. if h.file.NumRows() == 0 { @@ -77,6 +80,9 @@ func newParquetHandler(param *ExternalParam) (*ParquetHandler, error) { // Caller treats (nil, nil) as "empty file, advance to next". return nil, nil } + if h.rowGroupRows == 0 { + return nil, nil + } err = h.prepare(param) if err != nil { @@ -86,6 +92,39 @@ func newParquetHandler(param *ExternalParam) (*ParquetHandler, error) { return &h, nil } +func (h *ParquetHandler) initRowGroupSelection(param *ExternalParam) error { + all := h.file.RowGroups() + if len(param.ParquetRowGroupShards) == 0 { + h.rowGroups = all + } else { + currentFileIndex := int32(0) + if param.Fileparam != nil && param.Fileparam.FileIndex > 0 { + currentFileIndex = int32(param.Fileparam.FileIndex - 1) + } + for _, shard := range param.ParquetRowGroupShards { + if shard.FileIndex != currentFileIndex { + continue + } + start := int(shard.RowGroupStart) + end := int(shard.RowGroupEnd) + if start < 0 || end <= start || end > len(all) { + return moerr.NewInvalidInputf(param.Ctx, + "invalid parquet row group shard [%d,%d) for file index %d with %d row groups", + start, end, currentFileIndex, len(all)) + } + h.rowGroups = append(h.rowGroups, all[start:end]...) + } + } + if len(h.rowGroups) == 0 { + h.rowGroup = parquet.MultiRowGroup() + h.rowGroupRows = 0 + return nil + } + h.rowGroup = parquet.MultiRowGroup(h.rowGroups...) + h.rowGroupRows = h.rowGroup.NumRows() + return nil +} + func hasPhysicalParquetAttrs(param *ExternalParam) bool { for _, attr := range param.Attrs { colIdx := int(attr.ColIndex) @@ -114,6 +153,7 @@ func (h *ParquetHandler) openFile(param *ExternalParam, prefetchS3 bool) error { data := util.UnsafeStringToBytes(param.Extern.Data) r = bytes.NewReader(data) fileSize = int64(len(data)) + param.addParquetProfile(process.ParquetProfileStats{BytesRead: fileSize}) case param.Extern.Local: return moerr.NewNYI(param.Ctx, "load parquet local") default: @@ -128,7 +168,7 @@ func (h *ParquetHandler) openFile(param *ExternalParam, prefetchS3 bool) error { } fileSize = param.FileSize[param.Fileparam.FileIndex-1] - if shouldPrefetchS3Parquet(param.Extern.ScanType, prefetchS3, fileSize) { + if shouldPrefetchS3Parquet(param.Extern.ScanType, prefetchS3, fileSize, len(param.ParquetRowGroupShards) > 0) { data := make([]byte, int(fileSize)) vec := fileservice.IOVector{ FilePath: readPath, @@ -143,12 +183,17 @@ func (h *ParquetHandler) openFile(param *ExternalParam, prefetchS3 bool) error { if err := fs.Read(param.Ctx, &vec); err != nil { return err } + param.addParquetProfile(process.ParquetProfileStats{ + BytesRead: fileSize, + PrefetchBytes: fileSize, + }) r = bytes.NewReader(data) } else { r = &fsReaderAt{ fs: fs, readPath: readPath, ctx: param.Ctx, + param: param, } } } @@ -157,30 +202,37 @@ func (h *ParquetHandler) openFile(param *ExternalParam, prefetchS3 bool) error { return moerr.ConvertGoError(param.Ctx, err) } -func shouldPrefetchS3Parquet(scanType int, prefetchS3 bool, fileSize int64) bool { - return scanType == tree.S3 && prefetchS3 && fileSize >= 0 && fileSize <= maxParquetS3PrefetchSize +func shouldPrefetchS3Parquet(scanType int, prefetchS3 bool, fileSize int64, hasRowGroupShards bool) bool { + return scanType == tree.S3 && + prefetchS3 && + !hasRowGroupShards && + fileSize >= 0 && + fileSize <= maxParquetS3PrefetchSize } -// findColumnIgnoreCase finds a column in the Parquet schema with case-insensitive matching. -// It first tries exact match for performance, then falls back to case-insensitive match. -// Returns error if multiple columns match case-insensitively (ambiguous), even if one is an exact match. -func (h *ParquetHandler) findColumnIgnoreCase(ctx context.Context, name string) (*parquet.Column, error) { - root := h.file.Root() - nameLower := strings.ToLower(name) - - // Single pass: find all columns that match case-insensitively - var exactMatch *parquet.Column - var caseInsensitiveMatches []*parquet.Column +type parquetColumnLookup struct { + exact map[string]*parquet.Column + folded map[string][]*parquet.Column +} +func newParquetColumnLookup(root *parquet.Column) parquetColumnLookup { + lookup := parquetColumnLookup{ + exact: make(map[string]*parquet.Column), + folded: make(map[string][]*parquet.Column), + } for _, col := range root.Columns() { - if col.Name() == name { - exactMatch = col - caseInsensitiveMatches = append(caseInsensitiveMatches, col) - } else if strings.ToLower(col.Name()) == nameLower { - caseInsensitiveMatches = append(caseInsensitiveMatches, col) - } + lookup.exact[col.Name()] = col + nameLower := strings.ToLower(col.Name()) + lookup.folded[nameLower] = append(lookup.folded[nameLower], col) } + return lookup +} +// find finds a column in the Parquet schema with case-insensitive matching. +// It returns an ambiguity error if multiple columns match case-insensitively, +// even when one of them is an exact match. +func (lookup parquetColumnLookup) find(ctx context.Context, name string) (*parquet.Column, error) { + caseInsensitiveMatches := lookup.folded[strings.ToLower(name)] // Check for ambiguity: multiple columns match case-insensitively if len(caseInsensitiveMatches) > 1 { return nil, moerr.NewInvalidInputf(ctx, @@ -189,7 +241,7 @@ func (h *ParquetHandler) findColumnIgnoreCase(ctx context.Context, name string) } // Return exact match if found, otherwise the single case-insensitive match - if exactMatch != nil { + if exactMatch := lookup.exact[name]; exactMatch != nil { return exactMatch, nil } if len(caseInsensitiveMatches) == 1 { @@ -199,12 +251,33 @@ func (h *ParquetHandler) findColumnIgnoreCase(ctx context.Context, name string) return nil, nil } +// findColumnIgnoreCase is kept for direct unit tests; prepare() builds the +// lookup once and uses it for all target columns. +func (h *ParquetHandler) findColumnIgnoreCase(ctx context.Context, name string) (*parquet.Column, error) { + return newParquetColumnLookup(h.file.Root()).find(ctx, name) +} + func (h *ParquetHandler) prepare(param *ExternalParam) error { + if h.rowGroup == nil && h.file != nil { + if len(h.rowGroups) == 0 { + h.rowGroups = h.file.RowGroups() + } + if len(h.rowGroups) > 0 { + h.rowGroup = parquet.MultiRowGroup(h.rowGroups...) + h.rowGroupRows = h.rowGroup.NumRows() + } + } + h.cols = make([]*parquet.Column, len(param.Cols)) h.mappers = make([]*columnMapper, len(param.Cols)) h.pages = make([]parquet.Pages, len(param.Cols)) h.currentPage = make([]parquet.Page, len(param.Cols)) h.pageOffset = make([]int64, len(param.Cols)) + columnLookup := newParquetColumnLookup(h.file.Root()) + var rowGroupChunks []parquet.ColumnChunk + if h.rowGroup != nil { + rowGroupChunks = h.rowGroup.ColumnChunks() + } for _, attr := range param.Attrs { colIdx := int(attr.ColIndex) if colIdx < 0 || colIdx >= len(param.Cols) { @@ -228,7 +301,7 @@ func (h *ParquetHandler) prepare(param *ExternalParam) error { h.hasPhysicalCol = true // Use case-insensitive column lookup (fix for issue #15621) - col, err := h.findColumnIgnoreCase(param.Ctx, attr.ColName) + col, err := columnLookup.find(param.Ctx, attr.ColName) if err != nil { return err } @@ -272,7 +345,12 @@ func (h *ParquetHandler) prepare(param *ExternalParam) error { h.cols[colIdx] = physicalCol h.mappers[colIdx] = fn if physicalCol.Leaf() { - h.pages[colIdx] = physicalCol.Pages() + leafIdx := int(physicalCol.Index()) + if leafIdx < 0 || leafIdx >= len(rowGroupChunks) { + return moerr.NewInvalidInputf(param.Ctx, + "invalid parquet leaf column index %d for column %s", leafIdx, attr.ColName) + } + h.pages[colIdx] = rowGroupChunks[leafIdx].Pages() } } @@ -282,7 +360,7 @@ func (h *ParquetHandler) prepare(param *ExternalParam) error { // init row reader if has nested columns if h.hasNestedCols { - h.rowReader = parquet.NewReader(h.file) + h.rowReader = h.rowGroup.Rows() } return nil @@ -2493,6 +2571,10 @@ func (h *ParquetHandler) getData(bat *batch.Batch, param *ExternalParam, proc *p return h.getDataByPage(bat, param, proc) } +func (h *ParquetHandler) isFinished() bool { + return h == nil || h.offset >= h.rowGroupRows +} + func (h *ParquetHandler) closePages(ctx context.Context) error { var firstErr error for i, pages := range h.pages { @@ -2523,12 +2605,11 @@ func (h *ParquetHandler) getDataRowCountOnly(bat *batch.Batch) error { rowCount = min(h.rowCountRemaining, batchLimit) h.rowCountRemaining -= rowCount } else { - rgs := h.file.RowGroups() - if h.currentRowGroup >= len(rgs) { + if h.currentRowGroup >= len(h.rowGroups) { bat.SetRowCount(0) return nil } - total := int(rgs[h.currentRowGroup].NumRows()) + total := int(h.rowGroups[h.currentRowGroup].NumRows()) h.currentRowGroup++ rowCount = min(total, batchLimit) h.rowCountRemaining = total - rowCount @@ -2564,7 +2645,11 @@ func (h *ParquetHandler) getDataByPage(bat *batch.Batch, param *ExternalParam, p page := h.currentPage[colIdx] if page == nil { var err error + readStart := time.Now() page, err = pages.ReadPage() + param.addParquetProfile(process.ParquetProfileStats{ + ReadPageTime: time.Since(readStart).Nanoseconds(), + }) switch { case errors.Is(err, io.EOF): finish = true @@ -2607,7 +2692,11 @@ func (h *ParquetHandler) getDataByPage(bat *batch.Batch, param *ExternalParam, p h.pageOffset[colIdx] = 0 } + mapStart := time.Now() err := h.mappers[colIdx].mapping(slicedPage, proc, vec) + param.addParquetProfile(process.ParquetProfileStats{ + MapTime: time.Since(mapStart).Nanoseconds(), + }) if err != nil { return h.closePagesOnError(param.Ctx, err) } @@ -2618,7 +2707,7 @@ func (h *ParquetHandler) getDataByPage(bat *batch.Batch, param *ExternalParam, p bat.SetRowCount(length) h.offset += int64(length) - if h.file != nil && h.offset >= h.file.NumRows() { + if h.isFinished() { finish = true } @@ -2640,6 +2729,7 @@ type fsReaderAt struct { fs fileservice.ETLFileService readPath string ctx context.Context + param *ExternalParam } func (r *fsReaderAt) ReadAt(p []byte, off int64) (n int, err error) { @@ -2659,7 +2749,11 @@ func (r *fsReaderAt) ReadAt(p []byte, off int64) (n int, err error) { if err != nil { return 0, err } - return int(vec.Entries[0].Size), nil + n = int(vec.Entries[0].Size) + if n > 0 { + r.param.addParquetProfile(process.ParquetProfileStats{BytesRead: int64(n)}) + } + return n, nil } // parseStringToDecimal64 converts a string to DECIMAL64 with given precision and scale. diff --git a/pkg/sql/colexec/external/parquet_nested.go b/pkg/sql/colexec/external/parquet_nested.go index df4d5b918d030..4deb37c3bd92f 100644 --- a/pkg/sql/colexec/external/parquet_nested.go +++ b/pkg/sql/colexec/external/parquet_nested.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "io" + "time" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/container/batch" @@ -26,6 +27,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/sql/plan" + "github.com/matrixorigin/matrixone/pkg/util/trace" "github.com/matrixorigin/matrixone/pkg/vm/process" "github.com/parquet-go/parquet-go" ) @@ -52,6 +54,16 @@ func (h *ParquetHandler) getNestedMapper(col *parquet.Column, dt plan.Type) *col // getDataByRow reads data row by row (used when has nested columns) func (h *ParquetHandler) getDataByRow(bat *batch.Batch, param *ExternalParam, proc *process.Process) error { + _, span := trace.Start(proc.Ctx, "ParquetHandler.getDataByRow") + defer span.End() + + rowModeStart := time.Now() + defer func() { + param.addParquetProfile(process.ParquetProfileStats{ + RowModeTime: time.Since(rowModeStart).Nanoseconds(), + }) + }() + if h.offset > 0 { if err := h.rowReader.SeekToRow(h.offset); err != nil { return moerr.ConvertGoError(param.Ctx, err) @@ -74,7 +86,7 @@ func (h *ParquetHandler) getDataByRow(bat *batch.Batch, param *ExternalParam, pr bat.SetRowCount(n) h.offset += int64(n) - finish := n == 0 || h.offset >= h.file.NumRows() + finish := n == 0 || h.isFinished() if finish { h.cleanup() // File completion (FileFin/End) is now handled by Call's finishCurrentFile diff --git a/pkg/sql/colexec/external/parquet_test.go b/pkg/sql/colexec/external/parquet_test.go index ce90be83d2038..867435cfd1540 100644 --- a/pkg/sql/colexec/external/parquet_test.go +++ b/pkg/sql/colexec/external/parquet_test.go @@ -31,6 +31,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/fileservice" + "github.com/matrixorigin/matrixone/pkg/pb/pipeline" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" "github.com/matrixorigin/matrixone/pkg/testutil" @@ -1466,11 +1467,12 @@ func TestParquet_openFile_localNYI(t *testing.T) { } func TestParquetShouldPrefetchS3Parquet(t *testing.T) { - require.True(t, shouldPrefetchS3Parquet(tree.S3, true, maxParquetS3PrefetchSize)) - require.False(t, shouldPrefetchS3Parquet(tree.S3, true, maxParquetS3PrefetchSize+1)) - require.False(t, shouldPrefetchS3Parquet(tree.S3, false, maxParquetS3PrefetchSize)) - require.False(t, shouldPrefetchS3Parquet(tree.INFILE, true, maxParquetS3PrefetchSize)) - require.False(t, shouldPrefetchS3Parquet(tree.S3, true, -1)) + require.True(t, shouldPrefetchS3Parquet(tree.S3, true, maxParquetS3PrefetchSize, false)) + require.False(t, shouldPrefetchS3Parquet(tree.S3, true, maxParquetS3PrefetchSize+1, false)) + require.False(t, shouldPrefetchS3Parquet(tree.S3, false, maxParquetS3PrefetchSize, false)) + require.False(t, shouldPrefetchS3Parquet(tree.INFILE, true, maxParquetS3PrefetchSize, false)) + require.False(t, shouldPrefetchS3Parquet(tree.S3, true, -1, false)) + require.False(t, shouldPrefetchS3Parquet(tree.S3, true, 80*1024*1024, true)) } func TestParquet_prepare_missingColumn(t *testing.T) { @@ -1655,6 +1657,47 @@ func TestParquet_ScanParquetFile_MappingErrorClosesPages(t *testing.T) { require.NoError(t, r.Close()) } +func TestParquet_RowGroupSelection_MappingErrorClosesPages(t *testing.T) { + var buf bytes.Buffer + schema := parquet.NewSchema("x", parquet.Group{"c": parquet.Uint(64)}) + w := parquet.NewWriter(&buf, schema, parquet.MaxRowsPerRowGroup(1)) + _, err := w.WriteRows([]parquet.Row{ + {parquet.ValueOf(uint64(1)).Level(0, 0, 0)}, + {parquet.ValueOf(uint64(1<<63)).Level(0, 0, 0)}, + }) + require.NoError(t, err) + require.NoError(t, w.Close()) + + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_int64), NotNullable: true}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE, Format: tree.PARQUET}}, + FileSize: []int64{int64(buf.Len())}, + ParquetRowGroupShards: []*pipeline.ParquetRowGroupShard{{FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 2}}, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(buf.Bytes()) + + proc := testutil.NewProc(t) + bat := vectorBatch([]types.Type{types.T_int64.ToType()}) + + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + _, err = r.ReadBatch(context.Background(), bat, proc, nil) + require.ErrorContains(t, err, "overflows BIGINT") + require.NotNil(t, r.h) + require.Len(t, r.h.pages, 1) + require.Nil(t, r.h.pages[0]) + require.Nil(t, r.h.currentPage[0]) + require.Zero(t, r.h.pageOffset[0]) + require.NoError(t, r.Close()) +} + func TestParquet_ScanParquetFile_CountStarNoAttrs(t *testing.T) { save := maxParquetBatchCnt maxParquetBatchCnt = 2 @@ -1704,6 +1747,309 @@ func TestParquet_ScanParquetFile_CountStarNoAttrs(t *testing.T) { require.Equal(t, 3, total) } +func TestParquet_RowGroupSelection_PagePath(t *testing.T) { + save := maxParquetBatchCnt + maxParquetBatchCnt = 3 + defer func() { maxParquetBatchCnt = save }() + + data := writeInt32ParquetWithRowGroups(t, []int32{0, 1, 2, 3, 4, 5}, 2) + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_int32), NotNullable: true}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 3}, + }, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + got := make([]int32, 0, 4) + for attempts := 0; attempts < 4; attempts++ { + bat := vectorBatch([]types.Type{types.New(types.T_int32, 0, 0)}) + finished, rerr := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, rerr) + values := vector.MustFixedColWithTypeCheck[int32](bat.Vecs[0]) + got = append(got, values[:bat.RowCount()]...) + if finished { + break + } + } + require.Equal(t, []int32{2, 3, 4, 5}, got) +} + +func TestParquet_RowGroupSelection_RowCountOnly(t *testing.T) { + save := maxParquetBatchCnt + maxParquetBatchCnt = 3 + defer func() { maxParquetBatchCnt = save }() + + data := writeInt32ParquetWithRowGroups(t, []int32{0, 1, 2, 3, 4, 5}, 2) + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_int32), NotNullable: true}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 3}, + }, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + var total int + for attempts := 0; attempts < 4; attempts++ { + bat := batch.NewWithSize(0) + finished, rerr := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, rerr) + total += bat.RowCount() + if finished { + break + } + } + require.Equal(t, 4, total) +} + +func TestParquet_RowGroupSelection_InvalidShard(t *testing.T) { + data := writeInt32ParquetWithRowGroups(t, []int32{0, 1, 2, 3}, 2) + for _, shard := range []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 4}, + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 1}, + } { + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_int32), NotNullable: true}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: []*pipeline.ParquetRowGroupShard{ + shard, + }, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.Error(t, err) + require.False(t, fileEmpty) + require.Contains(t, err.Error(), "invalid parquet row group shard") + } +} + +func TestParquet_RowGroupSelection_NestedRowMode(t *testing.T) { + save := maxParquetBatchCnt + maxParquetBatchCnt = 4 + defer func() { maxParquetBatchCnt = save }() + + data := writeNestedParquetWithRowGroups(t, []int32{0, 1, 2, 3}, 2) + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_text)}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 2}, + }, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + bat := vectorBatch([]types.Type{types.New(types.T_text, 0, 0)}) + finished, err := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, err) + require.True(t, finished) + require.Equal(t, 2, bat.RowCount()) + require.Equal(t, 2, bat.Vecs[0].Length()) +} + +func TestParquet_RowGroupSelection_SerialVsShards_Nulls(t *testing.T) { + save := maxParquetBatchCnt + maxParquetBatchCnt = 2 + defer func() { maxParquetBatchCnt = save }() + + data := writeNullableInt32ParquetWithRowGroups(t, []nullableInt32{ + {value: 1, valid: true}, + {valid: false}, + {value: 3, valid: true}, + {valid: false}, + {value: 5, valid: true}, + {value: 6, valid: true}, + }, 2) + + serial := scanNullableInt32Parquet(t, data, nil, false) + var sharded []string + for i := int32(0); i < 3; i++ { + sharded = append(sharded, scanNullableInt32Parquet(t, data, []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: i, RowGroupEnd: i + 1}, + }, false)...) + } + require.Equal(t, []string{"1", "NULL", "3", "NULL", "5", "6"}, serial) + require.Equal(t, serial, sharded) +} + +func TestParquet_RowGroupSelection_NotNullViolation(t *testing.T) { + data := writeNullableInt32ParquetWithRowGroups(t, []nullableInt32{ + {value: 1, valid: true}, + {value: 2, valid: true}, + {valid: false}, + {value: 4, valid: true}, + }, 2) + + param := nullableInt32ParquetParam(data, []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 2}, + }, true) + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + bat := vectorBatch([]types.Type{types.New(types.T_int32, 0, 0)}) + _, err = r.ReadBatch(context.Background(), bat, proc, nil) + require.Error(t, err) + require.True(t, moerr.IsMoErrCode(err, moerr.ErrConstraintViolation), "unexpected error: %v", err) + require.Contains(t, err.Error(), "cannot load NULL value into NOT NULL column") +} + +func TestParquet_RowGroupSelection_SerialVsShards_NestedRowMode(t *testing.T) { + save := maxParquetBatchCnt + maxParquetBatchCnt = 2 + defer func() { maxParquetBatchCnt = save }() + + data := writeNestedParquetWithRowGroups(t, []int32{0, 1, 2, 3}, 2) + serial := scanNestedTextParquet(t, data, nil) + var sharded []string + for i := int32(0); i < 2; i++ { + sharded = append(sharded, scanNestedTextParquet(t, data, []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: i, RowGroupEnd: i + 1}, + })...) + } + require.Equal(t, serial, sharded) + require.Len(t, serial, 4) +} + +func TestParquet_ProfileStats_PagePath(t *testing.T) { + save := maxParquetBatchCnt + maxParquetBatchCnt = 2 + defer func() { maxParquetBatchCnt = save }() + + data := writeInt32ParquetWithRowGroups(t, []int32{0, 1, 2, 3, 4, 5}, 2) + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_int32), NotNullable: true}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE, Format: tree.PARQUET, Data: string(data)}}, + FileSize: []int64{int64(len(data))}, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + var totalRows int + for attempts := 0; attempts < 5; attempts++ { + bat := vectorBatch([]types.Type{types.New(types.T_int32, 0, 0)}) + finished, rerr := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, rerr) + totalRows += bat.RowCount() + if finished { + break + } + } + require.Equal(t, 6, totalRows) + + stats := param.takeParquetProfile() + require.Equal(t, int64(1), stats.Files) + require.Equal(t, int64(3), stats.RowGroups) + require.Equal(t, int64(6), stats.RowsRead) + require.Equal(t, int64(len(data)), stats.BytesRead) + require.Positive(t, stats.OpenTime) + require.Positive(t, stats.ReadPageTime) + require.Positive(t, stats.MapTime) + require.Zero(t, stats.RowModeTime) + require.True(t, param.takeParquetProfile().Empty()) +} + +func TestParquet_ProfileStats_RowMode(t *testing.T) { + data := writeNestedParquetWithRowGroups(t, []int32{0, 1, 2, 3}, 2) + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_text)}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE, Format: tree.PARQUET}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: []*pipeline.ParquetRowGroupShard{ + {FileIndex: 0, RowGroupStart: 1, RowGroupEnd: 2}, + }, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + bat := vectorBatch([]types.Type{types.New(types.T_text, 0, 0)}) + finished, err := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, err) + require.True(t, finished) + require.Equal(t, 2, bat.RowCount()) + + stats := param.takeParquetProfile() + require.Equal(t, int64(1), stats.Files) + require.Equal(t, int64(1), stats.RowGroups) + require.Equal(t, int64(2), stats.RowsRead) + require.Equal(t, int64(len(data)), stats.BytesRead) + require.Positive(t, stats.OpenTime) + require.Positive(t, stats.RowModeTime) +} + // helper to build a batch with provided vector types func vectorBatch(ts []types.Type) *batch.Batch { bat := batch.NewWithSize(len(ts)) @@ -1713,6 +2059,160 @@ func vectorBatch(ts []types.Type) *batch.Batch { return bat } +func writeInt32ParquetWithRowGroups(t *testing.T, values []int32, rowsPerGroup int64) []byte { + t.Helper() + var buf bytes.Buffer + schema := parquet.NewSchema("x", parquet.Group{"c": parquet.Leaf(parquet.Int32Type)}) + w := parquet.NewWriter(&buf, schema, parquet.MaxRowsPerRowGroup(rowsPerGroup)) + rows := make([]parquet.Row, len(values)) + for i, value := range values { + rows[i] = parquet.Row{parquet.Int32Value(value).Level(0, 0, 0)} + } + _, err := w.WriteRows(rows) + require.NoError(t, err) + require.NoError(t, w.Close()) + f, err := parquet.OpenFile(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + require.NoError(t, err) + require.Len(t, f.RowGroups(), (len(values)+int(rowsPerGroup)-1)/int(rowsPerGroup)) + return buf.Bytes() +} + +func writeNestedParquetWithRowGroups(t *testing.T, values []int32, rowsPerGroup int64) []byte { + t.Helper() + var buf bytes.Buffer + schema := parquet.NewSchema("x", parquet.Group{ + "c": parquet.Group{ + "v": parquet.Leaf(parquet.Int32Type), + }, + }) + w := parquet.NewWriter(&buf, schema, parquet.MaxRowsPerRowGroup(rowsPerGroup)) + rows := make([]parquet.Row, len(values)) + for i, value := range values { + rows[i] = parquet.Row{parquet.Int32Value(value).Level(0, 0, 0)} + } + _, err := w.WriteRows(rows) + require.NoError(t, err) + require.NoError(t, w.Close()) + f, err := parquet.OpenFile(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + require.NoError(t, err) + require.Len(t, f.RowGroups(), (len(values)+int(rowsPerGroup)-1)/int(rowsPerGroup)) + return buf.Bytes() +} + +type nullableInt32 struct { + value int32 + valid bool +} + +func writeNullableInt32ParquetWithRowGroups(t *testing.T, values []nullableInt32, rowsPerGroup int64) []byte { + t.Helper() + var buf bytes.Buffer + schema := parquet.NewSchema("x", parquet.Group{"c": parquet.Optional(parquet.Leaf(parquet.Int32Type))}) + w := parquet.NewWriter(&buf, schema, parquet.MaxRowsPerRowGroup(rowsPerGroup)) + rows := make([]parquet.Row, len(values)) + for i, value := range values { + if value.valid { + rows[i] = parquet.Row{parquet.Int32Value(value.value).Level(0, 1, 0)} + } else { + rows[i] = parquet.Row{parquet.NullValue().Level(0, 0, 0)} + } + } + _, err := w.WriteRows(rows) + require.NoError(t, err) + require.NoError(t, w.Close()) + f, err := parquet.OpenFile(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + require.NoError(t, err) + require.Len(t, f.RowGroups(), (len(values)+int(rowsPerGroup)-1)/int(rowsPerGroup)) + return buf.Bytes() +} + +func nullableInt32ParquetParam(data []byte, shards []*pipeline.ParquetRowGroupShard, notNull bool) *ExternalParam { + return &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_int32), NotNullable: notNull}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE, Format: tree.PARQUET, Data: string(data)}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: shards, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } +} + +func scanNullableInt32Parquet(t *testing.T, data []byte, shards []*pipeline.ParquetRowGroupShard, notNull bool) []string { + t.Helper() + param := nullableInt32ParquetParam(data, shards, notNull) + param.Extern.Data = string(data) + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + var rows []string + for attempts := 0; attempts < 8; attempts++ { + bat := vectorBatch([]types.Type{types.New(types.T_int32, 0, 0)}) + finished, rerr := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, rerr) + values := vector.MustFixedColWithTypeCheck[int32](bat.Vecs[0]) + for i := 0; i < bat.RowCount(); i++ { + if bat.Vecs[0].IsNull(uint64(i)) { + rows = append(rows, "NULL") + } else { + rows = append(rows, fmt.Sprintf("%d", values[i])) + } + } + if finished { + return rows + } + } + t.Fatalf("parquet scan did not finish") + return nil +} + +func scanNestedTextParquet(t *testing.T, data []byte, shards []*pipeline.ParquetRowGroupShard) []string { + t.Helper() + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Ctx: context.Background(), + Attrs: []plan.ExternAttr{{ColName: "c", ColIndex: 0}}, + Cols: []*plan.ColDef{{Typ: plan.Type{Id: int32(types.T_text)}}}, + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{ScanType: tree.INLINE, Format: tree.PARQUET}}, + FileSize: []int64{int64(len(data))}, + ParquetRowGroupShards: shards, + }, + ExParam: ExParam{Fileparam: &ExFileparam{FileIndex: 1, FileCnt: 1}}, + } + param.Extern.Data = string(data) + proc := testutil.NewProc(t) + r := NewParquetReader(param, proc) + fileEmpty, err := r.Open(param, proc) + require.NoError(t, err) + require.False(t, fileEmpty) + defer r.Close() + + var rows []string + for attempts := 0; attempts < 8; attempts++ { + bat := vectorBatch([]types.Type{types.New(types.T_text, 0, 0)}) + finished, rerr := r.ReadBatch(context.Background(), bat, proc, nil) + require.NoError(t, rerr) + for i := 0; i < bat.RowCount(); i++ { + if bat.Vecs[0].IsNull(uint64(i)) { + rows = append(rows, "NULL") + } else { + rows = append(rows, bat.Vecs[0].GetStringAt(i)) + } + } + if finished { + return rows + } + } + t.Fatalf("parquet scan did not finish") + return nil +} + func Test_parquet_strLoader(t *testing.T) { var ld strLoader // ByteArray @@ -1972,7 +2472,12 @@ func Test_wrapParseError(t *testing.T) { func Test_fsReaderAt_ReadAt(t *testing.T) { data := []byte("hello world") fs := &fakeFS{b: data} - r := &fsReaderAt{fs: fs, readPath: "fake:hello", ctx: context.Background()} + param := &ExternalParam{ + ExParamConst: ExParamConst{ + Extern: &tree.ExternParam{ExParamConst: tree.ExParamConst{Format: tree.PARQUET}}, + }, + } + r := &fsReaderAt{fs: fs, readPath: "fake:hello", ctx: context.Background(), param: param} buf := make([]byte, 5) n, err := r.ReadAt(buf, 6) require.NoError(t, err) @@ -1981,6 +2486,7 @@ func Test_fsReaderAt_ReadAt(t *testing.T) { require.Equal(t, fileservice.Policy(fileservice.SkipFullFilePreloads), fs.lastPolicy) require.Equal(t, int64(6), fs.lastOffset) require.Equal(t, int64(5), fs.lastSize) + require.Equal(t, int64(5), param.takeParquetProfile().BytesRead) } func TestParquetS3ReadAmplificationRepro(t *testing.T) { diff --git a/pkg/sql/colexec/external/reader_parquet.go b/pkg/sql/colexec/external/reader_parquet.go index 78f3b5b59aadf..5a916da7893b0 100644 --- a/pkg/sql/colexec/external/reader_parquet.go +++ b/pkg/sql/colexec/external/reader_parquet.go @@ -16,6 +16,7 @@ package external import ( "context" + "time" "github.com/matrixorigin/matrixone/pkg/container/batch" "github.com/matrixorigin/matrixone/pkg/util/trace" @@ -33,7 +34,14 @@ func NewParquetReader(param *ExternalParam, proc *process.Process) *ParquetReade } func (r *ParquetReader) Open(param *ExternalParam, proc *process.Process) (fileEmpty bool, err error) { + openStart := time.Now() r.param = param + defer func() { + param.addParquetProfile(process.ParquetProfileStats{ + OpenTime: time.Since(openStart).Nanoseconds(), + }) + }() + if err := param.refreshPartitionValues(proc); err != nil { return false, err } @@ -41,6 +49,11 @@ func (r *ParquetReader) Open(param *ExternalParam, proc *process.Process) (fileE if err != nil { return false, err } + stats := process.ParquetProfileStats{Files: 1} + if r.h != nil { + stats.RowGroups = int64(len(r.h.rowGroups)) + } + param.addParquetProfile(stats) // newParquetHandler returns (nil, nil) for empty files if r.h == nil { return true, nil @@ -76,9 +89,13 @@ func (r *ParquetReader) ReadBatch( return false, err } } + if buf.RowCount() > 0 { + r.param.addParquetProfile(process.ParquetProfileStats{RowsRead: int64(buf.RowCount())}) + } - // Check if file is finished: getData sets offset and checks NumRows - if r.h.file != nil && r.h.offset >= r.h.file.NumRows() { + // Check if file is finished: getData sets offset against the selected + // row groups, not necessarily the whole file. + if r.h.isFinished() { return true, nil } return false, nil diff --git a/pkg/sql/colexec/external/types.go b/pkg/sql/colexec/external/types.go index 34b8af6478889..fd8eb27012a41 100644 --- a/pkg/sql/colexec/external/types.go +++ b/pkg/sql/colexec/external/types.go @@ -18,6 +18,7 @@ import ( "bufio" "context" "io" + "strings" "github.com/parquet-go/parquet-go" @@ -66,15 +67,18 @@ type ExParamConst struct { FileSize []int64 FileOffset []int64 FileOffsetTotal []*pipeline.FileOffset - Ctx context.Context - Extern *tree.ExternParam - ClusterTable *plan.ClusterTable + // Optional Parquet row group shards. Empty means whole-file scan. + ParquetRowGroupShards []*pipeline.ParquetRowGroupShard + Ctx context.Context + Extern *tree.ExternParam + ClusterTable *plan.ClusterTable } type ExParam struct { Fileparam *ExFileparam Filter *FilterParam currentPartValues map[string]string + parquetProfile process.ParquetProfileStats } type ExFileparam struct { @@ -156,6 +160,32 @@ func NewArgument() *External { return reuse.Alloc[External](nil) } +func (param *ExternalParam) addParquetProfile(stats process.ParquetProfileStats) { + if param == nil || param.Extern == nil || !strings.EqualFold(param.Extern.Format, tree.PARQUET) || stats.Empty() { + return + } + param.parquetProfile.Add(stats) +} + +func (param *ExternalParam) takeParquetProfile() process.ParquetProfileStats { + if param == nil { + return process.ParquetProfileStats{} + } + stats := param.parquetProfile + param.parquetProfile = process.ParquetProfileStats{} + return stats +} + +func (param *ExternalParam) flushParquetProfile(analyzer process.Analyzer) { + if analyzer == nil { + return + } + stats := param.takeParquetProfile() + if !stats.Empty() { + analyzer.AddParquetProfile(stats) + } +} + func (external *External) WithEs(es *ExternalParam) *External { external.Es = es return external @@ -274,18 +304,21 @@ func newCSVParserFromReader(extern *tree.ExternParam, r io.Reader) (*csvparser.C } type ParquetHandler struct { - file *parquet.File - offset int64 - batchCnt int64 - cols []*parquet.Column - mappers []*columnMapper - pages []parquet.Pages // cached pages iterators for each column - currentPage []parquet.Page // cached current page for each column - pageOffset []int64 // current offset within each cached page + file *parquet.File + rowGroup parquet.RowGroup + rowGroups []parquet.RowGroup + rowGroupRows int64 + offset int64 + batchCnt int64 + cols []*parquet.Column + mappers []*columnMapper + pages []parquet.Pages // cached pages iterators for each column + currentPage []parquet.Page // cached current page for each column + pageOffset []int64 // current offset within each cached page // for nested types support hasNestedCols bool - rowReader *parquet.Reader + rowReader parquet.Rows // virtual column support (hive partitions + __mo_filepath) partitionColIndices []int diff --git a/pkg/sql/compile/compile.go b/pkg/sql/compile/compile.go index 768939e978e4d..aff65fa066ebd 100644 --- a/pkg/sql/compile/compile.go +++ b/pkg/sql/compile/compile.go @@ -19,12 +19,15 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" "net" "sort" "strconv" "strings" "time" + "github.com/parquet-go/parquet-go" + "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/cnservice/cnclient" "github.com/matrixorigin/matrixone/pkg/common/moerr" @@ -1598,6 +1601,9 @@ func (c *Compile) getReadWriteParallelFlag(param *tree.ExternParam, fileList []s if !param.Parallel { return false, false } + if param.Format == tree.PARQUET { + return false, true + } if param.Local || crt.GetCompressType(param.CompressType, fileList[0]) != tree.NOCOMPRESS { return false, true } @@ -1637,8 +1643,15 @@ func (c *Compile) getExternalFileListAndSize(node *plan.Node, param *tree.Extern return nil, nil, err } case int32(plan.ExternType_LOAD): - fileList = []string{param.Filepath} - fileSize = []int64{param.FileSize} + if param.Format == tree.PARQUET && strings.ContainsAny(strings.TrimSpace(param.Filepath), "*?[") { + fileList, fileSize, err = plan2.ReadDir(param) + if err != nil { + return nil, nil, err + } + } else { + fileList = []string{param.Filepath} + fileSize = []int64{param.FileSize} + } } return fileList, fileSize, nil } @@ -1887,6 +1900,23 @@ func (c *Compile) compileExternScan(node *plan.Node) ([]*Scope, error) { if param.HivePartitioning { return c.compileExternScanHiveFileFanout(node, param, fileList, fileSize, strictSqlMode) } + if param.ExternType == int32(plan.ExternType_LOAD) && + param.Format == tree.PARQUET && + param.Parallel { + rowGroups, footerStats, err := c.readLoadParquetRowGroupMetadata(param, fileList, fileSize) + if err != nil { + return nil, err + } + logutil.Debugf("parquet load footer metadata: files=%d row_groups=%d rows=%d read_calls=%d read_bytes=%d duration=%s", + footerStats.Files, footerStats.RowGroups, footerStats.Rows, + footerStats.ReadCalls, footerStats.ReadBytes, footerStats.Duration) + if footerStats.RowGroups > len(fileList) { + return c.compileExternScanParquetRowGroupFanout(node, param, fileList, fileSize, rowGroups, strictSqlMode) + } + if len(fileList) > 1 { + return c.compileExternScanParquetLoadFileFanout(node, param, fileList, fileSize, strictSqlMode) + } + } readParallel, writeParallel := c.getReadWriteParallelFlag(param, fileList) @@ -1984,7 +2014,40 @@ type hiveFileShard struct { fileSize []int64 } +type parquetRowGroupMeta struct { + fileIndex int32 + rowGroupIndex int32 + numRows int64 + bytes int64 +} + +type parquetFooterStats struct { + Files int + RowGroups int + Rows int64 + Bytes int64 + ReadCalls int64 + ReadBytes int64 + Duration time.Duration +} + +type parquetRowGroupScopeShard struct { + node engine.Node + fileList []string + fileSize []int64 + rowGroupShards []*pipeline.ParquetRowGroupShard + originalToLocal map[int32]int32 +} + func (c *Compile) compileExternScanHiveFileFanout(node *plan.Node, param *tree.ExternParam, fileList []string, fileSize []int64, strictSqlMode bool) ([]*Scope, error) { + return c.compileExternScanWholeFileFanout(node, param, fileList, fileSize, strictSqlMode) +} + +func (c *Compile) compileExternScanParquetLoadFileFanout(node *plan.Node, param *tree.ExternParam, fileList []string, fileSize []int64, strictSqlMode bool) ([]*Scope, error) { + return c.compileExternScanWholeFileFanout(node, param, fileList, fileSize, strictSqlMode) +} + +func (c *Compile) compileExternScanWholeFileFanout(node *plan.Node, param *tree.ExternParam, fileList []string, fileSize []int64, strictSqlMode bool) ([]*Scope, error) { nodes := c.getHiveFileFanoutNodes(param, len(fileList)) shards := splitHiveFileShards(fileList, fileSize, nodes) if len(shards) <= 1 { @@ -2022,6 +2085,171 @@ func (c *Compile) compileExternScanHiveFileFanout(node *plan.Node, param *tree.E return ss, nil } +func (c *Compile) compileExternScanParquetRowGroupFanout( + node *plan.Node, + param *tree.ExternParam, + fileList []string, + fileSize []int64, + rowGroups []parquetRowGroupMeta, + strictSqlMode bool, +) ([]*Scope, error) { + nodes := c.getHiveFileFanoutNodes(param, len(rowGroups)) + shards, err := splitParquetRowGroupShards(fileList, fileSize, rowGroups, nodes) + if err != nil { + return nil, err + } + if len(shards) <= 1 { + serialParam := *param + serialParam.Parallel = false + return c.compileExternScanSerialReadWrite(node, &serialParam, fileList, fileSize, strictSqlMode) + } + + ss := make([]*Scope, 0, len(shards)) + currentFirstFlag := c.anal.isFirst + for i := range shards { + shard := shards[i] + shardParam := new(tree.ExternParam) + *shardParam = *param + shardParam.Parallel = false + + remote := param.ScanType == tree.S3 && len(c.cnList) > 0 + scope := c.constructScopeForExternal(shard.node.Addr, remote) + scope.NodeInfo.Mcpu = 1 + scope.IsLoad = true + op := constructExternal( + node, shardParam, c.proc.Ctx, + shard.fileList, shard.fileSize, + makeWholeFileOffsets(len(shard.fileList)), + strictSqlMode, + ) + op.Es.ParquetRowGroupShards = shard.rowGroupShards + op.SetAnalyzeControl(c.anal.curNodeIdx, currentFirstFlag) + scope.setRootOperator(op) + ss = append(ss, scope) + } + c.anal.isFirst = false + return ss, nil +} + +func (c *Compile) readLoadParquetRowGroupMetadata( + param *tree.ExternParam, + fileList []string, + fileSize []int64, +) ([]parquetRowGroupMeta, parquetFooterStats, error) { + ctx := param.Ctx + if ctx == nil && c.proc != nil { + ctx = c.proc.Ctx + } + if ctx == nil { + ctx = context.Background() + } + + start := time.Now() + stats := parquetFooterStats{Files: len(fileList)} + metas := make([]parquetRowGroupMeta, 0) + for fileIdx, filePath := range fileList { + size := hiveFileSizeAt(fileSize, fileIdx) + var reader io.ReaderAt + var footerReader *compileParquetFooterReaderAt + if param.ScanType == tree.INLINE { + reader = strings.NewReader(param.Data) + size = int64(len(param.Data)) + } else { + fs, readPath, err := plan2.GetForETLWithType(param, filePath) + if err != nil { + return nil, stats, err + } + if size <= 0 { + st, err := fs.StatFile(ctx, readPath) + if err != nil { + return nil, stats, err + } + size = st.Size + } + footerReader = &compileParquetFooterReaderAt{ + fs: fs, + readPath: readPath, + ctx: ctx, + } + reader = footerReader + } + stats.Bytes += size + + f, err := parquet.OpenFile(reader, size) + if footerReader != nil { + stats.ReadCalls += footerReader.readCalls + stats.ReadBytes += footerReader.readBytes + } + if err != nil { + return nil, stats, moerr.ConvertGoError(ctx, err) + } + rowGroups := f.RowGroups() + stats.RowGroups += len(rowGroups) + totalRows := f.NumRows() + for rowGroupIdx, rowGroup := range rowGroups { + rows := rowGroup.NumRows() + stats.Rows += rows + metas = append(metas, parquetRowGroupMeta{ + fileIndex: int32(fileIdx), + rowGroupIndex: int32(rowGroupIdx), + numRows: rows, + bytes: estimateParquetRowGroupBytes(size, totalRows, rows, len(rowGroups)), + }) + } + } + stats.Duration = time.Since(start) + return metas, stats, nil +} + +type compileParquetFooterReaderAt struct { + fs fileservice.ETLFileService + readPath string + ctx context.Context + readCalls int64 + readBytes int64 +} + +func (r *compileParquetFooterReaderAt) ReadAt(p []byte, off int64) (n int, err error) { + vec := fileservice.IOVector{ + FilePath: r.readPath, + Policy: fileservice.SkipFullFilePreloads, + Entries: []fileservice.IOEntry{ + { + Offset: off, + Size: int64(len(p)), + Data: p, + }, + }, + } + if err := r.fs.Read(r.ctx, &vec); err != nil { + return 0, err + } + size := vec.Entries[0].Size + r.readCalls++ + r.readBytes += size + return int(size), nil +} + +func estimateParquetRowGroupBytes(fileSize, totalRows, rowGroupRows int64, rowGroupCount int) int64 { + if fileSize <= 0 { + return 1 + } + if totalRows > 0 && rowGroupRows > 0 { + size := int64(float64(fileSize) * float64(rowGroupRows) / float64(totalRows)) + if size > 0 { + return size + } + return 1 + } + if rowGroupCount > 0 { + size := fileSize / int64(rowGroupCount) + if size > 0 { + return size + } + } + return 1 +} + func (c *Compile) getHiveFileFanoutNodes(param *tree.ExternParam, fileCount int) []engine.Node { if fileCount <= 0 { return nil @@ -2109,6 +2337,134 @@ func splitHiveFileShards(fileList []string, fileSize []int64, nodes []engine.Nod return nonEmpty } +func splitParquetRowGroupShards( + fileList []string, + fileSize []int64, + rowGroups []parquetRowGroupMeta, + nodes []engine.Node, +) ([]parquetRowGroupScopeShard, error) { + if len(rowGroups) == 0 || len(nodes) == 0 { + return nil, nil + } + shardCount := len(nodes) + if shardCount > len(rowGroups) { + shardCount = len(rowGroups) + } + shards := make([]parquetRowGroupScopeShard, shardCount) + loads := make([]int64, shardCount) + for i := range shards { + shards[i].node = nodes[i] + shards[i].originalToLocal = make(map[int32]int32) + } + + indices := make([]int, len(rowGroups)) + for i := range indices { + indices[i] = i + } + sort.SliceStable(indices, func(i, j int) bool { + left := rowGroups[indices[i]] + right := rowGroups[indices[j]] + if parquetRowGroupLoad(left) != parquetRowGroupLoad(right) { + return parquetRowGroupLoad(left) > parquetRowGroupLoad(right) + } + if left.fileIndex != right.fileIndex { + return left.fileIndex < right.fileIndex + } + return left.rowGroupIndex < right.rowGroupIndex + }) + + for _, rowGroupIdx := range indices { + meta := rowGroups[rowGroupIdx] + if meta.fileIndex < 0 || int(meta.fileIndex) >= len(fileList) { + return nil, moerr.NewInternalErrorNoCtxf( + "invalid parquet row group file index %d for %d files", + meta.fileIndex, len(fileList), + ) + } + if meta.rowGroupIndex < 0 { + return nil, moerr.NewInternalErrorNoCtxf( + "invalid parquet row group index %d for file index %d", + meta.rowGroupIndex, meta.fileIndex, + ) + } + + shardIdx := 0 + for i := 1; i < shardCount; i++ { + if loads[i] < loads[shardIdx] || + (loads[i] == loads[shardIdx] && len(shards[i].rowGroupShards) < len(shards[shardIdx].rowGroupShards)) { + shardIdx = i + } + } + + localFileIndex := appendParquetShardFile(&shards[shardIdx], fileList, fileSize, meta.fileIndex) + shards[shardIdx].rowGroupShards = append(shards[shardIdx].rowGroupShards, &pipeline.ParquetRowGroupShard{ + FileIndex: localFileIndex, + RowGroupStart: meta.rowGroupIndex, + RowGroupEnd: meta.rowGroupIndex + 1, + NumRows: meta.numRows, + Bytes: meta.bytes, + }) + loads[shardIdx] += parquetRowGroupLoad(meta) + } + + nonEmpty := shards[:0] + for _, shard := range shards { + if len(shard.rowGroupShards) == 0 { + continue + } + sort.SliceStable(shard.rowGroupShards, func(i, j int) bool { + left := shard.rowGroupShards[i] + right := shard.rowGroupShards[j] + if left.FileIndex != right.FileIndex { + return left.FileIndex < right.FileIndex + } + return left.RowGroupStart < right.RowGroupStart + }) + shard.rowGroupShards = mergeAdjacentParquetRowGroupShards(shard.rowGroupShards) + shard.originalToLocal = nil + nonEmpty = append(nonEmpty, shard) + } + return nonEmpty, nil +} + +func appendParquetShardFile(shard *parquetRowGroupScopeShard, fileList []string, fileSize []int64, originalFileIndex int32) int32 { + if localFileIndex, ok := shard.originalToLocal[originalFileIndex]; ok { + return localFileIndex + } + localFileIndex := int32(len(shard.fileList)) + shard.originalToLocal[originalFileIndex] = localFileIndex + shard.fileList = append(shard.fileList, fileList[originalFileIndex]) + shard.fileSize = append(shard.fileSize, hiveFileSizeAt(fileSize, int(originalFileIndex))) + return localFileIndex +} + +func mergeAdjacentParquetRowGroupShards(shards []*pipeline.ParquetRowGroupShard) []*pipeline.ParquetRowGroupShard { + merged := shards[:0] + for _, shard := range shards { + if len(merged) > 0 { + last := merged[len(merged)-1] + if last.FileIndex == shard.FileIndex && last.RowGroupEnd == shard.RowGroupStart { + last.RowGroupEnd = shard.RowGroupEnd + last.NumRows += shard.NumRows + last.Bytes += shard.Bytes + continue + } + } + merged = append(merged, shard) + } + return merged +} + +func parquetRowGroupLoad(meta parquetRowGroupMeta) int64 { + if meta.bytes > 0 { + return meta.bytes + } + if meta.numRows > 0 { + return meta.numRows + } + return 1 +} + func hiveFileSizeAt(fileSize []int64, idx int) int64 { if idx >= 0 && idx < len(fileSize) { return fileSize[idx] @@ -2125,6 +2481,9 @@ func makeWholeFileOffsets(count int) []*pipeline.FileOffset { } func (c *Compile) compileExternScanParallelReadWrite(node *plan.Node, param *tree.ExternParam, fileList []string, fileSize []int64, strictSqlMode bool) ([]*Scope, error) { + if param.Format == tree.PARQUET { + return nil, moerr.NewInternalError(c.proc.Ctx, "parquet load cannot use byte-offset parallel read") + } visibleCols := make([]*plan.ColDef, 0) if param.Strict { for _, col := range node.TableDef.Cols { diff --git a/pkg/sql/compile/operator.go b/pkg/sql/compile/operator.go index 7e87ac848cf5d..1ff5afc43a03b 100644 --- a/pkg/sql/compile/operator.go +++ b/pkg/sql/compile/operator.go @@ -353,6 +353,7 @@ func dupOperator(sourceOp vm.Operator, index int, maxParallel int) vm.Operator { FileList: t.Es.FileList, FileSize: t.Es.FileSize, FileOffsetTotal: t.Es.FileOffsetTotal, + ParquetRowGroupShards: t.Es.ParquetRowGroupShards, Extern: t.Es.Extern, StrictSqlMode: t.Es.StrictSqlMode, ParallelLoad: t.Es.ParallelLoad, diff --git a/pkg/sql/compile/remoterun.go b/pkg/sql/compile/remoterun.go index ff9f77f0978ea..d03674954c47d 100644 --- a/pkg/sql/compile/remoterun.go +++ b/pkg/sql/compile/remoterun.go @@ -664,6 +664,7 @@ func convertToPipelineInstruction(op vm.Operator, proc *process.Process, ctx *sc StrictSqlMode: t.Es.StrictSqlMode, ParallelLoad: t.Es.ParallelLoad, LoadEmptyNumericAsZero: t.Es.LoadEmptyNumericAsZero, + ParquetRowGroupShards: t.Es.ParquetRowGroupShards, } in.ProjectList = t.ProjectList case *source.Source: @@ -1124,6 +1125,7 @@ func convertToVmOperator(opr *pipeline.Instruction, ctx *scopeContext, eng engin StrictSqlMode: t.StrictSqlMode, ParallelLoad: t.ParallelLoad, LoadEmptyNumericAsZero: t.LoadEmptyNumericAsZero, + ParquetRowGroupShards: t.ParquetRowGroupShards, }, ExParam: external.ExParam{ Fileparam: new(external.ExFileparam), diff --git a/pkg/sql/compile/remoterun_test.go b/pkg/sql/compile/remoterun_test.go index 4ccabab2b352e..517e245c6f323 100644 --- a/pkg/sql/compile/remoterun_test.go +++ b/pkg/sql/compile/remoterun_test.go @@ -304,6 +304,49 @@ func Test_convertToVmInstruction(t *testing.T) { } } +func TestExternalScanParquetRowGroupShardsRoundtrip(t *testing.T) { + ctx := &scopeContext{ + id: 1, + root: &scopeContext{}, + parent: &scopeContext{}, + } + proc := &process.Process{} + proc.Base = &process.BaseProcess{} + + shards := []*pipeline.ParquetRowGroupShard{ + { + FileIndex: 2, + RowGroupStart: 3, + RowGroupEnd: 5, + NumRows: 1024, + Bytes: 4096, + }, + } + op := external.NewArgument().WithEs( + &external.ExternalParam{ + ExParamConst: external.ExParamConst{ + FileList: []string{"s3://bucket/part.parquet"}, + FileSize: []int64{8192}, + FileOffsetTotal: []*pipeline.FileOffset{{Offset: []int64{0, -1}}}, + ParquetRowGroupShards: shards, + }, + ExParam: external.ExParam{ + Fileparam: &external.ExFileparam{}, + Filter: &external.FilterParam{}, + }, + }, + ) + + _, pipeInstr, err := convertToPipelineInstruction(op, proc, ctx, 1) + require.NoError(t, err) + require.Equal(t, shards, pipeInstr.ExternalScan.ParquetRowGroupShards) + + restored, err := convertToVmOperator(pipeInstr, ctx, nil) + require.NoError(t, err) + restoredExternal := restored.(*external.External) + require.Equal(t, shards, restoredExternal.Es.ParquetRowGroupShards) +} + func Test_DMLOperatorSerializationRoundtrip(t *testing.T) { ctx := &scopeContext{ id: 1, diff --git a/pkg/sql/compile/scope_test.go b/pkg/sql/compile/scope_test.go index c9ac7c40ee16a..2535b111d135a 100644 --- a/pkg/sql/compile/scope_test.go +++ b/pkg/sql/compile/scope_test.go @@ -15,13 +15,19 @@ package compile import ( + "bytes" "context" + "encoding/json" "fmt" + "os" "path" + "path/filepath" "testing" "time" "github.com/golang/mock/gomock" + "github.com/parquet-go/parquet-go" + "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/common/buffer" "github.com/matrixorigin/matrixone/pkg/common/moerr" @@ -457,6 +463,99 @@ func TestCompileExternScanParallelReadWrite(t *testing.T) { require.Error(t, err) } +func TestCompileExternScanParallelReadWriteRejectsParquet(t *testing.T) { + testCompile := NewMockCompile(t) + testCompile.proc.Ctx = context.Background() + testCompile.cnList = engine.Nodes{{Addr: "cn1:6001", Mcpu: 4}} + testCompile.addr = "cn1:6001" + testCompile.anal = &AnalyzeModule{qry: &plan.Query{}} + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + Filepath: "test.parq", + Tail: &tree.TailParameter{}, + }, + ExParam: tree.ExParam{ + Parallel: true, + }, + } + n := &plan.Node{ + TableDef: &plan.TableDef{}, + ExternScan: &plan.ExternScan{}, + } + + _, err := testCompile.compileExternScanParallelReadWrite(n, param, []string{"test.parq"}, []int64{int64(colexec.WriteS3Threshold) * 2}, true) + require.Error(t, err) + require.True(t, moerr.IsMoErrCode(err, moerr.ErrInternal)) + require.Contains(t, err.Error(), "parquet load cannot use byte-offset parallel read") +} + +func TestGetReadWriteParallelFlagDisablesParquetReadSplit(t *testing.T) { + testCompile := NewMockCompile(t) + fileList := []string{"test.parq"} + + readParallel, writeParallel := testCompile.getReadWriteParallelFlag( + &tree.ExternParam{ + ExParamConst: tree.ExParamConst{Format: tree.PARQUET}, + ExParam: tree.ExParam{Parallel: true}, + }, + fileList, + ) + require.False(t, readParallel) + require.True(t, writeParallel) + + readParallel, writeParallel = testCompile.getReadWriteParallelFlag( + &tree.ExternParam{ + ExParamConst: tree.ExParamConst{Format: tree.PARQUET}, + ExParam: tree.ExParam{ + Local: true, + Parallel: true, + }, + }, + fileList, + ) + require.False(t, readParallel) + require.True(t, writeParallel) + + readParallel, writeParallel = testCompile.getReadWriteParallelFlag( + &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + CompressType: tree.GZIP, + }, + ExParam: tree.ExParam{ + Parallel: true, + }, + }, + []string{"test.parq.gz"}, + ) + require.False(t, readParallel) + require.True(t, writeParallel) + + readParallel, writeParallel = testCompile.getReadWriteParallelFlag( + &tree.ExternParam{ + ExParamConst: tree.ExParamConst{Format: tree.CSV}, + ExParam: tree.ExParam{Parallel: true}, + }, + []string{"test.csv"}, + ) + require.True(t, readParallel) + require.True(t, writeParallel) + + readParallel, writeParallel = testCompile.getReadWriteParallelFlag( + &tree.ExternParam{ + ExParamConst: tree.ExParamConst{Format: tree.PARQUET}, + ExParam: tree.ExParam{ + Parallel: false, + ParallelLoadRequested: true, + }, + }, + fileList, + ) + require.False(t, readParallel) + require.False(t, writeParallel) +} + func TestCompileExternScanHiveFileFanout(t *testing.T) { testCompile := NewMockCompile(t) testCompile.cnList = engine.Nodes{{Addr: "cn1:6001", Mcpu: 2}, {Addr: "cn2:6001", Mcpu: 2}} @@ -513,6 +612,358 @@ func TestCompileExternScanHiveFileFanout(t *testing.T) { require.Equal(t, len(fileList), totalFiles) } +func TestCompileExternScanParquetLoadFileFanout(t *testing.T) { + testCompile := NewMockCompile(t) + testCompile.cnList = engine.Nodes{{Addr: "cn1:6001", Mcpu: 2}, {Addr: "cn2:6001", Mcpu: 2}} + testCompile.addr = "cn1:6001" + testCompile.execType = plan2.ExecTypeAP_MULTICN + testCompile.anal = &AnalyzeModule{qry: &plan.Query{}} + + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.S3, + Filepath: "warehouse/load/*.parquet", + Format: tree.PARQUET, + Tail: &tree.TailParameter{}, + }, + ExParam: tree.ExParam{ + ExternType: int32(plan.ExternType_LOAD), + Parallel: true, + }, + } + n := &plan.Node{ + TableDef: &plan.TableDef{}, + ExternScan: &plan.ExternScan{ + Type: int32(plan.ExternType_LOAD), + TbColToDataCol: map[string]int32{}, + }, + } + fileList := []string{ + "warehouse/load/part-000.parquet", + "warehouse/load/part-001.parquet", + "warehouse/load/part-002.parquet", + } + fileSize := []int64{30, 10, 20} + + ss, err := testCompile.compileExternScanParquetLoadFileFanout(n, param, fileList, fileSize, true) + require.NoError(t, err) + require.Len(t, ss, 3) + require.Equal(t, "warehouse/load/*.parquet", param.Filepath) + require.True(t, param.Parallel) + require.False(t, param.HivePartitioning) + + totalFiles := 0 + for _, scope := range ss { + require.NoError(t, checkScopeWithExpectedList(scope, []vm.OpType{vm.External})) + require.Equal(t, 1, scope.NodeInfo.Mcpu) + ext, ok := scope.RootOp.(*external.External) + require.True(t, ok) + require.False(t, ext.Es.Extern.Parallel) + require.False(t, ext.Es.Extern.HivePartitioning) + require.Equal(t, "warehouse/load/*.parquet", ext.Es.Extern.Filepath) + require.Len(t, ext.Es.FileOffsetTotal, len(ext.Es.FileList)) + for _, off := range ext.Es.FileOffsetTotal { + require.Equal(t, []int64{0, -1}, off.Offset) + } + totalFiles += len(ext.Es.FileList) + } + require.Equal(t, len(fileList), totalFiles) +} + +func TestSplitParquetRowGroupShardsBalancesAndReindexesFiles(t *testing.T) { + fileList := []string{"warehouse/load/part-000.parquet", "warehouse/load/part-001.parquet"} + fileSize := []int64{190, 30} + rowGroups := []parquetRowGroupMeta{ + {fileIndex: 0, rowGroupIndex: 0, numRows: 10, bytes: 100}, + {fileIndex: 0, rowGroupIndex: 1, numRows: 9, bytes: 90}, + {fileIndex: 1, rowGroupIndex: 0, numRows: 1, bytes: 10}, + {fileIndex: 1, rowGroupIndex: 1, numRows: 2, bytes: 20}, + } + nodes := engine.Nodes{{Addr: "cn1:6001", Mcpu: 1}, {Addr: "cn2:6001", Mcpu: 1}} + + shards, err := splitParquetRowGroupShards(fileList, fileSize, rowGroups, nodes) + require.NoError(t, err) + require.Len(t, shards, 2) + + loads := make(map[string]int64) + seen := make(map[string]bool) + for _, shard := range shards { + require.NotEmpty(t, shard.fileList) + require.Len(t, shard.fileList, len(shard.fileSize)) + require.Nil(t, shard.originalToLocal) + for _, rowGroupShard := range shard.rowGroupShards { + require.GreaterOrEqual(t, rowGroupShard.FileIndex, int32(0)) + require.Less(t, int(rowGroupShard.FileIndex), len(shard.fileList)) + file := shard.fileList[rowGroupShard.FileIndex] + for rowGroupIdx := rowGroupShard.RowGroupStart; rowGroupIdx < rowGroupShard.RowGroupEnd; rowGroupIdx++ { + seen[fmt.Sprintf("%s:%d", file, rowGroupIdx)] = true + } + loads[shard.node.Addr] += rowGroupShard.Bytes + } + } + require.Equal(t, map[string]bool{ + "warehouse/load/part-000.parquet:0": true, + "warehouse/load/part-000.parquet:1": true, + "warehouse/load/part-001.parquet:0": true, + "warehouse/load/part-001.parquet:1": true, + }, seen) + require.Equal(t, int64(110), loads["cn1:6001"]) + require.Equal(t, int64(110), loads["cn2:6001"]) +} + +func TestCompileExternScanParquetRowGroupFanout(t *testing.T) { + testCompile := NewMockCompile(t) + testCompile.cnList = engine.Nodes{{Addr: "cn1:6001", Mcpu: 2}, {Addr: "cn2:6001", Mcpu: 2}} + testCompile.addr = "cn1:6001" + testCompile.execType = plan2.ExecTypeAP_MULTICN + testCompile.anal = &AnalyzeModule{qry: &plan.Query{}} + + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.S3, + Filepath: "warehouse/load/big.parquet", + Format: tree.PARQUET, + Tail: &tree.TailParameter{}, + }, + ExParam: tree.ExParam{ + ExternType: int32(plan.ExternType_LOAD), + Parallel: true, + }, + } + n := &plan.Node{ + TableDef: &plan.TableDef{}, + ExternScan: &plan.ExternScan{ + Type: int32(plan.ExternType_LOAD), + TbColToDataCol: map[string]int32{}, + }, + } + fileList := []string{"warehouse/load/big.parquet"} + fileSize := []int64{220} + rowGroups := []parquetRowGroupMeta{ + {fileIndex: 0, rowGroupIndex: 0, numRows: 10, bytes: 100}, + {fileIndex: 0, rowGroupIndex: 1, numRows: 9, bytes: 90}, + {fileIndex: 0, rowGroupIndex: 2, numRows: 2, bytes: 20}, + {fileIndex: 0, rowGroupIndex: 3, numRows: 1, bytes: 10}, + } + + ss, err := testCompile.compileExternScanParquetRowGroupFanout(n, param, fileList, fileSize, rowGroups, true) + require.NoError(t, err) + require.Len(t, ss, 4) + require.True(t, param.Parallel) + + seen := make(map[int32]bool) + var totalRows int64 + for _, scope := range ss { + require.NoError(t, checkScopeWithExpectedList(scope, []vm.OpType{vm.External})) + require.Equal(t, 1, scope.NodeInfo.Mcpu) + require.True(t, scope.IsLoad) + ext, ok := scope.RootOp.(*external.External) + require.True(t, ok) + require.False(t, ext.Es.Extern.Parallel) + require.Equal(t, []string{"warehouse/load/big.parquet"}, ext.Es.FileList) + require.Equal(t, []int64{int64(220)}, ext.Es.FileSize) + require.Equal(t, []*pipeline.FileOffset{{Offset: []int64{0, -1}}}, ext.Es.FileOffsetTotal) + require.NotEmpty(t, ext.Es.ParquetRowGroupShards) + for _, shard := range ext.Es.ParquetRowGroupShards { + require.Equal(t, int32(0), shard.FileIndex) + for rowGroupIdx := shard.RowGroupStart; rowGroupIdx < shard.RowGroupEnd; rowGroupIdx++ { + seen[rowGroupIdx] = true + } + totalRows += shard.NumRows + } + } + require.Equal(t, map[int32]bool{0: true, 1: true, 2: true, 3: true}, seen) + require.Equal(t, int64(22), totalRows) +} + +func TestReadLoadParquetRowGroupMetadataLocalFile(t *testing.T) { + testCompile := NewMockCompile(t) + data := writeCompileInt32ParquetWithRowGroups(t, []int32{0, 1, 2, 3, 4, 5}, 2) + filePath := filepath.Join(t.TempDir(), "rg.parquet") + require.NoError(t, os.WriteFile(filePath, data, 0o600)) + + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.INFILE, + Filepath: filePath, + Format: tree.PARQUET, + }, + ExParam: tree.ExParam{ + Ctx: context.Background(), + }, + } + + metas, stats, err := testCompile.readLoadParquetRowGroupMetadata( + param, []string{filePath}, []int64{int64(len(data))}) + require.NoError(t, err) + require.Len(t, metas, 3) + t.Logf("parquet footer metadata stats: files=%d row_groups=%d rows=%d bytes=%d read_calls=%d read_bytes=%d duration=%s", + stats.Files, stats.RowGroups, stats.Rows, stats.Bytes, stats.ReadCalls, stats.ReadBytes, stats.Duration) + require.Equal(t, parquetFooterStats{ + Files: 1, + RowGroups: 3, + Rows: 6, + Bytes: int64(len(data)), + ReadCalls: stats.ReadCalls, + ReadBytes: stats.ReadBytes, + Duration: stats.Duration, + }, stats) + require.Positive(t, stats.ReadCalls) + require.Positive(t, stats.ReadBytes) + require.Positive(t, stats.Duration) + for i, meta := range metas { + require.Equal(t, int32(0), meta.fileIndex) + require.Equal(t, int32(i), meta.rowGroupIndex) + require.Equal(t, int64(2), meta.numRows) + require.Positive(t, meta.bytes) + } +} + +func TestCompileExternScanParquetLoadUsesRowGroupMetadata(t *testing.T) { + testCompile := NewMockCompile(t) + testCompile.addr = "cn1:6001" + testCompile.anal = &AnalyzeModule{qry: &plan.Query{}} + testCompile.proc.SetResolveVariableFunc(func(varName string, isSystemVar, isGlobalVar bool) (interface{}, error) { + if varName == "sql_mode" { + return "", nil + } + return nil, nil + }) + + data := writeCompileInt32ParquetWithRowGroups(t, []int32{0, 1, 2, 3, 4, 5}, 2) + filePath := filepath.Join(t.TempDir(), "rg.parquet") + require.NoError(t, os.WriteFile(filePath, data, 0o600)) + + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.INFILE, + Filepath: filePath, + Format: tree.PARQUET, + FileSize: int64(len(data)), + Tail: &tree.TailParameter{}, + }, + ExParam: tree.ExParam{ + ExternType: int32(plan.ExternType_LOAD), + Parallel: true, + }, + } + createSQL, err := json.Marshal(param) + require.NoError(t, err) + node := &plan.Node{ + Stats: &plan.Stats{Cost: float64(len(data)), Rowsize: 1}, + TableDef: &plan.TableDef{ + Createsql: string(createSQL), + }, + ExternScan: &plan.ExternScan{ + Type: int32(plan.ExternType_LOAD), + TbColToDataCol: map[string]int32{}, + }, + } + + ss, err := testCompile.compileExternScan(node) + require.NoError(t, err) + require.Len(t, ss, 3) + + seen := make(map[int32]bool) + for _, scope := range ss { + require.NoError(t, checkScopeWithExpectedList(scope, []vm.OpType{vm.External})) + ext := scope.RootOp.(*external.External) + require.False(t, ext.Es.Extern.Parallel) + require.Equal(t, []string{filePath}, ext.Es.FileList) + require.Equal(t, []*pipeline.FileOffset{{Offset: []int64{0, -1}}}, ext.Es.FileOffsetTotal) + require.NotEmpty(t, ext.Es.ParquetRowGroupShards) + for _, shard := range ext.Es.ParquetRowGroupShards { + require.Equal(t, int32(0), shard.FileIndex) + for rowGroupIdx := shard.RowGroupStart; rowGroupIdx < shard.RowGroupEnd; rowGroupIdx++ { + seen[rowGroupIdx] = true + } + } + } + require.Equal(t, map[int32]bool{0: true, 1: true, 2: true}, seen) +} + +func TestCompileExternScanParquetLoadUsesFileFanoutMainPath(t *testing.T) { + testCompile := NewMockCompile(t) + testCompile.addr = "cn1:6001" + testCompile.anal = &AnalyzeModule{qry: &plan.Query{}} + testCompile.proc.SetResolveVariableFunc(func(varName string, isSystemVar, isGlobalVar bool) (interface{}, error) { + if varName == "sql_mode" { + return "", nil + } + return nil, nil + }) + + dir := t.TempDir() + data0 := writeCompileInt32ParquetWithRowGroups(t, []int32{0, 1}, 10) + data1 := writeCompileInt32ParquetWithRowGroups(t, []int32{2, 3}, 10) + file0 := filepath.Join(dir, "part-0.parquet") + file1 := filepath.Join(dir, "part-1.parquet") + require.NoError(t, os.WriteFile(file0, data0, 0o600)) + require.NoError(t, os.WriteFile(file1, data1, 0o600)) + pattern := filepath.Join(dir, "part-*.parquet") + + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.INFILE, + Filepath: pattern, + Format: tree.PARQUET, + FileSize: int64(len(data0) + len(data1)), + Tail: &tree.TailParameter{}, + }, + ExParam: tree.ExParam{ + ExternType: int32(plan.ExternType_LOAD), + Parallel: true, + }, + } + createSQL, err := json.Marshal(param) + require.NoError(t, err) + node := &plan.Node{ + Stats: &plan.Stats{Cost: float64(len(data0) + len(data1)), Rowsize: 1}, + TableDef: &plan.TableDef{ + Createsql: string(createSQL), + }, + ExternScan: &plan.ExternScan{ + Type: int32(plan.ExternType_LOAD), + TbColToDataCol: map[string]int32{}, + }, + } + + ss, err := testCompile.compileExternScan(node) + require.NoError(t, err) + require.Len(t, ss, 2) + + seen := make(map[string]bool) + for _, scope := range ss { + require.NoError(t, checkScopeWithExpectedList(scope, []vm.OpType{vm.External})) + ext := scope.RootOp.(*external.External) + require.False(t, ext.Es.Extern.Parallel) + require.Empty(t, ext.Es.ParquetRowGroupShards) + require.Len(t, ext.Es.FileList, 1) + require.Len(t, ext.Es.FileOffsetTotal, 1) + require.Equal(t, []int64{0, -1}, ext.Es.FileOffsetTotal[0].Offset) + seen[ext.Es.FileList[0]] = true + } + require.Equal(t, map[string]bool{file0: true, file1: true}, seen) +} + +func writeCompileInt32ParquetWithRowGroups(t *testing.T, values []int32, rowsPerGroup int64) []byte { + t.Helper() + var buf bytes.Buffer + schema := parquet.NewSchema("x", parquet.Group{"c": parquet.Leaf(parquet.Int32Type)}) + w := parquet.NewWriter(&buf, schema, parquet.MaxRowsPerRowGroup(rowsPerGroup)) + rows := make([]parquet.Row, len(values)) + for i, value := range values { + rows[i] = parquet.Row{parquet.Int32Value(value).Level(0, 0, 0)} + } + _, err := w.WriteRows(rows) + require.NoError(t, err) + require.NoError(t, w.Close()) + f, err := parquet.OpenFile(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + require.NoError(t, err) + require.Len(t, f.RowGroups(), (len(values)+int(rowsPerGroup)-1)/int(rowsPerGroup)) + return buf.Bytes() +} + func TestCompileBuildSideForBroadcastJoinSkipsEmptyCN(t *testing.T) { testCompile := NewMockCompile(t) testCompile.cnList = engine.Nodes{{Addr: "cn1:6001", Mcpu: 2}, {Addr: "cn2:6001", Mcpu: 2}} diff --git a/pkg/sql/plan/bind_load.go b/pkg/sql/plan/bind_load.go index 22a96a3bd2ccf..3ec94d3942c54 100644 --- a/pkg/sql/plan/bind_load.go +++ b/pkg/sql/plan/bind_load.go @@ -58,6 +58,9 @@ func (builder *QueryBuilder) bindExternalScan( if err := InitNullMap(stmt.Param, ctx); err != nil { return -1, nil, err } + if err := validateLoadParquetOptions(stmt.Param, ctx); err != nil { + return -1, nil, err + } tableDef := DeepCopyTableDef(dmlCtx.tableDefs[0], true) objRef := dmlCtx.objRefs[0] @@ -145,7 +148,7 @@ func (builder *QueryBuilder) bindExternalScan( } stmt.Param.FileStartOff = offset } - stmt.Param.ParallelLoadRequested = stmt.Param.Parallel + stmt.Param.ParallelLoadRequested = stmt.Param.ParallelLoadRequested || stmt.Param.Parallel if stmt.Param.FileSize-offset < int64(LoadParallelMinSize) { stmt.Param.Parallel = false @@ -181,7 +184,7 @@ func (builder *QueryBuilder) bindExternalScan( externalScanNode := &plan.Node{ NodeType: plan.Node_EXTERNAL_SCAN, - Stats: &plan.Stats{}, + Stats: makeLoadExternalStats(stmt.Param, tableDef, offset, ctx.GetContext()), ObjRef: objRef, TableDef: tableDef, ExternScan: &plan.ExternScan{ diff --git a/pkg/sql/plan/build_load.go b/pkg/sql/plan/build_load.go index e7289328edfa5..8d46b2f73a87b 100644 --- a/pkg/sql/plan/build_load.go +++ b/pkg/sql/plan/build_load.go @@ -16,8 +16,10 @@ package plan import ( "bufio" + "context" "encoding/json" "io" + "math" "strings" "time" @@ -30,6 +32,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" "github.com/matrixorigin/matrixone/pkg/sql/util/csvparser" v2 "github.com/matrixorigin/matrixone/pkg/util/metric/v2" + "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/options" ) const ( @@ -211,6 +214,246 @@ func IgnoredLines(param *tree.ExternParam, ctx CompilerContext) (offset int64, e return csvReader.Pos(), nil } +func validateLoadParquetOptions(param *tree.ExternParam, ctx CompilerContext) error { + if param == nil || !isLoadParquetFormat(param) { + return nil + } + if param.Local { + return moerr.NewNYI(ctx.GetContext(), "load parquet local") + } + if loadOptionExists(param, "compression") || hasExplicitLoadCompression(param.CompressType) { + return moerr.NewBadConfig(ctx.GetContext(), "LOAD DATA with format='parquet' does not support compression option") + } + if loadOptionExists(param, "jsondata") || param.JsonData != "" { + return moerr.NewBadConfig(ctx.GetContext(), "LOAD DATA with format='parquet' does not support jsondata option") + } + if loadOptionExists(param, "hive_partitioning") || loadOptionExists(param, "hive_partition_columns") || + param.HivePartitioning || len(param.HivePartitionCols) > 0 { + return moerr.NewBadConfig(ctx.GetContext(), "LOAD DATA with format='parquet' does not support hive partitioning options") + } + if param.Tail == nil { + return nil + } + if param.Tail.Fields != nil { + return moerr.NewBadConfig(ctx.GetContext(), "LOAD DATA with format='parquet' does not support FIELDS option") + } + if param.Tail.Lines != nil { + return moerr.NewBadConfig(ctx.GetContext(), "LOAD DATA with format='parquet' does not support LINES option") + } + if param.Tail.IgnoredLines > 0 { + return moerr.NewBadConfig(ctx.GetContext(), "LOAD DATA with format='parquet' does not support IGNORE LINES") + } + if hasLoadUserVariable(param.Tail.ColumnList) { + return moerr.NewNYI(ctx.GetContext(), "parquet load with @variables in column list") + } + if len(param.Tail.Assignments) > 0 { + return moerr.NewNYI(ctx.GetContext(), "parquet load with SET clause") + } + return nil +} + +func isLoadParquetFormat(param *tree.ExternParam) bool { + if param.Format == tree.PARQUET { + return true + } + for i := 0; i+1 < len(param.Option); i += 2 { + if strings.EqualFold(param.Option[i], "format") && + strings.EqualFold(param.Option[i+1], tree.PARQUET) { + return true + } + } + return false +} + +func loadOptionExists(param *tree.ExternParam, key string) bool { + key = strings.ToLower(key) + for i := 0; i+1 < len(param.Option); i += 2 { + if strings.ToLower(param.Option[i]) == key { + return true + } + } + return false +} + +func hasExplicitLoadCompression(compressType string) bool { + return compressType != "" && !strings.EqualFold(compressType, tree.AUTO) +} + +func hasLoadUserVariable(cols []tree.LoadColumn) bool { + for _, col := range cols { + if _, ok := col.(*tree.VarExpr); ok { + return true + } + } + return false +} + +func makeLoadExternalStats(param *tree.ExternParam, tableDef *TableDef, offset int64, ctx context.Context) *plan.Stats { + // LOAD external scan parallelism is currently sized by + // getParallelSizeForExternalScan as Cost*Rowsize/WriteS3Threshold. + // Keep Cost*Rowsize close to input bytes, but express Cost/Outcnt/BlockNum + // as row/cardinality estimates so large LOAD keeps the expected AP path. + stats := &plan.Stats{Rowsize: 1} + if param == nil { + return stats + } + var inputSize int64 + if param.ScanType == tree.INLINE { + inputSize = int64(len(param.Data)) + } else { + inputSize = param.FileSize - offset + } + if inputSize < 0 { + inputSize = 0 + } + if inputSize == 0 { + return stats + } + + rowSize := estimateLoadRowsize(param, tableDef, inputSize, offset, ctx) + rowCount := math.Ceil(float64(inputSize) / rowSize) + if rowCount < 1 { + rowCount = 1 + } + stats.Cost = rowCount + stats.Outcnt = rowCount + stats.TableCnt = rowCount + stats.Rowsize = rowSize + stats.Selectivity = 1 + stats.BlockNum = int32(math.Ceil(rowCount / float64(options.DefaultBlockMaxRows))) + return stats +} + +func estimateLoadRowsize(param *tree.ExternParam, tableDef *TableDef, inputSize int64, offset int64, ctx context.Context) float64 { + if param != nil && param.ScanType == tree.INLINE && param.Format == tree.CSV { + if rowSize := inlineCSVRowsize(param.Data, loadLinesTerminatedBy(param)); rowSize > 0 { + return clampLoadRowsize(rowSize, inputSize) + } + } + if rowSize := estimateLoadRowsizeFromFirstLine(param, inputSize, offset, ctx); rowSize > 0 { + return rowSize + } + if tableDef != nil { + if rowSize := GetRowSizeFromTableDef(tableDef, true) * 0.8; rowSize > 0 { + return clampLoadRowsize(rowSize, inputSize) + } + } + return clampLoadRowsize(1, inputSize) +} + +func inlineCSVRowsize(data string, terminatedBy string) float64 { + if terminatedBy == "" { + terminatedBy = "\n" + } + if idx := strings.Index(data, terminatedBy); idx >= 0 { + return float64(idx + len(terminatedBy)) + } + return float64(len(data)) +} + +func loadLinesTerminatedBy(param *tree.ExternParam) string { + if param != nil && param.Tail != nil && param.Tail.Lines != nil { + if terminated := param.Tail.Lines.TerminatedBy; terminated != nil && terminated.Value != "" { + return terminated.Value + } + } + return "\n" +} + +func estimateLoadRowsizeFromFirstLine(param *tree.ExternParam, inputSize int64, offset int64, ctx context.Context) float64 { + lineTerminator := loadLinesTerminatedBy(param) + if param == nil || + param.ScanType == tree.INLINE || + param.Local || + param.Format == tree.PARQUET || + getCompressType(param, param.Filepath) != tree.NOCOMPRESS || + (lineTerminator != "\n" && lineTerminator != "\r\n") || + strings.HasPrefix(param.Filepath, "SHARED:/query_result/") { + return 0 + } + + if size := readExternalFirstLineSize(param, offset, ctx); size > 0 { + return clampLoadRowsize(float64(size), inputSize) + } + return 0 +} + +func readExternalFirstLineSize(param *tree.ExternParam, offset int64, ctx context.Context) int { + if param == nil { + return 0 + } + if ctx == nil { + ctx = param.Ctx + } + if ctx == nil { + return 0 + } + + fs, readPath, err := GetForETLWithType(param, param.Filepath) + if err != nil { + return 0 + } + var r io.ReadCloser + vec := fileservice.IOVector{ + FilePath: readPath, + Entries: []fileservice.IOEntry{ + 0: { + Offset: offset, + Size: -1, + ReadCloserForRead: &r, + }, + }, + } + if err = fs.Read(ctx, &vec); err != nil { + return 0 + } + if r == nil { + return 0 + } + defer r.Close() + + reader := bufio.NewReader(r) + if offset == 0 && param.Tail != nil { + for i := uint64(0); i < param.Tail.IgnoredLines; i++ { + if _, err := reader.ReadString('\n'); err != nil { + return 0 + } + } + } + + line, err := reader.ReadString('\n') + if len(line) == 0 && err != nil { + return 0 + } + return len(line) +} + +func clampLoadRowsize(rowSize float64, inputSize int64) float64 { + if rowSize < 1 { + return 1 + } + if inputSize > 0 && rowSize > float64(inputSize) { + return float64(inputSize) + } + return rowSize +} + +func loadParquetMayListFiles(param *tree.ExternParam) bool { + return param != nil && + param.Format == tree.PARQUET && + strings.ContainsAny(strings.TrimSpace(param.Filepath), "*?[") +} + +func totalLoadFileSize(fileSize []int64) int64 { + var total int64 + for _, size := range fileSize { + if size > 0 { + total += size + } + } + return total +} + func buildLoad(stmt *tree.Load, ctx CompilerContext, isPrepareStmt bool) (*Plan, error) { start := time.Now() defer func() { @@ -238,6 +481,9 @@ func buildLoad(stmt *tree.Load, ctx CompilerContext, isPrepareStmt bool) (*Plan, if err := InitNullMap(stmt.Param, ctx); err != nil { return nil, err } + if err := validateLoadParquetOptions(stmt.Param, ctx); err != nil { + return nil, err + } tableDef := tblInfo.tableDefs[0] objRef := tblInfo.objRef[0] originTableDef := tableDef @@ -260,7 +506,7 @@ func buildLoad(stmt *tree.Load, ctx CompilerContext, isPrepareStmt bool) (*Plan, } stmt.Param.FileStartOff = offset } - stmt.Param.ParallelLoadRequested = stmt.Param.Parallel + stmt.Param.ParallelLoadRequested = stmt.Param.ParallelLoadRequested || stmt.Param.Parallel if stmt.Param.FileSize-offset < int64(LoadParallelMinSize) { stmt.Param.Parallel = false @@ -298,7 +544,7 @@ func buildLoad(stmt *tree.Load, ctx CompilerContext, isPrepareStmt bool) (*Plan, externalScanNode := &plan.Node{ NodeType: plan.Node_EXTERNAL_SCAN, - Stats: &plan.Stats{}, + Stats: makeLoadExternalStats(stmt.Param, tableDef, offset, ctx.GetContext()), ProjectList: externalProject, ObjRef: objRef, TableDef: tableDef, @@ -336,7 +582,7 @@ func buildLoad(stmt *tree.Load, ctx CompilerContext, isPrepareStmt bool) (*Plan, builder.qry.LoadWriteS3 = false } - if stmt.Param.Parallel && noCompress { + if stmt.Param.Parallel && noCompress && stmt.Param.Format != tree.PARQUET { projectNode.ProjectList = makeCastExpr(stmt, fileName, originTableDef, projectNode) } lastNodeId = builder.appendNode(projectNode, bindCtx) @@ -415,6 +661,18 @@ func checkFileExist(param *tree.ExternParam, ctx CompilerContext) (string, error } param.Ctx = ctx.GetContext() + if loadParquetMayListFiles(param) { + fileList, fileSize, err := ReadDir(param) + param.Ctx = nil + if err != nil { + return "", err + } + if len(fileList) == 0 { + return "", moerr.NewInvalidInput(ctx.GetContext(), "the file does not exist in load flow") + } + param.FileSize = totalLoadFileSize(fileSize) + return param.Filepath, nil + } if err := StatFile(param); err != nil { if moerror, ok := err.(*moerr.Error); ok { if moerror.ErrorCode() == moerr.ErrFileNotFound { diff --git a/pkg/sql/plan/build_load_parquet_test.go b/pkg/sql/plan/build_load_parquet_test.go new file mode 100644 index 0000000000000..b9f260169fd94 --- /dev/null +++ b/pkg/sql/plan/build_load_parquet_test.go @@ -0,0 +1,543 @@ +// Copyright 2024 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plan + +import ( + "context" + "testing" + + "github.com/matrixorigin/matrixone/pkg/common/moerr" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/fileservice" + pbplan "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/options" + "github.com/stretchr/testify/require" +) + +type parquetLoadTestCtx struct { + CompilerContext + ctx context.Context +} + +func (c parquetLoadTestCtx) GetContext() context.Context { + return c.ctx +} + +func TestValidateLoadParquetOptionsRejectsUnsupportedOptions(t *testing.T) { + ctx := parquetLoadTestCtx{ctx: context.Background()} + + cases := []struct { + name string + mutate func(*tree.ExternParam) + wantCode uint16 + wantError string + }{ + { + name: "compression option", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"compression", "gzip"} + }, + wantCode: moerr.ErrBadConfig, + wantError: "compression option", + }, + { + name: "compression option upper value", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"compression", "GZIP"} + }, + wantCode: moerr.ErrBadConfig, + wantError: "compression option", + }, + { + name: "compression option empty value", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"compression", ""} + }, + wantCode: moerr.ErrBadConfig, + wantError: "compression option", + }, + { + name: "resolved compression field", + mutate: func(param *tree.ExternParam) { + param.CompressType = tree.GZIP + }, + wantCode: moerr.ErrBadConfig, + wantError: "compression option", + }, + { + name: "fields option", + mutate: func(param *tree.ExternParam) { + param.Tail.Fields = &tree.Fields{} + }, + wantCode: moerr.ErrBadConfig, + wantError: "FIELDS option", + }, + { + name: "lines option", + mutate: func(param *tree.ExternParam) { + param.Tail.Lines = &tree.Lines{} + }, + wantCode: moerr.ErrBadConfig, + wantError: "LINES option", + }, + { + name: "ignore lines", + mutate: func(param *tree.ExternParam) { + param.Tail.IgnoredLines = 1 + }, + wantCode: moerr.ErrBadConfig, + wantError: "IGNORE LINES", + }, + { + name: "jsondata option", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"jsondata", "object"} + }, + wantCode: moerr.ErrBadConfig, + wantError: "jsondata option", + }, + { + name: "jsondata value", + mutate: func(param *tree.ExternParam) { + param.JsonData = tree.OBJECT + }, + wantCode: moerr.ErrBadConfig, + wantError: "jsondata option", + }, + { + name: "format parquet followed by jsondata", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"format", "parquet", "jsondata", "object"} + param.Format = tree.JSONLINE + param.JsonData = tree.OBJECT + }, + wantCode: moerr.ErrBadConfig, + wantError: "jsondata option", + }, + { + name: "hive partitioning option", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"hive_partitioning", "true"} + }, + wantCode: moerr.ErrBadConfig, + wantError: "hive partitioning options", + }, + { + name: "hive partition columns option", + mutate: func(param *tree.ExternParam) { + param.Option = []string{"hive_partition_columns", "dt,region"} + }, + wantCode: moerr.ErrBadConfig, + wantError: "hive partitioning options", + }, + { + name: "hive partitioning value", + mutate: func(param *tree.ExternParam) { + param.HivePartitioning = true + }, + wantCode: moerr.ErrBadConfig, + wantError: "hive partitioning options", + }, + { + name: "local", + mutate: func(param *tree.ExternParam) { + param.Local = true + }, + wantCode: moerr.ErrNYI, + wantError: "load parquet local", + }, + { + name: "user variable", + mutate: func(param *tree.ExternParam) { + param.Tail.ColumnList = []tree.LoadColumn{&tree.VarExpr{Name: "v"}} + }, + wantCode: moerr.ErrNYI, + wantError: "parquet load with @variables in column list", + }, + { + name: "user variable mixed with regular column", + mutate: func(param *tree.ExternParam) { + param.Tail.ColumnList = []tree.LoadColumn{ + tree.NewUnresolvedColName("id"), + &tree.VarExpr{Name: "v"}, + } + }, + wantCode: moerr.ErrNYI, + wantError: "parquet load with @variables in column list", + }, + { + name: "set clause", + mutate: func(param *tree.ExternParam) { + param.Tail.Assignments = tree.UpdateExprs{&tree.UpdateExpr{}} + }, + wantCode: moerr.ErrNYI, + wantError: "parquet load with SET clause", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + Tail: &tree.TailParameter{}, + }, + } + tc.mutate(param) + + err := validateLoadParquetOptions(param, ctx) + require.Error(t, err) + require.True(t, moerr.IsMoErrCode(err, tc.wantCode), "unexpected error: %v", err) + require.Contains(t, err.Error(), tc.wantError) + }) + } +} + +func TestValidateLoadParquetOptionsAllowsPlainParquet(t *testing.T) { + ctx := parquetLoadTestCtx{ctx: context.Background()} + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + Tail: &tree.TailParameter{}, + Option: []string{"format", "parquet"}, + }, + } + + require.NoError(t, validateLoadParquetOptions(param, ctx)) +} + +func TestValidateLoadParquetOptionsAllowsStageDefaultCompressionAuto(t *testing.T) { + ctx := parquetLoadTestCtx{ctx: context.Background()} + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + CompressType: tree.AUTO, + Tail: &tree.TailParameter{}, + }, + } + + require.NoError(t, validateLoadParquetOptions(param, ctx)) +} + +func TestValidateLoadParquetOptionsUsesResolvedFormatForS3(t *testing.T) { + ctx := parquetLoadTestCtx{ctx: context.Background()} + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.S3, + Format: tree.PARQUET, + Tail: &tree.TailParameter{}, + Option: []string{"jsondata", "object"}, + }, + } + + err := validateLoadParquetOptions(param, ctx) + require.Error(t, err) + require.True(t, moerr.IsMoErrCode(err, moerr.ErrBadConfig)) + require.Contains(t, err.Error(), "jsondata option") +} + +func TestValidateLoadParquetOptionsUsesOptionFormatForLocal(t *testing.T) { + ctx := parquetLoadTestCtx{ctx: context.Background()} + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.CSV, + Tail: &tree.TailParameter{}, + Option: []string{"filepath", "/tmp/data.parquet", "format", "parquet"}, + }, + ExParam: tree.ExParam{ + Local: true, + }, + } + + err := validateLoadParquetOptions(param, ctx) + require.Error(t, err) + require.True(t, moerr.IsMoErrCode(err, moerr.ErrNYI)) + require.Contains(t, err.Error(), "load parquet local") +} + +func TestValidateLoadParquetOptionsIgnoresNonParquet(t *testing.T) { + ctx := parquetLoadTestCtx{ctx: context.Background()} + param := &tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.JSONLINE, + Tail: &tree.TailParameter{}, + Option: []string{"jsondata", "object"}, + }, + ExParam: tree.ExParam{ + JsonData: tree.OBJECT, + }, + } + + require.NoError(t, validateLoadParquetOptions(param, ctx)) +} + +func TestMakeLoadExternalStatsUsesInputBytes(t *testing.T) { + tableDef := &TableDef{ + Cols: []*ColDef{ + {Name: "a", Typ: Type{Id: int32(types.T_int64), Width: 64}}, + {Name: "b", Typ: Type{Id: int32(types.T_char), Width: 1}}, + }, + } + stats := makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{FileSize: 100}, + }, tableDef, 25, context.Background()) + require.GreaterOrEqual(t, stats.Cost, float64(1)) + require.GreaterOrEqual(t, stats.Rowsize, float64(1)) + requireLoadByteHint(t, stats, 75) + require.Equal(t, stats.Cost, stats.TableCnt) + require.Equal(t, stats.Cost, stats.Outcnt) + + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{FileSize: 10}, + }, tableDef, 20, context.Background()) + require.Equal(t, float64(0), stats.Cost) + require.Equal(t, float64(1), stats.Rowsize) + require.Equal(t, int32(0), stats.BlockNum) + + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.INLINE, + Format: tree.CSV, + Data: "1,2\n3,4\n", + }, + }, tableDef, 0, context.Background()) + require.Equal(t, float64(4), stats.Rowsize) + require.Equal(t, float64(2), stats.Cost) + requireLoadByteHint(t, stats, 8) + + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.INLINE, + Format: tree.CSV, + Data: "a\nb\nc\n", + }, + }, tableDef, 0, context.Background()) + require.Equal(t, float64(2), stats.Rowsize) + require.Equal(t, float64(3), stats.Cost) + requireLoadByteHint(t, stats, 6) + + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + ScanType: tree.INLINE, + Format: tree.CSV, + Data: "a|b|c|", + Tail: &tree.TailParameter{ + Lines: &tree.Lines{ + TerminatedBy: &tree.Terminated{Value: "|"}, + }, + }, + }, + }, tableDef, 0, context.Background()) + require.Equal(t, float64(2), stats.Rowsize) + require.Equal(t, float64(3), stats.Cost) + requireLoadByteHint(t, stats, 6) + + rowSize := GetRowSizeFromTableDef(tableDef, true) * 0.8 + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{FileSize: int64(float64(options.DefaultBlockMaxRows) * rowSize)}, + }, tableDef, 0, context.Background()) + require.Equal(t, int32(1), stats.BlockNum) +} + +func TestMakeLoadExternalStatsKeepsLargeLoadMultiCN(t *testing.T) { + tableDef := &TableDef{ + Cols: []*ColDef{ + {Name: "a", Typ: Type{Id: int32(types.T_int64), Width: 64}}, + {Name: "b", Typ: Type{Id: int32(types.T_char), Width: 1}}, + }, + } + inputSize := int64(float64(options.DefaultBlockMaxRows) * GetRowSizeFromTableDef(tableDef, true) * 0.8 * float64(BlockThresholdForOneCN+1)) + stats := makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{FileSize: inputSize}, + }, tableDef, 0, context.Background()) + require.Greater(t, stats.BlockNum, int32(BlockThresholdForOneCN)) + require.Greater(t, stats.Cost, float64(costThresholdForOneCN)) + require.Equal(t, ExecTypeAP_MULTICN, GetExecType(&Query{ + Nodes: []*Node{{ + NodeType: pbplan.Node_EXTERNAL_SCAN, + Stats: stats, + }}, + Steps: []int32{0}, + }, false, false)) +} + +func TestMakeLoadExternalStatsUsesFirstLineForTextLoad(t *testing.T) { + ctx := context.Background() + fs, err := fileservice.NewMemoryFS("memory", fileservice.DisabledCacheConfig, nil) + require.NoError(t, err) + filePath := fileservice.JoinPath(fs.Name(), "wide.csv") + require.NoError(t, fs.Write(ctx, fileservice.IOVector{ + FilePath: filePath, + Entries: []fileservice.IOEntry{{ + Offset: 0, + Size: int64(len("1,2\n3,4\n")), + Data: []byte("1,2\n3,4\n"), + }}, + })) + + tableDef := &TableDef{ + Cols: []*ColDef{ + {Name: "a", Typ: Type{Id: int32(types.T_varchar), Width: 65535}}, + {Name: "b", Typ: Type{Id: int32(types.T_varchar), Width: 65535}}, + }, + } + inputSize := int64(4 * options.DefaultBlockMaxRows * (BlockThresholdForOneCN + 1)) + stats := makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Filepath: filePath, + FileSize: inputSize, + Format: tree.CSV, + }, + ExParam: tree.ExParam{ + FileService: fs, + Ctx: ctx, + }, + }, tableDef, 0, context.Background()) + + require.Equal(t, float64(4), stats.Rowsize) + require.Greater(t, stats.BlockNum, int32(BlockThresholdForOneCN)) + require.Equal(t, ExecTypeAP_MULTICN, GetExecType(&Query{ + Nodes: []*Node{{ + NodeType: pbplan.Node_EXTERNAL_SCAN, + Stats: stats, + }}, + Steps: []int32{0}, + }, false, false)) + + crlfPath := fileservice.JoinPath(fs.Name(), "crlf.csv") + require.NoError(t, fs.Write(ctx, fileservice.IOVector{ + FilePath: crlfPath, + Entries: []fileservice.IOEntry{{ + Offset: 0, + Size: int64(len("1,2\r\n3,4\r\n")), + Data: []byte("1,2\r\n3,4\r\n"), + }}, + })) + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Filepath: crlfPath, + FileSize: inputSize, + Format: tree.CSV, + Tail: &tree.TailParameter{ + Lines: &tree.Lines{ + TerminatedBy: &tree.Terminated{Value: "\r\n"}, + }, + }, + }, + ExParam: tree.ExParam{ + FileService: fs, + Ctx: ctx, + }, + }, tableDef, 0, context.Background()) + require.Equal(t, float64(5), stats.Rowsize) + + ignoredPath := fileservice.JoinPath(fs.Name(), "ignored.csv") + require.NoError(t, fs.Write(ctx, fileservice.IOVector{ + FilePath: ignoredPath, + Entries: []fileservice.IOEntry{{ + Offset: 0, + Size: int64(len("long_header_value\n1,2\n3,4\n")), + Data: []byte("long_header_value\n1,2\n3,4\n"), + }}, + })) + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Filepath: ignoredPath, + FileSize: inputSize, + Format: tree.CSV, + Tail: &tree.TailParameter{ + IgnoredLines: 1, + }, + }, + ExParam: tree.ExParam{ + FileService: fs, + Ctx: ctx, + }, + }, tableDef, 0, context.Background()) + require.Equal(t, float64(4), stats.Rowsize) + + schemaRowSize := GetRowSizeFromTableDef(tableDef, true) * 0.8 + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Filepath: filePath, + FileSize: inputSize, + Format: tree.CSV, + CompressType: tree.GZIP, + }, + ExParam: tree.ExParam{ + FileService: fs, + Ctx: ctx, + }, + }, tableDef, 0, context.Background()) + require.Equal(t, schemaRowSize, stats.Rowsize) + + stats = makeLoadExternalStats(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Filepath: filePath, + FileSize: inputSize, + Format: tree.CSV, + Tail: &tree.TailParameter{ + Lines: &tree.Lines{ + TerminatedBy: &tree.Terminated{Value: "|"}, + }, + }, + }, + ExParam: tree.ExParam{ + FileService: fs, + Ctx: ctx, + }, + }, tableDef, 0, context.Background()) + require.Equal(t, schemaRowSize, stats.Rowsize) +} + +func requireLoadByteHint(t *testing.T, stats *Stats, inputSize int64) { + t.Helper() + estimatedBytes := stats.Cost * stats.Rowsize + require.GreaterOrEqual(t, estimatedBytes, float64(inputSize)) + require.Less(t, estimatedBytes, float64(inputSize)+stats.Rowsize) +} + +func TestLoadParquetMayListFiles(t *testing.T) { + require.True(t, loadParquetMayListFiles(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + Filepath: "/data/*.parquet", + }, + })) + require.True(t, loadParquetMayListFiles(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + Filepath: "/data/part-??.parquet", + }, + })) + require.False(t, loadParquetMayListFiles(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.PARQUET, + Filepath: "/data/part-000.parquet", + }, + })) + require.False(t, loadParquetMayListFiles(&tree.ExternParam{ + ExParamConst: tree.ExParamConst{ + Format: tree.CSV, + Filepath: "/data/*.csv", + }, + })) +} + +func TestTotalLoadFileSize(t *testing.T) { + require.Equal(t, int64(0), totalLoadFileSize(nil)) + require.Equal(t, int64(30), totalLoadFileSize([]int64{10, -1, 20, 0})) +} diff --git a/pkg/sql/plan/external.go b/pkg/sql/plan/external.go index 6dbb8c3766ba5..13ed0953af7b0 100644 --- a/pkg/sql/plan/external.go +++ b/pkg/sql/plan/external.go @@ -15,17 +15,14 @@ package plan import ( - "bufio" "context" "encoding/json" - "io" "strings" "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/container/batch" "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/container/vector" - "github.com/matrixorigin/matrixone/pkg/fileservice" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/colexec" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" @@ -153,7 +150,7 @@ func getExternalStats(node *plan.Node, builder *QueryBuilder) *Stats { totolSize := len(externScan.Data) lineSize := float64(0.0) if externScan.Format == tree.CSV { - lineSize = float64(strings.Index(externScan.Data, "\n")) + lineSize = inlineCSVRowsize(externScan.Data, "\n") } if externScan.Format == tree.JSONLINE { @@ -220,28 +217,10 @@ func getExternalStats(node *plan.Node, builder *QueryBuilder) *Stats { return DefaultStats() } - //read one line - fs, readPath, err := GetForETLWithType(param, param.Filepath) - if err != nil { - return DefaultHugeStats() - } - var r io.ReadCloser - vec := fileservice.IOVector{ - FilePath: readPath, - Entries: []fileservice.IOEntry{ - 0: { - Offset: 0, - Size: -1, - ReadCloserForRead: &r, - }, - }, - } - if err = fs.Read(param.Ctx, &vec); err != nil { + size := readExternalFirstLineSize(param, 0, param.Ctx) + if size == 0 { return DefaultHugeStats() } - r2 := bufio.NewReader(r) - line, _ := r2.ReadString('\n') - size := len(line) cost = cost / float64(size) return &plan.Stats{ diff --git a/pkg/sql/plan/utils.go b/pkg/sql/plan/utils.go index b2bb878677d6f..4f1c214f7cd8a 100644 --- a/pkg/sql/plan/utils.go +++ b/pkg/sql/plan/utils.go @@ -2087,6 +2087,11 @@ func InitStageS3Param(param *tree.ExternParam, s stage.StageDef) error { continue } switch key { + case "filepath": + // stage:// paths have already been expanded to s.Url by + // InitInfileOrStageParam. Keep the raw option for show/serialization + // compatibility, but never let it override the resolved S3 prefix. + continue case "format": format := strings.ToLower(param.Option[i+1]) if format != tree.CSV && format != tree.JSONLINE && format != tree.PARQUET { diff --git a/pkg/sql/plan/utils_test.go b/pkg/sql/plan/utils_test.go index 3761622005636..5bddf91526a52 100644 --- a/pkg/sql/plan/utils_test.go +++ b/pkg/sql/plan/utils_test.go @@ -1446,6 +1446,21 @@ func TestInitStageS3Param_HappyAndErrors(t *testing.T) { require.Error(t, InitStageS3Param(param, sd)) }) + t.Run("stage_filepath_option_ignored", func(t *testing.T) { + param := &tree.ExternParam{} + param.Ctx = context.Background() + param.Option = []string{"filepath", "stage://pq_stage/data.parquet", "format", "parquet"} + sd := stage.StageDef{ + Url: parse("s3://b/prefix/data.parquet"), + Credentials: baseCreds, + } + require.NoError(t, InitStageS3Param(param, sd)) + assert.Equal(t, tree.S3, param.ScanType) + assert.Equal(t, "parquet", param.Format) + assert.Equal(t, "b", param.S3Param.Bucket) + assert.Equal(t, "/prefix/data.parquet", param.Filepath) + }) + t.Run("jsonline_without_jsondata", func(t *testing.T) { param := &tree.ExternParam{} param.Ctx = context.Background() diff --git a/pkg/vm/process/operator_analyzer.go b/pkg/vm/process/operator_analyzer.go index b554562731a72..3da9c699154b5 100644 --- a/pkg/vm/process/operator_analyzer.go +++ b/pkg/vm/process/operator_analyzer.go @@ -56,6 +56,7 @@ type Analyzer interface { AddFileServiceCacheInfo(counter *perfcounter.CounterSet) AddDiskIO(counter *perfcounter.CounterSet) AddReadSizeInfo(counter *perfcounter.CounterSet) + AddParquetProfile(stats ParquetProfileStats) GetOpCounterSet() *perfcounter.CounterSet GetOpStats() *OperatorStats @@ -66,6 +67,36 @@ type Analyzer interface { Reset() } +type ParquetProfileStats struct { + Files int64 + RowGroups int64 + RowsRead int64 + BytesRead int64 + PrefetchBytes int64 + OpenTime int64 + ReadPageTime int64 + MapTime int64 + RowModeTime int64 + PeakBatchBytes int64 +} + +func (stats ParquetProfileStats) Empty() bool { + return stats == ParquetProfileStats{} +} + +func (stats *ParquetProfileStats) Add(delta ParquetProfileStats) { + stats.Files += delta.Files + stats.RowGroups += delta.RowGroups + stats.RowsRead += delta.RowsRead + stats.BytesRead += delta.BytesRead + stats.PrefetchBytes += delta.PrefetchBytes + stats.OpenTime += delta.OpenTime + stats.ReadPageTime += delta.ReadPageTime + stats.MapTime += delta.MapTime + stats.RowModeTime += delta.RowModeTime + stats.PeakBatchBytes = max(stats.PeakBatchBytes, delta.PeakBatchBytes) +} + // Operator Resource operatorAnalyzer type operatorAnalyzer struct { nodeIdx int @@ -332,6 +363,23 @@ func (opAlyzr *operatorAnalyzer) AddReadSizeInfo(counter *perfcounter.CounterSet opAlyzr.opStats.DiskReadSize += counter.FileService.DiskReadSize.Load() } +func (opAlyzr *operatorAnalyzer) AddParquetProfile(stats ParquetProfileStats) { + if opAlyzr.opStats == nil { + panic("operatorAnalyzer.AddParquetProfile: operatorAnalyzer.opStats is nil") + } + + opAlyzr.opStats.ParquetFiles += stats.Files + opAlyzr.opStats.ParquetRowGroups += stats.RowGroups + opAlyzr.opStats.ParquetRowsRead += stats.RowsRead + opAlyzr.opStats.ParquetBytesRead += stats.BytesRead + opAlyzr.opStats.ParquetPrefetchBytes += stats.PrefetchBytes + opAlyzr.opStats.ParquetOpenTime += stats.OpenTime + opAlyzr.opStats.ParquetReadPageTime += stats.ReadPageTime + opAlyzr.opStats.ParquetMapTime += stats.MapTime + opAlyzr.opStats.ParquetRowModeTime += stats.RowModeTime + opAlyzr.opStats.ParquetPeakBatchBytes = max(opAlyzr.opStats.ParquetPeakBatchBytes, stats.PeakBatchBytes) +} + func (opAlyzr *operatorAnalyzer) GetOpStats() *OperatorStats { if opAlyzr.opStats == nil { panic("operatorAnalyzer.GetOpStats(): operatorAnalyzer.opStats is nil") @@ -378,6 +426,17 @@ type OperatorStats struct { CacheRemoteRead int64 `json:"CacheRemoteRead,omitempty"` CacheRemoteHit int64 `json:"CacheRemoteHit,omitempty"` + ParquetFiles int64 `json:"ParquetFiles,omitempty"` + ParquetRowGroups int64 `json:"ParquetRowGroups,omitempty"` + ParquetRowsRead int64 `json:"ParquetRowsRead,omitempty"` + ParquetBytesRead int64 `json:"ParquetBytesRead,omitempty"` + ParquetPrefetchBytes int64 `json:"ParquetPrefetchBytes,omitempty"` + ParquetOpenTime int64 `json:"ParquetOpenTime,omitempty"` + ParquetReadPageTime int64 `json:"ParquetReadPageTime,omitempty"` + ParquetMapTime int64 `json:"ParquetMapTime,omitempty"` + ParquetRowModeTime int64 `json:"ParquetRowModeTime,omitempty"` + ParquetPeakBatchBytes int64 `json:"ParquetPeakBatchBytes,omitempty"` + OperatorMetrics map[MetricType]int64 `json:"OperatorMetrics,omitempty"` BackgroundQueries []*plan.Query `json:"BackgroundQueries,omitempty"` @@ -499,6 +558,36 @@ func (ps *OperatorStats) String() string { if ps.CacheRemoteHit > 0 { dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("CacheRemoteHit:%d ", ps.CacheRemoteHit)) } + if ps.ParquetFiles > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetFiles:%d ", ps.ParquetFiles)) + } + if ps.ParquetRowGroups > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetRowGroups:%d ", ps.ParquetRowGroups)) + } + if ps.ParquetRowsRead > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetRowsRead:%d ", ps.ParquetRowsRead)) + } + if ps.ParquetBytesRead > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetBytesRead:%dbytes ", ps.ParquetBytesRead)) + } + if ps.ParquetPrefetchBytes > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetPrefetchBytes:%dbytes ", ps.ParquetPrefetchBytes)) + } + if ps.ParquetOpenTime > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetOpenTime:%dns ", ps.ParquetOpenTime)) + } + if ps.ParquetReadPageTime > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetReadPageTime:%dns ", ps.ParquetReadPageTime)) + } + if ps.ParquetMapTime > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetMapTime:%dns ", ps.ParquetMapTime)) + } + if ps.ParquetRowModeTime > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetRowModeTime:%dns ", ps.ParquetRowModeTime)) + } + if ps.ParquetPeakBatchBytes > 0 { + dynamicAttrs = append(dynamicAttrs, fmt.Sprintf("ParquetPeakBatchBytes:%dbytes ", ps.ParquetPeakBatchBytes)) + } // Join and append S3 stats if any if len(dynamicAttrs) > 0 { diff --git a/pkg/vm/process/operator_analyzer_test.go b/pkg/vm/process/operator_analyzer_test.go index a67e4c16a878e..c0d61e37fe44a 100644 --- a/pkg/vm/process/operator_analyzer_test.go +++ b/pkg/vm/process/operator_analyzer_test.go @@ -498,3 +498,55 @@ func Test_operatorAnalyzer_AddReadSizeInfo(t *testing.T) { }) } } + +func Test_operatorAnalyzer_AddParquetProfile(t *testing.T) { + opAlyzr := NewTempAnalyzer() + opAlyzr.AddParquetProfile(ParquetProfileStats{ + Files: 1, + RowGroups: 2, + RowsRead: 100, + BytesRead: 1024, + PrefetchBytes: 512, + OpenTime: 10, + ReadPageTime: 20, + MapTime: 30, + RowModeTime: 40, + PeakBatchBytes: 4096, + }) + opAlyzr.AddParquetProfile(ParquetProfileStats{ + Files: 2, + RowGroups: 3, + RowsRead: 200, + BytesRead: 2048, + PrefetchBytes: 256, + OpenTime: 11, + ReadPageTime: 21, + MapTime: 31, + RowModeTime: 41, + PeakBatchBytes: 1024, + }) + + stats := opAlyzr.GetOpStats() + assert.Equal(t, int64(3), stats.ParquetFiles) + assert.Equal(t, int64(5), stats.ParquetRowGroups) + assert.Equal(t, int64(300), stats.ParquetRowsRead) + assert.Equal(t, int64(3072), stats.ParquetBytesRead) + assert.Equal(t, int64(768), stats.ParquetPrefetchBytes) + assert.Equal(t, int64(21), stats.ParquetOpenTime) + assert.Equal(t, int64(41), stats.ParquetReadPageTime) + assert.Equal(t, int64(61), stats.ParquetMapTime) + assert.Equal(t, int64(81), stats.ParquetRowModeTime) + assert.Equal(t, int64(4096), stats.ParquetPeakBatchBytes) + + got := stats.String() + assert.Contains(t, got, "ParquetFiles:3 ") + assert.Contains(t, got, "ParquetRowGroups:5 ") + assert.Contains(t, got, "ParquetRowsRead:300 ") + assert.Contains(t, got, "ParquetBytesRead:3072bytes ") + assert.Contains(t, got, "ParquetPrefetchBytes:768bytes ") + assert.Contains(t, got, "ParquetOpenTime:21ns ") + assert.Contains(t, got, "ParquetReadPageTime:41ns ") + assert.Contains(t, got, "ParquetMapTime:61ns ") + assert.Contains(t, got, "ParquetRowModeTime:81ns ") + assert.Contains(t, got, "ParquetPeakBatchBytes:4096bytes ") +} diff --git a/proto/pipeline.proto b/proto/pipeline.proto index ef2fb8b449f9d..932142092bb1b 100644 --- a/proto/pipeline.proto +++ b/proto/pipeline.proto @@ -353,6 +353,14 @@ message file_offset { repeated int64 offset = 1; } +message parquet_row_group_shard { + int32 file_index = 1; + int32 row_group_start = 2; + int32 row_group_end = 3; + int64 num_rows = 4; + int64 bytes = 5; +} + message ExternalScan { repeated plan.ExternAttr attrs = 1 [(gogoproto.nullable) = false]; repeated int64 file_size = 2; @@ -366,6 +374,7 @@ message ExternalScan { int32 column_list_len = 10; bool parallel_load = 11; bool load_empty_numeric_as_zero = 12; + repeated parquet_row_group_shard parquet_row_group_shards = 13; } message StreamScan { diff --git a/test/distributed/cases/load_data/load_data_parquet.result b/test/distributed/cases/load_data/load_data_parquet.result index 94cdc5bbfca88..c18bc2cfd5387 100644 --- a/test/distributed/cases/load_data/load_data_parquet.result +++ b/test/distributed/cases/load_data/load_data_parquet.result @@ -13,6 +13,38 @@ select * from t1; 12 ¦ user12 𝄀 15 ¦ user15 𝄀 16 ¦ user16 +create table pq_parallel_guard(id bigint, name varchar); +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_parallel_guard parallel 'true'; +select count(*) from pq_parallel_guard; +➤ count(*)[-5,64,0] 𝄀 +8 +drop table pq_parallel_guard; +create table pq_option_reject(id bigint, name varchar); +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet', 'compression'='gzip'} into table pq_option_reject; +invalid configuration: LOAD DATA with format='parquet' does not support compression option +load data infile {'filepath'='$resources/load_data/simple.parq', 'jsondata'='object', 'format'='parquet'} into table pq_option_reject; +invalid configuration: LOAD DATA with format='parquet' does not support jsondata option +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet', 'jsondata'='object'} into table pq_option_reject; +invalid configuration: LOAD DATA with format='parquet' does not support jsondata option +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet', 'hive_partitioning'='true'} into table pq_option_reject; +invalid configuration: LOAD DATA with format='parquet' does not support hive partitioning options +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject fields terminated by ','; +invalid configuration: LOAD DATA with format='parquet' does not support FIELDS option +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject lines terminated by '\n'; +invalid configuration: LOAD DATA with format='parquet' does not support LINES option +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject ignore 1 lines; +invalid configuration: LOAD DATA with format='parquet' does not support IGNORE LINES +load data local infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject; +load parquet local is not yet implemented +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject (id, @name); +parquet load with @variables in column list is not yet implemented +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject set name=nullif(name,'x'); +parquet load with SET clause is not yet implemented +drop table pq_option_reject; +create table pq_column_error(missing_col int); +load data infile {'filepath'='$resources/parquet/supported_types.parquet', 'format'='parquet'} into table pq_column_error; +invalid input: column missing_col not found +drop table pq_column_error; create table t2(id bigint not null, name varchar not null, sex bool, f32 float(5,2)); load data infile {'filepath'='$resources/load_data/simple2.parq', 'format'='parquet'} into table t2; select * from t2; @@ -127,9 +159,9 @@ CREATE TABLE `parquet_complex_types` ( load data infile {'filepath'='$resources/parquet/complex.parquet', 'format'='parquet'} into table parquet_complex_types; select * from parquet_complex_types order by test_id limit 3; ➤ test_id[-5,64,0] ¦ decimal_small[3,10,2] ¦ decimal_medium[3,18,4] ¦ decimal_large[3,38,6] ¦ timestamp_utc[93,64,0] ¦ timestamp_micros[93,64,0] ¦ timestamp_millis[93,64,0] ¦ map_string_int[-1,2147483647,0] ¦ map_string_string[-1,2147483647,0] ¦ struct_simple[-1,2147483647,0] ¦ struct_nested[-1,2147483647,0] ¦ list_int[-1,2147483647,0] ¦ list_string[-1,2147483647,0] ¦ list_struct[-1,2147483647,0] 𝄀 -0 ¦ -84956395.77 ¦ 48549239237439.8438 ¦ -3657605477313590430068499283968.000000 ¦ 2022-07-24 14:35:36 ¦ 2023-12-13 18:32:17.360544000 ¦ 2023-09-11 02:26:10.731000000 ¦ {"key_0": 469} ¦ {"key_0": "value_844", "key_1": "value_15"} ¦ {"age": 74, "name": "user_1305"} ¦ {"user": {"name": "user_4084", "profile": {"city": "Guangzhou", "country": "China"}}} ¦ [114, 625, 391, 582, 144, 912, 593] ¦ ["item_858", "item_97", "item_4", "item_211", "item_726", "item_406"] ¦ [{"id": 0, "name": "item_0"}, {"id": 1, "name": "item_1"}, {"id": 2, "name": "item_2"}, {"id": 3, "name": "item_3"}, {"id": 4, "name": "item_4"}] 𝄀 -1 ¦ 5912648.53 ¦ -94462690481611.3281 ¦ 2937746366852315562009560088576.000000 ¦ 2023-12-02 02:34:01 ¦ 2022-03-13 01:41:19.637821000 ¦ 2023-12-22 18:27:03.641000000 ¦ {"key_0": 889, "key_1": 817, "key_2": 386} ¦ {"key_0": "value_100", "key_1": "value_307", "key_2": "value_134", "key_3": "value_133"} ¦ {"age": 34, "name": "user_9994"} ¦ {"user": {"name": "user_5901", "profile": {"city": "Guangzhou", "country": "China"}}} ¦ [437] ¦ ["item_642", "item_911", "item_778"] ¦ [{"id": 0, "name": "item_0"}, {"id": 1, "name": "item_1"}, {"id": 2, "name": "item_2"}, {"id": 3, "name": "item_3"}, {"id": 4, "name": "item_4"}] 𝄀 -2 ¦ -1488207.76 ¦ 54995689092595.9688 ¦ 5839644290979080503870820974592.000000 ¦ 2022-04-29 03:19:37 ¦ 2020-09-23 03:59:34.427134000 ¦ 2022-08-10 11:33:01.333000000 ¦ {"key_0": 262} ¦ {"key_0": "value_363", "key_1": "value_769", "key_2": "value_931", "key_3": "value_216", "key_4": "value_514"} ¦ {"age": 69, "name": "user_2212"} ¦ {"user": {"name": "user_6646", "profile": {"city": "Guangzhou", "country": "China"}}} ¦ [271, 499, 705, 309, 964, 921, 734] ¦ ["item_145", "item_396", "item_985"] ¦ [{"id": 0, "name": "item_0"}, {"id": 1, "name": "item_1"}, {"id": 2, "name": "item_2"}, {"id": 3, "name": "item_3"}, {"id": 4, "name": "item_4"}] +0 ¦ -84956395.77 ¦ 48549239237439.8438 ¦ -3657605477313590430068499283968.000000 ¦ 2022-07-24 22:35:36 ¦ 2023-12-14 02:32:17.360544000 ¦ 2023-09-11 10:26:10.731000000 ¦ {"key_0": 469} ¦ {"key_0": "value_844", "key_1": "value_15"} ¦ {"age": 74, "name": "user_1305"} ¦ {"user": {"name": "user_4084", "profile": {"city": "Guangzhou", "country": "China"}}} ¦ [114, 625, 391, 582, 144, 912, 593] ¦ ["item_858", "item_97", "item_4", "item_211", "item_726", "item_406"] ¦ [{"id": 0, "name": "item_0"}, {"id": 1, "name": "item_1"}, {"id": 2, "name": "item_2"}, {"id": 3, "name": "item_3"}, {"id": 4, "name": "item_4"}] 𝄀 +1 ¦ 5912648.53 ¦ -94462690481611.3281 ¦ 2937746366852315562009560088576.000000 ¦ 2023-12-02 10:34:01 ¦ 2022-03-13 09:41:19.637821000 ¦ 2023-12-23 02:27:03.641000000 ¦ {"key_0": 889, "key_1": 817, "key_2": 386} ¦ {"key_0": "value_100", "key_1": "value_307", "key_2": "value_134", "key_3": "value_133"} ¦ {"age": 34, "name": "user_9994"} ¦ {"user": {"name": "user_5901", "profile": {"city": "Guangzhou", "country": "China"}}} ¦ [437] ¦ ["item_642", "item_911", "item_778"] ¦ [{"id": 0, "name": "item_0"}, {"id": 1, "name": "item_1"}, {"id": 2, "name": "item_2"}, {"id": 3, "name": "item_3"}, {"id": 4, "name": "item_4"}] 𝄀 +2 ¦ -1488207.76 ¦ 54995689092595.9688 ¦ 5839644290979080503870820974592.000000 ¦ 2022-04-29 11:19:37 ¦ 2020-09-23 11:59:34.427134000 ¦ 2022-08-10 19:33:01.333000000 ¦ {"key_0": 262} ¦ {"key_0": "value_363", "key_1": "value_769", "key_2": "value_931", "key_3": "value_216", "key_4": "value_514"} ¦ {"age": 69, "name": "user_2212"} ¦ {"user": {"name": "user_6646", "profile": {"city": "Guangzhou", "country": "China"}}} ¦ [271, 499, 705, 309, 964, 921, 734] ¦ ["item_145", "item_396", "item_985"] ¦ [{"id": 0, "name": "item_0"}, {"id": 1, "name": "item_1"}, {"id": 2, "name": "item_2"}, {"id": 3, "name": "item_3"}, {"id": 4, "name": "item_4"}] SELECT COUNT(*) FROM parquet_complex_types; ➤ COUNT(*)[-5,64,0] 𝄀 1000 @@ -213,6 +245,18 @@ load data infile {'filepath'='$resources/parquet/test_brotli.parquet', 'format'= select 'NONE' as codec, count(*), sum(value) from brotli_compression; ➤ codec[12,-1,0] ¦ count(*)[-5,64,0] ¦ sum(value)[8,54,0] 𝄀 NONE ¦ 9 ¦ 55.35 +drop table if exists pq_multi_file_pattern; +CREATE TABLE pq_multi_file_pattern ( +id BIGINT, +name VARCHAR(100), +value DOUBLE, +status BOOL +); +load data infile {'filepath'='$resources/parquet/test_[bgnslz]*.parquet', 'format'='parquet'} into table pq_multi_file_pattern parallel 'true'; +select count(*), cast(round(sum(value), 2) as decimal(10,2)) as total_value, min(id), max(id) from pq_multi_file_pattern; +➤ count(*)[-5,64,0] ¦ total_value[3,10,2] ¦ min(id)[-5,64,0] ¦ max(id)[-5,64,0] 𝄀 +54 ¦ 332.10 ¦ 1 ¦ 9 +drop table pq_multi_file_pattern; drop table if exists pq_version_compare; CREATE TABLE `pq_version_compare` ( `id` bigint DEFAULT NULL, @@ -335,6 +379,13 @@ select int32_col,int64_col,uint32_col,float32_col,float64_col,string_col,bool_co select count(*) from pq_supported_types; ➤ count(*)[-5,64,0] 𝄀 3 +drop table if exists pq_extra_parquet_columns; +create table pq_extra_parquet_columns(int32_col int); +load data infile {'filepath'='$resources/parquet/supported_types.parquet', 'format'='parquet'} into table pq_extra_parquet_columns; +select count(*), min(int32_col), max(int32_col) from pq_extra_parquet_columns; +➤ count(*)[-5,64,0] ¦ min(int32_col)[4,32,0] ¦ max(int32_col)[4,32,0] 𝄀 +3 ¦ 1 ¦ 3 +drop table pq_extra_parquet_columns; drop table if exists pq_new_types; create table pq_new_types ( col_int8 TINYINT, @@ -439,4 +490,46 @@ load data infile {'filepath'='$resources/parquet/Iris.parquet', 'format'='parque select count(*) from parquet_01; ➤ count(*)[-5,64,0] 𝄀 150 +drop table if exists pq_date32_datetime; +create table pq_date32_datetime(date_col datetime, value int); +load data infile {'filepath'='$resources/load_data/date32_datetime.parq', 'format'='parquet'} into table pq_date32_datetime; +select * from pq_date32_datetime; +➤ date_col[93,64,0] ¦ value[4,32,0] 𝄀 +2024-01-01 00:00:00 ¦ 100 𝄀 +2024-06-15 00:00:00 ¦ 200 𝄀 +1970-01-01 00:00:00 ¦ 300 𝄀 +null ¦ 400 +drop table if exists pq_file_fanout_rollback; +create table pq_file_fanout_rollback(id bigint, name varchar(100), value double, status bool); +insert into pq_file_fanout_rollback values(-1, 'seed', 0.0, false); +load data infile {'filepath'='$resources/parquet/test_*.parquet', 'format'='parquet'} into table pq_file_fanout_rollback parallel 'true'; +invalid input: column name not found +select count(*), min(id), max(id), cast(round(sum(value), 2) as decimal(10,2)) from pq_file_fanout_rollback; +➤ count(*)[-5,64,0] ¦ min(id)[-5,64,0] ¦ max(id)[-5,64,0] ¦ cast(round(sum(value), 2) as decimal(10, 2))[3,10,2] 𝄀 +1 ¦ -1 ¦ -1 ¦ 0.00 +drop table pq_file_fanout_rollback; +drop table if exists pq_not_null_rollback; +create table pq_not_null_rollback( +id bigint not null primary key, +col_nullable_with_null double not null, +col_nullable_no_null varchar(10), +col_not_nullable bigint not null, +col_all_null double +); +insert into pq_not_null_rollback values(-1, 0.0, 'seed', 0, null); +load data infile {'filepath'='$resources/parquet/nullable_test.parquet', 'format'='parquet'} into table pq_not_null_rollback parallel 'true'; +constraint violation: cannot load NULL value into NOT NULL column +select count(*), min(id), max(id), sum(col_nullable_with_null) from pq_not_null_rollback; +➤ count(*)[-5,64,0] ¦ min(id)[-5,64,0] ¦ max(id)[-5,64,0] ¦ sum(col_nullable_with_null)[8,54,0] 𝄀 +1 ¦ -1 ¦ -1 ¦ 0.0 +drop table pq_not_null_rollback; +drop table if exists pq_type_error_rollback; +create table pq_type_error_rollback(test_case varchar(50) not null, value int not null); +insert into pq_type_error_rollback values('seed', 0); +load data infile {'filepath'='$resources/load_data/string_to_int_invalid.parq', 'format'='parquet'} into table pq_type_error_rollback parallel 'true'; +internal error: row 0: strconv.ParseInt: parsing "abc": invalid syntax +select count(*), min(test_case), max(test_case), sum(value) from pq_type_error_rollback; +➤ count(*)[-5,64,0] ¦ min(test_case)[12,-1,0] ¦ max(test_case)[12,-1,0] ¦ sum(value)[-5,64,0] 𝄀 +1 ¦ seed ¦ seed ¦ 0 +drop table pq_type_error_rollback; drop database parq; diff --git a/test/distributed/cases/load_data/load_data_parquet.sql b/test/distributed/cases/load_data/load_data_parquet.sql index 8482762f29512..495890da04c32 100644 --- a/test/distributed/cases/load_data/load_data_parquet.sql +++ b/test/distributed/cases/load_data/load_data_parquet.sql @@ -8,6 +8,28 @@ create table t1(id bigint,name varchar); load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table t1; select * from t1; +create table pq_parallel_guard(id bigint, name varchar); +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_parallel_guard parallel 'true'; +select count(*) from pq_parallel_guard; +drop table pq_parallel_guard; + +create table pq_option_reject(id bigint, name varchar); +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet', 'compression'='gzip'} into table pq_option_reject; +load data infile {'filepath'='$resources/load_data/simple.parq', 'jsondata'='object', 'format'='parquet'} into table pq_option_reject; +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet', 'jsondata'='object'} into table pq_option_reject; +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet', 'hive_partitioning'='true'} into table pq_option_reject; +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject fields terminated by ','; +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject lines terminated by '\n'; +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject ignore 1 lines; +load data local infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject; +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject (id, @name); +load data infile {'filepath'='$resources/load_data/simple.parq', 'format'='parquet'} into table pq_option_reject set name=nullif(name,'x'); +drop table pq_option_reject; + +create table pq_column_error(missing_col int); +load data infile {'filepath'='$resources/parquet/supported_types.parquet', 'format'='parquet'} into table pq_column_error; +drop table pq_column_error; + create table t2(id bigint not null, name varchar not null, sex bool, f32 float(5,2)); load data infile {'filepath'='$resources/load_data/simple2.parq', 'format'='parquet'} into table t2; select * from t2; @@ -153,6 +175,17 @@ CREATE TABLE brotli_compression ( load data infile {'filepath'='$resources/parquet/test_brotli.parquet', 'format'='parquet'} into table brotli_compression; select 'NONE' as codec, count(*), sum(value) from brotli_compression; +drop table if exists pq_multi_file_pattern; +CREATE TABLE pq_multi_file_pattern ( + id BIGINT, + name VARCHAR(100), + value DOUBLE, + status BOOL +); +load data infile {'filepath'='$resources/parquet/test_[bgnslz]*.parquet', 'format'='parquet'} into table pq_multi_file_pattern parallel 'true'; +select count(*), cast(round(sum(value), 2) as decimal(10,2)) as total_value, min(id), max(id) from pq_multi_file_pattern; +drop table pq_multi_file_pattern; + -- v1 drop table if exists pq_version_compare; CREATE TABLE `pq_version_compare` ( @@ -249,6 +282,12 @@ load data infile {'filepath'='$resources/parquet/supported_types.parquet', 'form select int32_col,int64_col,uint32_col,float32_col,float64_col,string_col,bool_col,date_col,time_col from pq_supported_types; select count(*) from pq_supported_types; +drop table if exists pq_extra_parquet_columns; +create table pq_extra_parquet_columns(int32_col int); +load data infile {'filepath'='$resources/parquet/supported_types.parquet', 'format'='parquet'} into table pq_extra_parquet_columns; +select count(*), min(int32_col), max(int32_col) from pq_extra_parquet_columns; +drop table pq_extra_parquet_columns; + -- new types drop table if exists pq_new_types; create table pq_new_types ( @@ -324,5 +363,43 @@ load data infile {'filepath'='$resources/parquet/Iris.parquet', 'format'='parque select count(*) from parquet_01; +-- issue#24287: load parquet date32 to DATETIME +drop table if exists pq_date32_datetime; +create table pq_date32_datetime(date_col datetime, value int); +load data infile {'filepath'='$resources/load_data/date32_datetime.parq', 'format'='parquet'} into table pq_date32_datetime; +select * from pq_date32_datetime; + +-- rollback: multi-file pattern with one bad schema file must not leave partial rows. +-- The fixture is below LoadParallelMinSize, so CI validates rollback semantics; +-- compile fanout branches are covered by Go tests and perf probes. +drop table if exists pq_file_fanout_rollback; +create table pq_file_fanout_rollback(id bigint, name varchar(100), value double, status bool); +insert into pq_file_fanout_rollback values(-1, 'seed', 0.0, false); +load data infile {'filepath'='$resources/parquet/test_*.parquet', 'format'='parquet'} into table pq_file_fanout_rollback parallel 'true'; +select count(*), min(id), max(id), cast(round(sum(value), 2) as decimal(10,2)) from pq_file_fanout_rollback; +drop table pq_file_fanout_rollback; + +-- rollback: NOT NULL violation must not leave partial rows +drop table if exists pq_not_null_rollback; +create table pq_not_null_rollback( + id bigint not null primary key, + col_nullable_with_null double not null, + col_nullable_no_null varchar(10), + col_not_nullable bigint not null, + col_all_null double +); +insert into pq_not_null_rollback values(-1, 0.0, 'seed', 0, null); +load data infile {'filepath'='$resources/parquet/nullable_test.parquet', 'format'='parquet'} into table pq_not_null_rollback parallel 'true'; +select count(*), min(id), max(id), sum(col_nullable_with_null) from pq_not_null_rollback; +drop table pq_not_null_rollback; + +-- rollback: type conversion failure must not leave partial rows +drop table if exists pq_type_error_rollback; +create table pq_type_error_rollback(test_case varchar(50) not null, value int not null); +insert into pq_type_error_rollback values('seed', 0); +load data infile {'filepath'='$resources/load_data/string_to_int_invalid.parq', 'format'='parquet'} into table pq_type_error_rollback parallel 'true'; +select count(*), min(test_case), max(test_case), sum(value) from pq_type_error_rollback; +drop table pq_type_error_rollback; + -- post drop database parq; diff --git a/test/distributed/resources/load_data/date32_datetime.parq b/test/distributed/resources/load_data/date32_datetime.parq new file mode 100644 index 0000000000000..729f0009e622a Binary files /dev/null and b/test/distributed/resources/load_data/date32_datetime.parq differ