chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"golang.org/x/exp/constraints"
)
// Abs returns the absolute value of the given number.
func Abs[T constraints.Integer | constraints.Float](val T) T {
if val < 0 {
return -val
}
return val
}
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"fmt"
"strconv"
"strings"
"sync/atomic"
)
// uniqueAliasCounter is used to create unique aliases. Aliases are required by GMS when some expressions are contained
// within an AliasedTableExpr. Postgres does not have this restriction, so we must do this for compatibility.
var uniqueAliasCounter atomic.Uint64
// uniqueAliasPrefix is the prefix given to aliases that have been generated by GenerateUniqueAlias.
var uniqueAliasPrefix = "doltgres!|!alias!|_"
// GenerateUniqueAlias generates a unique alias. This is thread-safe.
func GenerateUniqueAlias() string {
return fmt.Sprintf("%s%d", uniqueAliasPrefix, uniqueAliasCounter.Add(1))
}
// IsGeneratedAlias returns whether the given alias was generated using GenerateUniqueAlias.
func IsGeneratedAlias(alias string) bool {
if strings.HasPrefix(alias, uniqueAliasPrefix) && len(alias) > len(uniqueAliasPrefix) {
aliasDigits := alias[len(uniqueAliasPrefix):]
if _, err := strconv.ParseUint(aliasDigits, 10, 64); err == nil {
return true
}
}
return false
}
+32
View File
@@ -0,0 +1,32 @@
package main
import (
"context"
"fmt"
"log"
"os"
builder "github.com/dolthub/doltgresql/utils/doltgres_builder"
)
func main() {
commitList := os.Args[1:]
if len(commitList) < 1 {
helpStr := "doltgres-builder takes DoltgreSQL commit shas or tags as arguments\n" +
"and builds corresponding binaries to a path specified\n" +
"by DOLTGRES_BIN\n" +
"If DOLTGRES_BIN is not set, ./doltgresBin will be used\n" +
"usage: doltgres-builder dccba46 4bad226 ...\n" +
"usage: doltgres-builder v0.19.0 v0.22.6 ...\n" +
"set DEBUG=1 to run in debug mode\n"
fmt.Print(helpStr)
os.Exit(2)
}
err := builder.Run(context.Background(), commitList)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
+5
View File
@@ -0,0 +1,5 @@
package doltgres_builder
const (
GithubDoltgres = "https://github.com/dolthub/doltgresql.git"
)
+175
View File
@@ -0,0 +1,175 @@
package doltgres_builder
import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime"
"sync"
"syscall"
builder "github.com/dolthub/dolt/go/performance/utils/dolt_builder"
"golang.org/x/sync/errgroup"
)
const envDoltgresBin = "DOLTGRES_BIN"
func Run(parentCtx context.Context, commitList []string) error {
doltgresBin, err := getDoltgresBin()
if err != nil {
return err
}
// check for git on path
err = builder.GitVersion(parentCtx)
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
// make temp dir for cloning/copying doltgres source
tempDir := filepath.Join(cwd, "clones-copies")
err = os.MkdirAll(tempDir, os.ModePerm)
if err != nil {
return err
}
// clone doltgres source
err = builder.GitCloneBare(parentCtx, tempDir, GithubDoltgres)
if err != nil {
return err
}
repoDir := filepath.Join(tempDir, "doltgresql.git")
withKeyCtx, cancel := context.WithCancel(parentCtx)
g, ctx := errgroup.WithContext(withKeyCtx)
// handle user interrupt
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
var wg sync.WaitGroup
wg.Add(1)
go func() {
<-quit
defer wg.Done()
signal.Stop(quit)
cancel()
}()
for _, commit := range commitList {
commit := commit // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
return buildBinaries(ctx, tempDir, repoDir, doltgresBin, commit)
})
}
builderr := g.Wait()
close(quit)
wg.Wait()
// remove clones-copies after all go routines complete
// will exit successfully if removal fails
if err := os.RemoveAll(tempDir); err != nil {
fmt.Printf("WARN: %s was not removed\n", tempDir)
fmt.Printf("WARN: error: %v\n", err)
}
if builderr != nil {
return builderr
}
return nil
}
// getDoltgresBin creates and returns the absolute path for DOLTGRES_BIN
// if it was found, otherwise uses the current working directory
// as the parent directory for a `doltgresBin` directory
func getDoltgresBin() (string, error) {
var doltgresBin string
dir := os.Getenv(envDoltgresBin)
if dir == "" {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
doltgresBin = filepath.Join(cwd, "doltgresBin")
} else {
abs, err := filepath.Abs(dir)
if err != nil {
return "", err
}
doltgresBin = abs
}
err := os.MkdirAll(doltgresBin, os.ModePerm)
if err != nil {
return "", err
}
return doltgresBin, nil
}
// buildBinaries builds a doltgres binary at the given commit and stores it in the doltgresBin
func buildBinaries(ctx context.Context, tempDir, repoDir, doltgresBinDir, commit string) error {
checkoutDir := filepath.Join(tempDir, commit)
if err := os.MkdirAll(checkoutDir, os.ModePerm); err != nil {
return err
}
err := builder.GitCheckoutTree(ctx, repoDir, checkoutDir, commit)
if err != nil {
return err
}
commitDir := filepath.Join(doltgresBinDir, commit)
if err := os.MkdirAll(commitDir, os.ModePerm); err != nil {
return err
}
parserScriptPath := filepath.Join(checkoutDir, "postgres", "parser", "build.sh")
doltgresCommandPath := filepath.Join(checkoutDir, "cmd", "doltgres")
command, err := goBuild(ctx, parserScriptPath, doltgresCommandPath, checkoutDir, commitDir)
if err != nil {
return err
}
return doltgresVersion(ctx, commitDir, command)
}
// goBuild builds the doltgres parser and doltgres binary and returns the filename for the doltgres binary
func goBuild(ctx context.Context, parserScriptPath, doltgresCommandPath, source, dest string) (string, error) {
buildParser := builder.ExecCommand(ctx, "/bin/bash", "-c", parserScriptPath)
err := buildParser.Run()
if err != nil {
return "", err
}
doltgresFileName := "doltgres"
if runtime.GOOS == "windows" {
doltgresFileName = "doltgres.exe"
}
toBuild := filepath.Join(dest, doltgresFileName)
build := builder.ExecCommand(ctx, "go", "build", "-o", toBuild, doltgresCommandPath)
build.Dir = source
err = build.Run()
if err != nil {
return "", err
}
return toBuild, nil
}
// doltgresVersion prints doltgres version of binary
func doltgresVersion(ctx context.Context, dir, command string) error {
doltgresVersion := builder.ExecCommand(ctx, command, "-version")
doltgresVersion.Stderr = os.Stderr
doltgresVersion.Stdout = os.Stdout
doltgresVersion.Dir = dir
return doltgresVersion.Run()
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"os"
"github.com/dolthub/dolt/go/libraries/utils/minver"
"github.com/dolthub/doltgresql/servercfg/cfgdetails"
)
func main() {
if len(os.Args) != 2 {
log.Fatal("Usage: genminver_validation <outfile>")
}
outFile := os.Args[1]
err := minver.GenValidationFile(&cfgdetails.DoltgresConfig{}, outFile)
if err != nil {
log.Fatal(err)
}
fmt.Printf("'%s' written successfully", outFile)
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"cmp"
"sort"
)
// KeyValue represents an entry in a map.
type KeyValue[K comparable, V any] struct {
Key K
Value V
}
// GetMapKeys returns the map's keys as an unsorted slice.
func GetMapKeys[K comparable, V any](m map[K]V) []K {
allKeys := make([]K, len(m))
i := 0
for k := range m {
allKeys[i] = k
i++
}
return allKeys
}
// GetMapKeysSorted returns the map's keys as a sorted slice. The keys are sorted in ascending order. For descending
// order, use GetMapKeysSortedDescending.
func GetMapKeysSorted[K cmp.Ordered, V any](m map[K]V) []K {
allKeys := GetMapKeys(m)
sort.Slice(allKeys, func(i, j int) bool {
return allKeys[i] < allKeys[j]
})
return allKeys
}
// GetMapKeysSortedDescending returns the map's keys as a sorted slice. The keys are sorted in descending order. For
// ascending order, use GetMapKeysSorted.
func GetMapKeysSortedDescending[K cmp.Ordered, V any](m map[K]V) []K {
allKeys := GetMapKeys(m)
sort.Slice(allKeys, func(i, j int) bool {
return allKeys[i] > allKeys[j]
})
return allKeys
}
// GetMapKVs returns the map's KeyValue entries as an unsorted slice.
func GetMapKVs[K comparable, V any](m map[K]V) []KeyValue[K, V] {
allEntries := make([]KeyValue[K, V], len(m))
i := 0
for k, v := range m {
allEntries[i] = KeyValue[K, V]{Key: k, Value: v}
i++
}
return allEntries
}
// GetMapKVsSorted returns the map's KeyValue entries as a sorted slice. The keys are sorted in ascending order. For
// descending order, use GetMapKVsSortedDescending.
func GetMapKVsSorted[K cmp.Ordered, V any](m map[K]V) []KeyValue[K, V] {
allEntries := GetMapKVs(m)
sort.Slice(allEntries, func(i, j int) bool {
return allEntries[i].Key < allEntries[j].Key
})
return allEntries
}
// GetMapKVsSortedDescending returns the map's KeyValue entries as a sorted slice. The keys are sorted in descending
// order. For ascending order, use GetMapKVsSorted.
func GetMapKVsSortedDescending[K cmp.Ordered, V any](m map[K]V) []KeyValue[K, V] {
allEntries := GetMapKVs(m)
sort.Slice(allEntries, func(i, j int) bool {
return allEntries[i].Key > allEntries[j].Key
})
return allEntries
}
// GetMapValues returns the map's values as a slice. Due to Go's map iteration, the values will be in a
// non-deterministic order.
func GetMapValues[K comparable, V any](m map[K]V) []V {
allValues := make([]V, len(m))
i := 0
for _, v := range m {
allValues[i] = v
i++
}
return allValues
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"golang.org/x/exp/constraints"
)
// Max returns the largest value of the given parameters. If no parameters are given, then returns the default value for
// the type.
func Max[T constraints.Ordered](vals ...T) (maximum T) {
if len(vals) == 0 {
return maximum
}
maximum = vals[0]
for i := 1; i < len(vals); i++ {
if vals[i] > maximum {
maximum = vals[i]
}
}
return maximum
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"golang.org/x/exp/constraints"
)
// Min returns the smallest value of the given parameters.
func Min[T constraints.Ordered](vals ...T) (minimum T) {
if len(vals) == 0 {
return minimum
}
minimum = vals[0]
for i := 1; i < len(vals); i++ {
if vals[i] < minimum {
minimum = vals[i]
}
}
return minimum
}
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"sync"
"github.com/pkg/profile"
)
// prof is the global profiler that is in use. This should only be interacted with via StartProfiling and StopProfiling.
var prof interface{ Stop() }
// profMutex is the mutex that handles concurrent manipulations of prof.
var profMutex = &sync.Mutex{}
// ProfilingOptions contains all options for starting the profiler.
type ProfilingOptions struct {
CPU bool
Memory bool
Block bool
Trace bool
Path string
}
// StartProfiling starts profiling the CPU.
func StartProfiling(options ProfilingOptions) {
profMutex.Lock()
defer profMutex.Unlock()
if !options.HasOptions() {
return
}
profOpts := []func(p *profile.Profile){profile.NoShutdownHook}
if options.CPU {
profOpts = append(profOpts, profile.CPUProfile)
}
if options.Memory {
profOpts = append(profOpts, profile.MemProfile)
}
if options.Block {
profOpts = append(profOpts, profile.BlockProfile)
}
if options.Trace {
profOpts = append(profOpts, profile.TraceProfile)
}
if len(options.Path) > 0 {
profOpts = append(profOpts, profile.ProfilePath(options.Path))
}
prof = profile.Start(profOpts...)
}
// StopProfiling stops profiling the CPU.
func StopProfiling() {
profMutex.Lock()
defer profMutex.Unlock()
if prof != nil {
prof.Stop()
prof = nil
}
}
// HasOptions returns true when at least one profiler target has been selected.
func (options ProfilingOptions) HasOptions() bool {
return options.CPU || options.Memory || options.Block || options.Trace
}
+418
View File
@@ -0,0 +1,418 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"encoding/binary"
"math"
"github.com/dolthub/doltgresql/core/id"
)
// Reader handles type-safe reading from a byte slice, which was created by a Writer. This is not safe for concurrent
// use.
type Reader struct {
buf []byte
offset uint64
}
// NewReader creates a new Reader that will read from the given data.
func NewReader(data []byte) *Reader {
return &Reader{
buf: data,
offset: 0,
}
}
// Bool reads a bool.
func (reader *Reader) Bool() bool {
reader.offset += 1
if reader.buf[reader.offset-1] == 1 {
return true
} else {
return false
}
}
// Int8 reads an int8.
func (reader *Reader) Int8() int8 {
return int8(reader.Uint8() - (1 << 7))
}
// Int16 reads an int16.
func (reader *Reader) Int16() int16 {
return int16(reader.Uint16() - (1 << 15))
}
// Int32 reads an int32.
func (reader *Reader) Int32() int32 {
return int32(reader.Uint32() - (1 << 31))
}
// Int64 reads an int64.
func (reader *Reader) Int64() int64 {
return int64(reader.Uint64() - (1 << 63))
}
// Uint8 reads a uint8.
func (reader *Reader) Uint8() uint8 {
reader.offset += 1
return reader.buf[reader.offset-1]
}
// Uint16 reads a uint16.
func (reader *Reader) Uint16() uint16 {
reader.offset += 2
return binary.BigEndian.Uint16(reader.buf[reader.offset-2:])
}
// Uint32 reads a uint32.
func (reader *Reader) Uint32() uint32 {
reader.offset += 4
return binary.BigEndian.Uint32(reader.buf[reader.offset-4:])
}
// Uint64 reads a uint64.
func (reader *Reader) Uint64() uint64 {
reader.offset += 8
return binary.BigEndian.Uint64(reader.buf[reader.offset-8:])
}
// Byte reads a byte. This is equivalent to Uint8, but is included since it is more common to refer to a byte rather
// than a uint8.
func (reader *Reader) Byte() byte {
return reader.Uint8()
}
// Float32 reads a float32.
func (reader *Reader) Float32() float32 {
// For more details, look at Writer.Float32
uval := reader.Uint32()
uval ^= 0x80000000
uval ^= uint32(int32(uval)>>31) & 0x7FFFFFFF
return math.Float32frombits(uval)
}
// Float64 reads a float64.
func (reader *Reader) Float64() float64 {
// For more details, look at Writer.Float64
uval := reader.Uint64()
uval ^= 0x8000000000000000
uval ^= uint64(int64(uval)>>63) & 0x7FFFFFFFFFFFFFFF
return math.Float64frombits(uval)
}
// VariableInt reads an int64 that was written using variable-length encoding.
func (reader *Reader) VariableInt() int64 {
uval := reader.VariableUint()
// binary.PutVarint performs the inverse of this and writes a uint64, so we're undoing it to get the original int64
val := int64(uval >> 1)
if uval&1 != 0 {
val = ^val
}
return val
}
// VariableUint reads a uint64 that was written using variable-length encoding.
func (reader *Reader) VariableUint() uint64 {
// This has been adapted from one of our blogs:
// https://www.dolthub.com/blog/2021-01-08-optimizing-varint-decoding/
b := uint64(reader.buf[reader.offset])
if b < 0x80 {
reader.offset += 1
return b
}
x := b & 0x7f
b = uint64(reader.buf[reader.offset+1])
if b < 0x80 {
reader.offset += 2
return x | (b << 7)
}
x |= (b & 0x7f) << 7
b = uint64(reader.buf[reader.offset+2])
if b < 0x80 {
reader.offset += 3
return x | (b << 14)
}
x |= (b & 0x7f) << 14
b = uint64(reader.buf[reader.offset+3])
if b < 0x80 {
reader.offset += 4
return x | (b << 21)
}
x |= (b & 0x7f) << 21
b = uint64(reader.buf[reader.offset+4])
if b < 0x80 {
reader.offset += 5
return x | (b << 28)
}
x |= (b & 0x7f) << 28
b = uint64(reader.buf[reader.offset+5])
if b < 0x80 {
reader.offset += 6
return x | (b << 35)
}
x |= (b & 0x7f) << 35
b = uint64(reader.buf[reader.offset+6])
if b < 0x80 {
reader.offset += 7
return x | (b << 42)
}
x |= (b & 0x7f) << 42
b = uint64(reader.buf[reader.offset+7])
if b < 0x80 {
reader.offset += 8
return x | (b << 49)
}
x |= (b & 0x7f) << 49
b = uint64(reader.buf[reader.offset+8])
if b < 0x80 {
reader.offset += 9
return x | (b << 56)
}
x |= (b & 0x7f) << 56
b = uint64(reader.buf[reader.offset+9])
if b < 0x80 {
reader.offset += 10
return x | (b << 63)
}
reader.offset += 10
return 0xffffffffffffffff
}
// String reads a string.
func (reader *Reader) String() string {
length := reader.VariableUint()
reader.offset += length
return string(reader.buf[reader.offset-length : reader.offset])
}
// Id reads an internal ID.
func (reader *Reader) Id() id.Id {
return id.Id(reader.String())
}
// BoolSlice reads a bool slice.
func (reader *Reader) BoolSlice() []bool {
count := reader.VariableUint()
vals := make([]bool, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Bool()
}
return vals
}
// Int8Slice reads an int8 slice.
func (reader *Reader) Int8Slice() []int8 {
count := reader.VariableUint()
vals := make([]int8, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Int8()
}
return vals
}
// Int16Slice reads an int16 slice.
func (reader *Reader) Int16Slice() []int16 {
count := reader.VariableUint()
vals := make([]int16, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Int16()
}
return vals
}
// Int32Slice reads an int32 slice.
func (reader *Reader) Int32Slice() []int32 {
count := reader.VariableUint()
vals := make([]int32, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Int32()
}
return vals
}
// Int64Slice reads an int64 slice.
func (reader *Reader) Int64Slice() []int64 {
count := reader.VariableUint()
vals := make([]int64, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Int64()
}
return vals
}
// Uint8Slice reads a uint8 slice.
func (reader *Reader) Uint8Slice() []uint8 {
count := reader.VariableUint()
vals := make([]uint8, count)
copy(vals, reader.buf[reader.offset:reader.offset+count])
reader.offset += count
return vals
}
// Uint16Slice reads a uint16 slice.
func (reader *Reader) Uint16Slice() []uint16 {
count := reader.VariableUint()
vals := make([]uint16, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Uint16()
}
return vals
}
// Uint32Slice reads a uint32 slice.
func (reader *Reader) Uint32Slice() []uint32 {
count := reader.VariableUint()
vals := make([]uint32, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Uint32()
}
return vals
}
// Uint64Slice reads a uint64 slice.
func (reader *Reader) Uint64Slice() []uint64 {
count := reader.VariableUint()
vals := make([]uint64, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Uint64()
}
return vals
}
// ByteSlice reads a byte slice. This is equivalent to Uint8Slice, but is included since it is more common to refer to
// byte slices than uint8 slices.
func (reader *Reader) ByteSlice() []byte {
return reader.Uint8Slice()
}
// Float32Slice reads a float32 slice.
func (reader *Reader) Float32Slice() []float32 {
count := reader.VariableUint()
vals := make([]float32, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Float32()
}
return vals
}
// Float64Slice reads a float64 slice.
func (reader *Reader) Float64Slice() []float64 {
count := reader.VariableUint()
vals := make([]float64, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Float64()
}
return vals
}
// VariableIntSlice reads an int64 slice that was written using variable-length encoding.
func (reader *Reader) VariableIntSlice() []int64 {
count := reader.VariableUint()
vals := make([]int64, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.VariableInt()
}
return vals
}
// VariableUintSlice reads a uint64 slice that was written using variable-length encoding.
func (reader *Reader) VariableUintSlice() []uint64 {
count := reader.VariableUint()
vals := make([]uint64, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.VariableUint()
}
return vals
}
// StringSlice reads a string slice.
func (reader *Reader) StringSlice() []string {
count := reader.VariableUint()
vals := make([]string, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.String()
}
return vals
}
// StringMap reads a map of strings, keyed by strings.
func (reader *Reader) StringMap() map[string]string {
count := reader.VariableUint()
vals := make(map[string]string, count)
for i := uint64(0); i < count; i++ {
key := reader.String()
vals[key] = reader.String()
}
return vals
}
// IdSlice reads a slice of internal IDs.
func (reader *Reader) IdSlice() []id.Id {
count := reader.VariableUint()
vals := make([]id.Id, count)
for i := uint64(0); i < count; i++ {
vals[i] = reader.Id()
}
return vals
}
// IdTypeSlice reads a slice of internal type IDs.
func (reader *Reader) IdTypeSlice() []id.Type {
count := reader.VariableUint()
vals := make([]id.Type, count)
for i := uint64(0); i < count; i++ {
vals[i] = id.Type(reader.Id())
}
return vals
}
// IsEmpty returns true when all of the data has been read.
func (reader *Reader) IsEmpty() bool {
return reader.offset >= uint64(len(reader.buf))
}
// RemainingBytes returns the number of bytes that have not yet been read.
func (reader *Reader) RemainingBytes() uint64 {
if reader.IsEmpty() {
return 0
}
return uint64(len(reader.buf)) - reader.offset
}
// BytesRead returns the number of bytes that have been read.
func (reader *Reader) BytesRead() uint64 {
return reader.offset
}
// AdvanceReader reads the next N bytes from the given Reader. This is only available for specific, performance-oriented
// circumstances, and should never be used otherwise. This is a standalone function to discourage its use, as it will
// not show up as a function of the Reader object in most IDEs. This uses a branchless comparison to limit the size of n
// to the end of the reader, and it does not allocate a new byte slice (returns a portion from the original byte slice).
func AdvanceReader(reader *Reader, n uint64) []byte {
// This branchless code makes an assumption that the reader contains less than 9223372036854775808 bytes.
// With this assumption, it is equivalent to the following:
// if reader.offset + n > uint64(len(reader.buf)) || n >= 0x8000000000000000 {
// n = uint64(len(reader.buf)) - reader.offset
// }
maxN := uint64(len(reader.buf)) - reader.offset
delta := int64(n - maxN)
mask := (delta >> 63) ^ (int64(n&0x8000000000000000) >> 63)
n = (n & uint64(mask)) | (maxN & ^uint64(mask))
reader.offset += n
return reader.buf[reader.offset-n : reader.offset]
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
// SliceToMapKeys converts the given slice into a map where all of the keys are represented by all of the slice's values.
func SliceToMapKeys[T comparable](slice []T) map[T]struct{} {
m := make(map[T]struct{})
for _, val := range slice {
m[val] = struct{}{}
}
return m
}
// SliceToMapValues converts the given slice into a map where all of the slice's values are keyed by the output of the
// `getKey` function, which takes a value and returns a key. It is up to the caller to return a unique key.
func SliceToMapValues[K comparable, V any](slice []V, getKey func(V) K) map[K]V {
m := make(map[K]V)
for _, val := range slice {
m[getKey(val)] = val
}
return m
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
// Stack is a generic stack.
type Stack[T any] struct {
values []T
}
// NewStack creates a new, empty stack.
func NewStack[T any]() *Stack[T] {
return &Stack[T]{}
}
// Len returns the size of the stack.
func (s *Stack[T]) Len() int {
return len(s.values)
}
// Peek returns the top value on the stack without removing it.
func (s *Stack[T]) Peek() (value T) {
if len(s.values) == 0 {
return
}
return s.values[len(s.values)-1]
}
// PeekDepth returns the n-th value from the top. PeekDepth(0) is equivalent to the standard Peek().
func (s *Stack[T]) PeekDepth(depth int) (value T) {
if len(s.values) <= depth {
return
}
return s.values[len(s.values)-(1+depth)]
}
// PeekReference returns a reference to the top value on the stack without removing it.
func (s *Stack[T]) PeekReference() *T {
if len(s.values) == 0 {
return nil
}
return &s.values[len(s.values)-1]
}
// Pop returns the top value on the stack while also removing it from the stack.
func (s *Stack[T]) Pop() (value T) {
if len(s.values) == 0 {
return
}
value = s.values[len(s.values)-1]
s.values = s.values[:len(s.values)-1]
return
}
// Push adds the given value to the stack.
func (s *Stack[T]) Push(value T) {
s.values = append(s.values, value)
}
// Empty returns whether the stack is empty.
func (s *Stack[T]) Empty() bool {
return len(s.values) == 0
}
+226
View File
@@ -0,0 +1,226 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"bytes"
"encoding/binary"
"math"
)
// WireRW handles all read and write operations for the Postgres binary wire format. This should only be used for
// interacting with the wire format, and not for internal use (utils.Reader and utils.Writer exist for internal use).
type WireRW struct {
buf *bytes.Buffer
readIdx uint32
numSlice []byte
}
// NewWireWriter creates a new WireRW for writing to. This is intended strictly for writing to the binary wire format
// that Postgres expects, and should not be used for internal write operations (utils.Writer is for internal use).
func NewWireWriter() *WireRW {
return &WireRW{
buf: bytes.NewBuffer(make([]byte, 0, 8)),
readIdx: 0,
numSlice: make([]byte, 8), // 8 bytes will cover all floats and integers
}
}
// NewWireReader creates a new WireRW for reading from data previously written by either a valid Postgres server or
// a WireRW instance. This is intended strictly for reading the binary wire format that Postgres expects, and should not
// be used for internal read operations (utils.Reader is for internal use).
func NewWireReader(data []byte) *WireRW {
return &WireRW{
buf: bytes.NewBuffer(data),
readIdx: 0,
numSlice: make([]byte, 8), // 8 bytes will cover all floats and integers
}
}
// ReadBool reads a 1-byte bool.
func (rw *WireRW) ReadBool() bool {
return rw.ReadInt8() != 0
}
// ReadInt8 reads an int8.
func (rw *WireRW) ReadInt8() int8 {
return int8(rw.ReadUint8())
}
// ReadInt16 reads an int16.
func (rw *WireRW) ReadInt16() int16 {
return int16(rw.ReadUint16())
}
// ReadInt32 reads an int32.
func (rw *WireRW) ReadInt32() int32 {
return int32(rw.ReadUint32())
}
// ReadInt64 reads an int64.
func (rw *WireRW) ReadInt64() int64 {
return int64(rw.ReadUint64())
}
// ReadUint8 reads a uint8.
func (rw *WireRW) ReadUint8() uint8 {
data := rw.buf.Bytes()[rw.readIdx]
rw.readIdx += 1
return data
}
// ReadUint16 reads a uint16.
func (rw *WireRW) ReadUint16() uint16 {
data := rw.buf.Bytes()[rw.readIdx : rw.readIdx+2]
rw.readIdx += 2
return binary.BigEndian.Uint16(data)
}
// ReadUint32 reads a uint32.
func (rw *WireRW) ReadUint32() uint32 {
data := rw.buf.Bytes()[rw.readIdx : rw.readIdx+4]
rw.readIdx += 4
return binary.BigEndian.Uint32(data)
}
// ReadUint64 reads a uint64.
func (rw *WireRW) ReadUint64() uint64 {
data := rw.buf.Bytes()[rw.readIdx : rw.readIdx+8]
rw.readIdx += 8
return binary.BigEndian.Uint64(data)
}
// ReadFloat32 reads a float32.
func (rw *WireRW) ReadFloat32() float32 {
return math.Float32frombits(rw.ReadUint32())
}
// ReadFloat64 reads a float64.
func (rw *WireRW) ReadFloat64() float64 {
return math.Float64frombits(rw.ReadUint64())
}
// ReadString reads the next N bytes as a string.
func (rw *WireRW) ReadString(n uint32) string {
return string(rw.ReadBytes(n))
}
// ReadBytes reads the next N bytes.
func (rw *WireRW) ReadBytes(n uint32) []byte {
data := rw.buf.Bytes()[rw.readIdx : rw.readIdx+n]
rw.readIdx += n
return data
}
// WriteBool writes a 1-byte bool.
func (rw *WireRW) WriteBool(val bool) *WireRW {
if val {
rw.buf.WriteByte(1)
} else {
rw.buf.WriteByte(0)
}
return rw
}
// WriteInt8 writes an int8.
func (rw *WireRW) WriteInt8(val int8) *WireRW {
rw.WriteUint8(byte(val))
return rw
}
// WriteInt16 writes an int16.
func (rw *WireRW) WriteInt16(val int16) *WireRW {
rw.WriteUint16(uint16(val))
return rw
}
// WriteInt32 writes an int32.
func (rw *WireRW) WriteInt32(val int32) *WireRW {
rw.WriteUint32(uint32(val))
return rw
}
// WriteInt64 writes an int64.
func (rw *WireRW) WriteInt64(val int64) *WireRW {
rw.WriteUint64(uint64(val))
return rw
}
// WriteUint8 writes a uint8.
func (rw *WireRW) WriteUint8(val uint8) *WireRW {
rw.buf.WriteByte(val)
return rw
}
// WriteUint16 writes a uint16.
func (rw *WireRW) WriteUint16(val uint16) *WireRW {
binary.BigEndian.PutUint16(rw.numSlice, val)
rw.buf.Write(rw.numSlice[:2])
return rw
}
// WriteUint32 writes a uint32.
func (rw *WireRW) WriteUint32(val uint32) *WireRW {
binary.BigEndian.PutUint32(rw.numSlice, val)
rw.buf.Write(rw.numSlice[:4])
return rw
}
// WriteUint64 writes a uint64.
func (rw *WireRW) WriteUint64(val uint64) *WireRW {
binary.BigEndian.PutUint64(rw.numSlice, val)
rw.buf.Write(rw.numSlice[:8])
return rw
}
// WriteFloat32 writes a float32.
func (rw *WireRW) WriteFloat32(val float32) *WireRW {
rw.WriteUint32(math.Float32bits(val))
return rw
}
// WriteFloat64 writes a float64.
func (rw *WireRW) WriteFloat64(val float64) *WireRW {
rw.WriteUint64(math.Float64bits(val))
return rw
}
// WriteString writes the raw string bytes.
func (rw *WireRW) WriteString(val string) *WireRW {
_, _ = rw.buf.WriteString(val)
return rw
}
// WriteBytes writes the bytes given.
func (rw *WireRW) WriteBytes(val []byte) *WireRW {
_, _ = rw.buf.Write(val)
return rw
}
// Reserve ensures that there are at least N bytes in the buffer, which can prevent reallocations if the space needed is
// known upfront.
func (rw *WireRW) Reserve(n uint64) {
rw.buf.Grow(int(n))
}
// BufferSize returns the number of bytes in the buffer.
func (rw *WireRW) BufferSize() uint64 {
return uint64(rw.buf.Len())
}
// BufferData returns the data in the buffer. This does not make a copy.
func (rw *WireRW) BufferData() []byte {
return rw.buf.Bytes()
}
+312
View File
@@ -0,0 +1,312 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"bytes"
"encoding/binary"
"maps"
"math"
"slices"
"github.com/dolthub/doltgresql/core/id"
)
// Writer handles type-safe writing into a byte buffer, which may later be read from using Reader. The Writer will
// automatically grow as it is written to. The serialized forms of booleans, ints, uints, and floats are
// byte-comparable, meaning it is valid to use bytes.Compare() without needing to deserialize them. Variable-length
// encoded values, strings, and slices are not byte-comparable. This is not safe for concurrent use.
type Writer struct {
buf *bytes.Buffer
numSlice []byte
}
// NewWriter creates a new Writer with the given starting capacity. A larger starting capacity reduces reallocations at
// the cost of potentially wasted memory.
func NewWriter(capacity uint64) *Writer {
// If capacity is zero, then we'll set it to something arbitrary to try and minimize reallocations
if capacity == 0 {
capacity = 32
}
return &Writer{
buf: bytes.NewBuffer(make([]byte, 0, capacity)),
numSlice: make([]byte, 10), // 10 bytes will cover all integers and variable-length integers
}
}
// Bool writes a bool.
func (writer *Writer) Bool(val bool) {
if val {
writer.buf.WriteByte(1)
} else {
writer.buf.WriteByte(0)
}
}
// Int8 writes an int8.
func (writer *Writer) Int8(val int8) {
writer.buf.WriteByte(byte(val) + (1 << 7))
}
// Int16 writes an int16.
func (writer *Writer) Int16(val int16) {
writer.Uint16(uint16(val) + (1 << 15))
}
// Int32 writes an int32.
func (writer *Writer) Int32(val int32) {
writer.Uint32(uint32(val) + (1 << 31))
}
// Int64 writes an int64.
func (writer *Writer) Int64(val int64) {
writer.Uint64(uint64(val) + (1 << 63))
}
// Uint8 writes a uint8.
func (writer *Writer) Uint8(val uint8) {
writer.buf.WriteByte(val)
}
// Uint16 writes a uint16.
func (writer *Writer) Uint16(val uint16) {
binary.BigEndian.PutUint16(writer.numSlice, val)
writer.buf.Write(writer.numSlice[:2])
}
// Uint32 writes a uint32.
func (writer *Writer) Uint32(val uint32) {
binary.BigEndian.PutUint32(writer.numSlice, val)
writer.buf.Write(writer.numSlice[:4])
}
// Uint64 writes a uint64.
func (writer *Writer) Uint64(val uint64) {
binary.BigEndian.PutUint64(writer.numSlice, val)
writer.buf.Write(writer.numSlice[:8])
}
// Byte writes a byte. This is equivalent to Uint8, but is included since it is more common to refer to a byte rather
// than a uint8.
func (writer *Writer) Byte(val byte) {
writer.buf.WriteByte(val)
}
// Float32 writes a float32.
func (writer *Writer) Float32(val float32) {
// Float encoding produces byte-comparable serialized values when looking at the exponent and mantissa. This means
// that we just have to flip to exponent and mantissa for negative values, and flip the sign bit so that negatives
// sort before positives. To do this, we take advantage of arithmetic shifting by casting to a signed integer to
// create a mask that only exists for negative values.
uval := math.Float32bits(val)
uval ^= uint32(int32(uval)>>31) & 0x7FFFFFFF
uval ^= 0x80000000
writer.Uint32(uval)
}
// Float64 writes a float64.
func (writer *Writer) Float64(val float64) {
// Float encoding produces byte-comparable serialized values when looking at the exponent and mantissa. This means
// that we just have to flip to exponent and mantissa for negative values, and flip the sign bit so that negatives
// sort before positives. To do this, we take advantage of arithmetic shifting by casting to a signed integer to
// create a mask that only exists for negative values.
uval := math.Float64bits(val)
uval ^= uint64(int64(uval)>>63) & 0x7FFFFFFFFFFFFFFF
uval ^= 0x8000000000000000
writer.Uint64(uval)
}
// VariableInt writes an int64 using variable-length encoding. Smaller values use less space at the cost of larger
// values using more bytes, but this is generally more space-efficient. This does carry a small computational hit when
// reading.
func (writer *Writer) VariableInt(val int64) {
count := binary.PutVarint(writer.numSlice, val)
writer.buf.Write(writer.numSlice[:count])
}
// VariableUint writes a uint64 using variable-length encoding. Smaller values use less space at the cost of larger
// values using more bytes, but this is generally more space-efficient. This does carry a small computational hit when
// reading.
func (writer *Writer) VariableUint(val uint64) {
count := binary.PutUvarint(writer.numSlice, val)
writer.buf.Write(writer.numSlice[:count])
}
// String writes a string.
func (writer *Writer) String(val string) {
writer.VariableUint(uint64(len(val)))
writer.buf.WriteString(val)
}
// Id writes an internal ID.
func (writer *Writer) Id(val id.Id) {
writer.String(string(val))
}
// BoolSlice writes a bool slice.
func (writer *Writer) BoolSlice(vals []bool) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Bool(vals[i])
}
}
// Int8Slice writes an int8 slice.
func (writer *Writer) Int8Slice(vals []int8) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Int8(vals[i])
}
}
// Int16Slice writes an int16 slice.
func (writer *Writer) Int16Slice(vals []int16) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Int16(vals[i])
}
}
// Int32Slice writes an int32 slice.
func (writer *Writer) Int32Slice(vals []int32) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Int32(vals[i])
}
}
// Int64Slice writes an int64 slice.
func (writer *Writer) Int64Slice(vals []int64) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Int64(vals[i])
}
}
// Uint8Slice writes a uint8 slice.
func (writer *Writer) Uint8Slice(vals []uint8) {
writer.VariableUint(uint64(len(vals)))
writer.buf.Write(vals)
}
// Uint16Slice writes a uint16 slice.
func (writer *Writer) Uint16Slice(vals []uint16) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Uint16(vals[i])
}
}
// Uint32Slice writes a uint32 slice.
func (writer *Writer) Uint32Slice(vals []uint32) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Uint32(vals[i])
}
}
// Uint64Slice writes a uint64 slice.
func (writer *Writer) Uint64Slice(vals []uint64) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Uint64(vals[i])
}
}
// ByteSlice writes a byte slice. This is equivalent to Uint8Slice, but is included since it is more common to refer to
// byte slices than uint8 slices.
func (writer *Writer) ByteSlice(vals []byte) {
writer.Uint8Slice(vals)
}
// Float32Slice writes a float32 slice.
func (writer *Writer) Float32Slice(vals []float32) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Float32(vals[i])
}
}
// Float64Slice writes a float64 slice.
func (writer *Writer) Float64Slice(vals []float64) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Float64(vals[i])
}
}
// VariableIntSlice writes an int64 slice using variable-length encoding. Smaller values use less space at the cost of
// larger values using more space, but this is generally more space-efficient. This does carry a computational hit when
// reading.
func (writer *Writer) VariableIntSlice(vals []int64) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.VariableInt(vals[i])
}
}
// VariableUintSlice writes a uint64 slice using variable-length encoding. Smaller values use less space at the cost of
// larger values using more space, but this is generally more space-efficient. This does carry a computational hit when
// reading.
func (writer *Writer) VariableUintSlice(vals []uint64) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.VariableUint(vals[i])
}
}
// StringSlice writes a string slice.
func (writer *Writer) StringSlice(vals []string) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.String(vals[i])
}
}
// StringMap writes a map of strings, keyed by strings.
func (writer *Writer) StringMap(m map[string]string) {
writer.VariableUint(uint64(len(m)))
// We iterate over the sorted set of keys for determinism
for _, k := range slices.Sorted(maps.Keys(m)) {
writer.String(k)
writer.String(m[k])
}
}
// IdSlice writes a slice of internal IDs.
func (writer *Writer) IdSlice(vals []id.Id) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Id(vals[i])
}
}
// IdTypeSlice writes a slice of internal type IDs.
func (writer *Writer) IdTypeSlice(vals []id.Type) {
writer.VariableUint(uint64(len(vals)))
for i := range vals {
writer.Id(vals[i].AsId())
}
}
// Data returns the data written to the Writer.
func (writer *Writer) Data() []byte {
return writer.buf.Bytes()
}
// Reset resets the Writer to be empty, but it retains the underlying storage for use by future writes.
func (writer *Writer) Reset() {
writer.buf.Reset()
}
+295
View File
@@ -0,0 +1,295 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"bytes"
"math"
"sort"
"testing"
"github.com/stretchr/testify/require"
)
// TestWriterReader tests that the writer can round-trip with the reader, preserving data while writing every data type.
func TestWriterReader(t *testing.T) {
writer := NewWriter(0)
writer.Bool(false)
writer.Bool(true)
writer.Int8(-128)
writer.Int8(-57)
writer.Int8(0)
writer.Int8(35)
writer.Int8(127)
writer.Int16(-32768)
writer.Int16(-25756)
writer.Int16(0)
writer.Int16(11527)
writer.Int16(32767)
writer.Int32(-2147483648)
writer.Int32(-4764286)
writer.Int32(0)
writer.Int32(17395298)
writer.Int32(2147483647)
writer.Int64(-9223372036854775808)
writer.Int64(-716243597812645)
writer.Int64(0)
writer.Int64(12784331275341)
writer.Int64(9223372036854775807)
writer.Uint8(0)
writer.Uint8(126)
writer.Uint8(255)
writer.Uint16(0)
writer.Uint16(8465)
writer.Uint16(65535)
writer.Uint32(0)
writer.Uint32(217357235)
writer.Uint32(4294967295)
writer.Uint64(0)
writer.Uint64(12465143251276)
writer.Uint64(18446744073709551615)
writer.Float32(-1716.5625)
writer.Float32(0)
writer.Float32(27465)
writer.Float64(-104451.625)
writer.Float64(0)
writer.Float64(26112.40625)
writer.VariableInt(-9223372036854775808)
writer.VariableInt(-716243597812645)
writer.VariableInt(0)
writer.VariableInt(12784331275341)
writer.VariableInt(9223372036854775807)
writer.VariableUint(0)
writer.VariableUint(12465143251276)
writer.VariableUint(18446744073709551615)
writer.String("")
writer.String("abc123")
writer.String("This is a full sentence. 素晴らしい")
writer.BoolSlice([]bool{true, false, false, true})
writer.Int8Slice([]int8{-128, -57, 0, 35, 127})
writer.Int16Slice([]int16{-32768, -25756, 0, 11527, 32767})
writer.Int32Slice([]int32{-2147483648, -4764286, 0, 17395298, 2147483647})
writer.Int64Slice([]int64{-9223372036854775808, -716243597812645, 0, 12784331275341, 9223372036854775807})
writer.Uint8Slice([]uint8{0, 126, 255})
writer.Uint16Slice([]uint16{0, 8465, 65535})
writer.Uint32Slice([]uint32{0, 217357235, 4294967295})
writer.Uint64Slice([]uint64{0, 12465143251276, 18446744073709551615})
writer.Float32Slice([]float32{-1716.5625, 0, 27465})
writer.Float64Slice([]float64{-104451.625, 0, 26112.40625})
writer.VariableIntSlice([]int64{-9223372036854775808, -716243597812645, 0, 12784331275341, 9223372036854775807})
writer.VariableUintSlice([]uint64{0, 12465143251276, 18446744073709551615})
writer.StringSlice([]string{"This", "is", "a", "string", "test."})
reader := NewReader(writer.Data())
require.Equal(t, false, reader.Bool())
require.Equal(t, true, reader.Bool())
require.Equal(t, int8(-128), reader.Int8())
require.Equal(t, int8(-57), reader.Int8())
require.Equal(t, int8(0), reader.Int8())
require.Equal(t, int8(35), reader.Int8())
require.Equal(t, int8(127), reader.Int8())
require.Equal(t, int16(-32768), reader.Int16())
require.Equal(t, int16(-25756), reader.Int16())
require.Equal(t, int16(0), reader.Int16())
require.Equal(t, int16(11527), reader.Int16())
require.Equal(t, int16(32767), reader.Int16())
require.Equal(t, int32(-2147483648), reader.Int32())
require.Equal(t, int32(-4764286), reader.Int32())
require.Equal(t, int32(0), reader.Int32())
require.Equal(t, int32(17395298), reader.Int32())
require.Equal(t, int32(2147483647), reader.Int32())
require.Equal(t, int64(-9223372036854775808), reader.Int64())
require.Equal(t, int64(-716243597812645), reader.Int64())
require.Equal(t, int64(0), reader.Int64())
require.Equal(t, int64(12784331275341), reader.Int64())
require.Equal(t, int64(9223372036854775807), reader.Int64())
require.Equal(t, uint8(0), reader.Uint8())
require.Equal(t, uint8(126), reader.Uint8())
require.Equal(t, uint8(255), reader.Uint8())
require.Equal(t, uint16(0), reader.Uint16())
require.Equal(t, uint16(8465), reader.Uint16())
require.Equal(t, uint16(65535), reader.Uint16())
require.Equal(t, uint32(0), reader.Uint32())
require.Equal(t, uint32(217357235), reader.Uint32())
require.Equal(t, uint32(4294967295), reader.Uint32())
require.Equal(t, uint64(0), reader.Uint64())
require.Equal(t, uint64(12465143251276), reader.Uint64())
require.Equal(t, uint64(18446744073709551615), reader.Uint64())
require.Equal(t, float32(-1716.5625), reader.Float32())
require.Equal(t, float32(0), reader.Float32())
require.Equal(t, float32(27465), reader.Float32())
require.Equal(t, float64(-104451.625), reader.Float64())
require.Equal(t, float64(0), reader.Float64())
require.Equal(t, float64(26112.40625), reader.Float64())
require.Equal(t, int64(-9223372036854775808), reader.VariableInt())
require.Equal(t, int64(-716243597812645), reader.VariableInt())
require.Equal(t, int64(0), reader.VariableInt())
require.Equal(t, int64(12784331275341), reader.VariableInt())
require.Equal(t, int64(9223372036854775807), reader.VariableInt())
require.Equal(t, uint64(0), reader.VariableUint())
require.Equal(t, uint64(12465143251276), reader.VariableUint())
require.Equal(t, uint64(18446744073709551615), reader.VariableUint())
require.Equal(t, "", reader.String())
require.Equal(t, "abc123", reader.String())
require.Equal(t, "This is a full sentence. 素晴らしい", reader.String())
require.Equal(t, []bool{true, false, false, true}, reader.BoolSlice())
require.Equal(t, []int8{-128, -57, 0, 35, 127}, reader.Int8Slice())
require.Equal(t, []int16{-32768, -25756, 0, 11527, 32767}, reader.Int16Slice())
require.Equal(t, []int32{-2147483648, -4764286, 0, 17395298, 2147483647}, reader.Int32Slice())
require.Equal(t, []int64{-9223372036854775808, -716243597812645, 0, 12784331275341, 9223372036854775807}, reader.Int64Slice())
require.Equal(t, []uint8{0, 126, 255}, reader.Uint8Slice())
require.Equal(t, []uint16{0, 8465, 65535}, reader.Uint16Slice())
require.Equal(t, []uint32{0, 217357235, 4294967295}, reader.Uint32Slice())
require.Equal(t, []uint64{0, 12465143251276, 18446744073709551615}, reader.Uint64Slice())
require.Equal(t, []float32{-1716.5625, 0, 27465}, reader.Float32Slice())
require.Equal(t, []float64{-104451.625, 0, 26112.40625}, reader.Float64Slice())
require.Equal(t, []int64{-9223372036854775808, -716243597812645, 0, 12784331275341, 9223372036854775807}, reader.VariableIntSlice())
require.Equal(t, []uint64{0, 12465143251276, 18446744073709551615}, reader.VariableUintSlice())
require.Equal(t, []string{"This", "is", "a", "string", "test."}, reader.StringSlice())
}
// TestWriterOrder ensures that some types that are traditionally not byte-comparable when serialized are
// byte-comparable using the writer.
func TestWriterOrder(t *testing.T) {
t.Run("int8", func(t *testing.T) {
var serializedData [][]byte
integers := []int8{8, -1, 99, 0, -45, -103, 4, 127, 1, 10, 0, -128, -33, 56}
for _, integer := range integers {
writer := NewWriter(1)
writer.Int8(integer)
serializedData = append(serializedData, writer.Data())
}
sort.Slice(integers, func(i, j int) bool {
return integers[i] < integers[j]
})
sort.Slice(serializedData, func(i, j int) bool {
return bytes.Compare(serializedData[i], serializedData[j]) == -1
})
deserializedIntegers := make([]int8, len(integers))
for i, data := range serializedData {
reader := NewReader(data)
deserializedIntegers[i] = reader.Int8()
}
require.Equal(t, integers, deserializedIntegers)
})
t.Run("int16", func(t *testing.T) {
var serializedData [][]byte
integers := []int16{8, -1, 999, 0, -455, -103, 4, 32767, 1, 100, 0, -32768, -33, 56}
for _, integer := range integers {
writer := NewWriter(2)
writer.Int16(integer)
serializedData = append(serializedData, writer.Data())
}
sort.Slice(integers, func(i, j int) bool {
return integers[i] < integers[j]
})
sort.Slice(serializedData, func(i, j int) bool {
return bytes.Compare(serializedData[i], serializedData[j]) == -1
})
deserializedIntegers := make([]int16, len(integers))
for i, data := range serializedData {
reader := NewReader(data)
deserializedIntegers[i] = reader.Int16()
}
require.Equal(t, integers, deserializedIntegers)
})
t.Run("int32", func(t *testing.T) {
var serializedData [][]byte
integers := []int32{8, -1, 999, 0, -4559775, -103, 4, 2147483647, 1, 100, 0, -2147483648, -33, 5667845}
for _, integer := range integers {
writer := NewWriter(4)
writer.Int32(integer)
serializedData = append(serializedData, writer.Data())
}
sort.Slice(integers, func(i, j int) bool {
return integers[i] < integers[j]
})
sort.Slice(serializedData, func(i, j int) bool {
return bytes.Compare(serializedData[i], serializedData[j]) == -1
})
deserializedIntegers := make([]int32, len(integers))
for i, data := range serializedData {
reader := NewReader(data)
deserializedIntegers[i] = reader.Int32()
}
require.Equal(t, integers, deserializedIntegers)
})
t.Run("int64", func(t *testing.T) {
var serializedData [][]byte
integers := []int64{8, -1, 999, 0, -4559775534255, -103, 4, 9223372036854775807, 1, 100, 0, -9223372036854775808, -33, 566782356235645}
for _, integer := range integers {
writer := NewWriter(8)
writer.Int64(integer)
serializedData = append(serializedData, writer.Data())
}
sort.Slice(integers, func(i, j int) bool {
return integers[i] < integers[j]
})
sort.Slice(serializedData, func(i, j int) bool {
return bytes.Compare(serializedData[i], serializedData[j]) == -1
})
deserializedIntegers := make([]int64, len(integers))
for i, data := range serializedData {
reader := NewReader(data)
deserializedIntegers[i] = reader.Int64()
}
require.Equal(t, integers, deserializedIntegers)
})
t.Run("float32", func(t *testing.T) {
var serializedData [][]byte
floats := []float32{8, -1.123479648834723, 999, 0, -4559775534255, -103.72453, 4, 3.40282347e+38,
9223372036854775807, 1, -3.40282347e+38, 100.1386723987453, 0, -9223372036854775808, -33,
float32(math.Inf(1)), float32(math.Inf(-1)), 566782356235645.1345}
for _, integer := range floats {
writer := NewWriter(4)
writer.Float32(integer)
serializedData = append(serializedData, writer.Data())
}
sort.Slice(floats, func(i, j int) bool {
return floats[i] < floats[j]
})
sort.Slice(serializedData, func(i, j int) bool {
return bytes.Compare(serializedData[i], serializedData[j]) == -1
})
deserializedFloats := make([]float32, len(floats))
for i, data := range serializedData {
reader := NewReader(data)
deserializedFloats[i] = reader.Float32()
}
require.Equal(t, floats, deserializedFloats)
})
t.Run("float64", func(t *testing.T) {
var serializedData [][]byte
floats := []float64{8, -1.123479648834723, 999, 0, -4559775534255, -103.72453, 4, 1.7976931348623157e+308,
9223372036854775807, 1, -1.7976931348623157e+308, 100.1386723987453, 0, -9223372036854775808, -33,
math.Inf(1), math.Inf(-1), 566782356235645.1345}
for _, integer := range floats {
writer := NewWriter(8)
writer.Float64(integer)
serializedData = append(serializedData, writer.Data())
}
sort.Slice(floats, func(i, j int) bool {
return floats[i] < floats[j]
})
sort.Slice(serializedData, func(i, j int) bool {
return bytes.Compare(serializedData[i], serializedData[j]) == -1
})
deserializedFloats := make([]float64, len(floats))
for i, data := range serializedData {
reader := NewReader(data)
deserializedFloats[i] = reader.Float64()
}
require.Equal(t, floats, deserializedFloats)
})
}