github.com/matrixorigin/matrixone@v1.2.0/pkg/vm/engine/tae/catalog/database.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 catalog
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"sort"
    22  	"sync"
    23  	"unsafe"
    24  
    25  	pkgcatalog "github.com/matrixorigin/matrixone/pkg/catalog"
    26  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    27  	"github.com/matrixorigin/matrixone/pkg/container/types"
    28  	"github.com/matrixorigin/matrixone/pkg/logutil"
    29  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common"
    30  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/txnif"
    31  )
    32  
    33  type accessInfo struct {
    34  	TenantID, UserID, RoleID uint32
    35  	CreateAt                 types.Timestamp
    36  }
    37  
    38  const (
    39  	AccessInfoSize int64 = int64(unsafe.Sizeof(accessInfo{}))
    40  )
    41  
    42  func EncodeAccessInfo(ai *accessInfo) []byte {
    43  	return unsafe.Slice((*byte)(unsafe.Pointer(ai)), AccessInfoSize)
    44  }
    45  
    46  func (ai *accessInfo) WriteTo(w io.Writer) (n int64, err error) {
    47  	w.Write(EncodeAccessInfo(ai))
    48  	return AccessInfoSize, nil
    49  }
    50  
    51  func (ai *accessInfo) ReadFrom(r io.Reader) (n int64, err error) {
    52  	r.Read(EncodeAccessInfo(ai))
    53  	return AccessInfoSize, nil
    54  }
    55  
    56  func dbVisibilityFn[T *DBEntry](n *common.GenericDLNode[*DBEntry], txn txnif.TxnReader) (visible, dropped bool, name string) {
    57  	db := n.GetPayload()
    58  	visible, dropped = db.GetVisibility(txn)
    59  	return
    60  }
    61  
    62  type DBEntry struct {
    63  	ID uint64
    64  	*BaseEntryImpl[*EmptyMVCCNode]
    65  	catalog *Catalog
    66  	*DBNode
    67  	fullName string
    68  	isSys    bool
    69  
    70  	entries map[uint64]*common.GenericDLNode[*TableEntry]
    71  	// nameNodes[ABC] is a linked list of all table entries had been once named as ABC
    72  	nameNodes map[string]*nodeList[*TableEntry]
    73  	link      *common.GenericSortedDList[*TableEntry]
    74  
    75  	nodesMu sync.RWMutex
    76  }
    77  
    78  func (entry *TableEntry) Less(b *TableEntry) int {
    79  	return CompareUint64(entry.ID, b.ID)
    80  }
    81  
    82  func NewDBEntryWithID(catalog *Catalog, name string, createSql, datTyp string, id uint64, txn txnif.AsyncTxn) *DBEntry {
    83  	e := &DBEntry{
    84  		ID: id,
    85  		BaseEntryImpl: NewBaseEntry(
    86  			func() *EmptyMVCCNode { return &EmptyMVCCNode{} }),
    87  		catalog: catalog,
    88  		DBNode: &DBNode{
    89  			name:      name,
    90  			createSql: createSql,
    91  			datType:   datTyp,
    92  		},
    93  		entries:   make(map[uint64]*common.GenericDLNode[*TableEntry]),
    94  		nameNodes: make(map[string]*nodeList[*TableEntry]),
    95  		link:      common.NewGenericSortedDList((*TableEntry).Less),
    96  	}
    97  	if txn != nil {
    98  		// Only in unit test, txn can be nil
    99  		e.acInfo.TenantID = txn.GetTenantID()
   100  		e.acInfo.UserID, e.acInfo.RoleID = txn.GetUserAndRoleID()
   101  	}
   102  	e.CreateWithTxn(txn, &EmptyMVCCNode{})
   103  	e.acInfo.CreateAt = types.CurrentTimestamp()
   104  	return e
   105  }
   106  
   107  func NewSystemDBEntry(catalog *Catalog) *DBEntry {
   108  	entry := &DBEntry{
   109  		ID: pkgcatalog.MO_CATALOG_ID,
   110  		BaseEntryImpl: NewBaseEntry(
   111  			func() *EmptyMVCCNode {
   112  				return &EmptyMVCCNode{}
   113  			}),
   114  		catalog: catalog,
   115  		DBNode: &DBNode{
   116  			name:      pkgcatalog.MO_CATALOG,
   117  			createSql: "create database " + pkgcatalog.MO_CATALOG,
   118  		},
   119  		entries:   make(map[uint64]*common.GenericDLNode[*TableEntry]),
   120  		nameNodes: make(map[string]*nodeList[*TableEntry]),
   121  		link:      common.NewGenericSortedDList((*TableEntry).Less),
   122  		isSys:     true,
   123  	}
   124  	entry.CreateWithTS(types.SystemDBTS, &EmptyMVCCNode{})
   125  	return entry
   126  }
   127  
   128  func NewReplayDBEntry() *DBEntry {
   129  	entry := &DBEntry{
   130  		BaseEntryImpl: NewReplayBaseEntry(
   131  			func() *EmptyMVCCNode { return &EmptyMVCCNode{} }),
   132  		entries:   make(map[uint64]*common.GenericDLNode[*TableEntry]),
   133  		nameNodes: make(map[string]*nodeList[*TableEntry]),
   134  		link:      common.NewGenericSortedDList((*TableEntry).Less),
   135  	}
   136  	return entry
   137  }
   138  func (e *DBEntry) GetID() uint64    { return e.ID }
   139  func (e *DBEntry) IsSystemDB() bool { return e.isSys }
   140  func (e *DBEntry) CoarseTableCnt() int {
   141  	e.RLock()
   142  	defer e.RUnlock()
   143  	return len(e.entries)
   144  }
   145  
   146  func (e *DBEntry) Less(b *DBEntry) int {
   147  	return CompareUint64(e.ID, b.ID)
   148  }
   149  
   150  func (e *DBEntry) GetTenantID() uint32          { return e.acInfo.TenantID }
   151  func (e *DBEntry) GetUserID() uint32            { return e.acInfo.UserID }
   152  func (e *DBEntry) GetRoleID() uint32            { return e.acInfo.RoleID }
   153  func (e *DBEntry) GetCreateAt() types.Timestamp { return e.acInfo.CreateAt }
   154  func (e *DBEntry) GetName() string              { return e.name }
   155  func (e *DBEntry) GetCreateSql() string         { return e.createSql }
   156  func (e *DBEntry) IsSubscription() bool {
   157  	return e.datType == pkgcatalog.SystemDBTypeSubscription
   158  }
   159  func (e *DBEntry) GetDatType() string { return e.datType }
   160  func (e *DBEntry) GetFullName() string {
   161  	if len(e.fullName) == 0 {
   162  		e.fullName = genDBFullName(e.acInfo.TenantID, e.name)
   163  	}
   164  	return e.fullName
   165  }
   166  
   167  func (e *DBEntry) String() string {
   168  	e.RLock()
   169  	defer e.RUnlock()
   170  	return e.StringLocked()
   171  }
   172  
   173  func (e *DBEntry) StringLocked() string {
   174  	return e.StringWithlevelLocked(common.PPL1)
   175  }
   176  func (e *DBEntry) StringWithLevel(level common.PPLevel) string {
   177  	e.RLock()
   178  	defer e.RUnlock()
   179  	return e.StringWithlevelLocked(level)
   180  }
   181  
   182  func (e *DBEntry) StringWithlevelLocked(level common.PPLevel) string {
   183  	if level <= common.PPL1 {
   184  		return fmt.Sprintf("DB[%d][name=%s][C@%s,D@%s]",
   185  			e.ID, e.GetFullName(), e.GetCreatedAtLocked().ToString(), e.GetDeleteAtLocked().ToString())
   186  	}
   187  	return fmt.Sprintf("DB%s[name=%s, id=%d]", e.BaseEntryImpl.StringLocked(), e.GetFullName(), e.ID)
   188  }
   189  
   190  func (e *DBEntry) MakeTableIt(reverse bool) *common.GenericSortedDListIt[*TableEntry] {
   191  	e.RLock()
   192  	defer e.RUnlock()
   193  	return common.NewGenericSortedDListIt(e.RWMutex, e.link, reverse)
   194  }
   195  
   196  func (e *DBEntry) PPString(level common.PPLevel, depth int, prefix string) string {
   197  	var w bytes.Buffer
   198  	_, _ = w.WriteString(fmt.Sprintf("%s%s%s", common.RepeatStr("\t", depth), prefix, e.StringWithLevel(level)))
   199  	if level == common.PPL0 {
   200  		return w.String()
   201  	}
   202  	it := e.MakeTableIt(true)
   203  	for it.Valid() {
   204  		table := it.Get().GetPayload()
   205  		_ = w.WriteByte('\n')
   206  		_, _ = w.WriteString(table.PPString(level, depth+1, ""))
   207  		it.Next()
   208  	}
   209  	return w.String()
   210  }
   211  func (e *DBEntry) AsCommonID() *common.ID {
   212  	return &common.ID{
   213  		DbID: e.ID,
   214  	}
   215  }
   216  func (e *DBEntry) GetObjectEntryByID(id *common.ID) (obj *ObjectEntry, err error) {
   217  	e.RLock()
   218  	table, err := e.GetTableEntryByID(id.TableID)
   219  	e.RUnlock()
   220  	if err != nil {
   221  		return
   222  	}
   223  	obj, err = table.GetObjectByID(id.ObjectID())
   224  	return
   225  }
   226  
   227  func (e *DBEntry) GetItemNodeByIDLocked(id uint64) *common.GenericDLNode[*TableEntry] {
   228  	return e.entries[id]
   229  }
   230  
   231  func (e *DBEntry) GetTableEntryByID(id uint64) (table *TableEntry, err error) {
   232  	e.RLock()
   233  	defer e.RUnlock()
   234  	node := e.entries[id]
   235  	if node == nil {
   236  		return nil, moerr.GetOkExpectedEOB()
   237  	}
   238  	table = node.GetPayload()
   239  	return
   240  }
   241  
   242  func (e *DBEntry) txnGetNodeByName(
   243  	tenantID uint32,
   244  	name string,
   245  	txn txnif.TxnReader) (*common.GenericDLNode[*TableEntry], error) {
   246  	e.RLock()
   247  	defer e.RUnlock()
   248  	fullName := genTblFullName(tenantID, name)
   249  	node := e.nameNodes[fullName]
   250  	if node == nil {
   251  		return nil, moerr.GetOkExpectedEOB()
   252  	}
   253  	return node.TxnGetNodeLocked(txn, name)
   254  }
   255  
   256  func (e *DBEntry) TxnGetTableEntryByName(name string, txn txnif.AsyncTxn) (entry *TableEntry, err error) {
   257  	n, err := e.txnGetNodeByName(txn.GetTenantID(), name, txn)
   258  	if err != nil {
   259  		return
   260  	}
   261  	entry = n.GetPayload()
   262  	return
   263  }
   264  
   265  func (e *DBEntry) GetTableEntryByName(
   266  	tenantID uint32,
   267  	name string,
   268  	txn txnif.TxnReader) (entry *TableEntry, err error) {
   269  	n, err := e.txnGetNodeByName(tenantID, name, txn)
   270  	if err != nil {
   271  		return
   272  	}
   273  	entry = n.GetPayload()
   274  	return
   275  }
   276  
   277  func (e *DBEntry) TxnGetTableEntryByID(id uint64, txn txnif.AsyncTxn) (entry *TableEntry, err error) {
   278  	entry, err = e.GetTableEntryByID(id)
   279  	if err != nil {
   280  		return
   281  	}
   282  	//check whether visible and dropped.
   283  	visible, dropped := entry.GetVisibility(txn)
   284  	if !visible || dropped {
   285  		return nil, moerr.GetOkExpectedEOB()
   286  	}
   287  	return
   288  }
   289  
   290  // Catalog entry is dropped in following steps:
   291  // 1. Locate the record by timestamp
   292  // 2. Check conflication.
   293  // 2.1 Wait for the related txn if need.
   294  // 2.2 w-w conflict when 1. there's an active txn; or
   295  //  2. the CommitTS of the latest related txn is larger than StartTS of write txn
   296  //
   297  // 3. Check duplicate/not found.
   298  // If the entry has already been dropped, return ErrNotFound.
   299  func (e *DBEntry) DropTableEntry(name string, txn txnif.AsyncTxn) (newEntry bool, deleted *TableEntry, err error) {
   300  	tn, err := e.txnGetNodeByName(txn.GetTenantID(), name, txn)
   301  	if err != nil {
   302  		return
   303  	}
   304  	entry := tn.GetPayload()
   305  	entry.Lock()
   306  	defer entry.Unlock()
   307  	newEntry, err = entry.DropEntryLocked(txn)
   308  	if err == nil {
   309  		deleted = entry
   310  	}
   311  	return
   312  }
   313  
   314  func (e *DBEntry) DropTableEntryByID(id uint64, txn txnif.AsyncTxn) (newEntry bool, deleted *TableEntry, err error) {
   315  	entry, err := e.GetTableEntryByID(id)
   316  	if err != nil {
   317  		return
   318  	}
   319  
   320  	entry.Lock()
   321  	defer entry.Unlock()
   322  	newEntry, err = entry.DropEntryLocked(txn)
   323  	if err == nil {
   324  		deleted = entry
   325  	}
   326  	return
   327  }
   328  
   329  func (e *DBEntry) CreateTableEntry(schema *Schema, txn txnif.AsyncTxn, dataFactory TableDataFactory) (created *TableEntry, err error) {
   330  	e.Lock()
   331  	defer e.Unlock()
   332  	created = NewTableEntry(e, schema, txn, dataFactory)
   333  	err = e.AddEntryLocked(created, txn, false)
   334  
   335  	return created, err
   336  }
   337  
   338  func (e *DBEntry) CreateTableEntryWithTableId(schema *Schema, txn txnif.AsyncTxn, dataFactory TableDataFactory, tableId uint64) (created *TableEntry, err error) {
   339  	e.Lock()
   340  	defer e.Unlock()
   341  	if tableId < pkgcatalog.MO_RESERVED_MAX {
   342  		return nil, moerr.NewInternalErrorNoCtx("reserved table ID %d", tableId)
   343  	}
   344  	//Deduplicate for tableId
   345  	if _, exist := e.entries[tableId]; exist {
   346  		return nil, moerr.GetOkExpectedDup()
   347  	}
   348  	created = NewTableEntryWithTableId(e, schema, txn, dataFactory, tableId)
   349  	err = e.AddEntryLocked(created, txn, false)
   350  
   351  	return created, err
   352  }
   353  
   354  // For test only
   355  func (e *DBEntry) PrettyNameIndex() string {
   356  	buf := &bytes.Buffer{}
   357  	buf.WriteString(fmt.Sprintf("[%d]NameIndex:\n", len(e.nameNodes)))
   358  	// iterate all nodes in nameNodes, collect node ids to a string
   359  	ids := make([]uint64, 0)
   360  	// sort e.nameNodes by name
   361  	names := make([]string, 0, len(e.nameNodes))
   362  	for name := range e.nameNodes {
   363  		names = append(names, name)
   364  	}
   365  	sort.Strings(names)
   366  	for _, name := range names {
   367  		node := e.nameNodes[name]
   368  		ids = ids[:0]
   369  		node.ForEachNodes(func(nn *nameNode[*TableEntry]) bool {
   370  			ids = append(ids, nn.id)
   371  			return true
   372  		})
   373  		buf.WriteString(fmt.Sprintf("  %s: %v\n", name, ids))
   374  	}
   375  	return buf.String()
   376  }
   377  
   378  func (e *DBEntry) RenameTableInTxn(old, new string, tid uint64, tenantID uint32, txn txnif.TxnReader, first bool) error {
   379  	e.Lock()
   380  	defer e.Unlock()
   381  	// if alter again and again in the same txn, previous temp name should be deleted.
   382  	if !first {
   383  		e.removeNameIndexLocked(genTblFullName(tenantID, old), tid)
   384  	}
   385  
   386  	newFullName := genTblFullName(tenantID, new)
   387  	if err := e.checkAddNameConflictLocked(new, tid, e.nameNodes[newFullName], txn); err != nil {
   388  		return err
   389  	}
   390  	// make sure every name node has up to one table id. check case alter A -> B -> A
   391  	e.removeNameIndexLocked(newFullName, tid)
   392  	// add to the head of linked list
   393  	e.addNameIndexLocked(newFullName, tid)
   394  
   395  	return nil
   396  }
   397  
   398  func (e *DBEntry) addNameIndexLocked(fullname string, tid uint64) {
   399  	node := e.nameNodes[fullname]
   400  	if node == nil {
   401  		nn := newNodeList(e.GetItemNodeByIDLocked,
   402  			tableVisibilityFn[*TableEntry],
   403  			&e.nodesMu,
   404  			fullname)
   405  		e.nameNodes[fullname] = nn
   406  		nn.CreateNode(tid)
   407  	} else {
   408  		node.CreateNode(tid)
   409  	}
   410  }
   411  
   412  func (e *DBEntry) removeNameIndexLocked(fullname string, tid uint64) {
   413  	nn := e.nameNodes[fullname]
   414  	if nn == nil {
   415  		return
   416  	}
   417  	if _, empty := nn.DeleteNode(tid); empty {
   418  		delete(e.nameNodes, fullname)
   419  	}
   420  }
   421  
   422  func (e *DBEntry) RollbackRenameTable(fullname string, tid uint64) {
   423  	e.Lock()
   424  	defer e.Unlock()
   425  	e.removeNameIndexLocked(fullname, tid)
   426  }
   427  
   428  func (e *DBEntry) RemoveEntry(table *TableEntry) (err error) {
   429  	logutil.Info("[Catalog]", common.OperationField("remove"),
   430  		common.OperandField(table.String()))
   431  	e.Lock()
   432  	defer e.Unlock()
   433  	if n, ok := e.entries[table.ID]; !ok {
   434  		return moerr.GetOkExpectedEOB()
   435  	} else {
   436  		table.RLock()
   437  		defer table.RUnlock()
   438  		prevname := ""
   439  		// clean all name because RemoveEntry can be called by GC、
   440  		table.LoopChainLocked(func(m *MVCCNode[*TableMVCCNode]) bool {
   441  			if prevname == m.BaseNode.Schema.Name {
   442  				return true
   443  			}
   444  			prevname = m.BaseNode.Schema.Name
   445  			tenantID := m.BaseNode.Schema.AcInfo.TenantID
   446  			fullname := genTblFullName(tenantID, prevname)
   447  			nn := e.nameNodes[fullname]
   448  			if nn == nil {
   449  				return true
   450  			}
   451  			nn.DeleteNode(table.ID)
   452  			if nn.Length() == 0 {
   453  				delete(e.nameNodes, fullname)
   454  			}
   455  			return true
   456  		})
   457  		e.link.Delete(n)
   458  		delete(e.entries, table.ID)
   459  	}
   460  	return
   461  }
   462  
   463  // Catalog entry is created in following steps:
   464  // 1. Locate the record. Creating always gets the latest DBEntry.
   465  // 2.1 If there doesn't exist a DBEntry, add new entry and return.
   466  // 2.2 If there exists a DBEntry:
   467  // 2.2.1 Check conflication.
   468  //  1. Wait for the related txn if need.
   469  //  2. w-w conflict when: there's an active txn; or
   470  //     he CommitTS of the latest related txn is larger than StartTS of write txn
   471  //
   472  // 2.2.2 Check duplicate/not found.
   473  // If the entry hasn't been dropped, return ErrDuplicate.
   474  func (e *DBEntry) AddEntryLocked(table *TableEntry, txn txnif.TxnReader, skipDedup bool) (err error) {
   475  	fullName := table.GetFullName()
   476  	nn := e.nameNodes[fullName]
   477  	if nn == nil {
   478  		n := e.link.Insert(table)
   479  		e.entries[table.ID] = n
   480  
   481  		nn := newNodeList(e.GetItemNodeByIDLocked,
   482  			tableVisibilityFn[*TableEntry],
   483  			&e.nodesMu,
   484  			fullName)
   485  		e.nameNodes[fullName] = nn
   486  
   487  		nn.CreateNode(table.ID)
   488  	} else {
   489  		if !skipDedup {
   490  			name := table.GetLastestSchemaLocked().Name
   491  			err = e.checkAddNameConflictLocked(name, table.ID, nn, txn)
   492  			if err != nil {
   493  				return
   494  			}
   495  		}
   496  		n := e.link.Insert(table)
   497  		e.entries[table.ID] = n
   498  		nn.CreateNode(table.ID)
   499  	}
   500  	return
   501  }
   502  
   503  func (e *DBEntry) checkAddNameConflictLocked(name string, tid uint64, nn *nodeList[*TableEntry], txn txnif.TxnReader) (err error) {
   504  	if nn == nil {
   505  		return nil
   506  	}
   507  	node := nn.GetNode()
   508  	if node == nil {
   509  		return nil
   510  	}
   511  	// check ww conflict
   512  	tbl := nn.GetNode().GetPayload()
   513  	// skip the same table entry
   514  	if tbl.ID == tid {
   515  		return nil
   516  	}
   517  	if err = tbl.ConflictCheck(txn); err != nil {
   518  		return
   519  	}
   520  	// check name dup
   521  	if txn == nil {
   522  		// replay checkpoint
   523  		return nil
   524  	}
   525  	if existEntry, _ := nn.TxnGetNodeLocked(txn, name); existEntry != nil {
   526  		return moerr.GetOkExpectedDup()
   527  	}
   528  	return nil
   529  }
   530  
   531  func (e *DBEntry) MakeCommand(id uint32) (txnif.TxnCmd, error) {
   532  	cmdType := IOET_WALTxnCommand_Database
   533  	e.RLock()
   534  	defer e.RUnlock()
   535  	return newDBCmd(id, cmdType, e), nil
   536  }
   537  
   538  func (e *DBEntry) Set1PC() {
   539  	e.GetLatestNodeLocked().Set1PC()
   540  }
   541  func (e *DBEntry) Is1PC() bool {
   542  	return e.GetLatestNodeLocked().Is1PC()
   543  }
   544  func (e *DBEntry) GetCatalog() *Catalog { return e.catalog }
   545  
   546  func (e *DBEntry) RecurLoop(processor Processor) (err error) {
   547  	tableIt := e.MakeTableIt(true)
   548  	for tableIt.Valid() {
   549  		table := tableIt.Get().GetPayload()
   550  		if err = processor.OnTable(table); err != nil {
   551  			if moerr.IsMoErrCode(err, moerr.OkStopCurrRecur) {
   552  				err = nil
   553  				tableIt.Next()
   554  				continue
   555  			}
   556  			break
   557  		}
   558  		if err = table.RecurLoop(processor); err != nil {
   559  			return
   560  		}
   561  		if err = processor.OnPostTable(table); err != nil {
   562  			break
   563  		}
   564  		tableIt.Next()
   565  	}
   566  	if moerr.IsMoErrCode(err, moerr.OkStopCurrRecur) {
   567  		err = nil
   568  	}
   569  	return err
   570  }
   571  
   572  func (e *DBEntry) PrepareRollback() (err error) {
   573  	var isEmpty bool
   574  	if isEmpty, err = e.BaseEntryImpl.PrepareRollback(); err != nil {
   575  		return
   576  	}
   577  	if isEmpty {
   578  		if err = e.catalog.RemoveDBEntry(e); err != nil {
   579  			return
   580  		}
   581  	}
   582  	return
   583  }
   584  
   585  // IsActive is coarse API: no consistency check
   586  func (e *DBEntry) IsActive() bool {
   587  	return !e.HasDropCommitted()
   588  }
   589  
   590  // only for test
   591  func MockDBEntryWithAccInfo(accId uint64, dbId uint64) *DBEntry {
   592  	entry := &DBEntry{
   593  		ID: dbId,
   594  	}
   595  
   596  	entry.DBNode = &DBNode{}
   597  	entry.DBNode.acInfo.TenantID = uint32(accId)
   598  
   599  	return entry
   600  }
   601  
   602  func (e *DBEntry) GetBlockEntryByID(id *common.ID) (obj *ObjectEntry, err error) {
   603  	e.RLock()
   604  	table, err := e.GetTableEntryByID(id.TableID)
   605  	e.RUnlock()
   606  	if err != nil {
   607  		return
   608  	}
   609  	obj, err = table.GetObjectByID(id.ObjectID())
   610  	return
   611  }