Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ require (
github.com/PuerkitoBio/goquery v1.8.1
github.com/cockroachdb/apd/v3 v3.2.3
github.com/cockroachdb/errors v1.7.5
github.com/dolthub/dolt/go v0.40.5-0.20260827015047-6c83efb8fd6f
github.com/dolthub/dolt/go v0.40.5-0.20260827200740-8b73116fa3a0
github.com/dolthub/eventsapi_schema v0.0.0-20260715220557-d9b4a1c6b4d4
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2
github.com/dolthub/go-mysql-server v0.20.1-0.20260826232105-2476e46ebc77
github.com/dolthub/go-mysql-server v0.20.1-0.20260827195059-69dc4447f79d
github.com/dolthub/pg_query_go/v6 v6.0.0-20251215122834-fb20be4254d1
github.com/dolthub/sqllogictest/go v0.0.0-20260624223518-788480b24166
github.com/dolthub/vitess v0.0.0-20260819175407-19559ab533b7
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ github.com/dolthub/dolt-mcp v0.3.4 h1:AyG5cw+fNWXDHXujtQnqUPZrpWtPg6FN6yYtjv1pP4
github.com/dolthub/dolt-mcp v0.3.4/go.mod h1:bCZ7KHvDYs+M0e+ySgmGiNvLhcwsN7bbf5YCyillLrk=
github.com/dolthub/dolt/go v0.40.5-0.20260827015047-6c83efb8fd6f h1:OyTEzFYwj+AkWesMifuS3NKWfMgZaT14CGhQ7nZHTBI=
github.com/dolthub/dolt/go v0.40.5-0.20260827015047-6c83efb8fd6f/go.mod h1:LAIaD9W6cbyLdCc+B8GhtWqof4jg7Os9IZoGp3OXQkw=
github.com/dolthub/dolt/go v0.40.5-0.20260827194013-098bf0dd73ea h1:IqOekjewTKVDDGaG+YDczwp3NnlRrbOwQAlPzQB0hIo=
github.com/dolthub/dolt/go v0.40.5-0.20260827194013-098bf0dd73ea/go.mod h1:yGOSyFRHNRW3UtsLqzZUbfjdPGXBwLMg12P1PU/QHGo=
github.com/dolthub/dolt/go v0.40.5-0.20260827195913-9e9dfcea77de h1:SCsNUAmrrIONge0Skag9lIZrikj/1GX3lMpqwi8LgAI=
github.com/dolthub/dolt/go v0.40.5-0.20260827195913-9e9dfcea77de/go.mod h1:ex4vSajZXQXDdHwPY0kTHW09d1jmz+EiH0WqlSjMI/8=
github.com/dolthub/dolt/go v0.40.5-0.20260827200740-8b73116fa3a0 h1:ch0tcFOh2VOxwXpHyPkernZSd7GGpOBQolzulEY2wqM=
github.com/dolthub/dolt/go v0.40.5-0.20260827200740-8b73116fa3a0/go.mod h1:gz29UDKvKWw5QIpSPEB2QLZ9Ed9ToEOodllQiUqbSyM=
github.com/dolthub/eventsapi_schema v0.0.0-20260715220557-d9b4a1c6b4d4 h1:0mg9QEFdkkBwJMxvz1tCjHYmfG2iIC6aShj1InDq9/M=
github.com/dolthub/eventsapi_schema v0.0.0-20260715220557-d9b4a1c6b4d4/go.mod h1:SSLraQS/jGLYFgff3vuZ+JbVUct6vyEeMzjLBqWqoyM=
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww=
Expand All @@ -258,6 +264,10 @@ github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83 h1:FEMjCGEroD
github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83/go.mod h1:F3cnm+vMRK1HaU6+rNqQrOCyR03HHhR1GWG2gnPOqaE=
github.com/dolthub/go-mysql-server v0.20.1-0.20260826232105-2476e46ebc77 h1:VW2z70lyE+vvjNa20yAkkoLm13h9cJTa5LxlcMVnMOw=
github.com/dolthub/go-mysql-server v0.20.1-0.20260826232105-2476e46ebc77/go.mod h1:7Z71DCeZPBuG92QTM7KvQbpWCeNGZEL7chOHuSD4TOo=
github.com/dolthub/go-mysql-server v0.20.1-0.20260827194013-baefbed57445 h1:k3Z3cjLp1+pC0A+MOAb3HR97JVYRIYaWro3vafqXPDk=
github.com/dolthub/go-mysql-server v0.20.1-0.20260827194013-baefbed57445/go.mod h1:7Z71DCeZPBuG92QTM7KvQbpWCeNGZEL7chOHuSD4TOo=
github.com/dolthub/go-mysql-server v0.20.1-0.20260827195059-69dc4447f79d h1:mJRLM6HPGku+Pj8qZe7O05RJbXJLKoxsB99PeqfU29M=
github.com/dolthub/go-mysql-server v0.20.1-0.20260827195059-69dc4447f79d/go.mod h1:7Z71DCeZPBuG92QTM7KvQbpWCeNGZEL7chOHuSD4TOo=
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 h1:OAsXLAPL4du6tfbBgK0xXHZkOlos63RdKYS3Sgw/dfI=
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63/go.mod h1:lV7lUeuDhH5thVGDCKXbatwKy2KW80L4rMT46n+Y2/Q=
github.com/dolthub/ishell v0.0.0-20260414231531-5f031e3e9037 h1:oIW9HwuWrhxv+4HZxA+QQSKHLqWFyXZ2FmNjUYwkdiM=
Expand Down
36 changes: 36 additions & 0 deletions integration-tests/go-sql-server-driver/large_values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,49 @@ import (
"fmt"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"

driver "github.com/dolthub/doltgresql/integration-tests/go-sql-server-driver/driver"
)

// TestJSONBPrecisionAfterRestart verifies exact JSONB numerics through mutation, indexed storage, and server restart.
func TestJSONBPrecisionAfterRestart(t *testing.T) {
server := setupTestServer(t, "jsonb_precision_restart")
db, err := server.DB(driver.Connection{})
require.NoError(t, err)

_, err = db.Exec(`CREATE TABLE precision_docs (id int PRIMARY KEY, doc jsonb)`)
require.NoError(t, err)
_, err = db.Exec(`INSERT INTO precision_docs VALUES (1, '{"value":123456789012345678901234567890.123456789}')`)
require.NoError(t, err)
_, err = db.Exec(`UPDATE precision_docs SET doc = jsonb_set(doc, '{other}', 'true'::jsonb) WHERE id = 1`)
require.NoError(t, err)
_, err = db.Exec(`SELECT dolt_commit('-Am', 'persist precise jsonb')`)
require.NoError(t, err)
require.NoError(t, db.Close())

require.NoError(t, server.Restart(nil, nil))
db, err = server.DB(driver.Connection{})
require.NoError(t, err)
defer db.Close()
require.Eventually(t, func() bool { return db.Ping() == nil }, 10*time.Second, 100*time.Millisecond)

var extracted, castValue string
var equal, ordered bool
err = db.QueryRow(`SELECT doc ->> 'value', (doc -> 'value')::numeric::text,
doc -> 'value' = '123456789012345678901234567890.123456789'::jsonb,
doc -> 'value' < '123456789012345678901234567890.123456790'::jsonb
FROM precision_docs WHERE id = 1`).Scan(&extracted, &castValue, &equal, &ordered)
require.NoError(t, err)
require.Equal(t, "123456789012345678901234567890.123456789", extracted)
require.Equal(t, extracted, castValue)
require.True(t, equal)
require.True(t, ordered)
}

// setupTestServer creates a standard single-server test environment. The repo
// name is also used as the database name. Cleanup is registered with t.Cleanup.
func setupTestServer(t *testing.T, repoName string) *driver.SqlServer {
Expand Down
10 changes: 5 additions & 5 deletions server/cast/jsonb.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ func initJsonB(builtInCasts map[id.Cast]casts.Cast) {
func jsonbGetInterface(ctx *sql.Context, val any) (any, error) {
switch v := val.(type) {
case sql.JSONWrapper:
return v.ToInterface(ctx)
return pgtypes.JSONBWrapperToInterface(ctx, v)
case sql.StringWrapper:
var result any
s, err := v.Unwrap(ctx)
if err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(s), &result); err != nil {
result, err := pgtypes.DecodeJSONBValue([]byte(s))
if err != nil {
return nil, errors.Errorf("invalid JSON: %v", err)
}
return result, nil
case string:
var result any
if err := json.Unmarshal([]byte(v), &result); err != nil {
result, err := pgtypes.DecodeJSONBValue([]byte(v))
if err != nil {
return nil, errors.Errorf("invalid JSON: %v", err)
}
return result, nil
Expand Down
2 changes: 1 addition & 1 deletion server/connection_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,7 @@ func castSQLError(err error) *pgconn.PgError {
code = pgcode.DivisionByZero
case sql.ErrValueOutOfRange.Is(err), pgtypes.ErrValueIsOutOfRangeForType.Is(err),
pgtypes.ErrOutOfRange.Is(err), pgtypes.ErrInputOutOfRange.Is(err),
errors.Is(err, pgtypes.ErrCastOutOfRange):
pgtypes.ErrJSONNumberOutOfRange.Is(err), errors.Is(err, pgtypes.ErrCastOutOfRange):
code = pgcode.NumericValueOutOfRange
case pgtypes.ErrInvalidSyntaxForType.Is(err), sql.ErrInvalidValue.Is(err):
code = pgcode.InvalidTextRepresentation
Expand Down
6 changes: 5 additions & 1 deletion server/functions/array_to_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ func valueToJsonRaw(ctx *sql.Context, elemType *pgtypes.DoltgresType, val any) (
if val == nil {
return json.RawMessage("null"), nil
}
if v, ok := val.(pgtypes.JsonDocument); ok {
switch v := val.(type) {
case types.JSONBytes:
bytes, err := v.GetBytes(ctx)
return json.RawMessage(bytes), err
case pgtypes.JsonDocument:
sb := strings.Builder{}
pgtypes.JsonValueFormatter(&sb, v.Value)
return json.RawMessage(sb.String()), nil
Expand Down
4 changes: 2 additions & 2 deletions server/functions/binary/concatenate.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ func jsonb_concat_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1Int
if !ok1 || !ok2 {
return nil, fmt.Errorf("jsonb_concat: unexpected types %T, %T", val1Interface, val2Interface)
}
v1, err := wrapper1.ToInterface(ctx)
v1, err := pgtypes.JSONBWrapperToInterface(ctx, wrapper1)
if err != nil {
return nil, err
}
v2, err := wrapper2.ToInterface(ctx)
v2, err := pgtypes.JSONBWrapperToInterface(ctx, wrapper2)
if err != nil {
return nil, err
}
Expand Down
57 changes: 40 additions & 17 deletions server/functions/binary/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,14 @@ func toJSONWrapper(ctx *sql.Context, val any) (sql.JSONWrapper, error) {

// jsonWrapperElementToText converts a JSON element (sql.JSONWrapper) to its text representation.
// For string values, it returns the raw string without quotes. For other types, it returns the JSON representation.
func jsonWrapperElementToText(ctx *sql.Context, wrapper sql.JSONWrapper) (string, error) {
v, err := wrapper.ToInterface(ctx)
func jsonWrapperElementToText(ctx *sql.Context, wrapper sql.JSONWrapper, jsonb bool) (string, error) {
var v any
var err error
if jsonb {
v, err = pgtypes.JSONBWrapperToInterface(ctx, wrapper)
} else {
v, err = pgtypes.JSONWrapperToInterface(ctx, wrapper)
}
if err != nil {
return "", err
}
Expand Down Expand Up @@ -172,7 +178,7 @@ var jsonb_array_element = framework.Function2{
Callable: jsonb_array_element_callable,
}

func jsonb_array_element_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
func jsonb_array_element_callable(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return nil, nil
Expand Down Expand Up @@ -204,7 +210,13 @@ func jsonb_array_element_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType,
// Materialized fallback: covers wrappers that don't implement
// ComparableJSON (e.g. literal jsonb values) and the negative-index
// case on ComparableJSON arrays.
v, err := wrapper.ToInterface(ctx)
var v any
var err error
if dt[1] != nil {
v, err = pgtypes.JSONBWrapperToInterface(ctx, wrapper)
} else {
v, err = pgtypes.JSONWrapperToInterface(ctx, wrapper)
}
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -254,7 +266,7 @@ var jsonb_object_field = framework.Function2{
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
Callable: func(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return nil, nil
Expand Down Expand Up @@ -282,7 +294,12 @@ var jsonb_object_field = framework.Function2{
// ComparableJSON (e.g. literal jsonb values), where the embedded
// jsonpath library has trouble with edge cases like keys that
// contain escaped quotes, or array operands with text paths.
v, err := wrapper.ToInterface(ctx)
var v any
if dt[1] != nil {
v, err = pgtypes.JSONBWrapperToInterface(ctx, wrapper)
} else {
v, err = pgtypes.JSONWrapperToInterface(ctx, wrapper)
}
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -333,7 +350,7 @@ var jsonb_array_element_text = framework.Function2{
if !ok {
return nil, nil
}
return jsonWrapperElementToText(ctx, wrapper)
return jsonWrapperElementToText(ctx, wrapper, dt[1] != nil)
},
}

Expand Down Expand Up @@ -372,7 +389,7 @@ var jsonb_object_field_text = framework.Function2{
if !ok {
return nil, nil
}
return jsonWrapperElementToText(ctx, wrapper)
return jsonWrapperElementToText(ctx, wrapper, dt[1] != nil)
},
}

Expand Down Expand Up @@ -412,7 +429,7 @@ var jsonb_extract_path = framework.Function2{
Callable: jsonb_extract_path_callable,
}

func jsonb_extract_path_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
func jsonb_extract_path_callable(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
cur, ok := val1.(sql.JSONWrapper)
if !ok {
return nil, nil
Expand Down Expand Up @@ -442,7 +459,7 @@ func jsonb_extract_path_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, v
if !ok {
return nil, nil
}
next, err := extractOneJsonPathStep(ctx, cur, textPath)
next, err := extractOneJsonPathStep(ctx, cur, textPath, dt[1] != nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -495,7 +512,7 @@ func extractJsonPathBySingleLookup(ctx *sql.Context, doc sql.JSONWrapper, paths

if result != nil {
// A non-nil result could still be semantically NULL
i, err := result.ToInterface(ctx)
i, err := pgtypes.JSONWrapperToInterface(ctx, result)
if err != nil {
return nil, false, err
}
Expand All @@ -514,7 +531,7 @@ func extractJsonPathBySingleLookup(ctx *sql.Context, doc sql.JSONWrapper, paths
// object and as an integer index when the wrapper is an array. Returns nil if
// the step cannot be resolved (missing key, out-of-range index, scalar
// wrapper, or non-integer text on an array).
func extractOneJsonPathStep(ctx *sql.Context, cur sql.JSONWrapper, textPath string) (sql.JSONWrapper, error) {
func extractOneJsonPathStep(ctx *sql.Context, cur sql.JSONWrapper, textPath string, jsonb bool) (sql.JSONWrapper, error) {
// Fast path: use the ComparableJSON.JsonType / SearchableJSON.Lookup
// interfaces to avoid materializing the entire document.
if comparable, ok := cur.(types.ComparableJSON); ok {
Expand All @@ -541,7 +558,13 @@ func extractOneJsonPathStep(ctx *sql.Context, cur sql.JSONWrapper, textPath stri
}
// Materialized fallback for wrappers that don't implement ComparableJSON,
// and for negative array indices on ones that do.
v, err := cur.ToInterface(ctx)
var v any
var err error
if jsonb {
v, err = pgtypes.JSONBWrapperToInterface(ctx, cur)
} else {
v, err = pgtypes.JSONWrapperToInterface(ctx, cur)
}
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -607,7 +630,7 @@ func jsonb_extract_path_text_callable(ctx *sql.Context, dt [3]*pgtypes.DoltgresT
if !ok {
return nil, nil
}
return jsonWrapperElementToText(ctx, wrapper)
return jsonWrapperElementToText(ctx, wrapper, dt[1] != nil)
}

// jsonb_contains represents the PostgreSQL function of the same name, taking the same parameters.
Expand Down Expand Up @@ -658,7 +681,7 @@ var jsonb_exists = framework.Function2{
}
return found != nil, nil
}
value, err := wrapper.ToInterface(ctx)
value, err := pgtypes.JSONBWrapperToInterface(ctx, wrapper)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -731,7 +754,7 @@ var jsonb_exists_any = framework.Function2{
}
return false, nil
}
value, err := wrapper.ToInterface(ctx)
value, err := pgtypes.JSONBWrapperToInterface(ctx, wrapper)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -801,7 +824,7 @@ var jsonb_exists_all = framework.Function2{
}
return true, nil
}
value, err := wrapper.ToInterface(ctx)
value, err := pgtypes.JSONBWrapperToInterface(ctx, wrapper)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions server/functions/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func Init() {
initJsonObjectKeys()
initJsonStripNulls()
initJsonTypeof()
initJsonbSet()
initLcm()
initLeft()
initLength()
Expand Down
Loading
Loading