github.com/matrixorigin/matrixone@v1.2.0/pkg/frontend/query_result_test.go (about) 1 // Copyright 2021 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package frontend 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "testing" 22 23 "github.com/matrixorigin/matrixone/pkg/txn/clock" 24 25 "github.com/BurntSushi/toml" 26 "github.com/golang/mock/gomock" 27 "github.com/google/uuid" 28 "github.com/stretchr/testify/assert" 29 30 "github.com/matrixorigin/matrixone/pkg/common/mpool" 31 "github.com/matrixorigin/matrixone/pkg/config" 32 "github.com/matrixorigin/matrixone/pkg/container/batch" 33 "github.com/matrixorigin/matrixone/pkg/container/types" 34 "github.com/matrixorigin/matrixone/pkg/container/vector" 35 "github.com/matrixorigin/matrixone/pkg/defines" 36 "github.com/matrixorigin/matrixone/pkg/fileservice" 37 mock_frontend "github.com/matrixorigin/matrixone/pkg/frontend/test" 38 "github.com/matrixorigin/matrixone/pkg/pb/plan" 39 "github.com/matrixorigin/matrixone/pkg/sql/parsers" 40 "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" 41 "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" 42 "github.com/matrixorigin/matrixone/pkg/testutil" 43 "github.com/matrixorigin/matrixone/pkg/util/trace/impl/motrace" 44 "github.com/matrixorigin/matrixone/pkg/vm/process" 45 ) 46 47 func newLocalETLFS(t *testing.T, fsName string) fileservice.FileService { 48 dir := t.TempDir() 49 fs, err := fileservice.NewLocalETLFS(fsName, dir) 50 assert.Nil(t, err) 51 return fs 52 } 53 54 func newTestSession(t *testing.T, ctrl *gomock.Controller) *Session { 55 var err error 56 var testPool *mpool.MPool 57 //parameter 58 pu := config.NewParameterUnit(&config.FrontendParameters{}, nil, nil, nil) 59 _, err = toml.DecodeFile("test/system_vars_config.toml", pu.SV) 60 assert.Nil(t, err) 61 pu.SV.SetDefaultValues() 62 pu.SV.SaveQueryResult = "on" 63 testPool, err = mpool.NewMPool("testPool", pu.SV.GuestMmuLimitation, mpool.NoFixed) 64 if err != nil { 65 assert.Nil(t, err) 66 } 67 //file service 68 pu.FileService = newLocalETLFS(t, defines.SharedFileServiceName) 69 setGlobalPu(pu) 70 //io session 71 ioses := mock_frontend.NewMockIOSession(ctrl) 72 ioses.EXPECT().Write(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 73 ioses.EXPECT().RemoteAddress().Return("").AnyTimes() 74 ioses.EXPECT().Ref().AnyTimes() 75 proto := NewMysqlClientProtocol(0, ioses, 1024, pu.SV) 76 77 testutil.SetupAutoIncrService() 78 //new session 79 ses := NewSession(context.TODO(), proto, testPool, GSysVariables, true, nil) 80 var c clock.Clock 81 _ = ses.GetTxnHandler().CreateTempStorage(c) 82 tenant := &TenantInfo{ 83 Tenant: sysAccountName, 84 User: rootName, 85 DefaultRole: moAdminRoleName, 86 TenantID: sysAccountID, 87 UserID: rootID, 88 DefaultRoleID: moAdminRoleID, 89 } 90 ses.SetTenantInfo(tenant) 91 92 return ses 93 } 94 95 func newBatch(ts []types.Type, rows int, proc *process.Process) *batch.Batch { 96 bat := batch.NewWithSize(len(ts)) 97 bat.SetRowCount(rows) 98 for i, typ := range ts { 99 switch typ.Oid { 100 case types.T_int8: 101 vec, _ := proc.AllocVectorOfRows(typ, rows, nil) 102 vs := vector.MustFixedCol[int8](vec) 103 for j := range vs { 104 vs[j] = int8(j) 105 } 106 bat.Vecs[i] = vec 107 default: 108 panic("invalid type") 109 } 110 } 111 return bat 112 } 113 114 func Test_saveQueryResultMeta(t *testing.T) { 115 ctrl := gomock.NewController(t) 116 defer ctrl.Finish() 117 var err error 118 var retColDef *plan.ResultColDef 119 var files []resultFileInfo 120 //prepare session 121 ses := newTestSession(t, ctrl) 122 _ = ses.SetGlobalVar(context.TODO(), "save_query_result", int8(1)) 123 defer ses.Close() 124 125 const blockCnt int = 3 126 127 tenant := &TenantInfo{ 128 Tenant: sysAccountName, 129 TenantID: sysAccountID, 130 } 131 ses.SetTenantInfo(tenant) 132 proc := testutil.NewProcess() 133 proc.FileService = getGlobalPu().FileService 134 135 proc.SessionInfo = process.SessionInfo{Account: sysAccountName} 136 ses.GetTxnCompileCtx().execCtx = &ExecCtx{ 137 reqCtx: context.TODO(), 138 proc: proc, 139 } 140 141 //three columns 142 typs := []types.Type{ 143 types.T_int8.ToType(), 144 types.T_int8.ToType(), 145 types.T_int8.ToType(), 146 } 147 148 colDefs := make([]*plan.ColDef, len(typs)) 149 for i, ty := range typs { 150 colDefs[i] = &plan.ColDef{ 151 Name: fmt.Sprintf("a_%d", i), 152 Typ: plan.Type{ 153 Id: int32(ty.Oid), 154 Scale: ty.Scale, 155 Width: ty.Width, 156 }, 157 } 158 } 159 160 ses.rs = &plan.ResultColDef{ 161 ResultCols: colDefs, 162 } 163 164 testUUID := uuid.NullUUID{}.UUID 165 ses.tStmt = &motrace.StatementInfo{ 166 StatementID: testUUID, 167 } 168 169 ctx := context.Background() 170 asts, err := parsers.Parse(ctx, dialect.MYSQL, "select a,b,c from t", 1, 0) 171 assert.Nil(t, err) 172 173 ses.ast = asts[0] 174 ses.p = &plan.Plan{} 175 176 yes := openSaveQueryResult(ctx, ses) 177 assert.True(t, yes) 178 179 //result string 180 wantResult := "0,0,0\n1,1,1\n2,2,2\n0,0,0\n1,1,1\n2,2,2\n0,0,0\n1,1,1\n2,2,2\n" 181 //save blocks 182 183 for i := 0; i < blockCnt; i++ { 184 data := newBatch(typs, blockCnt, proc) 185 err = saveQueryResult(ctx, ses, data) 186 assert.Nil(t, err) 187 } 188 189 //save result meta 190 err = saveQueryResultMeta(ctx, ses) 191 assert.Nil(t, err) 192 193 retColDef, err = openResultMeta(ctx, ses, testUUID.String()) 194 assert.Nil(t, err) 195 assert.NotNil(t, retColDef) 196 197 files, err = getResultFiles(ctx, ses, testUUID.String()) 198 assert.Nil(t, err) 199 assert.Equal(t, len(files), blockCnt) 200 for i := 0; i < blockCnt; i++ { 201 assert.NotEqual(t, files[i].size, int64(0)) 202 assert.Equal(t, files[i].blockIndex, int64(i+1)) 203 } 204 205 //dump 206 exportFilePath := fileservice.JoinPath(defines.SharedFileServiceName, "/block3.csv") 207 ep := &tree.ExportParam{ 208 Outfile: true, 209 QueryId: testUUID.String(), 210 FilePath: exportFilePath, 211 Fields: &tree.Fields{ 212 Terminated: &tree.Terminated{ 213 Value: ",", 214 }, 215 EnclosedBy: &tree.EnclosedBy{ 216 Value: '"', 217 }, 218 }, 219 Lines: &tree.Lines{ 220 TerminatedBy: &tree.Terminated{ 221 Value: "\n", 222 }, 223 }, 224 MaxFileSize: 0, 225 Header: false, 226 ForceQuote: nil, 227 } 228 err = doDumpQueryResult(ctx, ses, ep) 229 assert.Nil(t, err) 230 231 fs := getGlobalPu().FileService 232 233 //csvBuf := &bytes.Buffer{} 234 var r io.ReadCloser 235 err = fs.Read(ctx, &fileservice.IOVector{ 236 FilePath: exportFilePath, 237 Entries: []fileservice.IOEntry{ 238 { 239 Offset: 0, 240 Size: -1, 241 //WriterForRead: csvBuf, 242 ReadCloserForRead: &r, 243 }, 244 }, 245 }) 246 assert.Nil(t, err) 247 content, err := io.ReadAll(r) 248 assert.Nil(t, err) 249 assert.Nil(t, r.Close()) 250 assert.Equal(t, wantResult, string(content)) 251 //fmt.Println(string(content)) 252 } 253 254 func Test_getFileSize(t *testing.T) { 255 files := []fileservice.DirEntry{ 256 {Name: "a", IsDir: false, Size: 1}, 257 } 258 assert.Equal(t, int64(1), getFileSize(files, "a")) 259 assert.Equal(t, int64(-1), getFileSize(files, "b")) 260 }