chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package doltgres_builder
|
||||
|
||||
const (
|
||||
GithubDoltgres = "https://github.com/dolthub/doltgresql.git"
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user