change module name 20
This commit is contained in:
@@ -479,6 +479,56 @@ func (j *JSONLFile) ReadWithPhysicalLineIndexes(options LineDbAdapterOptions) ([
|
||||
return records, lineIndexes, nil
|
||||
}
|
||||
|
||||
// HasBlankSlots возвращает true, если в файле есть хотя бы один пустой слот
|
||||
// (пробелы + перевод строки — типичный след точечного удаления BlankLinesAtPositions).
|
||||
func (j *JSONLFile) HasBlankSlots(options LineDbAdapterOptions) (bool, error) {
|
||||
if !options.InTransaction {
|
||||
j.mutex.RLock()
|
||||
defer j.mutex.RUnlock()
|
||||
}
|
||||
if !j.initialized {
|
||||
return false, fmt.Errorf("file not initialized")
|
||||
}
|
||||
if j.allocSize <= 0 {
|
||||
return false, fmt.Errorf("invalid allocSize")
|
||||
}
|
||||
|
||||
file, err := os.Open(j.filename)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("stat file: %w", err)
|
||||
}
|
||||
nSlots := int(info.Size()) / j.allocSize
|
||||
buf := make([]byte, j.allocSize)
|
||||
|
||||
for slot := 0; slot < nSlots; slot++ {
|
||||
n, err := io.ReadFull(file, buf)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("read slot %d: %w", slot, err)
|
||||
}
|
||||
if n != j.allocSize {
|
||||
break
|
||||
}
|
||||
|
||||
line := string(buf[:n])
|
||||
line = strings.TrimRight(line, "\n")
|
||||
line = strings.TrimRight(line, " ")
|
||||
if strings.TrimSpace(line) == "" {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ReadByLineIndexes читает записи по номерам строк (0-based) с использованием random access.
|
||||
// Ожидается, что файл нормализован и каждая строка имеет длину allocSize байт (включая \n),
|
||||
// как это обеспечивает Init/normalizeExistingFile/rewriteFile.
|
||||
@@ -739,6 +789,86 @@ func (j *JSONLFile) BlankLinesAtPositions(lineIndexes []int, options LineDbAdapt
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompactFile перезаписывает файл, копируя подряд только непустые слоты (сырые байты без JSON).
|
||||
// Пустой слот: после обрезки \n и пробелов строка пустая — как после BlankLinesAtPositions.
|
||||
// Один буфер на весь проход; без раскодирования и без удержания всех записей в памяти.
|
||||
func (j *JSONLFile) CompactFile(options LineDbAdapterOptions) error {
|
||||
if !options.InTransaction {
|
||||
j.mutex.Lock()
|
||||
defer j.mutex.Unlock()
|
||||
}
|
||||
if !j.initialized {
|
||||
return fmt.Errorf("file not initialized")
|
||||
}
|
||||
if j.allocSize <= 0 {
|
||||
return fmt.Errorf("invalid allocSize")
|
||||
}
|
||||
|
||||
info, err := os.Stat(j.filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compact stat: %w", err)
|
||||
}
|
||||
nSlots := int(info.Size()) / j.allocSize
|
||||
|
||||
tempFile := j.filename + ".tmp"
|
||||
dst, err := os.Create(tempFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compact create temp: %w", err)
|
||||
}
|
||||
|
||||
src, err := os.Open(j.filename)
|
||||
if err != nil {
|
||||
dst.Close()
|
||||
_ = os.Remove(tempFile)
|
||||
return fmt.Errorf("compact open source: %w", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, j.allocSize)
|
||||
compactErr := func() error {
|
||||
defer src.Close()
|
||||
for slot := 0; slot < nSlots; slot++ {
|
||||
n, err := io.ReadFull(src, buf)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("compact read slot %d: %w", slot, err)
|
||||
}
|
||||
if n != j.allocSize {
|
||||
break
|
||||
}
|
||||
|
||||
slot := buf[:n]
|
||||
t := bytes.TrimRight(bytes.TrimRight(slot, "\n"), " ")
|
||||
if len(bytes.TrimSpace(t)) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := dst.Write(slot); err != nil {
|
||||
return fmt.Errorf("compact write: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err := dst.Close(); err != nil {
|
||||
_ = os.Remove(tempFile)
|
||||
if compactErr != nil {
|
||||
return compactErr
|
||||
}
|
||||
return fmt.Errorf("compact close temp: %w", err)
|
||||
}
|
||||
if compactErr != nil {
|
||||
_ = os.Remove(tempFile)
|
||||
return compactErr
|
||||
}
|
||||
|
||||
if err := os.Rename(tempFile, j.filename); err != nil {
|
||||
_ = os.Remove(tempFile)
|
||||
return fmt.Errorf("compact rename: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LineCount возвращает число строк в файле (fileSize / allocSize).
|
||||
// Используется для точечного индексирования после Write.
|
||||
func (j *JSONLFile) LineCount() (int, error) {
|
||||
|
||||
Reference in New Issue
Block a user