github.com/geniusesgroup/libgo@v0.0.0-20220713101832-828057a9d3d4/pehrest/hash.go (about)

     1  /* For license and copyright information please see LEGAL file in repository */
     2  
     3  package pehrest
     4  
     5  import (
     6  	"../convert"
     7  	"../time"
     8  	"../ganjine"
     9  	"../mediatype"
    10  	"../os/persiaos"
    11  	"../protocol"
    12  	"../syllab"
    13  )
    14  
    15  const indexHashStructureID uint64 = 1404190754065006447
    16  
    17  var IndexHashStructure = indexHashStructure{
    18  	MediaType: mediatype.New("urn:giti:index.protocol:data-structure:hash", "application/vnd.index.protocol.hash", "", "").
    19  		SetInfo(protocol.Software_PreAlpha, 1599286551, IndexHash{}).
    20  		SetDetail(protocol.LanguageEnglish, "Index Hash",
    21  			"Store the hash index data by 32byte key as ObjectID and any 16byte value",
    22  			[]string{}),
    23  }
    24  
    25  type indexHashStructure struct {
    26  	mediatype.MediaType
    27  }
    28  
    29  // IndexHash is standard structure to store any hash byte index!!
    30  // It is simple secondary index e.g. hash(RecordStructureID, "user@email.com")
    31  type IndexHash struct {
    32  	WriteTime           time.Time
    33  	EarlierExpandTime   time.Time
    34  	LastExpandTime      time.Time
    35  	IndexValuesNumber   uint64     // IndexValues len
    36  	IndexValuesCapacity uint64     // IndexValues cap
    37  	IndexValues         [][16]byte // Can store RecordsID or any data up to 16 byte length and store by time of added to index
    38  }
    39  
    40  // ReadHeader get needed data from storage and decode to given ih without IndexValues array data
    41  func (ih *IndexHash) ReadHeader() (err protocol.Error) {
    42  	// Get first cluster of record to read header data
    43  	var header []byte
    44  	header, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, 0, uint64(ih.LenOfSyllabStack()))
    45  	if err != nil {
    46  		return
    47  	}
    48  
    49  	err = ih.FromSyllab(header)
    50  	if err != nil {
    51  		return
    52  	}
    53  
    54  	return
    55  }
    56  
    57  // Pop return last RecordID in given index and delete it from index!
    58  func (ih *IndexHash) Pop() (recordID [32]byte, err protocol.Error) {
    59  	// Get first cluster of record to read header data
    60  	var header []byte
    61  	header, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, 0, uint64(ih.LenOfSyllabStack()))
    62  	if err != nil {
    63  		return
    64  	}
    65  
    66  	err = ih.FromSyllab(header)
    67  	if err != nil {
    68  		return
    69  	}
    70  
    71  	if ih.IndexValuesNumber > 1 {
    72  		ih.IndexValuesNumber--
    73  		var record []byte
    74  		var recordOffset = uint64(ih.LenOfSyllabStack()) + (ih.IndexValuesNumber * 32)
    75  		record, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, recordOffset, 32)
    76  		if err != nil {
    77  			return
    78  		}
    79  		copy(recordID[:], record[:])
    80  
    81  		ih.WriteTime = time.Now()
    82  		var buf = make([]byte, ih.LenOfSyllabStack())
    83  		ih.ToSyllabHeader(buf)
    84  		err = persiaos.StorageWriteRecord(ih.RecordID, indexHashStructureID, 0, buf)
    85  		err = persiaos.StorageWriteRecord(ih.RecordID, indexHashStructureID, recordOffset, make([]byte, 32))
    86  	} else {
    87  		err = persiaos.StorageDeleteRecord(ih.RecordID, indexHashStructureID)
    88  		// err = ErrEndOfIndexRecord
    89  	}
    90  
    91  	return
    92  }
    93  
    94  // Peek return last recordID pushed to given index. unlike Pop() it won't delete it from index!
    95  func (ih *IndexHash) Peek() (recordID [32]byte, err protocol.Error) {
    96  	// Get first cluster of record to read header data
    97  	var header []byte
    98  	header, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, 0, uint64(ih.LenOfSyllabStack()))
    99  	if err != nil {
   100  		return
   101  	}
   102  
   103  	err = ih.FromSyllab(header)
   104  	if err != nil {
   105  		return
   106  	}
   107  
   108  	var record []byte
   109  	var recordOffset = uint64(ih.LenOfSyllabStack()) + ((ih.IndexValuesNumber - 1) * 32)
   110  	record, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, recordOffset, 32)
   111  	if err != nil {
   112  		return
   113  	}
   114  	copy(recordID[:], record[:])
   115  
   116  	return
   117  }
   118  
   119  // Get return related records ID to given index with offset and limit!
   120  func (ih *IndexHash) Get(offset, limit uint64) (indexValues [][32]byte, err protocol.Error) {
   121  	// Get first cluster of record to read header data
   122  	var header []byte
   123  	header, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, 0, uint64(ih.LenOfSyllabStack()))
   124  	if err != nil {
   125  		err = ganjine.ErrRecordNotFound
   126  		return
   127  	}
   128  
   129  	err = ih.FromSyllab(header)
   130  	if err != nil {
   131  		return
   132  	}
   133  
   134  	if limit > ih.IndexValuesNumber {
   135  		offset = 0
   136  		limit = ih.IndexValuesNumber
   137  	} else {
   138  		if offset > ih.IndexValuesNumber {
   139  			// If offset set to higher than exiting number of record always return last records by given limit!
   140  			offset = ih.IndexValuesNumber - limit
   141  		}
   142  
   143  		if limit == 0 {
   144  			if offset == 0 {
   145  				// it means returns all available IndexValues
   146  				limit = ih.IndexValuesNumber
   147  			} else {
   148  				// it means returns all available IndexValues after offset
   149  				limit = ih.IndexValuesNumber - offset
   150  			}
   151  		}
   152  	}
   153  
   154  	offset = uint64(ih.LenOfSyllabStack()) + (offset * 32)
   155  	limit = limit * 32
   156  	var record []byte
   157  	record, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, offset, limit)
   158  	if err != nil {
   159  		return
   160  	}
   161  
   162  	// We know e.g. 128*[32]byte == [4096]byte is same size but Go is safe default and don't let return simply, so:
   163  	indexValues = convert.UnsafeByteSliceTo32ByteArraySlice(record)
   164  	return
   165  }
   166  
   167  // Push add given RecordID to then end of given hash index!
   168  func (ih *IndexHash) Push(recordID [32]byte) (err protocol.Error) {
   169  	var timeNow = time.Now()
   170  
   171  	// Get first cluster of record to read header data
   172  	var record []byte
   173  	record, err = persiaos.StorageReadRecord(ih.RecordID, indexHashStructureID, 0, uint64(ih.LenOfSyllabStack()))
   174  	if err != nil {
   175  		if err.Equal(object.ErrNotExist) {
   176  			// desire record not found. write new one!
   177  			ih.RecordStructureID = indexHashStructureID
   178  			ih.RecordSize = uint64(ih.LenOfSyllabStack()) + (1 * 32)
   179  			ih.WriteTime = timeNow
   180  			ih.OwnerAppID = protocol.OS.AppManifest().AppUUID()
   181  			// ih.EarlierExpandTime = timeNow
   182  			ih.LastExpandTime = timeNow
   183  			ih.IndexValuesNumber = 1
   184  			ih.IndexValuesCapacity = 1
   185  			ih.IndexValues = make([][32]byte, 1)
   186  			ih.IndexValues[0] = recordID
   187  			err = persiaos.StorageSetRecord(ih.ToSyllabFull())
   188  		} else {
   189  			// err =
   190  		}
   191  		return
   192  	}
   193  
   194  	err = ih.FromSyllab(record)
   195  	if err != nil {
   196  		return
   197  	}
   198  
   199  	// Check and expand record if needed
   200  	err = ih.checkAndExpand(timeNow)
   201  	if err != nil {
   202  		return
   203  	}
   204  
   205  	persiaos.StorageWriteRecord(ih.RecordID, indexHashStructureID, uint64(ih.LenOfSyllabStack())+(ih.IndexValuesNumber*32), recordID[:])
   206  
   207  	ih.WriteTime = timeNow
   208  	ih.IndexValuesNumber++
   209  	ih.ToSyllabHeader(record)
   210  	err = persiaos.StorageWriteRecord(ih.RecordID, indexHashStructureID, 0, record)
   211  	return
   212  }
   213  
   214  // Insert add given RecordID to the end of given hash index and return error if it is exist already!
   215  func (ih *IndexHash) Insert(recordID [32]byte) (err protocol.Error) {
   216  	var timeNow = time.Now()
   217  
   218  	var record []byte
   219  	record, err = persiaos.StorageGetRecord(ih.RecordID, indexHashStructureID)
   220  	if err != nil {
   221  		if err.Equal(object.ErrNotExist) {
   222  			// desire record not found. write new one!
   223  			ih.RecordStructureID = indexHashStructureID
   224  			ih.RecordSize = uint64(ih.LenOfSyllabStack()) + (1 * 32)
   225  			ih.WriteTime = timeNow
   226  			ih.OwnerAppID = protocol.OS.AppManifest().AppUUID()
   227  			// ih.EarlierExpandTime = timeNow
   228  			ih.LastExpandTime = timeNow
   229  			ih.IndexValuesNumber = 1
   230  			ih.IndexValuesCapacity = 1
   231  			ih.IndexValues = make([][32]byte, 1)
   232  			ih.IndexValues[0] = recordID
   233  			err = persiaos.StorageSetRecord(ih.ToSyllabFull())
   234  		} else {
   235  			// err =
   236  		}
   237  		return
   238  	}
   239  
   240  	err = ih.FromSyllab(record)
   241  	if err != nil {
   242  		return
   243  	}
   244  
   245  	for _, value := range ih.IndexValues {
   246  		if recordID == value {
   247  			err = ErrIndexValueAlreadyExist
   248  			return
   249  		}
   250  	}
   251  
   252  	// Check and expand record if needed
   253  	err = ih.checkAndExpand(timeNow)
   254  	if err != nil {
   255  		return
   256  	}
   257  
   258  	persiaos.StorageWriteRecord(ih.RecordID, indexHashStructureID, uint64(ih.LenOfSyllabStack())+(ih.IndexValuesNumber*32), recordID[:])
   259  
   260  	ih.WriteTime = timeNow
   261  	ih.IndexValuesNumber++
   262  
   263  	var newRecord = record[:ih.LenOfSyllabStack()]
   264  	ih.ToSyllabHeader(newRecord)
   265  	err = persiaos.StorageWriteRecord(ih.RecordID, indexHashStructureID, 0, newRecord)
   266  	return
   267  }
   268  
   269  // InsertInOrder add given RecordID to the given hash index in order and return error if it is exist already!
   270  func (ih *IndexHash) InsertInOrder(recordID [32]byte) (err protocol.Error) {
   271  
   272  	return
   273  }
   274  
   275  // Check and expand record if needed
   276  func (ih *IndexHash) checkAndExpand(timeNow time.Time) (err protocol.Error) {
   277  	if ih.IndexValuesNumber == ih.IndexValuesCapacity {
   278  		var expandNumber = ih.calculateExpandNumber(timeNow)
   279  		var expandSize = expandNumber * 32
   280  		ih.RecordSize += expandSize
   281  		ih.EarlierExpandTime = ih.LastExpandTime
   282  		ih.LastExpandTime = timeNow
   283  		ih.IndexValuesCapacity += expandNumber
   284  		err = persiaos.StorageExpandRecord(ih.RecordID, indexHashStructureID, expandSize)
   285  		if err != nil {
   286  			return
   287  		}
   288  	}
   289  	return
   290  }
   291  
   292  // TODO::: Improve expand algorithm
   293  func (ih *IndexHash) calculateExpandNumber(timeNow time.Time) (expandNumber uint64) {
   294  	var ln = ih.IndexValuesCapacity
   295  	if ih.LastExpandTime-ih.EarlierExpandTime < (60 * 60) { // Expanded twice in less than 60 minutes
   296  		if timeNow < ih.LastExpandTime+(60*60) { // Last expand earlier than 60 minutes
   297  			expandNumber = ln
   298  		} else if timeNow < ih.LastExpandTime+(24*60*60) { // Last expand earlier than 1 day
   299  			expandNumber = ln / 2
   300  		} else { // Last expand more than 1 day
   301  			expandNumber = 8
   302  		}
   303  	} else if ih.LastExpandTime-ih.EarlierExpandTime < (24 * 60 * 60) { // Expanded twice in less than 1 day
   304  		if timeNow < ih.LastExpandTime+(24*60*60) { // Last expand earlier than 1 day
   305  			expandNumber = ln / 4
   306  		} else { // Last expand more than 1 day
   307  			expandNumber = 4
   308  		}
   309  	} else if ih.LastExpandTime-ih.EarlierExpandTime < (7 * 24 * 60 * 60) { // Expanded twice in less than 1 week
   310  		if timeNow < ih.LastExpandTime+(24*60*60) { // Last expand earlier than 1 day
   311  			expandNumber = ln / 8
   312  		} else { // Last expand more than 1 day
   313  			expandNumber = 1
   314  		}
   315  	}
   316  	if expandNumber == 0 { // Usually means expanded twice in more than 1 week
   317  		expandNumber = 1
   318  	}
   319  	return
   320  }
   321  
   322  // Append add given RecordID with any logic need like expand!
   323  func (ih *IndexHash) Append(recordID ...[32]byte) (err protocol.Error) {
   324  	return
   325  }
   326  
   327  // DeleteRecord use to delete given record ID form given indexHash!
   328  func (ih *IndexHash) DeleteRecord() (err protocol.Error) {
   329  	// Do for i=0 as local node
   330  	err = persiaos.StorageDeleteRecord(ih.RecordID, indexHashStructureID)
   331  	return
   332  }
   333  
   334  // Delete use to delete given record ID form given indexHash!
   335  func (ih *IndexHash) Delete(recordID [32]byte) (err protocol.Error) {
   336  	var record []byte
   337  	record, err = persiaos.StorageGetRecord(ih.RecordID, indexHashStructureID)
   338  	if err != nil {
   339  		return
   340  	}
   341  
   342  	err = ih.FromSyllab(record)
   343  	if err != nil {
   344  		return
   345  	}
   346  
   347  	var ln = len(ih.IndexValues)
   348  	for i := 0; i < ln; i++ {
   349  		if ih.IndexValues[i] == recordID {
   350  			copy(ih.IndexValues[i:], ih.IndexValues[i+1:])
   351  			ih.IndexValuesNumber--
   352  		}
   353  	}
   354  	for i := uint64(len(ih.IndexValues) - 1); i >= ih.IndexValuesNumber; i-- {
   355  		ih.IndexValues[i] = [32]byte{}
   356  	}
   357  
   358  	err = persiaos.StorageSetRecord(ih.ToSyllabFull())
   359  	return
   360  }
   361  
   362  // Deletes use to delete given records ID form given indexHash!
   363  func (ih *IndexHash) Deletes(indexValues [][32]byte) (err protocol.Error) {
   364  	// Get first cluster of record to read header data
   365  	var record []byte
   366  	record, err = persiaos.StorageGetRecord(ih.RecordID, indexHashStructureID)
   367  	if err != nil {
   368  		return
   369  	}
   370  
   371  	err = ih.FromSyllab(record)
   372  	if err != nil {
   373  		return
   374  	}
   375  
   376  	var ln = len(ih.IndexValues)
   377  	var inputLn = len(indexValues)
   378  	for i := 0; i < ln; i++ {
   379  		for j := 0; j < inputLn; j++ {
   380  			if ih.IndexValues[i] == indexValues[j] {
   381  				copy(ih.IndexValues[i:], ih.IndexValues[i+1:])
   382  				ih.IndexValuesNumber--
   383  			}
   384  		}
   385  	}
   386  	for i := uint64(len(ih.IndexValues) - 1); i >= ih.IndexValuesNumber; i-- {
   387  		ih.IndexValues[i] = [32]byte{}
   388  	}
   389  
   390  	err = persiaos.StorageSetRecord(ih.ToSyllabFull())
   391  	return
   392  }
   393  
   394  /*
   395  	-- Syllab Encoder & Decoder --
   396  */
   397  
   398  func (ih *IndexHash) FromSyllab(payload []byte, stackIndex uint32) {
   399  	if uint32(len(buf)) < ih.LenOfSyllabStack() {
   400  		err = syllab.ErrShortArrayDecode
   401  		return
   402  	}
   403  
   404  	copy(ih.RecordID[:], buf[0:])
   405  	ih.RecordStructureID = syllab.GetUInt64(buf, 32)
   406  	ih.RecordSize = syllab.GetUInt64(buf, 40)
   407  	ih.WriteTime = time.Time(syllab.GetInt64(buf, 48))
   408  	copy(ih.OwnerAppID[:], buf[56:])
   409  
   410  	if ih.RecordStructureID != indexHashStructureID {
   411  		err = ErrRecordNotValid
   412  		return
   413  	}
   414  
   415  	ih.EarlierExpandTime = time.Time(syllab.GetInt64(buf, 88))
   416  	ih.LastExpandTime = time.Time(syllab.GetInt64(buf, 96))
   417  	ih.IndexValuesNumber = syllab.GetUInt64(buf, 104)
   418  	ih.IndexValuesCapacity = syllab.GetUInt64(buf, 112)
   419  	// Break syllab rules and don't get IndexValues Add&&len
   420  	buf = buf[ih.LenOfSyllabStack():]
   421  	ih.IndexValues = convert.UnsafeByteSliceTo32ByteArraySlice(buf)
   422  	return
   423  }
   424  
   425  func (ih *IndexHash) ToSyllabFull() (buf []byte) {
   426  	buf = make([]byte, uint64(ih.LenAsSyllab()))
   427  	copy(buf[ih.LenOfSyllabStack():], convert.Unsafe32ByteArraySliceToByteSlice(ih.IndexValues))
   428  	ih.ToSyllabHeader(buf)
   429  	return
   430  }
   431  
   432  func (ih *IndexHash) ToSyllabHeader(buf []byte) {
   433  	copy(buf[0:], ih.RecordID[:])
   434  	syllab.SetUInt64(buf, 32, indexHashStructureID)
   435  	syllab.SetUInt64(buf, 40, ih.RecordSize)
   436  	syllab.SetInt64(buf, 48, int64(ih.WriteTime))
   437  	copy(buf[56:], ih.OwnerAppID[:])
   438  
   439  	syllab.SetInt64(buf, 88, int64(ih.EarlierExpandTime))
   440  	syllab.SetInt64(buf, 96, int64(ih.LastExpandTime))
   441  	syllab.SetUInt64(buf, 104, ih.IndexValuesNumber)
   442  	syllab.SetUInt64(buf, 112, ih.IndexValuesCapacity)
   443  	// Break syllab rules and don't set IndexValues Add&&len
   444  }
   445  
   446  func (ih *IndexHash) LenOfSyllabStack() uint32 {
   447  	return 120 // 88 + 8 + 8 + 8 + 8 + ?  !!don't need IndexValues Add&&len!!
   448  }
   449  
   450  func (ih *IndexHash) LenOfSyllabHeap() (ln uint32) {
   451  	ln += uint32(ih.IndexValuesCapacity * 32)
   452  	return
   453  }
   454  
   455  func (ih *IndexHash) LenAsSyllab() uint64 {
   456  	return uint64(ih.LenOfSyllabStack() + ih.LenOfSyllabHeap())
   457  }