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  }