2021-01-16 10:12:14 +08:00
|
|
|
package querynode
|
2020-08-25 15:45:19 +08:00
|
|
|
|
2020-08-29 17:42:41 +08:00
|
|
|
/*
|
|
|
|
|
2020-10-23 18:01:24 +08:00
|
|
|
#cgo CFLAGS: -I${SRCDIR}/../core/output/include
|
2020-08-29 17:42:41 +08:00
|
|
|
|
2020-10-31 15:11:47 +08:00
|
|
|
#cgo LDFLAGS: -L${SRCDIR}/../core/output/lib -lmilvus_segcore -Wl,-rpath=${SRCDIR}/../core/output/lib
|
2020-08-29 17:42:41 +08:00
|
|
|
|
2020-11-25 10:31:51 +08:00
|
|
|
#include "segcore/collection_c.h"
|
|
|
|
#include "segcore/plan_c.h"
|
|
|
|
#include "segcore/reduce_c.h"
|
2020-08-29 17:42:41 +08:00
|
|
|
*/
|
2020-08-25 15:45:19 +08:00
|
|
|
import "C"
|
2020-08-28 17:29:26 +08:00
|
|
|
import (
|
2020-11-04 17:58:43 +08:00
|
|
|
"strconv"
|
2021-01-12 18:03:24 +08:00
|
|
|
"sync"
|
2020-11-05 10:52:50 +08:00
|
|
|
"unsafe"
|
2020-11-04 17:58:43 +08:00
|
|
|
|
2020-11-09 16:27:11 +08:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
2020-10-19 18:31:00 +08:00
|
|
|
"github.com/zilliztech/milvus-distributed/internal/errors"
|
2020-11-04 16:28:14 +08:00
|
|
|
"github.com/zilliztech/milvus-distributed/internal/proto/commonpb"
|
2020-08-28 17:29:26 +08:00
|
|
|
)
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
const (
|
|
|
|
segTypeInvalid = C.Invalid
|
|
|
|
segTypeGrowing = C.Growing
|
|
|
|
segTypeSealed = C.Sealed
|
|
|
|
)
|
|
|
|
|
|
|
|
type segmentType = C.SegmentType
|
2021-01-12 18:03:24 +08:00
|
|
|
type indexParam = map[string]string
|
|
|
|
|
2020-08-25 15:45:19 +08:00
|
|
|
type Segment struct {
|
2021-01-19 11:37:16 +08:00
|
|
|
segmentPtr C.CSegmentInterface
|
2021-01-19 14:44:03 +08:00
|
|
|
segmentType C.SegmentType
|
2021-01-13 10:40:46 +08:00
|
|
|
segmentID UniqueID
|
|
|
|
partitionTag string // TODO: use partitionID
|
2021-01-19 14:44:03 +08:00
|
|
|
partitionID UniqueID
|
2021-01-13 10:40:46 +08:00
|
|
|
collectionID UniqueID
|
|
|
|
lastMemSize int64
|
|
|
|
lastRowCount int64
|
|
|
|
|
|
|
|
rmMutex sync.Mutex // guards recentlyModified
|
2020-11-05 10:52:50 +08:00
|
|
|
recentlyModified bool
|
2021-01-13 10:40:46 +08:00
|
|
|
|
|
|
|
paramMutex sync.RWMutex // guards indexParam
|
|
|
|
indexParam map[int64]indexParam
|
2020-08-25 15:45:19 +08:00
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
//-------------------------------------------------------------------------------------- common interfaces
|
2020-11-09 16:27:11 +08:00
|
|
|
func (s *Segment) ID() UniqueID {
|
|
|
|
return s.segmentID
|
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
func (s *Segment) Type() segmentType {
|
|
|
|
return s.segmentType
|
|
|
|
}
|
|
|
|
|
2021-01-12 18:03:24 +08:00
|
|
|
func (s *Segment) SetRecentlyModified(modify bool) {
|
2021-01-13 10:40:46 +08:00
|
|
|
s.rmMutex.Lock()
|
|
|
|
defer s.rmMutex.Unlock()
|
2021-01-12 18:03:24 +08:00
|
|
|
s.recentlyModified = modify
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) GetRecentlyModified() bool {
|
2021-01-13 10:40:46 +08:00
|
|
|
s.rmMutex.Lock()
|
|
|
|
defer s.rmMutex.Unlock()
|
2021-01-12 18:03:24 +08:00
|
|
|
return s.recentlyModified
|
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
func newSegment2(collection *Collection, segmentID int64, partitionTag string, collectionID UniqueID, segType segmentType) *Segment {
|
2020-11-09 16:27:11 +08:00
|
|
|
/*
|
2021-01-19 11:37:16 +08:00
|
|
|
CSegmentInterface
|
|
|
|
NewSegment(CCollection collection, uint64_t segment_id, SegmentType seg_type);
|
2020-11-09 16:27:11 +08:00
|
|
|
*/
|
2021-01-12 18:03:24 +08:00
|
|
|
initIndexParam := make(map[int64]indexParam)
|
2021-01-19 14:44:03 +08:00
|
|
|
segmentPtr := C.NewSegment(collection.collectionPtr, C.ulong(segmentID), segType)
|
2020-12-24 20:55:40 +08:00
|
|
|
var newSegment = &Segment{
|
|
|
|
segmentPtr: segmentPtr,
|
|
|
|
segmentID: segmentID,
|
|
|
|
partitionTag: partitionTag,
|
|
|
|
collectionID: collectionID,
|
2021-01-12 18:03:24 +08:00
|
|
|
indexParam: initIndexParam,
|
2020-12-24 20:55:40 +08:00
|
|
|
}
|
2020-11-09 16:27:11 +08:00
|
|
|
|
|
|
|
return newSegment
|
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
func newSegment(collection *Collection, segmentID int64, partitionID UniqueID, collectionID UniqueID, segType segmentType) *Segment {
|
|
|
|
/*
|
|
|
|
CSegmentInterface
|
|
|
|
NewSegment(CCollection collection, uint64_t segment_id, SegmentType seg_type);
|
|
|
|
*/
|
|
|
|
initIndexParam := make(map[int64]indexParam)
|
|
|
|
segmentPtr := C.NewSegment(collection.collectionPtr, C.ulong(segmentID), segType)
|
|
|
|
var newSegment = &Segment{
|
|
|
|
segmentPtr: segmentPtr,
|
|
|
|
segmentID: segmentID,
|
|
|
|
partitionID: partitionID,
|
|
|
|
collectionID: collectionID,
|
|
|
|
indexParam: initIndexParam,
|
|
|
|
}
|
|
|
|
|
|
|
|
return newSegment
|
|
|
|
}
|
|
|
|
|
2020-11-09 16:27:11 +08:00
|
|
|
func deleteSegment(segment *Segment) {
|
|
|
|
/*
|
|
|
|
void
|
2021-01-19 11:37:16 +08:00
|
|
|
deleteSegment(CSegmentInterface segment);
|
2020-11-09 16:27:11 +08:00
|
|
|
*/
|
|
|
|
cPtr := segment.segmentPtr
|
|
|
|
C.DeleteSegment(cPtr)
|
|
|
|
}
|
|
|
|
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) getRowCount() int64 {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-10-24 10:45:57 +08:00
|
|
|
long int
|
2021-01-19 11:37:16 +08:00
|
|
|
getRowCount(CSegmentInterface c_segment);
|
2020-09-03 19:58:33 +08:00
|
|
|
*/
|
2020-11-09 16:27:11 +08:00
|
|
|
var rowCount = C.GetRowCount(s.segmentPtr)
|
2020-09-03 19:58:33 +08:00
|
|
|
return int64(rowCount)
|
2020-08-25 15:45:19 +08:00
|
|
|
}
|
|
|
|
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) getDeletedCount() int64 {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-10-24 10:45:57 +08:00
|
|
|
long int
|
2021-01-19 11:37:16 +08:00
|
|
|
getDeletedCount(CSegmentInterface c_segment);
|
2020-09-03 19:58:33 +08:00
|
|
|
*/
|
2020-11-09 16:27:11 +08:00
|
|
|
var deletedCount = C.GetDeletedCount(s.segmentPtr)
|
2020-09-03 19:58:33 +08:00
|
|
|
return int64(deletedCount)
|
|
|
|
}
|
|
|
|
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) getMemSize() int64 {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-10-24 10:45:57 +08:00
|
|
|
long int
|
2021-01-19 11:37:16 +08:00
|
|
|
GetMemoryUsageInBytes(CSegmentInterface c_segment);
|
2020-09-21 15:10:54 +08:00
|
|
|
*/
|
2020-11-09 16:27:11 +08:00
|
|
|
var memoryUsageInBytes = C.GetMemoryUsageInBytes(s.segmentPtr)
|
2020-09-21 15:10:54 +08:00
|
|
|
|
2020-10-29 19:55:57 +08:00
|
|
|
return int64(memoryUsageInBytes)
|
2020-09-16 15:21:10 +08:00
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
func (s *Segment) segmentSearch(plan *Plan,
|
|
|
|
placeHolderGroups []*PlaceholderGroup,
|
|
|
|
timestamp []Timestamp) (*SearchResult, error) {
|
|
|
|
/*
|
|
|
|
CStatus
|
|
|
|
Search(void* plan,
|
|
|
|
void* placeholder_groups,
|
|
|
|
uint64_t* timestamps,
|
|
|
|
int num_groups,
|
|
|
|
long int* result_ids,
|
|
|
|
float* result_distances);
|
|
|
|
*/
|
|
|
|
|
|
|
|
cPlaceholderGroups := make([]C.CPlaceholderGroup, 0)
|
|
|
|
for _, pg := range placeHolderGroups {
|
|
|
|
cPlaceholderGroups = append(cPlaceholderGroups, (*pg).cPlaceholderGroup)
|
|
|
|
}
|
|
|
|
|
|
|
|
var searchResult SearchResult
|
|
|
|
var cTimestamp = (*C.ulong)(×tamp[0])
|
|
|
|
var cPlaceHolder = (*C.CPlaceholderGroup)(&cPlaceholderGroups[0])
|
|
|
|
var cNumGroups = C.int(len(placeHolderGroups))
|
|
|
|
var cQueryResult = (*C.CQueryResult)(&searchResult.cQueryResult)
|
|
|
|
|
|
|
|
var status = C.Search(s.segmentPtr, plan.cPlan, cPlaceHolder, cTimestamp, cNumGroups, cQueryResult)
|
|
|
|
errorCode := status.error_code
|
|
|
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
errorMsg := C.GoString(status.error_msg)
|
|
|
|
defer C.free(unsafe.Pointer(status.error_msg))
|
|
|
|
return nil, errors.New("Search failed, C runtime error detected, error code = " + strconv.Itoa(int(errorCode)) + ", error msg = " + errorMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &searchResult, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) fillTargetEntry(plan *Plan,
|
|
|
|
result *SearchResult) error {
|
|
|
|
|
|
|
|
var status = C.FillTargetEntry(s.segmentPtr, plan.cPlan, result.cQueryResult)
|
|
|
|
errorCode := status.error_code
|
|
|
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
errorMsg := C.GoString(status.error_msg)
|
|
|
|
defer C.free(unsafe.Pointer(status.error_msg))
|
|
|
|
return errors.New("FillTargetEntry failed, C runtime error detected, error code = " + strconv.Itoa(int(errorCode)) + ", error msg = " + errorMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// segment, err := loadIndexService.replica.getSegmentByID(segmentID)
|
|
|
|
func (s *Segment) updateSegmentIndex(loadIndexInfo *LoadIndexInfo) error {
|
|
|
|
status := C.UpdateSegmentIndex(s.segmentPtr, loadIndexInfo.cLoadIndexInfo)
|
|
|
|
errorCode := status.error_code
|
|
|
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
errorMsg := C.GoString(status.error_msg)
|
|
|
|
defer C.free(unsafe.Pointer(status.error_msg))
|
|
|
|
return errors.New("updateSegmentIndex failed, C runtime error detected, error code = " + strconv.Itoa(int(errorCode)) + ", error msg = " + errorMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) setIndexParam(fieldID int64, indexParamKv []*commonpb.KeyValuePair) error {
|
|
|
|
s.paramMutex.Lock()
|
|
|
|
defer s.paramMutex.Unlock()
|
|
|
|
indexParamMap := make(indexParam)
|
|
|
|
if indexParamKv == nil {
|
|
|
|
return errors.New("loadIndexMsg's indexParam empty")
|
|
|
|
}
|
|
|
|
for _, param := range indexParamKv {
|
|
|
|
indexParamMap[param.Key] = param.Value
|
|
|
|
}
|
|
|
|
s.indexParam[fieldID] = indexParamMap
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) matchIndexParam(fieldID int64, indexParamKv []*commonpb.KeyValuePair) bool {
|
|
|
|
s.paramMutex.RLock()
|
|
|
|
defer s.paramMutex.RUnlock()
|
|
|
|
fieldIndexParam := s.indexParam[fieldID]
|
|
|
|
if fieldIndexParam == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
paramSize := len(s.indexParam)
|
|
|
|
matchCount := 0
|
|
|
|
for _, param := range indexParamKv {
|
|
|
|
value, ok := fieldIndexParam[param.Key]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if param.Value != value {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
matchCount++
|
|
|
|
}
|
|
|
|
return paramSize == matchCount
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------- interfaces for growing segment
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) segmentPreInsert(numOfRecords int) int64 {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-10-24 10:45:57 +08:00
|
|
|
long int
|
2021-01-19 11:37:16 +08:00
|
|
|
PreInsert(CSegmentInterface c_segment, long int size);
|
2020-09-09 15:24:07 +08:00
|
|
|
*/
|
2020-11-09 16:27:11 +08:00
|
|
|
var offset = C.PreInsert(s.segmentPtr, C.long(int64(numOfRecords)))
|
2020-09-09 15:24:07 +08:00
|
|
|
|
2020-09-12 16:57:37 +08:00
|
|
|
return int64(offset)
|
2020-09-09 15:24:07 +08:00
|
|
|
}
|
|
|
|
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) segmentPreDelete(numOfRecords int) int64 {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-10-24 10:45:57 +08:00
|
|
|
long int
|
2021-01-19 11:37:16 +08:00
|
|
|
PreDelete(CSegmentInterface c_segment, long int size);
|
2020-09-09 15:24:07 +08:00
|
|
|
*/
|
2020-11-09 16:27:11 +08:00
|
|
|
var offset = C.PreDelete(s.segmentPtr, C.long(int64(numOfRecords)))
|
2020-09-09 15:24:07 +08:00
|
|
|
|
2020-09-12 16:57:37 +08:00
|
|
|
return int64(offset)
|
2020-09-09 15:24:07 +08:00
|
|
|
}
|
|
|
|
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) segmentInsert(offset int64, entityIDs *[]UniqueID, timestamps *[]Timestamp, records *[]*commonpb.Blob) error {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-11-26 16:01:31 +08:00
|
|
|
CStatus
|
2021-01-19 11:37:16 +08:00
|
|
|
Insert(CSegmentInterface c_segment,
|
2020-10-24 10:45:57 +08:00
|
|
|
long int reserved_offset,
|
|
|
|
signed long int size,
|
|
|
|
const long* primary_keys,
|
|
|
|
const unsigned long* timestamps,
|
|
|
|
void* raw_data,
|
|
|
|
int sizeof_per_row,
|
|
|
|
signed long int count);
|
2020-09-02 16:23:50 +08:00
|
|
|
*/
|
2020-09-08 10:39:09 +08:00
|
|
|
// Blobs to one big blob
|
2020-11-05 10:52:50 +08:00
|
|
|
var numOfRow = len(*entityIDs)
|
|
|
|
var sizeofPerRow = len((*records)[0].Value)
|
|
|
|
|
|
|
|
assert.Equal(nil, numOfRow, len(*records))
|
|
|
|
|
|
|
|
var rawData = make([]byte, numOfRow*sizeofPerRow)
|
|
|
|
var copyOffset = 0
|
|
|
|
for i := 0; i < len(*records); i++ {
|
|
|
|
copy(rawData[copyOffset:], (*records)[i].Value)
|
|
|
|
copyOffset += sizeofPerRow
|
|
|
|
}
|
|
|
|
|
|
|
|
var cOffset = C.long(offset)
|
|
|
|
var cNumOfRows = C.long(numOfRow)
|
|
|
|
var cEntityIdsPtr = (*C.long)(&(*entityIDs)[0])
|
|
|
|
var cTimestampsPtr = (*C.ulong)(&(*timestamps)[0])
|
|
|
|
var cSizeofPerRow = C.int(sizeofPerRow)
|
|
|
|
var cRawDataVoidPtr = unsafe.Pointer(&rawData[0])
|
|
|
|
|
2020-11-09 16:27:11 +08:00
|
|
|
var status = C.Insert(s.segmentPtr,
|
2020-11-05 10:52:50 +08:00
|
|
|
cOffset,
|
|
|
|
cNumOfRows,
|
|
|
|
cEntityIdsPtr,
|
|
|
|
cTimestampsPtr,
|
|
|
|
cRawDataVoidPtr,
|
|
|
|
cSizeofPerRow,
|
|
|
|
cNumOfRows)
|
|
|
|
|
2020-11-26 16:01:31 +08:00
|
|
|
errorCode := status.error_code
|
|
|
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
errorMsg := C.GoString(status.error_msg)
|
|
|
|
defer C.free(unsafe.Pointer(status.error_msg))
|
|
|
|
return errors.New("Insert failed, C runtime error detected, error code = " + strconv.Itoa(int(errorCode)) + ", error msg = " + errorMsg)
|
2020-11-05 10:52:50 +08:00
|
|
|
}
|
2020-09-02 17:18:49 +08:00
|
|
|
|
2021-01-12 18:03:24 +08:00
|
|
|
s.SetRecentlyModified(true)
|
2020-09-07 17:01:46 +08:00
|
|
|
return nil
|
2020-08-28 17:29:26 +08:00
|
|
|
}
|
|
|
|
|
2020-11-05 10:52:50 +08:00
|
|
|
func (s *Segment) segmentDelete(offset int64, entityIDs *[]UniqueID, timestamps *[]Timestamp) error {
|
2020-09-21 18:16:06 +08:00
|
|
|
/*
|
2020-11-26 16:01:31 +08:00
|
|
|
CStatus
|
2021-01-19 11:37:16 +08:00
|
|
|
Delete(CSegmentInterface c_segment,
|
2020-10-24 10:45:57 +08:00
|
|
|
long int reserved_offset,
|
|
|
|
long size,
|
|
|
|
const long* primary_keys,
|
|
|
|
const unsigned long* timestamps);
|
2020-09-02 16:23:50 +08:00
|
|
|
*/
|
2020-09-09 15:24:07 +08:00
|
|
|
var cOffset = C.long(offset)
|
|
|
|
var cSize = C.long(len(*entityIDs))
|
2020-09-12 16:57:37 +08:00
|
|
|
var cEntityIdsPtr = (*C.long)(&(*entityIDs)[0])
|
2020-09-09 15:24:07 +08:00
|
|
|
var cTimestampsPtr = (*C.ulong)(&(*timestamps)[0])
|
2020-09-02 16:23:50 +08:00
|
|
|
|
2020-11-09 16:27:11 +08:00
|
|
|
var status = C.Delete(s.segmentPtr, cOffset, cSize, cEntityIdsPtr, cTimestampsPtr)
|
2020-09-02 17:18:49 +08:00
|
|
|
|
2020-11-26 16:01:31 +08:00
|
|
|
errorCode := status.error_code
|
|
|
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
errorMsg := C.GoString(status.error_msg)
|
|
|
|
defer C.free(unsafe.Pointer(status.error_msg))
|
|
|
|
return errors.New("Delete failed, C runtime error detected, error code = " + strconv.Itoa(int(errorCode)) + ", error msg = " + errorMsg)
|
2020-09-02 17:18:49 +08:00
|
|
|
}
|
2020-09-02 16:23:50 +08:00
|
|
|
|
2020-09-07 17:01:46 +08:00
|
|
|
return nil
|
2020-08-28 17:29:26 +08:00
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
//-------------------------------------------------------------------------------------- interfaces for sealed segment
|
|
|
|
func (s *Segment) segmentLoadFieldData(fieldID int64, rowCount int, data unsafe.Pointer) error {
|
2020-11-09 16:27:11 +08:00
|
|
|
/*
|
2020-11-26 16:01:31 +08:00
|
|
|
CStatus
|
2021-01-19 14:44:03 +08:00
|
|
|
LoadFieldData(CSegmentInterface c_segment, CLoadFieldDataInfo load_field_data_info);
|
2020-11-09 16:27:11 +08:00
|
|
|
*/
|
2021-01-19 14:44:03 +08:00
|
|
|
if s.segmentType != segTypeSealed {
|
|
|
|
return errors.New("illegal segment type when loading field data")
|
2020-11-26 15:18:36 +08:00
|
|
|
}
|
2020-11-26 16:01:31 +08:00
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
/*
|
|
|
|
struct CLoadFieldDataInfo {
|
|
|
|
int64_t field_id;
|
|
|
|
void* blob;
|
|
|
|
int64_t row_count;
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
loadInfo := C.CLoadFieldDataInfo{
|
|
|
|
field_id: C.int64_t(fieldID),
|
|
|
|
blob: data,
|
|
|
|
row_count: C.int64_t(rowCount),
|
2020-12-03 19:00:11 +08:00
|
|
|
}
|
|
|
|
|
2021-01-19 14:44:03 +08:00
|
|
|
var status = C.LoadFieldData(s.segmentPtr, loadInfo)
|
2020-12-24 20:55:40 +08:00
|
|
|
errorCode := status.error_code
|
|
|
|
if errorCode != 0 {
|
|
|
|
errorMsg := C.GoString(status.error_msg)
|
|
|
|
defer C.free(unsafe.Pointer(status.error_msg))
|
2021-01-19 14:44:03 +08:00
|
|
|
return errors.New("LoadFieldData failed, C runtime error detected, error code = " + strconv.Itoa(int(errorCode)) + ", error msg = " + errorMsg)
|
2020-12-24 20:55:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|