github.com/balzaczyy/golucene@v0.0.0-20151210033525-d0be9ee89713/core/index/segmentInfos.go (about)

     1  package index
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/balzaczyy/golucene/core/codec"
     8  	. "github.com/balzaczyy/golucene/core/codec/spi"
     9  	. "github.com/balzaczyy/golucene/core/index/model"
    10  	"github.com/balzaczyy/golucene/core/store"
    11  	"github.com/balzaczyy/golucene/core/util"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  /* Prints the given message to the infoStream. */
    17  func message(format string, args ...interface{}) {
    18  	fmt.Printf("SIS: %v\n", fmt.Sprintf(format, args...))
    19  }
    20  
    21  type FindSegmentsFile struct {
    22  	directory                store.Directory
    23  	doBody                   func(segmentFileName string) (interface{}, error)
    24  	defaultGenLookaheadCount int
    25  }
    26  
    27  func NewFindSegmentsFile(directory store.Directory,
    28  	doBody func(segmentFileName string) (interface{}, error)) *FindSegmentsFile {
    29  	return &FindSegmentsFile{directory, doBody, 10}
    30  }
    31  
    32  // TODO support IndexCommit
    33  func (fsf *FindSegmentsFile) run(commit IndexCommit) (interface{}, error) {
    34  	// fmt.Println("Finding segments file...")
    35  	if commit != nil {
    36  		if fsf.directory != commit.Directory() {
    37  			return nil, errors.New("the specified commit does not match the specified Directory")
    38  		}
    39  		return fsf.doBody(commit.SegmentsFileName())
    40  	}
    41  
    42  	lastGen := int64(-1)
    43  	gen := int64(0)
    44  	genLookaheadCount := 0
    45  	var exc error
    46  	retryCount := 0
    47  
    48  	useFirstMethod := true
    49  
    50  	// Loop until we succeed in calling doBody() without
    51  	// hitting an IOException.  An IOException most likely
    52  	// means a commit was in process and has finished, in
    53  	// the time it took us to load the now-old infos files
    54  	// (and segments files).  It's also possible it's a
    55  	// true error (corrupt index).  To distinguish these,
    56  	// on each retry we must see "forward progress" on
    57  	// which generation we are trying to load.  If we
    58  	// don't, then the original error is real and we throw
    59  	// it.
    60  
    61  	// We have three methods for determining the current
    62  	// generation.  We try the first two in parallel (when
    63  	// useFirstMethod is true), and fall back to the third
    64  	// when necessary.
    65  
    66  	for {
    67  		// fmt.Println("Trying...")
    68  		if useFirstMethod {
    69  			// fmt.Println("Trying first method...")
    70  			// List the directory and use the highest
    71  			// segments_N file.  This method works well as long
    72  			// as there is no stale caching on the directory
    73  			// contents (NOTE: NFS clients often have such stale
    74  			// caching):
    75  			genA := int64(-1)
    76  
    77  			files, err := fsf.directory.ListAll()
    78  			if err != nil {
    79  				return nil, err
    80  			}
    81  			if files != nil {
    82  				genA = LastCommitGeneration(files)
    83  			}
    84  
    85  			// message("directory listing genA=%v", genA)
    86  
    87  			// Also open segments.gen and read its
    88  			// contents.  Then we take the larger of the two
    89  			// gens.  This way, if either approach is hitting
    90  			// a stale cache (NFS) we have a better chance of
    91  			// getting the right generation.
    92  			genB := int64(-1)
    93  			genInput, err := fsf.directory.OpenChecksumInput(INDEX_FILENAME_SEGMENTS_GEN, store.IO_CONTEXT_READ)
    94  			if err != nil {
    95  				message("segments.gen open: %v", err)
    96  			} else {
    97  				defer genInput.Close()
    98  				// fmt.Println("Reading segments info...")
    99  
   100  				var version int32
   101  				if version, err = genInput.ReadInt(); err != nil {
   102  					return nil, err
   103  				}
   104  				// fmt.Printf("Version: %v\n", version)
   105  				if version == FORMAT_SEGMENTS_GEN_47 || version == FORMAT_SEGMENTS_GEN_CURRENT {
   106  					// fmt.Println("Version is current.")
   107  					var gen0, gen1 int64
   108  					if gen0, err = genInput.ReadLong(); err != nil {
   109  						return nil, err
   110  					}
   111  					if gen1, err = genInput.ReadLong(); err != nil {
   112  						return nil, err
   113  					}
   114  					message("fallback check: %v; %v", gen0, gen1)
   115  					if version == FORMAT_SEGMENTS_GEN_CHECKSUM {
   116  						if _, err = codec.CheckFooter(genInput); err != nil {
   117  							return nil, err
   118  						}
   119  					} else {
   120  						if err = codec.CheckEOF(genInput); err != nil {
   121  							return nil, err
   122  						}
   123  					}
   124  					if gen0 == gen1 {
   125  						// The file is consistent.
   126  						genB = gen0
   127  					}
   128  				} else {
   129  					return nil, codec.NewIndexFormatTooNewError(genInput, version,
   130  						FORMAT_SEGMENTS_GEN_CURRENT, FORMAT_SEGMENTS_GEN_CURRENT)
   131  				}
   132  			}
   133  
   134  			message("%v check: genB=%v", INDEX_FILENAME_SEGMENTS_GEN, genB)
   135  
   136  			// Pick the larger of the two gen's:
   137  			gen = genA
   138  			if genB > gen {
   139  				gen = genB
   140  			}
   141  
   142  			if gen == -1 {
   143  				// Neither approach found a generation
   144  				return nil, errors.New(fmt.Sprintf("no segments* file found in %v: files: %#v", fsf.directory, files))
   145  			}
   146  		}
   147  
   148  		if useFirstMethod && lastGen == gen && retryCount >= 2 {
   149  			// Give up on first method -- this is 3rd cycle on
   150  			// listing directory and checking gen file to
   151  			// attempt to locate the segments file.
   152  			useFirstMethod = false
   153  		}
   154  
   155  		// Second method: since both directory cache and
   156  		// file contents cache seem to be stale, just
   157  		// advance the generation.
   158  		if !useFirstMethod {
   159  			if genLookaheadCount < fsf.defaultGenLookaheadCount {
   160  				gen++
   161  				genLookaheadCount++
   162  				message("look ahead increment gen to %v", gen)
   163  			} else {
   164  				// All attempts have failed -- throw first exc:
   165  				return nil, exc
   166  			}
   167  		} else if lastGen == gen {
   168  			// This means we're about to try the same
   169  			// segments_N last tried.
   170  			retryCount++
   171  		} else {
   172  			// Segment file has advanced since our last loop
   173  			// (we made "progress"), so reset retryCount:
   174  			retryCount = 0
   175  		}
   176  
   177  		lastGen = gen
   178  		segmentFileName := util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", gen)
   179  		// fmt.Printf("SegmentFileName: %v\n", segmentFileName)
   180  
   181  		var v interface{}
   182  		var err error
   183  		if v, err = fsf.doBody(segmentFileName); err == nil {
   184  			message("success on %v", segmentFileName)
   185  			return v, nil
   186  		}
   187  		// Save the original root cause:
   188  		if exc == nil {
   189  			exc = err
   190  		}
   191  
   192  		message("primary Exception on '%v': %v; will retry: retryCount = %v; gen = %v",
   193  			segmentFileName, err, retryCount, gen)
   194  
   195  		if gen > 1 && useFirstMethod && retryCount == 1 {
   196  			// This is our second time trying this same segments
   197  			// file (because retryCount is 1), and, there is
   198  			// possibly a segments_(N-1) (because gen > 1).
   199  			// So, check if the segments_(N-1) exists and
   200  			// try it if so:
   201  			prevSegmentFileName := util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", gen-1)
   202  
   203  			if prevExists := fsf.directory.FileExists(prevSegmentFileName); prevExists {
   204  				message("fallback to prior segment file '%v'", prevSegmentFileName)
   205  				if v, err = fsf.doBody(prevSegmentFileName); err != nil {
   206  					message("secondary Exception on '%v': %v; will retry", prevSegmentFileName, err)
   207  				} else {
   208  					message("success on fallback %v", prevSegmentFileName)
   209  					return v, nil
   210  				}
   211  			}
   212  		}
   213  	}
   214  }
   215  
   216  // index/SegmentInfos.java
   217  
   218  const (
   219  	VERSION_40 = 0
   220  	VERSION_46 = 1
   221  	VERSION_48 = 2
   222  	VERSION_49 = 3
   223  
   224  	// Used for the segments.gen file only!
   225  	// Whenver you add a new format, make it 1 smaller (negative version logic)!
   226  	FORMAT_SEGMENTS_GEN_47       = -2
   227  	FORMAT_SEGMENTS_GEN_CHECKSUM = -3
   228  	FORMAT_SEGMENTS_GEN_START    = FORMAT_SEGMENTS_GEN_47
   229  	// Current format of segments.gen
   230  	FORMAT_SEGMENTS_GEN_CURRENT = FORMAT_SEGMENTS_GEN_CHECKSUM
   231  )
   232  
   233  /*
   234  A collection of segmentInfo objects with methods for operating on
   235  those segments in relation to the file system.
   236  
   237  The active segments in the index are stored in the segment into file,
   238  segments_N. There may be one or more segments_N files in the index;
   239  however, hte one with the largest generation is the activbe one (when
   240  older segments_N files are present it's because they temporarily
   241  cannot be deleted, or, a writer is in the process of committing, or a
   242  custom IndexDeletionPolicy is in use). This file lists each segment
   243  by name and has details about the codec and generation of deletes.
   244  
   245  There is also a file segments.gen. This file contains the current
   246  generation (the _N in segments_N) of the index. This is used only as
   247  a fallback in case the current generation cannot be accurately
   248  determined by directory listing alone (as is the case for some NFS
   249  clients with time-based directory cache expiration). This file simply
   250  contains an Int32 version header (FORMAT_SEGMENTS_GEN_CURRENT),
   251  followed by the generation recorded as int64, written twice.
   252  
   253  Files:
   254  
   255  - segments.gen: GenHeader, Generation, Generation, Footer
   256  - segments_N: Header, Version, NameCounter, SegCount,
   257    <SegName, SegCodec, DelGen, DeletionCount, FieldInfosGen,
   258    DocValuesGen, UpdatesFiles>^SegCount, CommitUserData, Footer
   259  
   260  Data types:
   261  
   262  - Header --> CodecHeader
   263  - Genheader, NameCounter, SegCount, DeletionCount --> int32
   264  - Generation, Version, DelGen, Checksum --> int64
   265  - SegName, SegCodec --> string
   266  - CommitUserData --> map[string]string
   267  - UpdatesFiles --> map[int32]map[string]bool>
   268  - Footer --> CodecFooter
   269  
   270  Field Descriptions:
   271  
   272  - Version counts how often the index has been changed by adding or
   273    deleting docments.
   274  - NameCounter is used to generate names for new segment files.
   275  - SegName is the name of the segment, and is used as the file name
   276    prefix for all of the files that compose the segment's index.
   277  - DelGen is the generation count of the deletes file. If this is -1,
   278    there are no deletes. Anything above zero means there are deletes
   279    stored by LiveDocsFormat.
   280  - DeletionCount records the number of deleted documents in this segment.
   281  - SegCodec is the nme of the Codec that encoded this segment.
   282  - CommitUserData stores an optional user-spplied opaue
   283    map[string]string that was passed to SetCommitData().
   284  - FieldInfosGen is the generation count of the fieldInfos file. If
   285  	this is -1, there are no updates to the fieldInfos in that segment.
   286  	Anything above zero means there are updates to the fieldInfos
   287  	stored by FieldInfosFormat.
   288  - DocValuesGen is the generation count of the updatable DocValues. If
   289  	this is -1, there are no udpates to DocValues in that segment.
   290  	Anything above zero means there are updates to DocValues stored by
   291  	DocvaluesFormat.
   292  - UpdatesFiles stores the set of files that were updated in that
   293  	segment per file.
   294  */
   295  type SegmentInfos struct {
   296  	counter        int
   297  	version        int64
   298  	generation     int64
   299  	lastGeneration int64
   300  	userData       map[string]string
   301  	Segments       []*SegmentCommitInfo
   302  
   303  	// Only non-nil after prepareCommit has been called and before
   304  	// finishCommit is called
   305  	pendingSegnOutput store.IndexOutput
   306  }
   307  
   308  func LastCommitGeneration(files []string) int64 {
   309  	if files == nil {
   310  		return int64(-1)
   311  	}
   312  	max := int64(-1)
   313  	for _, file := range files {
   314  		if strings.HasPrefix(file, INDEX_FILENAME_SEGMENTS) && file != INDEX_FILENAME_SEGMENTS_GEN {
   315  			gen := GenerationFromSegmentsFileName(file)
   316  			if gen > max {
   317  				max = gen
   318  			}
   319  		}
   320  	}
   321  	return max
   322  }
   323  
   324  func (sis *SegmentInfos) SegmentsFileName() string {
   325  	return util.FileNameFromGeneration(util.SEGMENTS, "", sis.lastGeneration)
   326  }
   327  
   328  func GenerationFromSegmentsFileName(fileName string) int64 {
   329  	switch {
   330  	case fileName == INDEX_FILENAME_SEGMENTS:
   331  		return int64(0)
   332  	case strings.HasPrefix(fileName, INDEX_FILENAME_SEGMENTS):
   333  		d, err := strconv.ParseInt(fileName[1+len(INDEX_FILENAME_SEGMENTS):], 36, 64)
   334  		if err != nil {
   335  			panic(err)
   336  		}
   337  		return d
   338  	default:
   339  		panic(fmt.Sprintf("filename %v is not a segments file", fileName))
   340  	}
   341  }
   342  
   343  /*
   344  A utility for writing the SEGMENTS_GEN file to a Directory.
   345  
   346  NOTE: this is an internal utility which is kept public so that it's
   347  accessible by code from other packages. You should avoid calling this
   348  method unless you're absolutely sure what you're doing!
   349  */
   350  func writeSegmentsGen(dir store.Directory, generation int64) {
   351  	if err := func() (err error) {
   352  		var genOutput store.IndexOutput
   353  		genOutput, err = dir.CreateOutput(INDEX_FILENAME_SEGMENTS_GEN, store.IO_CONTEXT_READONCE)
   354  		if err != nil {
   355  			return err
   356  		}
   357  
   358  		defer func() {
   359  			err = mergeError(err, genOutput.Close())
   360  			err = mergeError(err, dir.Sync([]string{INDEX_FILENAME_SEGMENTS_GEN}))
   361  		}()
   362  
   363  		if err = genOutput.WriteInt(FORMAT_SEGMENTS_GEN_CURRENT); err == nil {
   364  			if err = genOutput.WriteLong(generation); err == nil {
   365  				if err = genOutput.WriteLong(generation); err == nil {
   366  					err = codec.WriteFooter(genOutput)
   367  				}
   368  			}
   369  		}
   370  		return err
   371  	}(); err != nil {
   372  		// It's OK if we fail to write this file since it's used only as
   373  		// one of the retry fallbacks.
   374  		dir.DeleteFile(INDEX_FILENAME_SEGMENTS_GEN)
   375  		// Ignore error; this file is only used in a retry fallback on init
   376  	}
   377  }
   378  
   379  /* Get the next segments_N filename that will be written. */
   380  func (sis *SegmentInfos) nextSegmentFilename() string {
   381  	var nextGeneration int64
   382  	if sis.generation == -1 {
   383  		nextGeneration = 1
   384  	} else {
   385  		nextGeneration = sis.generation + 1
   386  	}
   387  	return util.FileNameFromGeneration(util.SEGMENTS, "", nextGeneration)
   388  }
   389  
   390  /*
   391  Read a particular segmentFileName. Note that this may return IO error
   392  if a commit is in process.
   393  */
   394  func (sis *SegmentInfos) Read(directory store.Directory, segmentFileName string) (err error) {
   395  	// fmt.Printf("Reading segment info from %v...\n", segmentFileName)
   396  
   397  	// Clear any previous segments:
   398  	sis.Clear()
   399  
   400  	sis.generation = GenerationFromSegmentsFileName(segmentFileName)
   401  	sis.lastGeneration = sis.generation
   402  
   403  	var input store.ChecksumIndexInput
   404  	if input, err = directory.OpenChecksumInput(segmentFileName, store.IO_CONTEXT_READ); err != nil {
   405  		return
   406  	}
   407  
   408  	var success = false
   409  	defer func() {
   410  		if !success {
   411  			// Clear any segment infos we had loaded so we
   412  			// have a clean slate on retry:
   413  			sis.Clear()
   414  			util.CloseWhileSuppressingError(input)
   415  		} else {
   416  			err = input.Close()
   417  		}
   418  	}()
   419  
   420  	var format int
   421  	if format, err = asInt(input.ReadInt()); err != nil {
   422  		return
   423  	}
   424  
   425  	var actualFormat int
   426  	if format == codec.CODEC_MAGIC {
   427  		// 4.0+
   428  		if actualFormat, err = asInt(codec.CheckHeaderNoMagic(input, "segments", VERSION_40, VERSION_49)); err != nil {
   429  			return
   430  		}
   431  		if sis.version, err = input.ReadLong(); err != nil {
   432  			return
   433  		}
   434  		if sis.counter, err = asInt(input.ReadInt()); err != nil {
   435  			return
   436  		}
   437  		var numSegments int
   438  		if numSegments, err = asInt(input.ReadInt()); err != nil {
   439  			return
   440  		} else if numSegments < 0 {
   441  			return errors.New(fmt.Sprintf("invalid segment count: %v (resource: %v)", numSegments, input))
   442  		}
   443  		var segName, codecName string
   444  		var fCodec Codec
   445  		var delGen, fieldInfosGen, dvGen int64
   446  		var delCount int
   447  		for seg := 0; seg < numSegments; seg++ {
   448  			if segName, err = input.ReadString(); err != nil {
   449  				return
   450  			}
   451  			if codecName, err = input.ReadString(); err != nil {
   452  				return
   453  			}
   454  			fCodec = LoadCodec(codecName)
   455  			assert2(fCodec != nil, "Invalid codec name: %v", codecName)
   456  			// fmt.Printf("SIS.read seg=%v codec=%v\n", seg, fCodec)
   457  			var info *SegmentInfo
   458  			if info, err = fCodec.SegmentInfoFormat().SegmentInfoReader().Read(directory, segName, store.IO_CONTEXT_READ); err != nil {
   459  				return
   460  			}
   461  			info.SetCodec(fCodec)
   462  			if delGen, err = input.ReadLong(); err != nil {
   463  				return
   464  			}
   465  			if delCount, err = asInt(input.ReadInt()); err != nil {
   466  				return
   467  			} else if delCount < 0 || delCount > info.DocCount() {
   468  				return errors.New(fmt.Sprintf(
   469  					"invalid deletion count: %v vs docCount=%v (resource: %v)",
   470  					delCount, info.DocCount(), input))
   471  			}
   472  			fieldInfosGen = -1
   473  			if actualFormat >= VERSION_46 {
   474  				if fieldInfosGen, err = input.ReadLong(); err != nil {
   475  					return
   476  				}
   477  			}
   478  			dvGen = -1
   479  			if actualFormat >= VERSION_49 {
   480  				if dvGen, err = input.ReadLong(); err != nil {
   481  					return
   482  				}
   483  			} else {
   484  				dvGen = fieldInfosGen
   485  			}
   486  			siPerCommit := NewSegmentCommitInfo(info, delCount, delGen, fieldInfosGen, dvGen)
   487  			if actualFormat >= VERSION_46 {
   488  				if actualFormat < VERSION_49 {
   489  					panic("not implemented yet")
   490  				} else {
   491  					var ss map[string]bool
   492  					if ss, err = input.ReadStringSet(); err != nil {
   493  						return err
   494  					}
   495  					siPerCommit.SetFieldInfosFiles(ss)
   496  					var dvUpdatesFiles map[int]map[string]bool
   497  					var numDVFields int
   498  					if numDVFields, err = asInt(input.ReadInt()); err != nil {
   499  						return err
   500  					}
   501  					if numDVFields == 0 {
   502  						dvUpdatesFiles = make(map[int]map[string]bool)
   503  					} else {
   504  						panic("not implemented yet")
   505  					}
   506  					siPerCommit.SetDocValuesUpdatesFiles(dvUpdatesFiles)
   507  				}
   508  			}
   509  			sis.Segments = append(sis.Segments, siPerCommit)
   510  		}
   511  		if sis.userData, err = input.ReadStringStringMap(); err != nil {
   512  			return err
   513  		}
   514  	} else {
   515  		// TODO support <4.0 index
   516  		panic("Index format pre-4.0 not supported yet")
   517  	}
   518  
   519  	if actualFormat >= VERSION_48 {
   520  		if _, err = codec.CheckFooter(input); err != nil {
   521  			return
   522  		}
   523  	} else {
   524  		var checksumNow = int64(input.Checksum())
   525  		var checksumThen int64
   526  		if checksumThen, err = input.ReadLong(); err != nil {
   527  			return
   528  		}
   529  		if checksumNow != checksumThen {
   530  			return errors.New(fmt.Sprintf(
   531  				"checksum mismatch in segments file: %v vs %v (resource: %v)",
   532  				checksumNow, checksumThen, input))
   533  		}
   534  		if err = codec.CheckEOF(input); err != nil {
   535  			return
   536  		}
   537  	}
   538  
   539  	success = true
   540  	return nil
   541  }
   542  
   543  func asInt(n int32, err error) (int, error) {
   544  	if err != nil {
   545  		return 0, err
   546  	}
   547  	return int(n), nil
   548  }
   549  
   550  func (sis *SegmentInfos) ReadAll(directory store.Directory) error {
   551  	sis.generation, sis.lastGeneration = -1, -1
   552  	_, err := NewFindSegmentsFile(directory, func(segmentFileName string) (obj interface{}, err error) {
   553  		err = sis.Read(directory, segmentFileName)
   554  		return nil, err
   555  	}).run(nil)
   556  	return err
   557  }
   558  
   559  func (sis *SegmentInfos) write(directory store.Directory) (err error) {
   560  	segmentsFilename := sis.nextSegmentFilename()
   561  
   562  	// Always advance the generation on write:
   563  	if sis.generation == -1 {
   564  		sis.generation = 1
   565  	} else {
   566  		sis.generation++
   567  	}
   568  
   569  	var segnOutput store.IndexOutput
   570  	var success = false
   571  	// var upgradedSIFiles = make(map[string]bool)
   572  
   573  	defer func() {
   574  		if !success {
   575  			// We hit an error above; try to close the file but suppress
   576  			// any errors
   577  			util.CloseWhileSuppressingError(segnOutput)
   578  
   579  			// for filename, _ := range upgradedSIFiles {
   580  			// 	directory.DeleteFile(filename) // ignore error
   581  			// }
   582  
   583  			// Try not to leave a truncated segments_N fle in the index:
   584  			directory.DeleteFile(segmentsFilename) // ignore error
   585  		}
   586  	}()
   587  
   588  	if segnOutput, err = directory.CreateOutput(segmentsFilename, store.IO_CONTEXT_DEFAULT); err != nil {
   589  		return
   590  	}
   591  	if err = codec.WriteHeader(segnOutput, "segments", VERSION_49); err != nil {
   592  		return
   593  	}
   594  	if err = segnOutput.WriteLong(sis.version); err == nil {
   595  		if err = segnOutput.WriteInt(int32(sis.counter)); err == nil {
   596  			err = segnOutput.WriteInt(int32(len(sis.Segments)))
   597  		}
   598  	}
   599  	if err != nil {
   600  		return
   601  	}
   602  	for _, siPerCommit := range sis.Segments {
   603  		si := siPerCommit.Info
   604  		if err = segnOutput.WriteString(si.Name); err == nil {
   605  			if err = segnOutput.WriteString(si.Codec().(Codec).Name()); err == nil {
   606  				if err = segnOutput.WriteLong(siPerCommit.DelGen()); err == nil {
   607  					assert2(siPerCommit.DelCount() >= 0 && siPerCommit.DelCount() <= si.DocCount(),
   608  						"cannot write segment: invalid docCount segment=%v docCount=%v delCount=%v",
   609  						si.Name, si.DocCount(), siPerCommit.DelCount())
   610  					if err = segnOutput.WriteInt(int32(siPerCommit.DelCount())); err == nil {
   611  						if err = segnOutput.WriteLong(siPerCommit.FieldInfosGen()); err == nil {
   612  							if err = segnOutput.WriteLong(siPerCommit.DocValuesGen()); err == nil {
   613  								if err = segnOutput.WriteStringSet(siPerCommit.FieldInfosFiles()); err == nil {
   614  									dvUpdatesFiles := siPerCommit.DocValuesUpdatesFiles()
   615  									if err = segnOutput.WriteInt(int32(len(dvUpdatesFiles))); err == nil {
   616  										for k, v := range dvUpdatesFiles {
   617  											if err = segnOutput.WriteInt(int32(k)); err != nil {
   618  												break
   619  											}
   620  											if err = segnOutput.WriteStringSet(v); err != nil {
   621  												break
   622  											}
   623  										}
   624  									}
   625  								}
   626  							}
   627  						}
   628  					}
   629  				}
   630  			}
   631  		}
   632  		if err != nil {
   633  			return
   634  		}
   635  		assert(si.Dir == directory)
   636  
   637  		// If this segment is pre-4.x, perform a one-time "upgrade" to
   638  		// write the .si file for it:
   639  		if version := si.Version(); len(version) == 0 || !version.OnOrAfter(util.VERSION_4_0) {
   640  			panic("not implemented yet")
   641  		}
   642  	}
   643  	if err = segnOutput.WriteStringStringMap(sis.userData); err != nil {
   644  		return
   645  	}
   646  	sis.pendingSegnOutput = segnOutput
   647  	success = true
   648  	return nil
   649  }
   650  
   651  // func versionLess(a, b string) bool {
   652  // 	parts1 := strings.Split(a, ".")
   653  // 	parts2 := strings.Split(b, ".")
   654  // 	for i, v := range parts1 {
   655  // 		n1, _ := strconv.Atoi(v)
   656  // 		if i < len(parts2) {
   657  // 			if n2, _ := strconv.Atoi(parts2[i]); n1 != n2 {
   658  // 				return n1 < n2
   659  // 			}
   660  // 		} else if n1 != 0 {
   661  // 			// a has some extra trailing tokens.
   662  // 			// if these are all zeroes, that's ok.
   663  // 			return false
   664  // 		}
   665  // 	}
   666  
   667  // 	// b has some extra trailing tokens.
   668  // 	// if these are all zeroes, that's ok.
   669  // 	for i := len(parts1); i < len(parts2); i++ {
   670  // 		if n, _ := strconv.Atoi(parts2[i]); n != 0 {
   671  // 			return true
   672  // 		}
   673  // 	}
   674  
   675  // 	return false
   676  // }
   677  
   678  /*
   679  Returns a copy of this instance, also copying each SegmentInfo.
   680  */
   681  func (sis *SegmentInfos) Clone() *SegmentInfos {
   682  	return sis.clone(false)
   683  }
   684  
   685  func (sis *SegmentInfos) clone(cloneSegmentInfo bool) *SegmentInfos {
   686  	clone := &SegmentInfos{
   687  		counter:        sis.counter,
   688  		version:        sis.version,
   689  		generation:     sis.generation,
   690  		lastGeneration: sis.lastGeneration,
   691  		userData:       make(map[string]string),
   692  		Segments:       nil,
   693  	}
   694  	for _, info := range sis.Segments {
   695  		assert(info.Info.Codec() != nil)
   696  		clone.Segments = append(clone.Segments, info.CloneDeep(cloneSegmentInfo))
   697  	}
   698  	for k, v := range sis.userData {
   699  		clone.userData[k] = v
   700  	}
   701  	return clone
   702  }
   703  
   704  // L873
   705  /* Carry over generation numbers from another SegmentInfos */
   706  func (sis *SegmentInfos) updateGeneration(other *SegmentInfos) {
   707  	sis.lastGeneration = other.lastGeneration
   708  	sis.generation = other.generation
   709  }
   710  
   711  func (sis *SegmentInfos) rollbackCommit(dir store.Directory) {
   712  	if sis.pendingSegnOutput != nil {
   713  		// Suppress so we keep throwing the original error in our caller
   714  		util.CloseWhileSuppressingError(sis.pendingSegnOutput)
   715  		sis.pendingSegnOutput = nil
   716  
   717  		// Must carefully compute filename from "generation" since
   718  		// lastGeneration isn't incremented:
   719  		segmentFilename := util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", sis.generation)
   720  
   721  		// Suppress so we keep throwing the original error in our caller
   722  		util.DeleteFilesIgnoringErrors(dir, segmentFilename)
   723  	}
   724  }
   725  
   726  func (sis *SegmentInfos) toString(dir store.Directory) string {
   727  	var buffer bytes.Buffer
   728  	buffer.WriteString(sis.SegmentsFileName())
   729  	buffer.WriteString(":")
   730  	for _, info := range sis.Segments {
   731  		buffer.WriteString(info.StringOf(dir, 0))
   732  	}
   733  	return buffer.String()
   734  }
   735  
   736  /*
   737  Call this to start a commit. This writes the new segments file, but
   738  writes an invalid checksum at the end, so that it is not visible to
   739  readers. Once this is called you must call finishCommit() to complete
   740  the commit or rollbackCommit() to abort it.
   741  
   742  Note: changed() should be called prior to this method if changes have
   743  been made to this SegmentInfos instance.
   744  */
   745  func (sis *SegmentInfos) prepareCommit(dir store.Directory) error {
   746  	assert2(sis.pendingSegnOutput == nil, "prepareCommit was already called")
   747  	return sis.write(dir)
   748  }
   749  
   750  /*
   751  Returns all file names referenced by SegmentInfo instances matching
   752  the provided Directory (ie files associated with any "external"
   753  segments are skipped). The returned collection is recomputed on each
   754  invocation.
   755  */
   756  func (sis *SegmentInfos) files(dir store.Directory, includeSegmentsFile bool) []string {
   757  	files := make(map[string]bool)
   758  	if includeSegmentsFile {
   759  		if segmentFileName := sis.SegmentsFileName(); segmentFileName != "" {
   760  			files[segmentFileName] = true
   761  		}
   762  	}
   763  	for _, info := range sis.Segments {
   764  		assert(info.Info.Dir == dir)
   765  		// if info.Info.dir == dir {
   766  		for _, file := range info.Files() {
   767  			files[file] = true
   768  		}
   769  		// }
   770  	}
   771  	var res = make([]string, 0, len(files))
   772  	for file, _ := range files {
   773  		res = append(res, file)
   774  	}
   775  	return res
   776  }
   777  
   778  func (sis *SegmentInfos) finishCommit(dir store.Directory) (fileName string, err error) {
   779  	assert(dir != nil)
   780  	assert2(sis.pendingSegnOutput != nil, "prepareCommit was not called")
   781  	if err = func() error {
   782  		var success = false
   783  		defer func() {
   784  			if !success {
   785  				// Closes pendingSegnOutput & delets partial segments_N:
   786  				sis.rollbackCommit(dir)
   787  			} else {
   788  				err := func() error {
   789  					var success = false
   790  					defer func() {
   791  						if !success {
   792  							// Closes pendingSegnOutput & delets partial segments_N:
   793  							sis.rollbackCommit(dir)
   794  						} else {
   795  							sis.pendingSegnOutput = nil
   796  						}
   797  					}()
   798  
   799  					err := sis.pendingSegnOutput.Close()
   800  					success = err == nil
   801  					return err
   802  				}()
   803  				assertn(err == nil, "%v", err) // no shadow
   804  			}
   805  		}()
   806  
   807  		if err := codec.WriteFooter(sis.pendingSegnOutput); err != nil {
   808  			return err
   809  		}
   810  		success = true
   811  		return nil
   812  	}(); err != nil {
   813  		return
   814  	}
   815  
   816  	// NOTE: if we crash here, we have left a segments_N file in the
   817  	// directory in a possibly corrupt state (if some bytes made it to
   818  	// stable storage and others didn't). But, the segments_N file
   819  	// includes checksum at the end, which should catch this case. So
   820  	// when a reader tries to read it, it will return an index corrupt
   821  	// error, which should cause the retry logic in SegmentInfos to
   822  	// kick in and load the last good (previous) segments_N-1 file.
   823  
   824  	fileName = util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", sis.generation)
   825  	if err = func() error {
   826  		var success = false
   827  		defer func() {
   828  			if !success {
   829  				dir.DeleteFile(fileName)
   830  				// suppress error so we keep returning the original error
   831  			}
   832  		}()
   833  
   834  		err := dir.Sync([]string{fileName})
   835  		success = err == nil
   836  		return err
   837  	}(); err != nil {
   838  		return
   839  	}
   840  
   841  	sis.lastGeneration = sis.generation
   842  	writeSegmentsGen(dir, sis.generation)
   843  	return
   844  }
   845  
   846  // L1041
   847  /*
   848  Replaces all segments in this instance in this instance, but keeps
   849  generation, version, counter so that future commits remain write once.
   850  */
   851  func (sis *SegmentInfos) replace(other *SegmentInfos) {
   852  	sis.rollbackSegmentInfos(other.Segments)
   853  	sis.lastGeneration = other.lastGeneration
   854  }
   855  
   856  func (sis *SegmentInfos) changed() {
   857  	sis.version++
   858  }
   859  
   860  func (sis *SegmentInfos) createBackupSegmentInfos() []*SegmentCommitInfo {
   861  	ans := make([]*SegmentCommitInfo, len(sis.Segments))
   862  	for i, info := range sis.Segments {
   863  		assert(info.Info.Codec() != nil)
   864  		ans[i] = info.Clone()
   865  	}
   866  	return ans
   867  }
   868  
   869  // L1104
   870  func (sis *SegmentInfos) rollbackSegmentInfos(infos []*SegmentCommitInfo) {
   871  	if cap(sis.Segments) < len(infos) {
   872  		sis.Segments = make([]*SegmentCommitInfo, len(infos))
   873  		copy(sis.Segments, infos)
   874  	} else {
   875  		n := len(sis.Segments)
   876  		copy(sis.Segments, infos)
   877  		for i, limit := len(infos), n; i < limit; i++ {
   878  			sis.Segments[i] = nil
   879  		}
   880  		sis.Segments = sis.Segments[0:len(infos)]
   881  	}
   882  }
   883  
   884  func (sis *SegmentInfos) Clear() {
   885  	for i, _ := range sis.Segments {
   886  		sis.Segments[i] = nil
   887  	}
   888  	sis.Segments = sis.Segments[:0] // reuse existing space
   889  }
   890  
   891  /*
   892  Remove the provided SegmentCommitInfo.
   893  
   894  WARNING: O(N) cost
   895  */
   896  func (sis *SegmentInfos) remove(si *SegmentCommitInfo) {
   897  	panic("not implemented yet")
   898  }