func read(e sqlExecutor, inodeID, from, to uint64) ([]byte, error) {
	readRange := newBlockRange(from, to-from)
	end := readRange.last
	if readRange.lastLength == 0 {
		end--
	}
	blockInfos, err := getBlocksBetween(e, inodeID, readRange.start, end)
	if err != nil {
		return nil, err
	}
	if len(blockInfos) != end-readRange.start+1 {
		return nil, fmt.Errorf("wrong number of blocks, asked for [%d-%d], got %d back",
			readRange.start, end, len(blockInfos))
	}
	if readRange.lastLength != 0 {
		last := len(blockInfos) - 1
		blockInfos[last].data = blockInfos[last].data[:readRange.lastLength]
	}
	blockInfos[0].data = blockInfos[0].data[readRange.startOffset:]
	var data []byte
	for _, b := range blockInfos {
		data = append(data, b.data...)
	}
	return data, nil
}
read returns the data [from, to).
Requires: to > from and [to, from) is contained in the file.
package main
import (
	"fmt"
	"strings"
)
const BlockSize = 4 << 10  
func min(a, b uint64) uint64 {
	if a < b {
		return a
	}
	return b
}
type blockRange struct {
	start       int     
	startOffset uint64  
	startLength uint64  
	last        int     
	lastLength  uint64  
}
func newBlockRange(from, length uint64) blockRange {
	end := from + length
	return blockRange{
		start:       int(from / BlockSize),
		startOffset: from % BlockSize,
		startLength: min(length, BlockSize-(from%BlockSize)),
		last:        int(end / BlockSize),
		lastLength:  end % BlockSize,
	}
}
func shrink(e sqlExecutor, inodeID, from, to uint64) error {
	delRange := newBlockRange(to, from-to)
	deleteFrom := delRange.start
	if delRange.startOffset > 0 {
		data, err := getBlockData(e, inodeID, delRange.start)
		if err != nil {
			return err
		}
		data = data[:delRange.startOffset]
		if err := updateBlockData(e, inodeID, delRange.start, data); err != nil {
			return err
		}
		deleteFrom++
	}
	deleteTo := delRange.last
	if delRange.lastLength == 0 {
		deleteTo--
	}
	if deleteTo < deleteFrom {
		return nil
	}
	delStmt := `DELETE FROM fs.BLOCK WHERE id = $1 AND block >= $2`
	if _, err := e.Exec(delStmt, inodeID, deleteFrom); err != nil {
		return err
	}
	return nil
}
func grow(e sqlExecutor, inodeID, from, to uint64) error {
	addRange := newBlockRange(from, to-from)
	insertFrom := addRange.start
	if addRange.startOffset > 0 {
		data, err := getBlockData(e, inodeID, addRange.start)
		if err != nil {
			return err
		}
		data = append(data, make([]byte, addRange.startLength, addRange.startLength)...)
		if err := updateBlockData(e, inodeID, addRange.start, data); err != nil {
			return err
		}
		insertFrom++
	}
	insertTo := addRange.last
	if insertTo < insertFrom {
		return nil
	}
	paramStrings := []string{}
	params := []interface{}{}
	count := 1  
	if insertFrom != insertTo {
		params = append(params, make([]byte, BlockSize, BlockSize))
		count++
	}
	for i := insertFrom; i < insertTo; i++ {
		paramStrings = append(paramStrings, fmt.Sprintf("(%d, %d, $1)", inodeID, i))
	}
	if addRange.lastLength > 0 {
		params = append(params, make([]byte, addRange.lastLength, addRange.lastLength))
		paramStrings = append(paramStrings, fmt.Sprintf("(%d, %d, $%d)",
			inodeID, addRange.last, count))
		count++
	}
	if len(paramStrings) == 0 {
		return nil
	}
	insStmt := fmt.Sprintf(`INSERT INTO fs.block VALUES %s`, strings.Join(paramStrings, ","))
	if _, err := e.Exec(insStmt, params...); err != nil {
		return err
	}
	return nil
}