Skip to content

Commit

Permalink
tree: make AsJSON aware of DataConversionConfig
Browse files Browse the repository at this point in the history
This enable JSON based operations to correctly apply the IntervalStyle
session setting.

Release note: None
  • Loading branch information
otan committed Jun 30, 2021
1 parent f5a3eae commit 2591fe7
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 33 deletions.
1 change: 1 addition & 0 deletions pkg/ccl/changefeedccl/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ go_library(
"//pkg/sql/rowexec",
"//pkg/sql/sem/builtins",
"//pkg/sql/sem/tree",
"//pkg/sql/sessiondatapb",
"//pkg/sql/types",
"//pkg/storage/cloud",
"//pkg/util",
Expand Down
19 changes: 16 additions & 3 deletions pkg/ccl/changefeedccl/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/rowenc"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondatapb"
"github.com/cockroachdb/cockroach/pkg/util/hlc"
"github.com/cockroachdb/cockroach/pkg/util/json"
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
Expand Down Expand Up @@ -168,7 +169,11 @@ func (e *jsonEncoder) encodeKeyRaw(row encodeRow) ([]interface{}, error) {
return nil, err
}
var err error
jsonEntries[i], err = tree.AsJSON(datum.Datum, time.UTC)
jsonEntries[i], err = tree.AsJSON(
datum.Datum,
sessiondatapb.DataConversionConfig{},
time.UTC,
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -203,7 +208,11 @@ func (e *jsonEncoder) EncodeValue(_ context.Context, row encodeRow) ([]byte, err
return nil, err
}
var err error
after[col.GetName()], err = tree.AsJSON(datum.Datum, time.UTC)
after[col.GetName()], err = tree.AsJSON(
datum.Datum,
sessiondatapb.DataConversionConfig{},
time.UTC,
)
if err != nil {
return nil, err
}
Expand All @@ -220,7 +229,11 @@ func (e *jsonEncoder) EncodeValue(_ context.Context, row encodeRow) ([]byte, err
return nil, err
}
var err error
before[col.GetName()], err = tree.AsJSON(datum.Datum, time.UTC)
before[col.GetName()], err = tree.AsJSON(
datum.Datum,
sessiondatapb.DataConversionConfig{},
time.UTC,
)
if err != nil {
return nil, err
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/interval
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,13 @@ SELECT (i,) FROM intervals ORDER BY pk
("1 day 04:06:08.123")
("2 years 11 mons -2 days +03:25:45.678")

query T
SELECT row_to_json(intervals) FROM intervals ORDER BY pk
----
{"i": "-2 years -11 mons 1 day +04:05:06.123", "pk": 1}
{"i": "1 day 04:06:08.123", "pk": 2}
{"i": "2 years 11 mons -2 days +03:25:45.678", "pk": 3}

statement ok
SET intervalstyle = 'iso_8601'

Expand Down Expand Up @@ -471,6 +478,13 @@ SELECT (i,) FROM intervals ORDER BY pk
(P1DT4H6M8.123S)
(P2Y11M-2DT3H25M45.678S)

query T
SELECT row_to_json(intervals) FROM intervals ORDER BY pk
----
{"i": "P-2Y-11M1DT4H5M6.123S", "pk": 1}
{"i": "P1DT4H6M8.123S", "pk": 2}
{"i": "P2Y11M-2DT3H25M45.678S", "pk": 3}

statement ok
SET intervalstyle = 'sql_standard'

Expand Down Expand Up @@ -504,3 +518,10 @@ SELECT (i,) FROM intervals ORDER BY pk
("-2-11 +1 +4:05:06.123")
("1 4:06:08.123")
("+2-11 -2 +3:25:45.678")

query T
SELECT row_to_json(intervals) FROM intervals ORDER BY pk
----
{"i": "-2-11 +1 +4:05:06.123", "pk": 1}
{"i": "1 4:06:08.123", "pk": 2}
{"i": "+2-11 -2 +3:25:45.678", "pk": 3}
27 changes: 19 additions & 8 deletions pkg/sql/sem/builtins/aggregate_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"context"
"fmt"
"math"
"time"
"unsafe"

"github.com/cockroachdb/apd/v2"
Expand Down Expand Up @@ -3630,23 +3629,27 @@ func (a *intXorAggregate) Size() int64 {
type jsonAggregate struct {
singleDatumAggregateBase

loc *time.Location
evalCtx *tree.EvalContext
builder *json.ArrayBuilderWithCounter
sawNonNull bool
}

func newJSONAggregate(_ []*types.T, evalCtx *tree.EvalContext, _ tree.Datums) tree.AggregateFunc {
return &jsonAggregate{
singleDatumAggregateBase: makeSingleDatumAggregateBase(evalCtx),
loc: evalCtx.GetLocation(),
evalCtx: evalCtx,
builder: json.NewArrayBuilderWithCounter(),
sawNonNull: false,
}
}

// Add accumulates the transformed json into the JSON array.
func (a *jsonAggregate) Add(ctx context.Context, datum tree.Datum, _ ...tree.Datum) error {
j, err := tree.AsJSON(datum, a.loc)
j, err := tree.AsJSON(
datum,
a.evalCtx.SessionData.DataConversionConfig,
a.evalCtx.GetLocation(),
)
if err != nil {
return err
}
Expand Down Expand Up @@ -3950,7 +3953,7 @@ func (a *percentileContAggregate) Size() int64 {
type jsonObjectAggregate struct {
singleDatumAggregateBase

loc *time.Location
evalCtx *tree.EvalContext
builder *json.ObjectBuilderWithCounter
sawNonNull bool
}
Expand All @@ -3960,7 +3963,7 @@ func newJSONObjectAggregate(
) tree.AggregateFunc {
return &jsonObjectAggregate{
singleDatumAggregateBase: makeSingleDatumAggregateBase(evalCtx),
loc: evalCtx.GetLocation(),
evalCtx: evalCtx,
builder: json.NewObjectBuilderWithCounter(),
sawNonNull: false,
}
Expand All @@ -3980,11 +3983,19 @@ func (a *jsonObjectAggregate) Add(
"field name must not be null")
}

key, err := asJSONBuildObjectKey(datum, a.loc)
key, err := asJSONBuildObjectKey(
datum,
a.evalCtx.SessionData.DataConversionConfig,
a.evalCtx.GetLocation(),
)
if err != nil {
return err
}
val, err := tree.AsJSON(others[0], a.loc)
val, err := tree.AsJSON(
others[0],
a.evalCtx.SessionData.DataConversionConfig,
a.evalCtx.GetLocation(),
)
if err != nil {
return err
}
Expand Down
49 changes: 38 additions & 11 deletions pkg/sql/sem/builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -2715,7 +2715,11 @@ may increase either contention or retry errors, or both.`,
if label == "" {
label = fmt.Sprintf("f%d", i+1)
}
val, err := tree.AsJSON(d, ctx.GetLocation())
val, err := tree.AsJSON(
d,
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -6010,12 +6014,20 @@ var jsonBuildObjectImpl = tree.Overload{
"argument %d cannot be null", i+1)
}

key, err := asJSONBuildObjectKey(args[i], ctx.GetLocation())
key, err := asJSONBuildObjectKey(
args[i],
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}

val, err := tree.AsJSON(args[i+1], ctx.GetLocation())
val, err := tree.AsJSON(
args[i+1],
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}
Expand All @@ -6033,7 +6045,7 @@ var toJSONImpl = tree.Overload{
Types: tree.ArgTypes{{"val", types.Any}},
ReturnType: tree.FixedReturnType(types.Jsonb),
Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
return toJSONObject(args[0], ctx.GetLocation())
return toJSONObject(ctx, args[0])
},
Info: "Returns the value as JSON or JSONB.",
Volatility: tree.VolatilityStable,
Expand All @@ -6057,7 +6069,7 @@ var arrayToJSONImpls = makeBuiltin(jsonProps(),
if prettyPrint {
return nil, prettyPrintNotSupportedError
}
return toJSONObject(args[0], ctx.GetLocation())
return toJSONObject(ctx, args[0])
},
Info: "Returns the array as JSON or JSONB.",
Volatility: tree.VolatilityStable,
Expand All @@ -6070,7 +6082,11 @@ var jsonBuildArrayImpl = tree.Overload{
Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
builder := json.NewArrayBuilder(len(args))
for _, arg := range args {
j, err := tree.AsJSON(arg, ctx.GetLocation())
j, err := tree.AsJSON(
arg,
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -6100,7 +6116,11 @@ var jsonObjectImpls = makeBuiltin(jsonProps(),
if err != nil {
return nil, err
}
val, err := tree.AsJSON(arr.Array[i+1], ctx.GetLocation())
val, err := tree.AsJSON(
arr.Array[i+1],
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -6132,7 +6152,11 @@ var jsonObjectImpls = makeBuiltin(jsonProps(),
if err != nil {
return nil, err
}
val, err := tree.AsJSON(values.Array[i], ctx.GetLocation())
val, err := tree.AsJSON(
values.Array[i],
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -7301,7 +7325,9 @@ func truncateTimestamp(fromTime time.Time, timeSpan string) (*tree.DTimestampTZ,
}

// Converts a scalar Datum to its string representation
func asJSONBuildObjectKey(d tree.Datum, loc *time.Location) (string, error) {
func asJSONBuildObjectKey(
d tree.Datum, dcc sessiondatapb.DataConversionConfig, loc *time.Location,
) (string, error) {
switch t := d.(type) {
case *tree.DJSON, *tree.DArray, *tree.DTuple:
return "", pgerror.New(pgcode.InvalidParameterValue,
Expand All @@ -7318,6 +7344,7 @@ func asJSONBuildObjectKey(d tree.Datum, loc *time.Location) (string, error) {
return tree.AsStringWithFlags(
ts,
tree.FmtBareStrings,
tree.FmtDataConversionConfig(dcc),
), nil
case *tree.DBool, *tree.DInt, *tree.DFloat, *tree.DDecimal, *tree.DTimestamp,
*tree.DDate, *tree.DUuid, *tree.DInterval, *tree.DBytes, *tree.DIPAddr, *tree.DOid,
Expand All @@ -7337,8 +7364,8 @@ func asJSONObjectKey(d tree.Datum) (string, error) {
}
}

func toJSONObject(d tree.Datum, loc *time.Location) (tree.Datum, error) {
j, err := tree.AsJSON(d, loc)
func toJSONObject(ctx *tree.EvalContext, d tree.Datum) (tree.Datum, error) {
j, err := tree.AsJSON(d, ctx.SessionData.DataConversionConfig, ctx.GetLocation())
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/sql/sem/builtins/geo_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -7309,7 +7309,11 @@ func stAsGeoJSONFromTuple(
)
}
}
tupleJSON, err := tree.AsJSON(d, ctx.GetLocation())
tupleJSON, err := tree.AsJSON(
d,
ctx.SessionData.DataConversionConfig,
ctx.GetLocation(),
)
if err != nil {
return nil, err
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/sem/tree/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ go_library(
"//pkg/sql/privilege",
"//pkg/sql/roleoption",
"//pkg/sql/sessiondata",
"//pkg/sql/sessiondatapb",
"//pkg/sql/sqlliveness",
"//pkg/sql/sqltelemetry",
"//pkg/sql/types",
Expand Down Expand Up @@ -221,6 +222,7 @@ go_test(
"//pkg/sql/randgen",
"//pkg/sql/sem/builtins",
"//pkg/sql/sessiondata",
"//pkg/sql/sessiondatapb",
"//pkg/sql/types",
"//pkg/testutils",
"//pkg/testutils/sqlutils",
Expand Down
16 changes: 7 additions & 9 deletions pkg/sql/sem/tree/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/lexbase"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondatapb"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util/bitarray"
"github.com/cockroachdb/cockroach/pkg/util/duration"
Expand Down Expand Up @@ -2773,11 +2774,6 @@ func (d *DInterval) Min(_ *EvalContext) (Datum, bool) {
return dMinInterval, true
}

// ValueAsString returns the interval as a string (e.g. "1h2m").
func (d *DInterval) ValueAsString() string {
return d.Duration.String()
}

// AmbiguousFormat implements the Datum interface.
func (*DInterval) AmbiguousFormat() bool { return true }

Expand Down Expand Up @@ -3189,7 +3185,9 @@ func MustBeDJSON(e Expr) DJSON {
}

// AsJSON converts a datum into our standard json representation.
func AsJSON(d Datum, loc *time.Location) (json.JSON, error) {
func AsJSON(
d Datum, dcc sessiondatapb.DataConversionConfig, loc *time.Location,
) (json.JSON, error) {
d = UnwrapDatum(nil /* evalCtx */, d)
switch t := d.(type) {
case *DBool:
Expand All @@ -3211,7 +3209,7 @@ func AsJSON(d Datum, loc *time.Location) (json.JSON, error) {
case *DArray:
builder := json.NewArrayBuilder(t.Len())
for _, e := range t.Array {
j, err := AsJSON(e, loc)
j, err := AsJSON(e, dcc, loc)
if err != nil {
return nil, err
}
Expand All @@ -3226,7 +3224,7 @@ func AsJSON(d Datum, loc *time.Location) (json.JSON, error) {
t.maybePopulateType()
labels := t.typ.TupleLabels()
for i, e := range t.D {
j, err := AsJSON(e, loc)
j, err := AsJSON(e, dcc, loc)
if err != nil {
return nil, err
}
Expand All @@ -3249,7 +3247,7 @@ func AsJSON(d Datum, loc *time.Location) (json.JSON, error) {
// This is RFC3339Nano, but without the TZ fields.
return json.FromString(t.UTC().Format("2006-01-02T15:04:05.999999999")), nil
case *DDate, *DUuid, *DOid, *DInterval, *DBytes, *DIPAddr, *DTime, *DTimeTZ, *DBitArray, *DBox2D:
return json.FromString(AsStringWithFlags(t, FmtBareStrings)), nil
return json.FromString(AsStringWithFlags(t, FmtBareStrings, FmtDataConversionConfig(dcc))), nil
case *DGeometry:
return json.FromSpatialObject(t.Geometry.SpatialObject(), geo.DefaultGeoJSONDecimalDigits)
case *DGeography:
Expand Down
Loading

0 comments on commit 2591fe7

Please sign in to comment.