diff --git a/pkg/container/batch/batch.go b/pkg/container/batch/batch.go index aa511dfac7700..ee8b0939039bb 100644 --- a/pkg/container/batch/batch.go +++ b/pkg/container/batch/batch.go @@ -37,13 +37,15 @@ const ( prepareParamKindBatchModeNone = byte(0) prepareParamKindBatchModeUniform = byte(1) prepareParamKindBatchModeRows = byte(2) + prepareParamKindBatchBinaryFlag = byte(0x80) prepareParamKindBatchMaxRows = int32(1 << 24) ) type prepareParamKindBatchRecord struct { - mode byte - kind vector.PrepareParamKind - rows []vector.PrepareParamKind + mode byte + kind vector.PrepareParamKind + encodedRows []byte + binaryString bool } func New(attrs []string) *Batch { @@ -152,6 +154,9 @@ func (bat *Batch) HasPrepareParamKindMetadata() bool { if vec == nil { continue } + if vec.GetIsBinaryString() { + return true + } if len(vec.GetPrepareParamKinds()) != 0 { return true } @@ -162,6 +167,26 @@ func (bat *Batch) HasPrepareParamKindMetadata() bool { return false } +func (bat *Batch) HasBinaryStringMetadata() bool { + if bat == nil { + return false + } + for _, vec := range bat.Vecs { + if vec == nil { + continue + } + switch vec.GetType().Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + // Static binary types need no dynamic trailer. + continue + } + if vec.GetIsBinaryString() { + return true + } + } + return false +} + // AppendPrepareParamKindMetadata appends a self-identifying transient trailer // after the stable Batch bytes. It is intentionally not part of // MarshalBinaryTo: persisted/stable Vector and Batch bytes remain unchanged. @@ -183,25 +208,39 @@ func (bat *Batch) AppendPrepareParamKindMetadata(w *bytes.Buffer) error { return moerr.NewInvalidInputNoCtx("cannot encode prepared parameter metadata for nil vector") } kinds := vec.GetPrepareParamKinds() + mixedBinaryString := vec.HasBinaryStringRows() + binaryFlag := byte(0) + if vec.GetIsBinaryString() && !mixedBinaryString { + binaryFlag = prepareParamKindBatchBinaryFlag + } switch { - case len(kinds) != 0: - if len(kinds) != vec.Length() || int64(len(kinds)) > int64(prepareParamKindBatchMaxRows) { + case len(kinds) != 0 || mixedBinaryString: + if (len(kinds) != 0 && len(kinds) != vec.Length()) || + int64(vec.Length()) > int64(prepareParamKindBatchMaxRows) { return moerr.NewInvalidInputNoCtx("invalid prepared parameter metadata row count") } ext.WriteByte(prepareParamKindBatchModeRows) - rowLen := int32(len(kinds)) + rowLen := int32(vec.Length()) ext.Write(types.EncodeInt32(&rowLen)) - for _, kind := range kinds { + for row := 0; row < vec.Length(); row++ { + kind := vector.PrepareParamNone + if len(kinds) != 0 { + kind = kinds[row] + } if kind > vector.PrepareParamBoolean { return moerr.NewInvalidInputNoCtx("invalid prepared parameter metadata kind") } - ext.WriteByte(byte(kind)) + encoded := byte(kind) + if vec.GetIsBinaryStringAt(row) { + encoded |= prepareParamKindBatchBinaryFlag + } + ext.WriteByte(encoded) } case vec.HasPrepareParamKind() && vec.GetPrepareParamKind() != vector.PrepareParamNone: - ext.WriteByte(prepareParamKindBatchModeUniform) + ext.WriteByte(prepareParamKindBatchModeUniform | binaryFlag) ext.WriteByte(byte(vec.GetPrepareParamKind())) default: - ext.WriteByte(prepareParamKindBatchModeNone) + ext.WriteByte(prepareParamKindBatchModeNone | binaryFlag) } } if uint64(ext.Len()) > uint64(^uint32(0))-4 { @@ -438,7 +477,6 @@ func (bat *Batch) UnmarshalBinaryWithPrepareParamKinds(data []byte, mp *mpool.MP if rowCount != int64(bat.RowCount()) { return moerr.NewInvalidInputNoCtx("prepared parameter metadata batch row count mismatch") } - cleared := 0 for i, record := range records { if i >= len(bat.Vecs) { return moerr.NewInvalidInputNoCtx("prepared parameter metadata vector count mismatch") @@ -447,29 +485,35 @@ func (bat *Batch) UnmarshalBinaryWithPrepareParamKinds(data []byte, mp *mpool.MP var applyErr error switch record.mode { case prepareParamKindBatchModeNone: - // The stable decoder reset any metadata from an earlier reuse. + vec.SetIsBinaryString(record.binaryString) case prepareParamKindBatchModeUniform: if record.kind == vector.PrepareParamNone { applyErr = moerr.NewInvalidInputNoCtx("uniform prepared parameter metadata cannot be None") } else { vec.SetPrepareParamKind(record.kind) } + vec.SetIsBinaryString(record.binaryString) case prepareParamKindBatchModeRows: - if len(record.rows) != vec.Length() { + if len(record.encodedRows) != vec.Length() { applyErr = moerr.NewInvalidInputNoCtx("prepared parameter metadata row count mismatch") } else { - applyErr = vec.SetPrepareParamKindsWithMP(record.rows, mp) + applyErr = vec.SetPrepareParamKindsAndBinaryStringFromReader( + bytes.NewReader(record.encodedRows), len(record.encodedRows), mp, + prepareParamKindBatchBinaryFlag, + ) } default: applyErr = moerr.NewInvalidInputNoCtx("invalid prepared parameter metadata mode") } if applyErr != nil { - for j := 0; j < cleared; j++ { - _ = bat.Vecs[j].SetPrepareParamKindsWithMP(nil, mp) + for _, resetVec := range bat.Vecs { + if resetVec != nil { + _ = resetVec.SetPrepareParamKindsWithMP(nil, mp) + resetVec.SetIsBinaryString(false) + } } return applyErr } - cleared = i + 1 } return nil } @@ -506,6 +550,7 @@ func (bat *Batch) UnmarshalFromReaderWithPrepareParamKinds( for _, vec := range bat.Vecs { if vec != nil { _ = vec.SetPrepareParamKindsWithMP(nil, mp) + vec.SetIsBinaryString(false) } } } @@ -563,9 +608,11 @@ func (bat *Batch) UnmarshalFromReaderWithPrepareParamKinds( if err != nil { return fail(err) } + binaryString := mode&prepareParamKindBatchBinaryFlag != 0 + mode &^= prepareParamKindBatchBinaryFlag switch mode { case prepareParamKindBatchModeNone: - // The stable decoder reset any metadata from an earlier reuse. + bat.Vecs[i].SetIsBinaryString(binaryString) case prepareParamKindBatchModeUniform: kind, err := readByte() if err != nil { @@ -576,6 +623,7 @@ func (bat *Batch) UnmarshalFromReaderWithPrepareParamKinds( return fail(moerr.NewInvalidInputNoCtx("invalid uniform prepared parameter metadata kind")) } bat.Vecs[i].SetPrepareParamKind(vector.PrepareParamKind(kind)) + bat.Vecs[i].SetIsBinaryString(binaryString) case prepareParamKindBatchModeRows: count, err := types.ReadInt32(limited) if err != nil { @@ -592,7 +640,9 @@ func (bat *Batch) UnmarshalFromReaderWithPrepareParamKinds( if limited.N < minimumRemaining || int64(count) > limited.N-minimumRemaining { return fail(io.ErrUnexpectedEOF) } - if err := bat.Vecs[i].SetPrepareParamKindsFromReader(limited, int(count), mp); err != nil { + if err := bat.Vecs[i].SetPrepareParamKindsAndBinaryStringFromReader( + limited, int(count), mp, prepareParamKindBatchBinaryFlag, + ); err != nil { return fail(err) } default: @@ -719,6 +769,8 @@ func parsePrepareParamKindBatchTrailer( if err != nil { return nil, 0, err } + records[i].binaryString = mode&prepareParamKindBatchBinaryFlag != 0 + mode &^= prepareParamKindBatchBinaryFlag records[i].mode = mode switch mode { case prepareParamKindBatchModeNone: @@ -743,13 +795,14 @@ func parsePrepareParamKindBatchTrailer( if reader.Len() < 4 || int64(count) > int64(reader.Len()-4) { return nil, 0, io.ErrUnexpectedEOF } - records[i].rows = make([]vector.PrepareParamKind, int(count)) - for row := range records[i].rows { - kind, err := types.ReadByte(reader) + records[i].encodedRows = make([]byte, int(count)) + for row := range records[i].encodedRows { + encoded, err := types.ReadByte(reader) + kind := encoded &^ prepareParamKindBatchBinaryFlag if err != nil || vector.PrepareParamKind(kind) > vector.PrepareParamBoolean { return nil, 0, moerr.NewInvalidInputNoCtx("invalid prepared parameter metadata kind") } - records[i].rows[row] = vector.PrepareParamKind(kind) + records[i].encodedRows[row] = encoded } default: return nil, 0, moerr.NewInvalidInputNoCtx("invalid prepared parameter metadata mode") @@ -1615,6 +1668,7 @@ func (bat *Batch) CloneTo(toBat *Batch, mp *mpool.MPool) (err error) { toBat.Clean(mp) return } + toVec.SetIsBinaryString(srcVec.GetIsBinaryString()) } } toBat.rowCount = bat.rowCount diff --git a/pkg/container/batch/batch_test.go b/pkg/container/batch/batch_test.go index c1f959537d953..ae154f0d51fc4 100644 --- a/pkg/container/batch/batch_test.go +++ b/pkg/container/batch/batch_test.go @@ -636,6 +636,26 @@ func TestClonePreservesPrepareParamKind(t *testing.T) { require.Equal(t, vector.PrepareParamDecimal, cloned.Vecs[0].GetPrepareParamKind()) } +func TestClonePreservesConstantBinaryStringMetadata(t *testing.T) { + mp := mpool.MustNewZero() + source := NewWithSize(1) + var err error + source.Vecs[0], err = vector.NewConstBytes( + types.T_varchar.ToType(), []byte{0xe4, 0xbd, 0xa0}, 3, mp) + require.NoError(t, err) + source.Vecs[0].SetIsBinaryString(true) + source.SetRowCount(3) + defer source.Clean(mp) + + cloned, err := source.Dup(mp) + require.NoError(t, err) + defer cloned.Clean(mp) + require.True(t, cloned.Vecs[0].GetIsBinaryString()) + for row := 0; row < 3; row++ { + require.True(t, cloned.Vecs[0].GetIsBinaryStringAt(row)) + } +} + func TestPrepareParamKindTransportRoundTripAndReuse(t *testing.T) { mp := mpool.MustNewZero() source := NewWithSize(1) @@ -646,6 +666,7 @@ func TestPrepareParamKindTransportRoundTripAndReuse(t *testing.T) { vector.PrepareParamFloat, vector.PrepareParamNone, }) + require.NoError(t, source.Vecs[0].SetBinaryStringRows([]bool{true, false})) source.SetRowCount(2) defer source.Clean(mp) @@ -662,11 +683,14 @@ func TestPrepareParamKindTransportRoundTripAndReuse(t *testing.T) { require.NoError(t, decoded.UnmarshalBinaryWithPrepareParamKinds(encoded, mp)) require.Equal(t, vector.PrepareParamFloat, decoded.Vecs[0].GetPrepareParamKindAt(0)) require.Equal(t, vector.PrepareParamNone, decoded.Vecs[0].GetPrepareParamKindAt(1)) + require.True(t, decoded.Vecs[0].GetIsBinaryStringAt(0)) + require.False(t, decoded.Vecs[0].GetIsBinaryStringAt(1)) // Reusing the receiver with a legacy payload must clear the previous // sidecar rather than leaking the first generation's provenance. require.NoError(t, decoded.UnmarshalBinaryWithPrepareParamKinds(legacy, mp)) require.Equal(t, vector.PrepareParamNone, decoded.Vecs[0].GetPrepareParamKindAt(0)) + require.False(t, decoded.Vecs[0].GetIsBinaryString()) decoded.Clean(mp) } diff --git a/pkg/container/pSpool/copy.go b/pkg/container/pSpool/copy.go index 9571aad903896..0a5982e0643d8 100644 --- a/pkg/container/pSpool/copy.go +++ b/pkg/container/pSpool/copy.go @@ -165,6 +165,7 @@ func (cb *cachedBatch) GetCopiedBatch( dst.Vecs[i].SetGrouping(vec.GetGrouping()) } dst.Vecs[i].SetIsBin(vec.GetIsBin()) + dst.Vecs[i].SetIsBinaryString(vec.GetIsBinaryString()) if vec.IsConst() { // GetUnionAllFunction already propagates row provenance for the // non-constant path. Constants still need their scalar metadata diff --git a/pkg/container/vector/vector.go b/pkg/container/vector/vector.go index bc75d99210145..896c1c20f3b3c 100644 --- a/pkg/container/vector/vector.go +++ b/pkg/container/vector/vector.go @@ -105,6 +105,15 @@ type Vector struct { // whenever that sidecar is released, so reused vectors cannot retain a stale // MPool pointer across query generations. prepareParamKindsMP *mpool.MPool + // binaryString records byte-string semantics for dynamically typed values. + // Unlike isBin, it does not change numeric conversion into big-endian literal + // conversion and is therefore safe to preserve across local materialization. + binaryString bool + // binaryStringRows is allocated only when one vector mixes character and + // byte-string rows. Set bits identify byte-string rows; nil keeps the uniform + // scalar fast path. A bitmap limits the exceptional representation to one bit + // per row instead of adding a byte to every Vector row. + binaryStringRows *bitmap.Bitmap offHeap bool @@ -180,6 +189,9 @@ func (v *Vector) Reset(typ types.Type) { v.nsp.Clear() v.gsp.Clear() v.sorted = false + v.isBin = false + v.binaryString = false + v.binaryStringRows = nil v.areaDisjoint = true } @@ -193,6 +205,9 @@ func (v *Vector) ResetWithSameType() { v.nsp.Reset() v.gsp.Reset() v.sorted = false + v.isBin = false + v.binaryString = false + v.binaryStringRows = nil v.areaDisjoint = true } @@ -213,6 +228,9 @@ func (v *Vector) ResetWithNewType(t *types.Type) { v.gsp.Clear() v.length = 0 v.sorted = false + v.isBin = false + v.binaryString = false + v.binaryStringRows = nil v.areaDisjoint = true } @@ -239,9 +257,14 @@ func (v *Vector) Capacity() int { // Allocated returns the total allocated memory size of the vector. // it can be used to estimate the memory usage of the vector. func (v *Vector) Allocated() int { + binaryStringBytes := 0 + if v.binaryStringRows != nil { + binaryStringBytes = v.binaryStringRows.Size() + } return cap(v.data) + cap(v.area) + cap(v.prepareParamKinds)*int(unsafe.Sizeof(PrepareParamKind(0))) + + binaryStringBytes + 8*v.nsp.GetBitmap().ExternalStorageCapacity() + 8*v.gsp.GetBitmap().ExternalStorageCapacity() } @@ -256,7 +279,16 @@ func (v *Vector) SetLength(n int) { if err := v.preExtendPrepareParamKinds(n, nil); err != nil { panic(err) } + oldLength := v.length v.setLengthAfterExtend(n) + if v.binaryStringRows != nil { + if n > oldLength { + v.binaryStringRows.TryExpandWithSize(n) + } else if n < oldLength { + v.binaryStringRows.RemoveRange(uint64(n), uint64(oldLength)) + } + v.normalizeBinaryStringRows() + } } // AppendCheckpoint captures the logical state changed by append operations. @@ -269,6 +301,8 @@ type AppendCheckpoint struct { prepareParamKind PrepareParamKind prepareParamKindSeen bool hadPrepareParamKinds bool + binaryString bool + hadBinaryStringRows bool } func (v *Vector) MakeAppendCheckpoint() AppendCheckpoint { @@ -279,6 +313,8 @@ func (v *Vector) MakeAppendCheckpoint() AppendCheckpoint { prepareParamKind: v.prepareParamKind, prepareParamKindSeen: v.prepareParamKindSeen, hadPrepareParamKinds: v.prepareParamKinds != nil, + binaryString: v.binaryString, + hadBinaryStringRows: v.binaryStringRows != nil, } } @@ -308,12 +344,26 @@ func (v *Vector) RollbackAppend(checkpoint AppendCheckpoint, attemptedRows int) } v.prepareParamKind = checkpoint.prepareParamKind v.prepareParamKindSeen = checkpoint.prepareParamKindSeen + if checkpoint.hadBinaryStringRows { + if v.binaryStringRows == nil { + panic("binary-string sidecar lost during append rollback") + } + v.binaryStringRows.RemoveRange(uint64(checkpoint.length), uint64(end)) + v.binaryString = checkpoint.binaryString + } else { + v.binaryStringRows = nil + v.binaryString = checkpoint.binaryString + } } // Size of data, I think this function is inherently broken. This // Size is not meaningful other than used in (approximate) memory accounting. func (v *Vector) Size() int { - return v.length*v.typ.TypeSize() + len(v.area) + + binaryStringBytes := 0 + if v.binaryStringRows != nil { + binaryStringBytes = v.binaryStringRows.Size() + } + return v.length*v.typ.TypeSize() + len(v.area) + binaryStringBytes + len(v.prepareParamKinds)*int(unsafe.Sizeof(PrepareParamKind(0))) } @@ -621,6 +671,99 @@ func (v *Vector) SetPrepareParamKindsFromReader(r io.Reader, n int, mp *mpool.MP return nil } +// SetPrepareParamKindsAndBinaryStringFromReader restores the two row-exact +// provenance sidecars from one encoded byte per row. binaryMask is removed +// before validating the prepared-parameter kind. The temporary kind storage +// is MPool-owned and is collapsed immediately when all non-NULL rows agree. +func (v *Vector) SetPrepareParamKindsAndBinaryStringFromReader( + r io.Reader, + n int, + mp *mpool.MPool, + binaryMask byte, +) error { + if v == nil || r == nil { + return io.ErrClosedPipe + } + if n < 0 || n != v.length { + return moerr.NewInvalidInputNoCtxf( + "prepared parameter row count %d does not match vector length %d", n, v.length) + } + if binaryMask == 0 || binaryMask&(binaryMask-1) != 0 || + binaryMask <= byte(PrepareParamBoolean) { + return moerr.NewInvalidInputNoCtxf("invalid binary-string row mask %d", binaryMask) + } + if n == 0 { + v.resetPrepareParamKind() + v.SetIsBinaryString(false) + return nil + } + kinds, owner, err := v.allocatePrepareParamKinds(n, mp) + if err != nil { + return err + } + releaseKinds := func() { + if owner != nil { + mpool.FreeSlice(owner, kinds) + } + } + var binaryRows *bitmap.Bitmap + var one [1]byte + for row := range kinds { + if _, err = io.ReadFull(r, one[:]); err != nil { + releaseKinds() + return err + } + kind := PrepareParamKind(one[0] &^ binaryMask) + if kind > PrepareParamBoolean { + releaseKinds() + return moerr.NewInvalidInputNoCtxf( + "invalid prepared parameter row kind %d", kind) + } + kinds[row] = kind + if one[0]&binaryMask != 0 && !v.IsNull(uint64(row)) { + if binaryRows == nil { + binaryRows = &bitmap.Bitmap{} + binaryRows.InitWithSize(int64(n)) + } + binaryRows.Add(uint64(row)) + } + } + + var first PrepareParamKind + seen := false + mixed := false + for row, kind := range kinds { + if v.IsNull(uint64(row)) { + continue + } + if !seen { + first, seen = kind, true + } else if first != kind { + mixed = true + } + } + v.releasePrepareParamKinds() + switch { + case !seen: + releaseKinds() + v.prepareParamKind = PrepareParamNone + v.prepareParamKindSeen = false + case !mixed: + releaseKinds() + v.prepareParamKind = first + v.prepareParamKindSeen = true + default: + v.prepareParamKind = PrepareParamNone + v.prepareParamKindSeen = true + v.prepareParamKinds = kinds + v.prepareParamKindsMP = owner + } + v.binaryString = false + v.binaryStringRows = binaryRows + v.normalizeBinaryStringRows() + return nil +} + // SetPrepareParamKindAt updates one logical row and promotes a scalar vector // to the sidecar representation only when that row conflicts with the scalar // category. It is intended for data movers that already copied the row. @@ -1560,6 +1703,218 @@ func (v *Vector) clearPrepareParamKindAt(row int) { } } +func (v *Vector) GetIsBinaryString() bool { + return v.binaryString +} + +func (v *Vector) HasBinaryStringRows() bool { + return v != nil && v.binaryStringRows != nil +} + +func (v *Vector) SetIsBinaryString(binaryString bool) { + v.binaryString = binaryString + v.binaryStringRows = nil +} + +// GetIsBinaryStringAt returns the selected value's string semantics. Constants +// have one physical value, while mixed flat vectors consult the optional bitmap. +func (v *Vector) GetIsBinaryStringAt(row int) bool { + if v == nil { + return false + } + if v.IsConst() { + row = 0 + } + if row < 0 || row >= v.length || v.IsNull(uint64(row)) { + return false + } + switch v.typ.Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + return true + } + if v.binaryStringRows != nil { + return v.binaryStringRows.Contains(uint64(row)) + } + return v.binaryString +} + +// SetIsBinaryStringAt records row-exact provenance and allocates the bitmap +// only when the new row disagrees with the uniform representation. +func (v *Vector) SetIsBinaryStringAt(row int, binaryString bool) { + if v == nil || row < 0 || v.length == 0 { + return + } + if v.IsConst() { + row = 0 + } + if row >= v.length || v.IsNull(uint64(row)) { + return + } + if v.binaryStringRows == nil { + if v.binaryString == binaryString { + return + } + v.binaryStringRows = &bitmap.Bitmap{} + v.binaryStringRows.InitWithSize(int64(v.length)) + if v.binaryString { + v.binaryStringRows.AddRange(0, uint64(v.length)) + nullsIterator := v.nsp.GetBitmap().Iterator() + for nullsIterator.HasNext() { + v.binaryStringRows.Remove(nullsIterator.Next()) + } + } + } else if v.binaryStringRows.Len() < int64(v.length) { + v.binaryStringRows.TryExpandWithSize(v.length) + } + if binaryString { + v.binaryStringRows.Add(uint64(row)) + } else { + v.binaryStringRows.Remove(uint64(row)) + } + v.normalizeBinaryStringRows() +} + +// SetBinaryStringRows installs row-exact provenance, collapsing uniform input +// back to the scalar representation. +func (v *Vector) SetBinaryStringRows(rows []bool) error { + if len(rows) != v.length { + return moerr.NewInvalidInputNoCtxf( + "binary-string row count %d does not match vector length %d", len(rows), v.length) + } + v.binaryStringRows = &bitmap.Bitmap{} + v.binaryStringRows.InitWithSize(int64(v.length)) + for row, binaryString := range rows { + if binaryString && !v.IsNull(uint64(row)) { + v.binaryStringRows.Add(uint64(row)) + } + } + v.normalizeBinaryStringRows() + return nil +} + +func (v *Vector) normalizeBinaryStringRows() { + if v.binaryStringRows == nil { + return + } + // Both bitmaps maintain their population count incrementally, so row-wise + // result construction remains O(n) instead of rescanning the vector after + // every SetIsBinaryStringAt call. + nonNull := v.length - v.nsp.GetBitmap().Count() + count := v.binaryStringRows.Count() + switch { + case count == 0: + v.binaryString = false + v.binaryStringRows = nil + case count == nonNull: + v.binaryString = true + v.binaryStringRows = nil + default: + // GetIsBinaryString remains a conservative summary for legacy callers. + v.binaryString = true + } +} + +func (v *Vector) clearBinaryStringAt(row int) { + if v.binaryStringRows != nil { + v.binaryStringRows.Remove(uint64(row)) + v.normalizeBinaryStringRows() + } else if v.AllNull() { + v.binaryString = false + } +} + +func (v *Vector) remapBinaryStringRows(sels []int64) { + if v.binaryStringRows == nil { + return + } + original := v.binaryStringRows + remapped := &bitmap.Bitmap{} + remapped.InitWithSize(int64(len(sels))) + for destination, source := range sels { + if source >= 0 && source < original.Len() && original.Contains(uint64(source)) { + remapped.Add(uint64(destination)) + } + } + v.binaryStringRows = remapped + v.normalizeBinaryStringRows() +} + +func (v *Vector) copyBinaryStringTo(dst *Vector) { + dst.binaryString = v.binaryString + if v.binaryStringRows == nil { + dst.binaryStringRows = nil + return + } + dst.binaryStringRows = v.binaryStringRows.Clone() +} + +func (v *Vector) copyBinaryStringWindowTo(dst *Vector, start, end int) { + if v.binaryStringRows == nil { + dst.binaryString = v.binaryString + dst.binaryStringRows = nil + return + } + if start == end { + dst.binaryString = v.binaryString + dst.binaryStringRows = nil + return + } + rows := &bitmap.Bitmap{} + rows.InitWithSize(int64(end - start)) + nonNull := 0 + for row := start; row < end; row++ { + if v.IsNull(uint64(row)) { + continue + } + nonNull++ + if v.binaryStringRows.Contains(uint64(row)) { + rows.Add(uint64(row - start)) + } + } + switch rows.Count() { + case 0: + dst.binaryString = false + dst.binaryStringRows = nil + case nonNull: + dst.binaryString = true + dst.binaryStringRows = nil + default: + dst.binaryString = true + dst.binaryStringRows = rows + } +} + +func (v *Vector) propagateBinaryStringAll(w *Vector, oldLength int) { + for row := 0; row < w.length; row++ { + if !w.IsNull(uint64(row)) { + v.SetIsBinaryStringAt(oldLength+row, w.GetIsBinaryStringAt(row)) + } + } +} + +func propagateBinaryStringSelection[T int32 | int64](v, w *Vector, oldLength int, sels []T) { + for output, selected := range sels { + row := int(selected) + if !w.IsNull(uint64(row)) { + v.SetIsBinaryStringAt(oldLength+output, w.GetIsBinaryStringAt(row)) + } + } +} + +func (v *Vector) propagateBinaryStringBatch(w *Vector, oldLength int, offset int64, cnt int, flags []uint8) { + output := oldLength + for i := 0; i < cnt; i++ { + if flags != nil && flags[i] == 0 { + continue + } + row := int(offset) + i + if !w.IsNull(uint64(row)) { + v.SetIsBinaryStringAt(output, w.GetIsBinaryStringAt(row)) + } + output++ + } +} + func (v *Vector) NeedDup() bool { return v.cantFreeArea || v.cantFreeData } @@ -2043,6 +2398,8 @@ func (v *Vector) Free(mp *mpool.MPool) { v.gsp.Reset() v.sorted = false v.isBin = false + v.binaryString = false + v.binaryStringRows = nil v.resetPrepareParamKind() v.prepareParamKindsMP = nil v.allocationAccount = nil @@ -2907,6 +3264,7 @@ func (v *Vector) dup( w.class = v.class w.typ = v.typ w.sorted = v.sorted + v.copyBinaryStringTo(w) if err := v.copyPrepareParamKindToWithMP(w, mp); err != nil { w.Free(mp) return nil, err @@ -3025,6 +3383,7 @@ func (v *Vector) cloneToFlatCompact( } return w, nil } + v.copyBinaryStringTo(w) if v.length == 0 { if err := v.copyPrepareParamKindToWithMP(w, mp); err != nil { @@ -3194,6 +3553,10 @@ func (v *Vector) Shrink(sels []int64, negate bool) { panic(fmt.Sprintf("unexpect type %s for function vector.Shrink", v.typ)) } v.remapPrepareParamKindsAfterShrink(oldKinds, oldLength, sels, negate) + if v.binaryStringRows != nil { + v.binaryStringRows.RemapOrdered(sels, negate) + v.normalizeBinaryStringRows() + } } func (v *Vector) ShrinkByMask(sels *bitmap.Bitmap, negate bool, offset uint64) { @@ -3272,6 +3635,19 @@ func (v *Vector) ShrinkByMask(sels *bitmap.Bitmap, negate bool, offset uint64) { panic(fmt.Sprintf("unexpect type %s for function vector.Shrink", v.typ)) } v.remapPrepareParamKindsAfterShrinkMask(oldKinds, oldLength, sels, negate, offset) + if v.binaryStringRows != nil { + if offset == 0 { + v.binaryStringRows.RemapMaskOrdered(sels, negate) + } else { + selected := make([]int64, 0, sels.Count()) + iterator := sels.Iterator() + for iterator.HasNext() { + selected = append(selected, int64(iterator.Next()+offset)) + } + v.binaryStringRows.RemapOrdered(selected, negate) + } + v.normalizeBinaryStringRows() + } } func (v *Vector) remapPrepareParamKindsAfterShrink( @@ -3515,6 +3891,7 @@ func (v *Vector) Shuffle(sels []int64, mp *mpool.MPool) (err error) { return err } v.remapPrepareParamKindsAfterShuffle(oldKinds, sels, preparedKinds, preparedOwner) + v.remapBinaryStringRows(sels) return err } @@ -3601,6 +3978,7 @@ func (v *Vector) ShuffleWithBuf(sels []int64, mp *mpool.MPool, buf *[]byte) (err if err == nil { v.remapPrepareParamKindsAfterShuffle(oldKinds, sels, nil, nil) + v.remapBinaryStringRows(sels) } return err } @@ -3648,6 +4026,7 @@ func (v *Vector) Copy(w *Vector, vi, wi int64, mp *mpool.MPool) error { if v.AllNull() { v.resetPrepareParamKind() } + v.clearBinaryStringAt(int(vi)) return nil } // Non-null constant vectors still share the regular null/data path below. @@ -3663,6 +4042,7 @@ func (v *Vector) Copy(w *Vector, vi, wi int64, mp *mpool.MPool) error { if v.AllNull() { v.resetPrepareParamKind() } + v.clearBinaryStringAt(int(vi)) return nil } if v.typ.IsFixedLen() { @@ -3694,6 +4074,7 @@ func (v *Vector) Copy(w *Vector, vi, wi int64, mp *mpool.MPool) error { if err := v.mergePrepareParamKindAt(int(vi), kind, true, destinationHasValue, mp); err != nil { return err } + v.SetIsBinaryStringAt(int(vi), w.GetIsBinaryStringAt(int(wi))) } return nil } @@ -3747,6 +4128,7 @@ func GetUnionAllFunction(typ types.Type, mp *mpool.MPool) func(v, w *Vector) err if err := v.propagatePrepareParamKindsAll(w, oldLength, mp); err != nil { return err } + v.propagateBinaryStringAll(w, oldLength) return nil } } @@ -5025,6 +5407,7 @@ func (v *Vector) UnionOne(w *Vector, sel int64, mp *mpool.MPool) error { if err := v.appendPrepareParamKindAt(oldLen, w.GetPrepareParamKindAt(int(sel)), mp); err != nil { return err } + v.SetIsBinaryStringAt(oldLen, w.GetIsBinaryStringAt(int(sel))) } return nil } @@ -5213,6 +5596,7 @@ func unionT[T int32 | int64](v, w *Vector, sels []T, mp *mpool.MPool) error { if err := propagatePrepareParamKindsSelection(v, w, oldLen, sels, mp); err != nil { return err } + propagateBinaryStringSelection(v, w, oldLen, sels) return nil } appendSelectedGrouping(v, w, oldLen, sels) @@ -5306,6 +5690,7 @@ func unionT[T int32 | int64](v, w *Vector, sels []T, mp *mpool.MPool) error { if err := propagatePrepareParamKindsSelection(v, w, oldLen, sels, mp); err != nil { return err } + propagateBinaryStringSelection(v, w, oldLen, sels) return nil } @@ -5369,6 +5754,7 @@ func (v *Vector) UnionBatch(w *Vector, offset int64, cnt int, flags []uint8, mp if err := v.propagatePrepareParamKindsBatch(w, oldLen, offset, cnt, flags, mp); err != nil { return err } + v.propagateBinaryStringBatch(w, oldLen, offset, cnt, flags) return nil } appendBatchGrouping(v, w, v.length, offset, cnt, flags) @@ -5443,6 +5829,7 @@ func (v *Vector) UnionBatch(w *Vector, offset int64, cnt int, flags []uint8, mp if err := v.propagatePrepareParamKindsBatch(w, oldLen, offset, cnt, flags, mp); err != nil { return err } + v.propagateBinaryStringBatch(w, oldLen, offset, cnt, flags) return nil } @@ -5589,6 +5976,7 @@ func (v *Vector) UnionBatch(w *Vector, offset int64, cnt int, flags []uint8, mp if err := v.propagatePrepareParamKindsBatch(w, oldLen, offset, cnt, flags, mp); err != nil { return err } + v.propagateBinaryStringBatch(w, oldLen, offset, cnt, flags) return nil } @@ -6856,6 +7244,7 @@ func (v *Vector) window( w.class = v.class w.length = end - start w.sorted = v.sorted + v.copyBinaryStringWindowTo(w, start, end) if v.prepareParamKinds != nil { if err := v.copyPrepareParamKindWindowToWithMP(w, start, end, mp); err != nil { w.Free(mp) @@ -6932,6 +7321,7 @@ func (v *Vector) CloneWindowWithAllocation( ) (*Vector, error) { if start == end { w := NewOffHeapVecWithType(v.typ) + w.binaryString = v.binaryString if selection != nil { if err := w.SetAllocationAccount(selection); err != nil { return nil, err @@ -6958,6 +7348,7 @@ func (v *Vector) CloneWindowWithAllocation( } func (v *Vector) CloneWindowTo(w *Vector, start, end int, mp *mpool.MPool) error { + v.copyBinaryStringWindowTo(w, start, end) if start == end { w.resetPrepareParamKind() return nil diff --git a/pkg/container/vector/vector_test.go b/pkg/container/vector/vector_test.go index 1a135ca18799b..eebafc2b84484 100644 --- a/pkg/container/vector/vector_test.go +++ b/pkg/container/vector/vector_test.go @@ -1696,6 +1696,133 @@ func TestCloneWindow(t *testing.T) { require.Equal(t, payload, v5.GetBytesAt(0)) } +func TestBinaryStringMetadataSurvivesPublicCopies(t *testing.T) { + mp := mpool.MustNewZero() + source := NewVec(types.T_text.ToType()) + require.NoError(t, AppendBytes(source, []byte{0xe4, 0xbd, 0xa0, 0xff}, false, mp)) + source.SetIsBinaryString(true) + t.Cleanup(func() { + source.Free(mp) + require.Equal(t, int64(0), mp.CurrNB()) + }) + + dup, err := source.Dup(mp) + require.NoError(t, err) + require.True(t, dup.GetIsBinaryString()) + dup.Free(mp) + + window, err := source.Window(0, 1) + require.NoError(t, err) + require.True(t, window.GetIsBinaryString()) + window.Free(mp) + + cloneWindow, err := source.CloneWindow(0, 1, mp) + require.NoError(t, err) + require.True(t, cloneWindow.GetIsBinaryString()) + cloneWindow.Free(mp) + + cloneTo := NewVec(types.T_text.ToType()) + require.NoError(t, source.CloneWindowTo(cloneTo, 0, 1, mp)) + require.True(t, cloneTo.GetIsBinaryString()) + cloneTo.Free(mp) + + compact, err := source.CloneToFlatCompact(mp) + require.NoError(t, err) + require.True(t, compact.GetIsBinaryString()) + compact.Free(mp) +} + +func TestMixedBinaryStringMetadataSurvivesMaterialization(t *testing.T) { + mp := mpool.MustNewZero() + source := NewVec(types.T_text.ToType()) + for _, value := range []string{"a", "你", "b"} { + require.NoError(t, AppendBytes(source, []byte(value), false, mp)) + } + source.SetIsBinaryStringAt(0, true) + source.SetIsBinaryStringAt(2, true) + t.Cleanup(func() { + source.Free(mp) + require.Equal(t, int64(0), mp.CurrNB()) + }) + + assertRows := func(t *testing.T, vec *Vector, want []bool) { + t.Helper() + for row, expected := range want { + require.Equal(t, expected, vec.GetIsBinaryStringAt(row), "row %d", row) + } + } + assertRows(t, source, []bool{true, false, true}) + + dup, err := source.Dup(mp) + require.NoError(t, err) + assertRows(t, dup, []bool{true, false, true}) + dup.Free(mp) + + window, err := source.Window(1, 3) + require.NoError(t, err) + assertRows(t, window, []bool{false, true}) + window.Free(mp) + + cloneWindow, err := source.CloneWindow(1, 3, mp) + require.NoError(t, err) + assertRows(t, cloneWindow, []bool{false, true}) + cloneWindow.Free(mp) + + cloneTo := NewVec(types.T_text.ToType()) + require.NoError(t, source.CloneWindowTo(cloneTo, 1, 3, mp)) + assertRows(t, cloneTo, []bool{false, true}) + cloneTo.Free(mp) + + destination := NewVec(types.T_text.ToType()) + require.NoError(t, destination.UnionBatch(source, 0, source.Length(), nil, mp)) + assertRows(t, destination, []bool{true, false, true}) + destination.Free(mp) + + shrunk, err := source.Dup(mp) + require.NoError(t, err) + shrunk.Shrink([]int64{1, 2}, false) + assertRows(t, shrunk, []bool{false, true}) + shrunk.Free(mp) + + shuffled, err := source.Dup(mp) + require.NoError(t, err) + require.NoError(t, shuffled.Shuffle([]int64{2, 1, 0}, mp)) + assertRows(t, shuffled, []bool{true, false, true}) + shuffled.Free(mp) + + copied, err := source.Dup(mp) + require.NoError(t, err) + require.NoError(t, copied.Copy(source, 0, 1, mp)) + assertRows(t, copied, []bool{false, false, true}) + copied.Free(mp) + + staticBinary := NewVec(types.T_varbinary.ToType()) + require.NoError(t, AppendBytes(staticBinary, nil, true, mp)) + require.False(t, staticBinary.GetIsBinaryStringAt(0), "NULL rows have no selected-value provenance") + staticBinary.Free(mp) + + nullable := NewVec(types.T_text.ToType()) + require.NoError(t, AppendBytes(nullable, []byte("a"), false, mp)) + require.NoError(t, AppendBytes(nullable, nil, true, mp)) + require.NoError(t, AppendBytes(nullable, []byte("b"), false, mp)) + nullable.SetIsBinaryString(true) + nullable.SetIsBinaryStringAt(0, false) + nullable.SetIsBinaryStringAt(2, false) + require.False(t, nullable.GetIsBinaryString()) + require.False(t, nullable.HasBinaryStringRows()) + nullable.Free(mp) + + rollback, err := source.Dup(mp) + require.NoError(t, err) + checkpoint := rollback.MakeAppendCheckpoint() + require.NoError(t, AppendBytes(rollback, []byte("c"), false, mp)) + rollback.SetIsBinaryStringAt(3, true) + rollback.RollbackAppend(checkpoint, 1) + assertRows(t, rollback, []bool{true, false, true}) + require.Equal(t, 3, rollback.Length()) + rollback.Free(mp) +} + func TestCloneWindowWithMpNil(t *testing.T) { mp := mpool.MustNewZero() vec1 := NewVec(types.T_int32.ToType()) @@ -4260,6 +4387,45 @@ func TestSetPrepareParamKindsFromReaderCollapsesUniformAndNullRows(t *testing.T) } } +func TestSetPrepareParamKindsAndBinaryStringFromReader(t *testing.T) { + mp := mpool.MustNewZero() + vec := makePrepareParamKindReaderVector(t, mp, 3) + defer func() { + vec.Free(mp) + require.Zero(t, mp.CurrNB()) + }() + + require.NoError(t, vec.SetPrepareParamKindsAndBinaryStringFromReader( + bytes.NewReader([]byte{ + byte(PrepareParamInteger) | 0x80, + byte(PrepareParamFloat), + byte(PrepareParamNone) | 0x80, + }), + 3, + mp, + 0x80, + )) + require.Equal(t, PrepareParamInteger, vec.GetPrepareParamKindAt(0)) + require.Equal(t, PrepareParamFloat, vec.GetPrepareParamKindAt(1)) + require.Equal(t, PrepareParamNone, vec.GetPrepareParamKindAt(2)) + require.True(t, vec.GetIsBinaryStringAt(0)) + require.False(t, vec.GetIsBinaryStringAt(1)) + require.True(t, vec.GetIsBinaryStringAt(2)) + + before := mp.CurrNB() + err := vec.SetPrepareParamKindsAndBinaryStringFromReader( + bytes.NewReader([]byte{byte(PrepareParamDecimal) | 0x80}), + 3, + mp, + 0x80, + ) + require.ErrorIs(t, err, io.EOF) + require.Equal(t, before, mp.CurrNB(), "a failed generation must release its temporary MPool slice") + // The last complete generation remains available after a truncated frame. + require.Equal(t, PrepareParamFloat, vec.GetPrepareParamKindAt(1)) + require.True(t, vec.GetIsBinaryStringAt(2)) +} + func TestSetPrepareParamKindsFromReaderErrorsReleaseTemporarySidecar(t *testing.T) { tests := []struct { name string diff --git a/pkg/defines/const.go b/pkg/defines/const.go index 8edeefc1416cc..6bb9c8340e7c6 100644 --- a/pkg/defines/const.go +++ b/pkg/defines/const.go @@ -49,7 +49,8 @@ const ( MORPCVersion11 int64 = 11 // bounded Sorted64 membership-filter wire format MORPCVersion12 int64 = 12 // prepared-parameter provenance in remote process metadata and aggregate trailers MORPCVersion13 int64 = 13 // lossless v2 prefix-index metadata - MORPCLatestVersion = MORPCVersion13 + MORPCVersion14 int64 = 14 // prepared-parameter binary-string metadata + MORPCLatestVersion = MORPCVersion14 ) // DefaultLockWaitTimeoutSeconds is shared by the frontend default and by diff --git a/pkg/frontend/back_exec.go b/pkg/frontend/back_exec.go index ace3040a79f2a..ef61d0d2c3c6a 100644 --- a/pkg/frontend/back_exec.go +++ b/pkg/frontend/back_exec.go @@ -413,6 +413,7 @@ func doComQueryInBack( proc.SetStmtProfile(&backSes.stmtProfile) proc.SetResolveVariableFunc(backSes.txnCompileCtx.ResolveVariable) proc.SetResolveVariableIsBinFunc(backSes.txnCompileCtx.ResolveVariableIsBin) + proc.SetResolveVariableBinaryStringFunc(backSes.txnCompileCtx.ResolveVariableBinaryString) proc.SetResolveVariablePrepareParamKindFunc(backSes.txnCompileCtx.ResolveVariablePrepareParamKind) // backExec.Exec and ExecRestore reject multi-statement SQL before reaching // this path, so one snapshot here covers the complete background statement. diff --git a/pkg/frontend/compiler_context.go b/pkg/frontend/compiler_context.go index 5edb11897eb88..920dd8754fbf6 100644 --- a/pkg/frontend/compiler_context.go +++ b/pkg/frontend/compiler_context.go @@ -874,6 +874,20 @@ func (tcc *TxnCompilerContext) ResolveVariableIsBin(varName string, isSystemVar, return udVar.IsBin, nil } +func (tcc *TxnCompilerContext) ResolveVariableBinaryString(varName string, isSystemVar, _ bool) (bool, error) { + if _, ok := resolveStoredProcedureVariable(tcc.execCtx.reqCtx, varName); ok { + return false, nil + } + if isSystemVar { + return false, nil + } + udVar, err := tcc.GetSession().GetUserDefinedVar(varName) + if err != nil { + return false, err + } + return udVar.BinaryString, nil +} + func (tcc *TxnCompilerContext) ResolveVariablePrepareParamKind( varName string, isSystemVar, isGlobalVar bool, diff --git a/pkg/frontend/computation_wrapper.go b/pkg/frontend/computation_wrapper.go index 54a6681797918..24a4a283447fc 100644 --- a/pkg/frontend/computation_wrapper.go +++ b/pkg/frontend/computation_wrapper.go @@ -905,9 +905,11 @@ func initExecuteStmtParamWithResolverInSession( } } if kinds == nil { - cwft.proc.SetPrepareParams(prepareStmt.params) + cwft.proc.SetPrepareParamsWithMetadata( + prepareStmt.params, nil, prepareStmt.paramsBinaryString) } else { - cwft.proc.SetPrepareParamsWithMeta(prepareStmt.params, nil, kinds) + cwft.proc.SetPrepareParamsWithMeta( + prepareStmt.params, nil, kinds, prepareStmt.paramsBinaryString) } cwft.paramVals, err = preparedParamValues(cwft.proc) if err != nil { @@ -917,11 +919,11 @@ func initExecuteStmtParamWithResolverInSession( if len(execPlan.Args) != numParams { return nil, nil, nil, originSQL, false, moerr.NewInvalidInput(reqCtx, "Incorrect arguments to EXECUTE") } - params, paramVals, paramIsBin, paramKinds, err := buildExecuteUserParams(cwft.proc, execPlan.Args) + params, paramVals, paramIsBin, paramKinds, paramBinaryString, err := buildExecuteUserParams(cwft.proc, execPlan.Args) if err != nil { return nil, nil, nil, originSQL, false, err } - cwft.proc.SetOwnedPrepareParamsWithMeta(params, paramIsBin, paramKinds) + cwft.proc.SetOwnedPrepareParamsWithMeta(params, paramIsBin, paramKinds, paramBinaryString) cwft.paramVals = paramVals } else { if numParams > 0 { @@ -1075,7 +1077,11 @@ func preparedParamValues(proc *process.Process) ([]any, error) { if err != nil { return nil, err } - values[i] = plan2.ParamValue{Value: string(raw), IsBin: proc.GetPrepareParamIsBin(i)} + values[i] = plan2.ParamValue{ + Value: string(raw), + IsBin: proc.GetPrepareParamIsBin(i), + BinaryString: proc.GetPrepareParamIsBinaryString(i), + } } return values, nil } @@ -1088,6 +1094,7 @@ func buildExecuteUserParams( paramVals []any, paramIsBin []bool, paramKinds []vector.PrepareParamKind, + paramBinaryString []bool, err error, ) { params = vector.NewVec(types.T_text.ToType()) @@ -1099,6 +1106,7 @@ func buildExecuteUserParams( paramVals = make([]any, len(args)) paramIsBin = make([]bool, len(args)) paramKinds = make([]vector.PrepareParamKind, len(args)) + paramBinaryString = make([]bool, len(args)) for i, arg := range args { exprImpl := arg.Expr.(*plan.Expr_V) var param any @@ -1122,11 +1130,20 @@ func buildExecuteUserParams( } else { paramKinds[i] = prepareParamKindFromValue(param) } + resolveBinaryString := proc.GetResolveVariableBinaryStringFunc() + if resolveBinaryString != nil { + paramBinaryString[i], err = resolveBinaryString(exprImpl.V.Name, exprImpl.V.System, exprImpl.V.Global) + if err != nil { + return + } + } err = util.AppendAnyToStringVector(proc, param, params) if err != nil { return } - paramVals[i] = plan2.ParamValue{Value: param, IsBin: paramIsBin[i]} + paramVals[i] = plan2.ParamValue{ + Value: param, IsBin: paramIsBin[i], BinaryString: paramBinaryString[i], + } } return } diff --git a/pkg/frontend/computation_wrapper_test.go b/pkg/frontend/computation_wrapper_test.go index 40e74a7d19e4b..65b0bc80f5fe9 100644 --- a/pkg/frontend/computation_wrapper_test.go +++ b/pkg/frontend/computation_wrapper_test.go @@ -171,19 +171,23 @@ func newPreparedExecuteEnvForSQL(t testing.TB, stmtID uint32, sql string) (*Sess ses.GetTxnCompileCtx().SetExecCtx(execCtx) proc.SetResolveVariableFunc(ses.txnCompileCtx.ResolveVariable) proc.SetResolveVariableIsBinFunc(ses.txnCompileCtx.ResolveVariableIsBin) + proc.SetResolveVariableBinaryStringFunc(ses.txnCompileCtx.ResolveVariableBinaryString) proc.SetResolveVariablePrepareParamKindFunc(ses.txnCompileCtx.ResolveVariablePrepareParamKind) return ses, prepareStmt, cw, execCtx } -func TestInitExecuteStmtParamPreservesBinaryFlagPerUserVariable(t *testing.T) { +func TestInitExecuteStmtParamSeparatesBinaryStringFromLiteralNumericMetadata(t *testing.T) { ses, prepareStmt, cw, execCtx := newPreparedExecuteEnvForSQL(t, 102, "select ?, ?") defer prepareStmt.Close() - require.NoError(t, ses.setUserDefinedVar("binary_param", "AB\x00\x00", "", true)) + require.NoError(t, ses.setUserDefinedVar("binary_param", "AB\x00\x00", "", false, true)) require.NoError(t, ses.SetUserDefinedVar("text_param", "text", "")) isBin, err := ses.txnCompileCtx.ResolveVariableIsBin("binary_param", false, false) require.NoError(t, err) - require.True(t, isBin) + require.False(t, isBin) + binaryString, err := ses.txnCompileCtx.ResolveVariableBinaryString("binary_param", false, false) + require.NoError(t, err) + require.True(t, binaryString) isBin, err = ses.txnCompileCtx.ResolveVariableIsBin("text_param", false, false) require.NoError(t, err) require.False(t, isBin) @@ -209,9 +213,11 @@ func TestInitExecuteStmtParamPreservesBinaryFlagPerUserVariable(t *testing.T) { _, _, _, _, _, err = initExecuteStmtParam(execCtx, ses, cw, execPlan, "") require.NoError(t, err) - require.True(t, cw.proc.GetPrepareParamIsBin(0)) + require.False(t, cw.proc.GetPrepareParamIsBin(0)) require.False(t, cw.proc.GetPrepareParamIsBin(1)) - require.Equal(t, plan2.ParamValue{Value: "AB\x00\x00", IsBin: true}, cw.paramVals[0]) + require.True(t, cw.proc.GetPrepareParamIsBinaryString(0)) + require.False(t, cw.proc.GetPrepareParamIsBinaryString(1)) + require.Equal(t, plan2.ParamValue{Value: "AB\x00\x00", BinaryString: true}, cw.paramVals[0]) require.Equal(t, plan2.ParamValue{Value: "text", IsBin: false}, cw.paramVals[1]) params := cw.proc.GetPrepareParams() @@ -221,6 +227,7 @@ func TestInitExecuteStmtParamPreservesBinaryFlagPerUserVariable(t *testing.T) { require.Zero(t, params.Length(), "the previous owned params must be released on successful replacement") require.Nil(t, params.GetData()) require.False(t, cw.proc.GetPrepareParamIsBin(0)) + require.False(t, cw.proc.GetPrepareParamIsBinaryString(0)) require.Equal(t, "now-text", cw.proc.GetPrepareParams().GetStringAt(0)) current := cw.proc.GetPrepareParams() @@ -228,6 +235,7 @@ func TestInitExecuteStmtParamPreservesBinaryFlagPerUserVariable(t *testing.T) { require.Zero(t, current.Length()) require.Nil(t, current.GetData()) require.False(t, cw.proc.GetPrepareParamIsBin(0), "binary metadata must not leak into the next execution") + require.False(t, cw.proc.GetPrepareParamIsBinaryString(0), "binary string metadata must not leak into the next execution") cw.proc.GetPrepareParams().Free(cw.proc.Mp()) cw.proc.SetPrepareParams(nil) } @@ -389,6 +397,24 @@ func TestPreparedSetExpressionParamsAfterInit(t *testing.T) { require.Same(t, second, prepareStmt.params) } +func TestBinaryProtocolPrepareMetadataInstalledOnProcess(t *testing.T) { + ses, prepareStmt, cw, execCtx := newPreparedExecuteEnvForSQL( + t, 109, "select char_length(?)") + defer prepareStmt.Close() + + params := vector.NewVec(types.T_text.ToType()) + require.NoError(t, vector.AppendBytes(params, []byte{0xe4, 0xbd, 0xa0}, false, cw.proc.Mp())) + prepareStmt.params = params + prepareStmt.paramsBinaryString = []bool{true} + + _, _, _, _, _, err := initExecuteStmtParam( + execCtx, ses, cw, nil, prepareStmt.Name) + require.NoError(t, err) + require.True(t, cw.proc.GetPrepareParamIsBinaryString(0)) + + cw.proc.SetPrepareParams(nil) +} + func TestInitExecuteStmtParamFreesParamsOnResolveError(t *testing.T) { ses, prepareStmt, cw, _ := newPreparedExecuteEnvForSQL(t, 103, "select ?, ?") defer prepareStmt.Close() @@ -410,7 +436,7 @@ func TestInitExecuteStmtParamFreesParamsOnResolveError(t *testing.T) { {Expr: &plan.Expr_V{V: &plan.VarRef{Name: "second"}}}, }, } - params, _, _, _, err := buildExecuteUserParams(cw.proc, execPlan.Args) + params, _, _, _, _, err := buildExecuteUserParams(cw.proc, execPlan.Args) require.ErrorIs(t, err, assert.AnError) require.Zero(t, params.Length()) require.Nil(t, params.GetData()) @@ -420,8 +446,8 @@ func TestInitExecuteStmtParamFreesParamsOnResolveError(t *testing.T) { func TestResolveVariableIsBinHonorsStoredProcedureScope(t *testing.T) { ses, prepareStmt, _, execCtx := newPreparedExecuteEnv(t, 104) defer prepareStmt.Close() - require.NoError(t, ses.setUserDefinedVar("v1", "session-binary", "", true)) - require.NoError(t, ses.setUserDefinedVar("session_only", "session-binary", "", true)) + require.NoError(t, ses.setUserDefinedVar("v1", "session-binary", "", true, false)) + require.NoError(t, ses.setUserDefinedVar("session_only", "session-binary", "", true, false)) scopes := []map[string]interface{}{ {"v1": int64(10), "declared_only_outer": "5.0"}, { @@ -484,8 +510,8 @@ func TestResolveVariableIsBinHonorsStoredProcedureScope(t *testing.T) { func TestBuildExecuteUserParamsHonorsStoredProcedureScope(t *testing.T) { ses, prepareStmt, cw, execCtx := newPreparedExecuteEnv(t, 105) defer prepareStmt.Close() - require.NoError(t, ses.setUserDefinedVar("local_shadow", "session-binary", "", true)) - require.NoError(t, ses.setUserDefinedVar("session_only", "session-binary", "", true)) + require.NoError(t, ses.setUserDefinedVar("local_shadow", "session-binary", "", false, true)) + require.NoError(t, ses.setUserDefinedVar("session_only", "session-binary", "", false, true)) scopes := []map[string]interface{}{ {"local_only": int64(10), "local_shadow": int64(20)}, } @@ -497,11 +523,12 @@ func TestBuildExecuteUserParamsHonorsStoredProcedureScope(t *testing.T) { {Expr: &plan.Expr_V{V: &plan.VarRef{Name: "local_shadow"}}}, {Expr: &plan.Expr_V{V: &plan.VarRef{Name: "session_only"}}}, } - params, paramVals, paramIsBin, paramKinds, err := buildExecuteUserParams(cw.proc, args) + params, paramVals, paramIsBin, paramKinds, paramBinaryString, err := buildExecuteUserParams(cw.proc, args) require.NoError(t, err) defer params.Free(cw.proc.Mp()) - require.Equal(t, []bool{false, false, true}, paramIsBin) + require.Equal(t, []bool{false, false, false}, paramIsBin) + require.Equal(t, []bool{false, false, true}, paramBinaryString) require.Equal(t, []vector.PrepareParamKind{ vector.PrepareParamInteger, vector.PrepareParamInteger, @@ -510,7 +537,7 @@ func TestBuildExecuteUserParamsHonorsStoredProcedureScope(t *testing.T) { require.Equal(t, []any{ plan2.ParamValue{Value: int64(10), IsBin: false}, plan2.ParamValue{Value: int64(20), IsBin: false}, - plan2.ParamValue{Value: "session-binary", IsBin: true}, + plan2.ParamValue{Value: "session-binary", BinaryString: true}, }, paramVals) require.Equal(t, "10", params.GetStringAt(0)) require.Equal(t, "20", params.GetStringAt(1)) diff --git a/pkg/frontend/mysql_cmd_executor.go b/pkg/frontend/mysql_cmd_executor.go index 4ff34ce00d419..fd91492368bc5 100644 --- a/pkg/frontend/mysql_cmd_executor.go +++ b/pkg/frontend/mysql_cmd_executor.go @@ -928,18 +928,21 @@ func doSetVar( var err error = nil var ok bool var userVarIsBin bool + var userVarBinaryString bool var userVarPrepareParamKind vector.PrepareParamKind type evaluatedAssignment struct { assign *tree.VarAssignmentExpr value interface{} userVarIsBin bool + userVarBinaryString bool userVarPrepareParamKind vector.PrepareParamKind } evaluateAssignment := func(assign *tree.VarAssignmentExpr) (evaluatedAssignment, error) { isBin := false + binaryString := false prepareParamKind := vector.PrepareParamNone value, evalErr := getExprValueWithPrepareMeta( - assign.Value, ses, execCtx, preparedExpression, &prepareParamKind, &isBin) + assign.Value, ses, execCtx, preparedExpression, &prepareParamKind, &isBin, &binaryString) if evalErr != nil { return evaluatedAssignment{}, evalErr } @@ -952,7 +955,8 @@ func doSetVar( return evaluatedAssignment{ assign: assign, value: value, - userVarIsBin: isBin, + userVarIsBin: false, + userVarBinaryString: binaryString, userVarPrepareParamKind: prepareParamKind, }, nil } @@ -993,7 +997,7 @@ func doSetVar( } } else { err = ses.setUserDefinedVarWithKind( - name, value, sql, userVarIsBin, userVarPrepareParamKind) + name, value, sql, userVarIsBin, userVarPrepareParamKind, userVarBinaryString) if err != nil { return err } @@ -1006,6 +1010,7 @@ func doSetVar( name := assign.Name value := item.value userVarIsBin = item.userVarIsBin + userVarBinaryString = item.userVarBinaryString userVarPrepareParamKind = item.userVarPrepareParamKind //TODO : fix SET NAMES after parser is ready @@ -4606,6 +4611,7 @@ func doComQuery(ses *Session, execCtx *ExecCtx, input *UserInput) (retErr error) proc.SetAffectedRows(ses.GetLastAffectedRows()) proc.SetResolveVariableFunc(ses.txnCompileCtx.ResolveVariable) proc.SetResolveVariableIsBinFunc(ses.txnCompileCtx.ResolveVariableIsBin) + proc.SetResolveVariableBinaryStringFunc(ses.txnCompileCtx.ResolveVariableBinaryString) proc.SetResolveVariablePrepareParamKindFunc(ses.txnCompileCtx.ResolveVariablePrepareParamKind) refreshStatementScopedSessionInfo(ses, proc) // Frontend client SQL — session-bound resolver. Procs constructed diff --git a/pkg/frontend/mysql_protocol.go b/pkg/frontend/mysql_protocol.go index c2d41d71c6288..442c7ab8c013d 100644 --- a/pkg/frontend/mysql_protocol.go +++ b/pkg/frontend/mysql_protocol.go @@ -769,6 +769,9 @@ func (mp *MysqlProtocolImpl) ParseSendLongData(ctx context.Context, proc *proces } } } + if len(stmt.paramsBinaryString) != numParams { + stmt.paramsBinaryString = make([]bool, numParams) + } length := len(data) - pos val, _, ok := mp.readCountOfBytes(data, pos, length) @@ -806,6 +809,9 @@ func (mp *MysqlProtocolImpl) ParseExecuteData(ctx context.Context, proc *process } } } + if len(stmt.paramsBinaryString) != numParams { + stmt.paramsBinaryString = make([]bool, numParams) + } var flag uint8 flag, pos, ok = mp.io.ReadUint8(data, pos) @@ -848,6 +854,12 @@ func (mp *MysqlProtocolImpl) ParseExecuteData(ctx context.Context, proc *process // get paramters and set value to session variables for i := 0; i < numParams; i++ { + if (i<<1)+1 >= len(stmt.ParamTypes) { + return moerr.NewInvalidInput(ctx, "mysql protocol error, malformed packet") + } + tp := stmt.ParamTypes[i<<1] + stmt.paramsBinaryString[i] = isBinaryStringParameterType(defines.MysqlType(tp)) + // if params had received via COM_STMT_SEND_LONG_DATA, use them directly(we set the params when deal with COM_STMT_SEND_LONG_DATA). // ref https://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html if _, ok := stmt.getFromSendLongData[i]; ok { @@ -862,11 +874,6 @@ func (mp *MysqlProtocolImpl) ParseExecuteData(ctx context.Context, proc *process continue } - if (i<<1)+1 >= len(stmt.ParamTypes) { - return moerr.NewInvalidInput(ctx, "mysql protocol error, malformed packet") - - } - tp := stmt.ParamTypes[i<<1] isUnsigned := (stmt.ParamTypes[(i<<1)+1] & 0x80) > 0 switch defines.MysqlType(tp) { @@ -1056,6 +1063,18 @@ func (mp *MysqlProtocolImpl) ParseExecuteData(ctx context.Context, proc *process return nil } +func isBinaryStringParameterType(tp defines.MysqlType) bool { + switch tp { + case defines.MYSQL_TYPE_BLOB, + defines.MYSQL_TYPE_TINY_BLOB, + defines.MYSQL_TYPE_MEDIUM_BLOB, + defines.MYSQL_TYPE_LONG_BLOB: + return true + default: + return false + } +} + func (mp *MysqlProtocolImpl) readDate(data []byte, pos int) (int, string) { year, pos, _ := mp.io.ReadUint16(data, pos) month := data[pos] diff --git a/pkg/frontend/mysql_protocol_test.go b/pkg/frontend/mysql_protocol_test.go index 437a07db67c0e..2336e4f591c1a 100644 --- a/pkg/frontend/mysql_protocol_test.go +++ b/pkg/frontend/mysql_protocol_test.go @@ -2952,6 +2952,42 @@ func buildStringExecutePacket(proto *MysqlProtocolImpl, tp defines.MysqlType, pa return data[:pos] } +func TestBinaryProtocolBlobParametersSetBinaryStringMetadata(t *testing.T) { + ctx := context.TODO() + for _, test := range []struct { + name string + tp defines.MysqlType + }{ + {name: "string", tp: defines.MYSQL_TYPE_STRING}, + {name: "blob", tp: defines.MYSQL_TYPE_BLOB}, + {name: "long_blob", tp: defines.MYSQL_TYPE_LONG_BLOB}, + } { + t.Run(test.name, func(t *testing.T) { + proto, proc, prepareStmt := newBinaryPrepareProtocolTestCase(t, "select char_length(?)") + defer prepareStmt.clearBinaryParamState(proc) + + require.NoError(t, proto.ParseExecuteData( + ctx, proc, prepareStmt, + buildStringExecutePacket(proto, test.tp, string([]byte{0xe4, 0xbd, 0xa0})), 0)) + require.Equal(t, test.tp != defines.MYSQL_TYPE_STRING, prepareStmt.paramsBinaryString[0]) + }) + } +} + +func TestBinaryProtocolLongDataBlobSetsBinaryStringMetadata(t *testing.T) { + ctx := context.TODO() + proto, proc, prepareStmt := newBinaryPrepareProtocolTestCase(t, "select char_length(?)") + defer prepareStmt.clearBinaryParamState(proc) + + require.NoError(t, proto.ParseSendLongData( + ctx, proc, prepareStmt, []byte{0, 0, 0xe4, 0xbd, 0xa0}, 0)) + require.NoError(t, proto.ParseExecuteData( + ctx, proc, prepareStmt, + []byte{0, 0, 0, 0, 0, 0, 1, byte(defines.MYSQL_TYPE_BLOB), 0}, 0)) + require.True(t, prepareStmt.paramsBinaryString[0]) + require.Equal(t, []byte{0xe4, 0xbd, 0xa0}, prepareStmt.params.GetBytesAt(0)) +} + func buildLongLongExecutePacket(value uint64, unsigned bool) []byte { data := make([]byte, 17) copy(data, []byte{0, 0, 0, 0, 0, 0, 1, byte(defines.MYSQL_TYPE_LONGLONG), 0}) diff --git a/pkg/frontend/session.go b/pkg/frontend/session.go index 49f844c46afbb..44e4356e5dc9d 100644 --- a/pkg/frontend/session.go +++ b/pkg/frontend/session.go @@ -364,11 +364,11 @@ func (ses *Session) getNextProcessId() string { // SetUserDefinedVar sets the user defined variable to the value in session func (ses *Session) SetUserDefinedVar(name string, value interface{}, sql string) error { - return ses.setUserDefinedVar(name, value, sql, false) + return ses.setUserDefinedVar(name, value, sql, false, false) } -func (ses *Session) setUserDefinedVar(name string, value interface{}, sql string, isBin bool) error { - return ses.setUserDefinedVarWithKind(name, value, sql, isBin, prepareParamKindFromValue(value)) +func (ses *Session) setUserDefinedVar(name string, value interface{}, sql string, isBin, binaryString bool) error { + return ses.setUserDefinedVarWithKind(name, value, sql, isBin, prepareParamKindFromValue(value), binaryString) } func (ses *Session) setUserDefinedVarWithKind( @@ -377,13 +377,19 @@ func (ses *Session) setUserDefinedVarWithKind( sql string, isBin bool, kind vector.PrepareParamKind, + binaryString ...bool, ) error { + var binary bool + if len(binaryString) > 0 { + binary = binaryString[0] + } ses.mu.Lock() defer ses.mu.Unlock() ses.userDefinedVars[strings.ToLower(name)] = &UserDefinedVar{ Value: value, Sql: sql, IsBin: isBin, + BinaryString: binary, PrepareParamKind: kind, } return nil diff --git a/pkg/frontend/types.go b/pkg/frontend/types.go index 6ed35db638c47..980a7aa608f2d 100644 --- a/pkg/frontend/types.go +++ b/pkg/frontend/types.go @@ -310,6 +310,7 @@ type PrepareStmt struct { defaultDatabase string params *vector.Vector + paramsBinaryString []bool getFromSendLongData map[int]struct{} compile *compile.Compile @@ -690,6 +691,7 @@ func (prepareStmt *PrepareStmt) Close() { if prepareStmt.ParamTypes != nil { prepareStmt.ParamTypes = nil } + prepareStmt.paramsBinaryString = nil if prepareStmt.ColDefData != nil { prepareStmt.ColDefData = nil } @@ -716,6 +718,7 @@ func (prepareStmt *PrepareStmt) clearBinaryParamState(proc *process.Process) { prepareStmt.params.Free(proc.Mp()) prepareStmt.params = nil } + prepareStmt.paramsBinaryString = nil for k := range prepareStmt.getFromSendLongData { delete(prepareStmt.getFromSendLongData, k) } diff --git a/pkg/frontend/util.go b/pkg/frontend/util.go index c5aab20a82418..2cf88277f8d35 100644 --- a/pkg/frontend/util.go +++ b/pkg/frontend/util.go @@ -312,6 +312,10 @@ func getExprValueWithPrepareMeta( if len(isBin) > 0 { *isBin[0] = resultVec.GetIsBin() } + if len(isBin) > 1 { + *isBin[1] = resultVec.GetIsBinaryString() || resultVec.GetType().Oid == types.T_binary || + resultVec.GetType().Oid == types.T_varbinary || resultVec.GetType().Oid == types.T_blob + } if prepareParamKind != nil { *prepareParamKind = resultVec.GetPrepareParamKind() if *prepareParamKind == vector.PrepareParamNone { diff --git a/pkg/frontend/variables.go b/pkg/frontend/variables.go index 04ec4c8fda6a0..cf02e177216e0 100644 --- a/pkg/frontend/variables.go +++ b/pkg/frontend/variables.go @@ -4269,6 +4269,7 @@ type UserDefinedVar struct { Value interface{} Sql string IsBin bool + BinaryString bool PrepareParamKind vector.PrepareParamKind } diff --git a/pkg/pb/pipeline/pipeline.pb.go b/pkg/pb/pipeline/pipeline.pb.go index 9428926e8b1e0..beda29061f8f4 100644 --- a/pkg/pb/pipeline/pipeline.pb.go +++ b/pkg/pb/pipeline/pipeline.pb.go @@ -5365,6 +5365,7 @@ type PrepareParamInfo struct { Area []byte `protobuf:"bytes,3,opt,name=area,proto3" json:"area,omitempty"` Nulls []bool `protobuf:"varint,4,rep,packed,name=nulls,proto3" json:"nulls,omitempty"` IsBin []bool `protobuf:"varint,5,rep,packed,name=is_bin,json=isBin,proto3" json:"is_bin,omitempty"` + IsBinaryString []bool `protobuf:"varint,6,rep,packed,name=is_binary_string,json=isBinaryString,proto3" json:"is_binary_string,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -5438,6 +5439,13 @@ func (m *PrepareParamInfo) GetIsBin() []bool { return nil } +func (m *PrepareParamInfo) GetIsBinaryString() []bool { + if m != nil { + return m.IsBinaryString + } + return nil +} + type ProcessInfo struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Sql string `protobuf:"bytes,2,opt,name=sql,proto3" json:"sql,omitempty"` @@ -6233,500 +6241,501 @@ func init() { func init() { proto.RegisterFile("pipeline.proto", fileDescriptor_7ac67a7adf3df9c7) } var fileDescriptor_7ac67a7adf3df9c7 = []byte{ - // 7875 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7c, 0x4d, 0x6c, 0x1c, 0xc7, - 0x9a, 0x98, 0x87, 0xf3, 0xff, 0xcd, 0x0c, 0x39, 0x2c, 0x52, 0xd4, 0x48, 0xb2, 0x2d, 0x79, 0x6c, - 0xd9, 0xb4, 0x2c, 0x53, 0x36, 0x6d, 0xbf, 0xe7, 0x7d, 0x6f, 0xdf, 0xf3, 0x52, 0x94, 0x64, 0xf3, - 0x59, 0x94, 0xb8, 0x4d, 0x2a, 0x06, 0x0c, 0x24, 0x8d, 0x66, 0x77, 0xcd, 0xb0, 0xcd, 0x9e, 0xee, - 0x56, 0x57, 0x8d, 0x44, 0xea, 0x92, 0x1c, 0x72, 0xca, 0x25, 0xc7, 0xdd, 0xe3, 0x03, 0x92, 0xc3, - 0x06, 0x39, 0xe4, 0x10, 0xbc, 0x1c, 0x73, 0x0c, 0x16, 0x41, 0x0e, 0x8b, 0x00, 0xc9, 0x31, 0x08, - 0xde, 0x1e, 0x03, 0x04, 0x41, 0x80, 0x04, 0x0b, 0x04, 0x01, 0x82, 0xef, 0xfb, 0xaa, 0xba, 0x7b, - 0x86, 0x23, 0xf9, 0x27, 0xc1, 0x5e, 0xf2, 0x4e, 0xd3, 0xf5, 0x7d, 0x5f, 0x55, 0xd7, 0x54, 0x7d, - 0xf5, 0xfd, 0x57, 0xc3, 0x72, 0x1a, 0xa6, 0x32, 0x0a, 0x63, 0xb9, 0x95, 0x66, 0x89, 0x4e, 0x44, - 0xcb, 0xb6, 0xaf, 0x7e, 0x38, 0x0e, 0xf5, 0xc9, 0xf4, 0x78, 0xcb, 0x4f, 0x26, 0x77, 0xc6, 0xc9, + // 7897 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7c, 0x4b, 0x8c, 0x1c, 0x47, + 0x96, 0x98, 0xaa, 0xab, 0xeb, 0xf7, 0xea, 0xd3, 0xd5, 0xd1, 0xcd, 0x66, 0x91, 0x94, 0x44, 0xaa, + 0x24, 0x4a, 0x2d, 0x8a, 0x6a, 0x4a, 0x2d, 0x69, 0x46, 0x3b, 0xb3, 0x33, 0xda, 0x66, 0x93, 0x94, + 0x7a, 0xc4, 0x26, 0x7b, 0xb3, 0x9b, 0x16, 0x20, 0xc0, 0x4e, 0x64, 0x67, 0x46, 0x55, 0xa7, 0x3a, + 0x2b, 0x33, 0x99, 0x11, 0x45, 0x76, 0xf3, 0xe4, 0x83, 0x4f, 0xbe, 0xf8, 0xb8, 0x7b, 0x1c, 0xc0, + 0x3e, 0xec, 0xc2, 0x07, 0x1f, 0x8c, 0xf1, 0xd1, 0x47, 0x63, 0x61, 0xf8, 0xb0, 0x30, 0x60, 0x1f, + 0x0d, 0x63, 0xf6, 0x68, 0xc0, 0x30, 0x0c, 0xd8, 0x58, 0xc0, 0x30, 0x60, 0xbc, 0xf7, 0x22, 0x32, + 0xb3, 0xaa, 0x8b, 0xd4, 0xc7, 0xc6, 0x5e, 0x3c, 0xa7, 0xca, 0x78, 0xef, 0x45, 0x64, 0x54, 0xc4, + 0x8b, 0xf7, 0x8f, 0x84, 0x5e, 0x1a, 0xa6, 0x32, 0x0a, 0x63, 0xb9, 0x95, 0x66, 0x89, 0x4e, 0x44, + 0xd3, 0xb6, 0xaf, 0x7e, 0x38, 0x0e, 0xf5, 0xc9, 0xf4, 0x78, 0xcb, 0x4f, 0x26, 0x77, 0xc6, 0xc9, 0x38, 0xb9, 0x43, 0x04, 0xc7, 0xd3, 0x11, 0xb5, 0xa8, 0x41, 0x4f, 0xdc, 0xf1, 0x2a, 0x44, 0x89, 0x7f, 0x6a, 0x9f, 0xd3, 0xc8, 0x8b, 0xcd, 0xf3, 0x8a, 0x0e, 0x27, 0x52, 0x69, 0x6f, 0x92, 0x1a, - 0x40, 0x5b, 0x9f, 0x19, 0xdc, 0xf0, 0xb7, 0x0d, 0x68, 0xee, 0x4b, 0xa5, 0xbc, 0xb1, 0x14, 0x43, - 0xa8, 0xaa, 0x30, 0x18, 0x54, 0x6e, 0x54, 0x36, 0x97, 0xb7, 0xfb, 0x5b, 0xf9, 0xb4, 0x0e, 0xb5, - 0xa7, 0xa7, 0xca, 0x41, 0x24, 0xd2, 0xf8, 0x93, 0x60, 0xb0, 0x34, 0x4f, 0xb3, 0x2f, 0xf5, 0x49, - 0x12, 0x38, 0x88, 0x14, 0x7d, 0xa8, 0xca, 0x2c, 0x1b, 0x54, 0x6f, 0x54, 0x36, 0xbb, 0x0e, 0x3e, - 0x0a, 0x01, 0xb5, 0xc0, 0xd3, 0xde, 0xa0, 0x46, 0x20, 0x7a, 0x16, 0xef, 0xc0, 0x72, 0x9a, 0x25, - 0xbe, 0x1b, 0xc6, 0xa3, 0xc4, 0x25, 0x6c, 0x9d, 0xb0, 0x5d, 0x84, 0xee, 0xc5, 0xa3, 0xe4, 0x1e, - 0x52, 0x0d, 0xa0, 0xe9, 0xc5, 0x5e, 0x74, 0xae, 0xe4, 0xa0, 0x41, 0x68, 0xdb, 0x14, 0xcb, 0xb0, - 0x14, 0x06, 0x83, 0xe6, 0x8d, 0xca, 0x66, 0xcd, 0x59, 0x0a, 0x03, 0x7c, 0xc7, 0x74, 0x1a, 0x06, - 0x83, 0x16, 0xbf, 0x03, 0x9f, 0xc5, 0x10, 0xba, 0xb1, 0x94, 0xc1, 0xa3, 0x44, 0x3b, 0x32, 0x8d, - 0xce, 0x07, 0xed, 0x1b, 0x95, 0xcd, 0x96, 0x33, 0x03, 0x13, 0x57, 0xa1, 0x15, 0xc8, 0xe3, 0xe9, - 0x78, 0x5f, 0x8d, 0x07, 0x70, 0xa3, 0xb2, 0xd9, 0x76, 0xf2, 0xb6, 0x38, 0x82, 0xcb, 0x99, 0x7c, + 0x40, 0x4b, 0x9f, 0x19, 0xdc, 0xf0, 0xb7, 0x75, 0x68, 0xec, 0x4b, 0xa5, 0xbc, 0xb1, 0x14, 0x43, + 0xa8, 0xaa, 0x30, 0x18, 0x54, 0x6e, 0x54, 0x36, 0x7b, 0xdb, 0xfd, 0xad, 0x7c, 0x5a, 0x87, 0xda, + 0xd3, 0x53, 0xe5, 0x20, 0x12, 0x69, 0xfc, 0x49, 0x30, 0x58, 0x9a, 0xa7, 0xd9, 0x97, 0xfa, 0x24, + 0x09, 0x1c, 0x44, 0x8a, 0x3e, 0x54, 0x65, 0x96, 0x0d, 0xaa, 0x37, 0x2a, 0x9b, 0x1d, 0x07, 0x1f, + 0x85, 0x80, 0xe5, 0xc0, 0xd3, 0xde, 0x60, 0x99, 0x40, 0xf4, 0x2c, 0xde, 0x81, 0x5e, 0x9a, 0x25, + 0xbe, 0x1b, 0xc6, 0xa3, 0xc4, 0x25, 0x6c, 0x8d, 0xb0, 0x1d, 0x84, 0xee, 0xc5, 0xa3, 0xe4, 0x1e, + 0x52, 0x0d, 0xa0, 0xe1, 0xc5, 0x5e, 0x74, 0xae, 0xe4, 0xa0, 0x4e, 0x68, 0xdb, 0x14, 0x3d, 0x58, + 0x0a, 0x83, 0x41, 0xe3, 0x46, 0x65, 0x73, 0xd9, 0x59, 0x0a, 0x03, 0x7c, 0xc7, 0x74, 0x1a, 0x06, + 0x83, 0x26, 0xbf, 0x03, 0x9f, 0xc5, 0x10, 0x3a, 0xb1, 0x94, 0xc1, 0xa3, 0x44, 0x3b, 0x32, 0x8d, + 0xce, 0x07, 0xad, 0x1b, 0x95, 0xcd, 0xa6, 0x33, 0x03, 0x13, 0x57, 0xa1, 0x19, 0xc8, 0xe3, 0xe9, + 0x78, 0x5f, 0x8d, 0x07, 0x70, 0xa3, 0xb2, 0xd9, 0x72, 0xf2, 0xb6, 0x38, 0x82, 0xcb, 0x99, 0x7c, 0x3a, 0x95, 0x4a, 0xcb, 0xc0, 0xd5, 0xd2, 0xcb, 0x82, 0xe4, 0x79, 0xec, 0x4e, 0x92, 0x40, 0x0e, - 0x3a, 0xb4, 0x02, 0xaf, 0x97, 0x57, 0x29, 0x93, 0xde, 0xe4, 0xc8, 0x10, 0xed, 0x27, 0x81, 0x74, - 0x2e, 0xe5, 0x9d, 0xcb, 0x60, 0xe1, 0xc0, 0x86, 0xe7, 0xfb, 0x32, 0xbd, 0x38, 0x68, 0xf7, 0x07, + 0xda, 0xb4, 0x02, 0xaf, 0x97, 0x57, 0x29, 0x93, 0xde, 0xe4, 0xc8, 0x10, 0xed, 0x27, 0x81, 0x74, + 0x2e, 0xe5, 0x9d, 0xcb, 0x60, 0xe1, 0xc0, 0x86, 0xe7, 0xfb, 0x32, 0xbd, 0x38, 0x68, 0xe7, 0x07, 0x0c, 0xba, 0x6e, 0xfb, 0xce, 0x8c, 0xf9, 0x05, 0xbc, 0x5e, 0xcc, 0xf4, 0xd8, 0xd3, 0xfe, 0x89, - 0xeb, 0x67, 0x32, 0x08, 0xb5, 0xeb, 0x27, 0xd3, 0x58, 0x0f, 0x7a, 0x37, 0x2a, 0x9b, 0x3d, 0xe7, + 0xeb, 0x67, 0x32, 0x08, 0xb5, 0xeb, 0x27, 0xd3, 0x58, 0x0f, 0xba, 0x37, 0x2a, 0x9b, 0x5d, 0xe7, 0x4a, 0x4e, 0x73, 0x17, 0x49, 0x76, 0x89, 0x62, 0x17, 0x09, 0x5e, 0x31, 0xc0, 0xf1, 0xb9, 0x96, - 0x6a, 0xb0, 0x4c, 0x0b, 0xbd, 0x70, 0x80, 0xbb, 0x48, 0x20, 0x7e, 0x05, 0xd7, 0xf2, 0x7f, 0xb5, - 0x60, 0x02, 0x2b, 0x34, 0x81, 0x81, 0x25, 0xb9, 0xf0, 0xfe, 0x97, 0x76, 0xe7, 0xd7, 0xf7, 0xe9, - 0xf5, 0x8b, 0xba, 0xf3, 0xdb, 0x6f, 0xc2, 0x32, 0xf7, 0x52, 0x38, 0xc1, 0xd8, 0x97, 0x83, 0x55, - 0xea, 0xd1, 0x23, 0xe8, 0xa1, 0x01, 0x8a, 0xdb, 0x20, 0x98, 0xcc, 0xf3, 0x4f, 0x0b, 0x52, 0x41, - 0xa4, 0x7d, 0xc2, 0xec, 0xf8, 0xa7, 0x96, 0xfa, 0x17, 0xb5, 0x3f, 0xff, 0xed, 0xf5, 0xd7, 0x86, - 0x4f, 0xa0, 0xbd, 0x9b, 0xc4, 0xb1, 0xf4, 0x75, 0x92, 0x89, 0xeb, 0xd0, 0xb1, 0x9b, 0xe3, 0x9a, - 0xb3, 0x52, 0x77, 0xc0, 0x82, 0xf6, 0x02, 0xf1, 0x1e, 0xac, 0xf8, 0x96, 0xda, 0x0d, 0xe3, 0x40, - 0x9e, 0xd1, 0x61, 0xa9, 0x3b, 0xcb, 0x39, 0x78, 0x0f, 0xa1, 0xc3, 0x7f, 0x53, 0x85, 0xe6, 0xe1, - 0xc9, 0x74, 0x34, 0x8a, 0xa4, 0x78, 0x07, 0x7a, 0xe6, 0x71, 0x37, 0x89, 0xf6, 0x82, 0x33, 0x33, - 0xee, 0x2c, 0x50, 0xdc, 0x80, 0x8e, 0x01, 0x1c, 0x9d, 0xa7, 0xd2, 0x0c, 0x5b, 0x06, 0xcd, 0x8e, - 0xb3, 0x1f, 0xc6, 0x74, 0x06, 0xab, 0xce, 0x2c, 0x70, 0x8e, 0xca, 0x3b, 0xa3, 0x63, 0x39, 0x4b, - 0xe5, 0xd1, 0xdb, 0x76, 0xa2, 0xf0, 0x99, 0x74, 0xe4, 0x78, 0x37, 0xd6, 0x74, 0x38, 0xeb, 0x4e, - 0x19, 0x24, 0xb6, 0xe1, 0x92, 0xe2, 0x2e, 0x6e, 0xe6, 0xc5, 0x63, 0xa9, 0xdc, 0x69, 0x18, 0xeb, - 0x9f, 0x7d, 0x3a, 0x68, 0xdc, 0xa8, 0x6e, 0xd6, 0x9c, 0x35, 0x83, 0x74, 0x08, 0xf7, 0x84, 0x50, - 0xe2, 0x23, 0x58, 0x9f, 0xeb, 0xc3, 0x5d, 0x9a, 0x37, 0xaa, 0x9b, 0x55, 0x47, 0xcc, 0x74, 0xd9, - 0xa3, 0x1e, 0xf7, 0x61, 0x35, 0x9b, 0xc6, 0x28, 0xc2, 0x1e, 0x84, 0x91, 0x96, 0xd9, 0x61, 0x2a, - 0x7d, 0x3a, 0xe4, 0x9d, 0xed, 0xcb, 0x5b, 0x24, 0xe5, 0x9c, 0x79, 0xb4, 0x73, 0xb1, 0x87, 0xb8, - 0x9d, 0x2f, 0xde, 0xfd, 0xb3, 0x34, 0x23, 0x49, 0xd0, 0xd9, 0x06, 0x1e, 0x00, 0x21, 0x4e, 0x19, - 0x2d, 0x6e, 0xc1, 0x6a, 0x90, 0x79, 0x61, 0xec, 0x7a, 0x51, 0xe4, 0x1e, 0x4f, 0xfd, 0x53, 0xa9, - 0x15, 0x49, 0x87, 0x96, 0xb3, 0x42, 0x88, 0x9d, 0x28, 0xba, 0xcb, 0xe0, 0xe1, 0xdf, 0x2c, 0x41, - 0xeb, 0x5e, 0xa8, 0x52, 0xe4, 0x1e, 0x71, 0x19, 0x9a, 0xa3, 0x69, 0xec, 0x17, 0xbc, 0xd1, 0xc0, - 0xe6, 0x5e, 0x20, 0xfe, 0x18, 0x56, 0xa2, 0xc4, 0xf7, 0x22, 0x37, 0x67, 0x83, 0xc1, 0xd2, 0x8d, - 0xea, 0x66, 0x67, 0x7b, 0xad, 0x38, 0xed, 0x39, 0x9b, 0x39, 0xcb, 0x44, 0x5b, 0xb0, 0xdd, 0xaf, - 0xa0, 0x9f, 0xc9, 0x49, 0xa2, 0x65, 0xa9, 0x7b, 0x95, 0xba, 0x8b, 0xa2, 0xfb, 0x37, 0x99, 0x97, - 0x3e, 0x42, 0x11, 0xb1, 0xc2, 0xb4, 0x45, 0xf7, 0x8f, 0x4b, 0x3b, 0x25, 0xc7, 0x6e, 0x18, 0x9c, - 0xb9, 0xf4, 0x82, 0x41, 0xed, 0x46, 0x75, 0xb3, 0x5e, 0x2c, 0xbb, 0x1c, 0xef, 0x05, 0x67, 0x0f, - 0x11, 0x23, 0x3e, 0x81, 0x8d, 0xf9, 0x2e, 0x3c, 0xea, 0xa0, 0x4e, 0x7d, 0xd6, 0x66, 0xfa, 0x38, - 0x84, 0x12, 0x6f, 0x41, 0xd7, 0x76, 0xd2, 0xc8, 0xa2, 0x0d, 0x66, 0x1a, 0x55, 0x62, 0xd1, 0xcb, - 0xd0, 0x0c, 0x95, 0xab, 0xc2, 0xf8, 0x94, 0x64, 0x77, 0xcb, 0x69, 0x84, 0xea, 0x30, 0x8c, 0x4f, - 0xc5, 0x15, 0x68, 0x65, 0xd2, 0x67, 0x4c, 0x8b, 0x30, 0xcd, 0x4c, 0xfa, 0x84, 0xba, 0x0c, 0xf8, - 0xe8, 0xfa, 0x5a, 0x1a, 0x09, 0xde, 0xc8, 0xa4, 0xbf, 0xab, 0xe5, 0x50, 0x41, 0x7d, 0x5f, 0x66, - 0x63, 0x89, 0x42, 0x1c, 0x3b, 0x1e, 0xfa, 0x5e, 0x4c, 0xeb, 0xde, 0x72, 0xf2, 0x36, 0xaa, 0x90, - 0xd4, 0xcb, 0x74, 0xe8, 0x45, 0x74, 0x64, 0x5a, 0x8e, 0x6d, 0x8a, 0x6b, 0xd0, 0x56, 0xda, 0xcb, - 0x34, 0xfe, 0x3b, 0x3a, 0x2a, 0x75, 0xa7, 0x45, 0x00, 0x3c, 0x6d, 0x97, 0xa1, 0x29, 0xe3, 0x80, - 0x50, 0x35, 0xde, 0x49, 0x19, 0x07, 0x7b, 0xc1, 0xd9, 0xf0, 0x5f, 0x56, 0xa0, 0xb7, 0x3f, 0x8d, + 0x6a, 0xd0, 0xa3, 0x85, 0x5e, 0x38, 0xc0, 0x5d, 0x24, 0x10, 0xbf, 0x82, 0x6b, 0xf9, 0xbf, 0x5a, + 0x30, 0x81, 0x15, 0x9a, 0xc0, 0xc0, 0x92, 0x5c, 0x78, 0xff, 0x4b, 0xbb, 0xf3, 0xeb, 0xfb, 0xf4, + 0xfa, 0x45, 0xdd, 0xf9, 0xed, 0x37, 0xa1, 0xc7, 0xbd, 0x14, 0x4e, 0x30, 0xf6, 0xe5, 0x60, 0x95, + 0x7a, 0x74, 0x09, 0x7a, 0x68, 0x80, 0xe2, 0x36, 0x08, 0x26, 0xf3, 0xfc, 0xd3, 0x82, 0x54, 0x10, + 0x69, 0x9f, 0x30, 0x3b, 0xfe, 0xa9, 0xa5, 0xfe, 0xc5, 0xf2, 0x9f, 0xff, 0xf6, 0xfa, 0x6b, 0xc3, + 0x27, 0xd0, 0xda, 0x4d, 0xe2, 0x58, 0xfa, 0x3a, 0xc9, 0xc4, 0x75, 0x68, 0xdb, 0xcd, 0x71, 0xcd, + 0x59, 0xa9, 0x39, 0x60, 0x41, 0x7b, 0x81, 0x78, 0x0f, 0x56, 0x7c, 0x4b, 0xed, 0x86, 0x71, 0x20, + 0xcf, 0xe8, 0xb0, 0xd4, 0x9c, 0x5e, 0x0e, 0xde, 0x43, 0xe8, 0xf0, 0xdf, 0x54, 0xa1, 0x71, 0x78, + 0x32, 0x1d, 0x8d, 0x22, 0x29, 0xde, 0x81, 0xae, 0x79, 0xdc, 0x4d, 0xa2, 0xbd, 0xe0, 0xcc, 0x8c, + 0x3b, 0x0b, 0x14, 0x37, 0xa0, 0x6d, 0x00, 0x47, 0xe7, 0xa9, 0x34, 0xc3, 0x96, 0x41, 0xb3, 0xe3, + 0xec, 0x87, 0x31, 0x9d, 0xc1, 0xaa, 0x33, 0x0b, 0x9c, 0xa3, 0xf2, 0xce, 0xe8, 0x58, 0xce, 0x52, + 0x79, 0xf4, 0xb6, 0x9d, 0x28, 0x7c, 0x26, 0x1d, 0x39, 0xde, 0x8d, 0x35, 0x1d, 0xce, 0x9a, 0x53, + 0x06, 0x89, 0x6d, 0xb8, 0xa4, 0xb8, 0x8b, 0x9b, 0x79, 0xf1, 0x58, 0x2a, 0x77, 0x1a, 0xc6, 0xfa, + 0x67, 0x9f, 0x0e, 0xea, 0x37, 0xaa, 0x9b, 0xcb, 0xce, 0x9a, 0x41, 0x3a, 0x84, 0x7b, 0x42, 0x28, + 0xf1, 0x11, 0xac, 0xcf, 0xf5, 0xe1, 0x2e, 0x8d, 0x1b, 0xd5, 0xcd, 0xaa, 0x23, 0x66, 0xba, 0xec, + 0x51, 0x8f, 0xfb, 0xb0, 0x9a, 0x4d, 0x63, 0x14, 0x61, 0x0f, 0xc2, 0x48, 0xcb, 0xec, 0x30, 0x95, + 0x3e, 0x1d, 0xf2, 0xf6, 0xf6, 0xe5, 0x2d, 0x92, 0x72, 0xce, 0x3c, 0xda, 0xb9, 0xd8, 0x43, 0xdc, + 0xce, 0x17, 0xef, 0xfe, 0x59, 0x9a, 0x91, 0x24, 0x68, 0x6f, 0x03, 0x0f, 0x80, 0x10, 0xa7, 0x8c, + 0x16, 0xb7, 0x60, 0x35, 0xc8, 0xbc, 0x30, 0x76, 0xbd, 0x28, 0x72, 0x8f, 0xa7, 0xfe, 0xa9, 0xd4, + 0x8a, 0xa4, 0x43, 0xd3, 0x59, 0x21, 0xc4, 0x4e, 0x14, 0xdd, 0x65, 0xf0, 0xf0, 0x6f, 0x97, 0xa0, + 0x79, 0x2f, 0x54, 0x29, 0x72, 0x8f, 0xb8, 0x0c, 0x8d, 0xd1, 0x34, 0xf6, 0x0b, 0xde, 0xa8, 0x63, + 0x73, 0x2f, 0x10, 0x7f, 0x0c, 0x2b, 0x51, 0xe2, 0x7b, 0x91, 0x9b, 0xb3, 0xc1, 0x60, 0xe9, 0x46, + 0x75, 0xb3, 0xbd, 0xbd, 0x56, 0x9c, 0xf6, 0x9c, 0xcd, 0x9c, 0x1e, 0xd1, 0x16, 0x6c, 0xf7, 0x2b, + 0xe8, 0x67, 0x72, 0x92, 0x68, 0x59, 0xea, 0x5e, 0xa5, 0xee, 0xa2, 0xe8, 0xfe, 0x4d, 0xe6, 0xa5, + 0x8f, 0x50, 0x44, 0xac, 0x30, 0x6d, 0xd1, 0xfd, 0xe3, 0xd2, 0x4e, 0xc9, 0xb1, 0x1b, 0x06, 0x67, + 0x2e, 0xbd, 0x60, 0xb0, 0x7c, 0xa3, 0xba, 0x59, 0x2b, 0x96, 0x5d, 0x8e, 0xf7, 0x82, 0xb3, 0x87, + 0x88, 0x11, 0x9f, 0xc0, 0xc6, 0x7c, 0x17, 0x1e, 0x75, 0x50, 0xa3, 0x3e, 0x6b, 0x33, 0x7d, 0x1c, + 0x42, 0x89, 0xb7, 0xa0, 0x63, 0x3b, 0x69, 0x64, 0xd1, 0x3a, 0x33, 0x8d, 0x2a, 0xb1, 0xe8, 0x65, + 0x68, 0x84, 0xca, 0x55, 0x61, 0x7c, 0x4a, 0xb2, 0xbb, 0xe9, 0xd4, 0x43, 0x75, 0x18, 0xc6, 0xa7, + 0xe2, 0x0a, 0x34, 0x33, 0xe9, 0x33, 0xa6, 0x49, 0x98, 0x46, 0x26, 0x7d, 0x42, 0x5d, 0x06, 0x7c, + 0x74, 0x7d, 0x2d, 0x8d, 0x04, 0xaf, 0x67, 0xd2, 0xdf, 0xd5, 0x72, 0xa8, 0xa0, 0xb6, 0x2f, 0xb3, + 0xb1, 0x44, 0x21, 0x8e, 0x1d, 0x0f, 0x7d, 0x2f, 0xa6, 0x75, 0x6f, 0x3a, 0x79, 0x1b, 0x55, 0x48, + 0xea, 0x65, 0x3a, 0xf4, 0x22, 0x3a, 0x32, 0x4d, 0xc7, 0x36, 0xc5, 0x35, 0x68, 0x29, 0xed, 0x65, + 0x1a, 0xff, 0x1d, 0x1d, 0x95, 0x9a, 0xd3, 0x24, 0x00, 0x9e, 0xb6, 0xcb, 0xd0, 0x90, 0x71, 0x40, + 0xa8, 0x65, 0xde, 0x49, 0x19, 0x07, 0x7b, 0xc1, 0xd9, 0xf0, 0x5f, 0x56, 0xa0, 0xbb, 0x3f, 0x8d, 0x74, 0xb8, 0x93, 0x8d, 0xa7, 0x72, 0x12, 0x6b, 0x54, 0x3d, 0xf7, 0x42, 0xa5, 0xcd, 0x9b, 0xe9, - 0x59, 0x6c, 0x42, 0xfb, 0xcb, 0x2c, 0x99, 0xa6, 0xc4, 0x6d, 0xbc, 0xd3, 0x65, 0x6e, 0x2b, 0x90, + 0x59, 0x6c, 0x42, 0xeb, 0xcb, 0x2c, 0x99, 0xa6, 0xc4, 0x6d, 0xbc, 0xd3, 0x65, 0x6e, 0x2b, 0x90, 0xc8, 0x99, 0x8f, 0xb3, 0x40, 0x66, 0x77, 0xcf, 0x89, 0xb6, 0x7a, 0x81, 0xb6, 0x8c, 0x16, 0xaf, - 0x43, 0xfb, 0x50, 0xa6, 0x5e, 0xe6, 0x21, 0x0b, 0xd4, 0x48, 0x5f, 0x15, 0x00, 0xfc, 0xaf, 0x44, - 0xbc, 0x17, 0x98, 0x03, 0x6b, 0x9b, 0xc3, 0x7f, 0x52, 0x81, 0xf6, 0xce, 0x78, 0x9c, 0xc9, 0xb1, - 0xa7, 0x49, 0x79, 0x26, 0x29, 0xcd, 0xb7, 0xea, 0x2c, 0x25, 0x29, 0x29, 0x68, 0xfc, 0x07, 0xbc, - 0x40, 0xf4, 0x2c, 0xde, 0x84, 0x9a, 0x5c, 0x3c, 0x21, 0x82, 0x8b, 0x0d, 0x68, 0xf8, 0x49, 0x3c, - 0x0a, 0xc7, 0x46, 0xad, 0x9b, 0x96, 0xf8, 0x05, 0x74, 0xf8, 0x89, 0x79, 0xa0, 0x4e, 0x3a, 0xed, - 0x0a, 0x77, 0xcf, 0x67, 0xb0, 0x4b, 0x14, 0xc8, 0x11, 0x0e, 0xf8, 0xf9, 0xf3, 0xf0, 0x3f, 0x54, - 0xa1, 0x4e, 0x2b, 0x83, 0x7b, 0x83, 0x6a, 0xda, 0x95, 0xcf, 0xbc, 0xc8, 0x6e, 0x29, 0x02, 0xee, - 0x3f, 0xf3, 0x22, 0x71, 0x03, 0xea, 0x38, 0x05, 0xb5, 0x60, 0x61, 0x19, 0x21, 0xde, 0x85, 0x3a, - 0xbe, 0x5d, 0xcd, 0xce, 0x1e, 0xdf, 0x71, 0xb7, 0xf6, 0x97, 0xff, 0xe9, 0xfa, 0x6b, 0x0e, 0xa3, - 0xc5, 0x7b, 0x50, 0xf3, 0xc6, 0x63, 0x45, 0x07, 0x61, 0xe6, 0x2c, 0xe6, 0x33, 0x75, 0x88, 0x40, - 0x7c, 0x06, 0x6d, 0xde, 0x74, 0xa4, 0xae, 0x13, 0xf5, 0xe5, 0x92, 0xf9, 0x53, 0xe6, 0x07, 0xa7, - 0xa0, 0xc4, 0xed, 0x0a, 0x95, 0x91, 0x2c, 0x74, 0x1c, 0x5a, 0x4e, 0x01, 0x40, 0xfb, 0x24, 0xcd, - 0xe4, 0x4e, 0x14, 0x25, 0xfe, 0x61, 0xf8, 0x42, 0x1a, 0x6b, 0x66, 0x06, 0x26, 0xde, 0x85, 0xe5, - 0x03, 0xe6, 0x57, 0x47, 0xaa, 0x69, 0xa4, 0x95, 0xb1, 0x70, 0xe6, 0xa0, 0x62, 0x0b, 0xc4, 0x0c, - 0xe4, 0x88, 0xfe, 0x7e, 0xfb, 0x46, 0x75, 0xb3, 0xe7, 0x2c, 0xc0, 0x88, 0xb7, 0xa1, 0x37, 0xc6, - 0x95, 0x0e, 0xe3, 0xb1, 0x3b, 0x8a, 0x3c, 0x34, 0x7e, 0xaa, 0x68, 0x1c, 0x59, 0xe0, 0x83, 0xc8, - 0x1b, 0xd3, 0x09, 0x49, 0xc3, 0x28, 0x72, 0x27, 0x72, 0x42, 0x26, 0x4f, 0xd5, 0x69, 0x11, 0x60, - 0x5f, 0x4e, 0xc4, 0xfb, 0xb0, 0x4a, 0xc4, 0xee, 0xf1, 0xb9, 0x7b, 0xe2, 0xa9, 0x13, 0xf7, 0x54, - 0x9e, 0x0f, 0xba, 0x24, 0x1d, 0x96, 0x09, 0x71, 0xf7, 0xfc, 0x2b, 0x4f, 0x9d, 0x7c, 0x2d, 0xcf, - 0x87, 0xff, 0xbc, 0x06, 0x8d, 0xbd, 0x58, 0xc9, 0x4c, 0xe3, 0x51, 0xf5, 0x46, 0x23, 0xe9, 0x6b, - 0xc9, 0x22, 0xb2, 0xe6, 0xe4, 0x6d, 0x5c, 0xad, 0xa3, 0xe4, 0x9b, 0x2c, 0xd4, 0xf2, 0xf0, 0x13, - 0xc3, 0x8b, 0x05, 0x00, 0x85, 0xb2, 0x17, 0x04, 0xae, 0xa5, 0x76, 0xb3, 0xe4, 0xb9, 0xa2, 0x63, - 0xdb, 0x72, 0x56, 0xbc, 0x20, 0xd8, 0x31, 0x70, 0x27, 0x79, 0xae, 0xc4, 0x5b, 0x50, 0xcd, 0xe4, - 0x88, 0x38, 0xb3, 0xb3, 0xbd, 0xc2, 0xbb, 0xff, 0xf8, 0xf8, 0x3b, 0xe9, 0x6b, 0x47, 0x8e, 0x1c, - 0xc4, 0x89, 0x75, 0xa8, 0x7b, 0x5a, 0x67, 0xbc, 0x9b, 0x6d, 0x87, 0x1b, 0x62, 0x0b, 0xd6, 0x48, - 0x3c, 0xe8, 0x30, 0x89, 0x5d, 0xed, 0x1d, 0x47, 0xa8, 0xe7, 0x95, 0x51, 0x69, 0xab, 0x39, 0xea, - 0x08, 0x31, 0x7b, 0x81, 0x42, 0x25, 0x38, 0x4f, 0x1f, 0x7b, 0x13, 0xa9, 0x48, 0xa3, 0xb5, 0x9d, - 0xb5, 0xd9, 0x1e, 0x8f, 0x10, 0x85, 0x4b, 0x5f, 0xf4, 0x41, 0x01, 0xd3, 0xa2, 0xb3, 0xda, 0xcd, - 0x81, 0x28, 0x7f, 0x2e, 0x41, 0x23, 0x54, 0xae, 0x8c, 0x03, 0x23, 0xf3, 0xea, 0xa1, 0xba, 0x1f, - 0x07, 0xe2, 0x03, 0x68, 0xf3, 0x5b, 0x02, 0x39, 0x22, 0x8d, 0xd4, 0xd9, 0x5e, 0x36, 0xcc, 0x8d, - 0xe0, 0x7b, 0x72, 0xe4, 0xb4, 0xb4, 0x79, 0x42, 0x6b, 0x45, 0x27, 0xae, 0x3c, 0xd3, 0x32, 0x8b, - 0xbd, 0x88, 0x36, 0xb0, 0xe5, 0x80, 0x4e, 0xee, 0x1b, 0x88, 0xf8, 0x0c, 0x2e, 0x5b, 0xac, 0xab, - 0xf4, 0x44, 0xbb, 0xd3, 0x38, 0x3c, 0x73, 0x63, 0x2f, 0x4e, 0xc8, 0x16, 0xad, 0x3a, 0xeb, 0x16, - 0x7d, 0xa8, 0x27, 0xfa, 0x49, 0x1c, 0x9e, 0x3d, 0xf2, 0xe2, 0x44, 0x6c, 0x42, 0x3f, 0xef, 0xa6, - 0x5f, 0xd0, 0x1f, 0x26, 0x0b, 0xb3, 0xed, 0x2c, 0x5b, 0xf8, 0xd1, 0x0b, 0xfc, 0xaf, 0xa8, 0x46, - 0xca, 0x94, 0xc9, 0x68, 0xa4, 0xa4, 0x76, 0x95, 0xf4, 0xc9, 0xa0, 0xac, 0x3b, 0x6b, 0x05, 0xfd, - 0x63, 0xc2, 0x1d, 0x4a, 0x7f, 0xf8, 0xbb, 0x0a, 0x74, 0xe8, 0x08, 0x3d, 0x49, 0x03, 0x94, 0x56, - 0x6f, 0x43, 0x6f, 0x76, 0xd3, 0x99, 0x6f, 0xba, 0x5e, 0x79, 0xc7, 0x37, 0xa0, 0xb1, 0xe3, 0xe3, - 0xe2, 0x11, 0xe3, 0xf4, 0x1c, 0xd3, 0x12, 0x3f, 0x87, 0x95, 0x29, 0x0d, 0xe3, 0xfa, 0xfa, 0xcc, - 0x8d, 0x50, 0xca, 0xb1, 0x4c, 0x30, 0x5c, 0xc1, 0xef, 0xd8, 0xd5, 0x67, 0x4e, 0x6f, 0x6a, 0x1f, - 0x1f, 0xa2, 0xfc, 0xfb, 0x08, 0xd6, 0x33, 0x89, 0x1c, 0xe3, 0xbe, 0x90, 0x59, 0xe2, 0x6a, 0x39, - 0x49, 0x93, 0x8c, 0x74, 0x26, 0xae, 0xa2, 0x60, 0xdc, 0xb7, 0x32, 0x4b, 0x8e, 0x0c, 0x66, 0xf8, - 0x06, 0xd4, 0x77, 0xb2, 0xcc, 0x3b, 0x27, 0xd6, 0xc2, 0x87, 0x41, 0x85, 0x4e, 0x03, 0x37, 0x86, - 0x3e, 0x54, 0xf7, 0xbd, 0x54, 0xdc, 0x84, 0xa5, 0x49, 0x4a, 0x98, 0xce, 0xf6, 0xa5, 0x92, 0x08, - 0xf1, 0xd2, 0xad, 0xfd, 0xf4, 0x7e, 0xac, 0xb3, 0x73, 0x67, 0x69, 0x92, 0x5e, 0xfd, 0x0c, 0x9a, - 0xa6, 0x89, 0x0e, 0x15, 0x1e, 0xad, 0x0a, 0xad, 0x30, 0x3e, 0xe2, 0x0b, 0x9e, 0x79, 0xd1, 0xd4, - 0x1a, 0x81, 0xdc, 0xf8, 0xc5, 0xd2, 0xe7, 0x95, 0xe1, 0xff, 0xa8, 0x41, 0xeb, 0x9e, 0x8c, 0x24, - 0xfd, 0xf7, 0x21, 0x74, 0xcb, 0xa7, 0xc2, 0xae, 0xdb, 0xcc, 0x49, 0x19, 0x42, 0x97, 0xb5, 0x37, - 0xf5, 0x92, 0xe6, 0xd8, 0xcd, 0xc0, 0x50, 0xad, 0xec, 0xb1, 0xb9, 0x43, 0xe7, 0xad, 0xe7, 0xd8, - 0x26, 0x62, 0x1e, 0x19, 0x4c, 0x8d, 0x31, 0xa6, 0x29, 0x5e, 0x07, 0xc8, 0x92, 0xe7, 0x6e, 0xc8, - 0x2a, 0x94, 0xb5, 0x51, 0x2b, 0x4b, 0x9e, 0xef, 0xa1, 0x12, 0xfd, 0x5b, 0x39, 0x66, 0x3f, 0x87, - 0x41, 0xe9, 0x98, 0xa1, 0xd1, 0xed, 0x86, 0x31, 0x3b, 0x17, 0xe6, 0xc4, 0x15, 0x63, 0x92, 0x4d, - 0xbe, 0x17, 0x93, 0x5f, 0x61, 0x85, 0x47, 0xfb, 0x15, 0xc2, 0x63, 0xa1, 0x2c, 0x82, 0xc5, 0xb2, - 0xe8, 0x2e, 0xc0, 0xa1, 0x1c, 0x4f, 0x64, 0xac, 0xf7, 0xbd, 0x74, 0xd0, 0xa1, 0x8d, 0x1f, 0x16, - 0x1b, 0x6f, 0x77, 0x6b, 0xab, 0x20, 0x62, 0x2e, 0x28, 0xf5, 0x42, 0xcb, 0xca, 0xf7, 0x62, 0x57, - 0x67, 0xd3, 0xd8, 0xf7, 0x34, 0x7b, 0x8a, 0x2d, 0xa7, 0xe3, 0x7b, 0xf1, 0x91, 0x01, 0x95, 0x04, - 0x46, 0xaf, 0x2c, 0x30, 0xde, 0x85, 0x95, 0x34, 0x0b, 0x27, 0x5e, 0x76, 0x8e, 0xf2, 0x99, 0x36, - 0x83, 0x8f, 0x5e, 0xcf, 0x80, 0xbf, 0x96, 0xe7, 0x7b, 0xc1, 0xd9, 0xd5, 0x5f, 0xc1, 0xca, 0xdc, - 0x04, 0x7e, 0x14, 0xdf, 0xfd, 0xfb, 0x2a, 0xb4, 0x0f, 0x32, 0x69, 0x84, 0xfc, 0x75, 0xe8, 0x28, - 0xff, 0x44, 0x4e, 0x3c, 0x96, 0x0d, 0x3c, 0x02, 0x30, 0x88, 0xe4, 0xc2, 0x8c, 0x18, 0x5b, 0xfa, - 0x1e, 0x31, 0xd6, 0x87, 0x2a, 0x5b, 0x68, 0x78, 0x98, 0xf0, 0xb1, 0x90, 0xdd, 0xb5, 0xb2, 0xec, - 0xbe, 0x01, 0xdd, 0x13, 0x4f, 0xb9, 0xde, 0x54, 0x27, 0xae, 0x9f, 0x44, 0xc4, 0x74, 0x2d, 0x07, - 0x4e, 0x3c, 0xb5, 0x33, 0xd5, 0xc9, 0x6e, 0x12, 0x89, 0x37, 0x00, 0xfc, 0x24, 0x32, 0x62, 0xc8, - 0x98, 0xa7, 0x6d, 0x3f, 0x89, 0x58, 0xf6, 0x20, 0x57, 0x4a, 0xa5, 0xc3, 0x89, 0x67, 0xb6, 0xd4, - 0xf8, 0xae, 0x4d, 0x12, 0x85, 0xab, 0x39, 0xca, 0x49, 0x9e, 0xb3, 0xd3, 0xfa, 0x11, 0x2c, 0xfb, - 0xc9, 0x24, 0x75, 0x53, 0x5c, 0x59, 0x32, 0x96, 0x5a, 0x17, 0xfc, 0x8a, 0x2e, 0x52, 0x1c, 0x9c, - 0x4a, 0x36, 0xdf, 0xb6, 0x61, 0xc5, 0x8f, 0xa6, 0x4a, 0xcb, 0x0c, 0xb5, 0xa6, 0x5c, 0xec, 0x8a, - 0xf4, 0x0c, 0x89, 0x31, 0xf9, 0x86, 0xd0, 0x0b, 0x95, 0x9b, 0x44, 0x81, 0xcb, 0x02, 0xca, 0xf0, - 0x59, 0x27, 0x54, 0x8f, 0xa3, 0xc0, 0x88, 0x48, 0xa6, 0x89, 0xe5, 0x73, 0x4b, 0xd3, 0xb1, 0x34, - 0x8f, 0xe4, 0x73, 0x43, 0xf3, 0x32, 0x81, 0xd6, 0x7d, 0xa9, 0x40, 0xfb, 0x8f, 0x4b, 0xd0, 0x3c, - 0x48, 0x94, 0xbe, 0x37, 0x89, 0xec, 0xa1, 0xa8, 0xfc, 0xd8, 0x43, 0xb1, 0xb4, 0xf8, 0x50, 0x2c, - 0x60, 0xcb, 0xea, 0x02, 0xb6, 0x44, 0x55, 0x53, 0xa6, 0x23, 0x76, 0x62, 0xb3, 0x77, 0xb9, 0x20, - 0x24, 0x96, 0xba, 0x86, 0xa6, 0x96, 0x1b, 0xb0, 0x14, 0xe3, 0xad, 0x6f, 0x85, 0xca, 0x48, 0x30, - 0x46, 0x86, 0xc4, 0x9d, 0xc6, 0x0e, 0x6b, 0x85, 0xca, 0x70, 0xeb, 0x1f, 0xc1, 0x95, 0xbc, 0xa7, - 0xfb, 0x3c, 0xd4, 0x27, 0xc9, 0x54, 0xbb, 0x23, 0xf2, 0x1d, 0x95, 0xf1, 0x52, 0x36, 0xec, 0x48, - 0xdf, 0x30, 0x9a, 0x3d, 0x4b, 0x32, 0x0b, 0x47, 0xd3, 0x28, 0x72, 0xb5, 0x3c, 0xd3, 0x66, 0xf3, - 0x07, 0xbc, 0x36, 0x66, 0xdd, 0x1e, 0x4c, 0xa3, 0xe8, 0x48, 0x9e, 0x69, 0x54, 0x30, 0xad, 0x91, - 0x69, 0x0c, 0xff, 0xac, 0x06, 0xf0, 0x30, 0xf1, 0x4f, 0x8f, 0xbc, 0x6c, 0x2c, 0x35, 0xfa, 0x3e, - 0x56, 0x06, 0x1a, 0x19, 0xdd, 0xd4, 0x2c, 0xf9, 0xc4, 0x36, 0x6c, 0xd8, 0xff, 0x8f, 0x9c, 0x8b, - 0x7e, 0x18, 0x0b, 0x31, 0x73, 0x04, 0x85, 0xc1, 0x72, 0x8c, 0x80, 0x24, 0x98, 0xf8, 0xbc, 0x58, - 0x5b, 0xec, 0xa3, 0xcf, 0x53, 0x5a, 0xdb, 0x45, 0x66, 0x70, 0xaf, 0xe8, 0x7e, 0x74, 0x9e, 0x8a, - 0x8f, 0xe0, 0x52, 0x26, 0x47, 0x99, 0x54, 0x27, 0xae, 0x56, 0xe5, 0x97, 0xb1, 0x0b, 0xb4, 0x6a, - 0x90, 0x47, 0x2a, 0x7f, 0xd7, 0x47, 0x70, 0x89, 0x57, 0x6a, 0x7e, 0x7a, 0x2c, 0xf1, 0x57, 0x19, - 0x59, 0x9e, 0xdd, 0x1b, 0x40, 0x81, 0x4a, 0x96, 0xe2, 0xd6, 0x26, 0x8e, 0x68, 0x31, 0x8e, 0x23, - 0x89, 0x36, 0xe0, 0xee, 0x09, 0xfa, 0xff, 0xf7, 0xe4, 0xc8, 0x2c, 0x7e, 0x01, 0x10, 0x43, 0xa8, - 0xed, 0x27, 0x81, 0xa4, 0xa5, 0x5e, 0xde, 0x5e, 0xde, 0xa2, 0x90, 0x27, 0xae, 0x24, 0xc5, 0xc6, - 0x08, 0x27, 0xde, 0x03, 0x1a, 0x8e, 0xd9, 0xef, 0xe2, 0xe9, 0x6a, 0x21, 0x92, 0x78, 0xf0, 0x23, - 0xb8, 0x54, 0xcc, 0xc4, 0xf5, 0xb4, 0xab, 0x4f, 0x24, 0x09, 0x50, 0x3e, 0x60, 0xab, 0xf9, 0xa4, - 0x76, 0xf4, 0xd1, 0x89, 0x44, 0x61, 0xba, 0x09, 0xcd, 0xe4, 0xf8, 0x3b, 0x17, 0x0f, 0x42, 0x67, - 0xf1, 0x41, 0x68, 0x24, 0xc7, 0xdf, 0x39, 0x72, 0x24, 0x7e, 0x56, 0x56, 0x3e, 0x73, 0x4b, 0xd3, - 0xa5, 0xa5, 0x59, 0xcf, 0xf1, 0xa5, 0xd5, 0x19, 0x7e, 0x0e, 0x0d, 0xfc, 0x3b, 0x8f, 0x53, 0xb1, - 0x05, 0x4d, 0x4d, 0xec, 0xa1, 0x8c, 0xb1, 0xb0, 0x5e, 0xe8, 0x8c, 0x82, 0x77, 0x1c, 0x4b, 0x34, - 0x74, 0x60, 0x25, 0x17, 0xc0, 0x4f, 0xe2, 0xf0, 0xe9, 0x54, 0x8a, 0x2f, 0x60, 0x35, 0xcd, 0xa4, - 0x61, 0x7b, 0x77, 0x7a, 0x8a, 0x26, 0x90, 0x39, 0xc1, 0xeb, 0x86, 0x4b, 0xf3, 0x1e, 0xa7, 0xc8, - 0xa1, 0xcb, 0xe9, 0x4c, 0x7b, 0xf8, 0x2d, 0x5c, 0xce, 0x29, 0x0e, 0xa5, 0x9f, 0xc4, 0x81, 0x97, - 0x9d, 0x93, 0xae, 0x9c, 0x1b, 0x5b, 0xfd, 0x98, 0xb1, 0x0f, 0x69, 0xec, 0xff, 0x56, 0x81, 0xce, - 0x83, 0xe9, 0x8b, 0x17, 0xe7, 0x7c, 0x96, 0x44, 0x17, 0x2a, 0x8f, 0x68, 0x80, 0x25, 0xa7, 0xf2, - 0x08, 0xcd, 0xb9, 0x83, 0x53, 0x3c, 0xd7, 0xc4, 0xe7, 0x6d, 0xc7, 0xb4, 0xd0, 0xb1, 0x3b, 0x38, - 0x3d, 0x7a, 0x05, 0x47, 0x33, 0x1a, 0xdd, 0x8c, 0xbb, 0xd3, 0x30, 0x42, 0x63, 0xc3, 0x30, 0x6f, - 0xde, 0x46, 0x57, 0x69, 0x6f, 0xc4, 0x53, 0x79, 0x90, 0x25, 0x13, 0x5e, 0x2c, 0x23, 0x32, 0x16, - 0x60, 0xc4, 0x97, 0xb0, 0x66, 0x02, 0x4a, 0x46, 0x2a, 0xb8, 0x2a, 0x95, 0x3e, 0xb1, 0xee, 0x8f, - 0x0a, 0x42, 0x0d, 0xff, 0xa6, 0x06, 0x2d, 0x74, 0x89, 0x7e, 0x93, 0x84, 0xb1, 0xf8, 0x08, 0xda, - 0xdf, 0x25, 0x61, 0xcc, 0x5e, 0x32, 0x07, 0xdd, 0xd7, 0x78, 0xac, 0x47, 0x49, 0x20, 0xb7, 0x90, - 0x86, 0xfc, 0xe3, 0xd6, 0x77, 0xe6, 0xc9, 0x08, 0xf9, 0x2c, 0x1c, 0x9f, 0x68, 0x17, 0x81, 0x46, - 0xb6, 0x76, 0x42, 0xe5, 0x20, 0x8c, 0x46, 0x7d, 0x1d, 0x80, 0x7c, 0xb1, 0x24, 0x76, 0xd3, 0x53, - 0xe3, 0x1d, 0xb5, 0x10, 0xf2, 0x38, 0x3e, 0x38, 0xc5, 0xb3, 0x17, 0x2a, 0xd7, 0xc4, 0x63, 0x8c, - 0x25, 0x5b, 0xf2, 0x47, 0xdf, 0x81, 0x65, 0xb4, 0x32, 0xd4, 0x69, 0x98, 0xba, 0x69, 0x96, 0x1c, - 0xdb, 0x45, 0x41, 0xdb, 0xe3, 0xf0, 0x34, 0x4c, 0x0f, 0x10, 0x46, 0xca, 0xdd, 0x44, 0x79, 0x50, - 0x6c, 0xb3, 0x16, 0x05, 0x03, 0xc2, 0xf5, 0xa5, 0x50, 0x4e, 0xc4, 0xb6, 0x76, 0x93, 0x94, 0x76, - 0x33, 0x93, 0x11, 0x19, 0xd5, 0x57, 0xa0, 0x85, 0x87, 0x81, 0x50, 0x2d, 0x46, 0xf9, 0x09, 0xa3, - 0xde, 0x07, 0x88, 0xe4, 0x48, 0xbb, 0xc8, 0x65, 0xec, 0xb8, 0xce, 0x85, 0x4c, 0x10, 0xbb, 0x8b, - 0x48, 0xf1, 0x01, 0x74, 0x78, 0x15, 0x98, 0x16, 0x2e, 0xd0, 0x02, 0xa1, 0x99, 0xf8, 0x16, 0x74, - 0xe2, 0x24, 0x76, 0xe5, 0x53, 0xa2, 0x36, 0xe7, 0x76, 0x66, 0xe0, 0x38, 0x89, 0xef, 0x3f, 0x45, - 0x62, 0x71, 0xc7, 0xcc, 0x81, 0x63, 0x07, 0xdd, 0x97, 0xc4, 0x0e, 0x68, 0x26, 0xec, 0x45, 0x7f, - 0x6c, 0x67, 0xc2, 0x3d, 0x7a, 0x2f, 0xe9, 0xc1, 0xf3, 0xe1, 0x2e, 0x37, 0xa0, 0x4b, 0xfb, 0x3e, - 0xf1, 0x52, 0x57, 0x7b, 0x63, 0x63, 0x8d, 0x01, 0xc2, 0xf6, 0xbd, 0xf4, 0xc8, 0x1b, 0x0b, 0x07, - 0xae, 0xcc, 0xf1, 0xdb, 0x31, 0xb2, 0x2e, 0xaf, 0xda, 0x8a, 0x8d, 0x3d, 0x2c, 0xe6, 0xba, 0x8d, - 0x19, 0xae, 0x23, 0x96, 0xc7, 0xd5, 0x1d, 0xfe, 0xd3, 0x25, 0x68, 0x3d, 0x4c, 0x92, 0xf4, 0x27, - 0xb2, 0x5e, 0x79, 0x4b, 0x97, 0x5e, 0xbe, 0xa5, 0xd5, 0xd9, 0x2d, 0x9d, 0x5b, 0xfa, 0xda, 0x0f, - 0x5f, 0xfa, 0xfa, 0x8f, 0x5e, 0xfa, 0xc6, 0x4f, 0x58, 0xfa, 0xe6, 0xfc, 0xd2, 0x0f, 0x9b, 0x50, - 0x3f, 0x94, 0xfa, 0x71, 0x3a, 0xfc, 0x17, 0x2d, 0x68, 0xdf, 0x93, 0xc1, 0x94, 0x17, 0xac, 0xfc, - 0xf7, 0x2b, 0x2f, 0xff, 0xfb, 0x4b, 0xb3, 0x7f, 0x1f, 0x15, 0x91, 0xe5, 0xe8, 0x05, 0x61, 0xb4, - 0x96, 0x65, 0x68, 0x64, 0xfd, 0x82, 0x9f, 0x4d, 0x2c, 0x6a, 0x66, 0x99, 0x72, 0x76, 0x7e, 0x35, - 0x6f, 0xd4, 0x7f, 0x12, 0x6f, 0xcc, 0x49, 0x85, 0x0b, 0x51, 0xaa, 0xef, 0x5d, 0xb5, 0x79, 0x89, - 0xd0, 0xba, 0x20, 0x11, 0x1e, 0xc2, 0x5a, 0x12, 0xbb, 0xc1, 0x34, 0x8d, 0x42, 0xf4, 0x55, 0x5c, - 0x8f, 0x3d, 0xf5, 0xb6, 0xcd, 0x77, 0xe5, 0xac, 0xf7, 0x38, 0xbe, 0x67, 0x89, 0xd8, 0x7f, 0x77, - 0x56, 0x93, 0x79, 0x10, 0x8a, 0xa9, 0x00, 0xb7, 0x86, 0xf4, 0x2a, 0x59, 0x84, 0x9c, 0xb8, 0xeb, - 0x12, 0x74, 0x37, 0x89, 0x48, 0x53, 0x7c, 0x0e, 0x2b, 0x05, 0x15, 0xf3, 0x48, 0xe7, 0x25, 0x3c, - 0xd2, 0xb3, 0x1d, 0x99, 0x4d, 0xfe, 0x36, 0xa4, 0xc0, 0x87, 0xb0, 0x66, 0xc3, 0x12, 0xc6, 0x38, - 0xa0, 0x1d, 0x5c, 0x26, 0x0e, 0xea, 0x9b, 0x48, 0x04, 0xd9, 0x05, 0xb4, 0x45, 0xbf, 0x84, 0xf5, - 0x12, 0x39, 0xfa, 0x0d, 0x65, 0x69, 0x50, 0xe6, 0x95, 0xd5, 0xbc, 0x2f, 0x36, 0x1f, 0x72, 0x24, - 0xb7, 0x13, 0xc8, 0xc8, 0xbe, 0x88, 0x72, 0x69, 0x75, 0xa7, 0x1d, 0xc8, 0xc8, 0x24, 0x96, 0xf6, - 0xe1, 0x1d, 0xf4, 0x2e, 0x10, 0xef, 0x7b, 0xa9, 0x9e, 0x66, 0xd2, 0x4d, 0x23, 0xcf, 0x97, 0x27, - 0x49, 0x14, 0xc8, 0xac, 0x98, 0xdc, 0x2a, 0x4d, 0xee, 0x7a, 0x12, 0x05, 0xbb, 0x49, 0xb4, 0xcb, - 0x94, 0x07, 0x05, 0xa1, 0x9d, 0xeb, 0x0e, 0xbc, 0x79, 0x61, 0x38, 0x54, 0x1c, 0xc5, 0x40, 0x82, - 0x06, 0xba, 0x32, 0x3b, 0x10, 0x92, 0xd8, 0x21, 0x3e, 0x86, 0x4b, 0xbc, 0x77, 0xcc, 0xdc, 0xa7, - 0x52, 0xa6, 0x6e, 0xe4, 0x29, 0x3d, 0x58, 0x63, 0x25, 0x4d, 0x48, 0x62, 0xe0, 0xaf, 0xa5, 0x4c, - 0x1f, 0x7a, 0xfc, 0x56, 0xee, 0x62, 0xec, 0x78, 0xea, 0x33, 0xb3, 0xb6, 0xeb, 0xfc, 0x56, 0xa2, - 0x62, 0x63, 0x1e, 0x3b, 0x97, 0x16, 0xf9, 0x8f, 0xe1, 0xda, 0xcc, 0x10, 0x13, 0x2f, 0x3b, 0x2d, - 0x0c, 0xdb, 0xc1, 0x25, 0x5a, 0xb7, 0xcb, 0xa5, 0xfe, 0xfb, 0x44, 0xc0, 0x23, 0x0c, 0xff, 0x6b, - 0x1d, 0x96, 0x49, 0x0f, 0xff, 0x41, 0x6c, 0xfc, 0x41, 0x6c, 0xfc, 0x7f, 0x20, 0x36, 0x86, 0xff, - 0xa0, 0x02, 0xcd, 0x83, 0x2c, 0x09, 0xa6, 0xbe, 0xfe, 0x89, 0x9c, 0x3e, 0xcb, 0x41, 0xd5, 0xef, - 0xe3, 0xa0, 0xda, 0x05, 0x75, 0xfd, 0xcf, 0x2a, 0xd0, 0x36, 0x53, 0x78, 0xb8, 0xfd, 0x13, 0x27, - 0x51, 0xe4, 0xb9, 0x2a, 0x0b, 0xf3, 0x5c, 0xdf, 0x3b, 0x0b, 0x64, 0xac, 0x67, 0x9c, 0xf0, 0x4f, - 0xd2, 0x22, 0xe9, 0xd5, 0x76, 0xba, 0x0c, 0x7d, 0x9c, 0x52, 0x6e, 0xeb, 0x39, 0xb4, 0xc9, 0x73, - 0x22, 0xc9, 0xb0, 0x01, 0x8d, 0x8c, 0x92, 0x31, 0x66, 0xa2, 0xa6, 0xf5, 0xea, 0x73, 0xba, 0xf4, - 0xd3, 0x4c, 0xbf, 0x7f, 0x5b, 0x85, 0x1e, 0xb9, 0xb1, 0x0f, 0xa6, 0x31, 0x9f, 0x84, 0x3c, 0x7c, - 0x56, 0x99, 0x0d, 0x9f, 0xd5, 0x32, 0xf4, 0x36, 0xf9, 0x35, 0x5d, 0x7e, 0xcd, 0x6e, 0x12, 0xdd, - 0x93, 0x23, 0x87, 0x30, 0xb8, 0x54, 0x5e, 0x36, 0x56, 0x8b, 0x52, 0x82, 0x08, 0xc7, 0x7f, 0x95, - 0x7a, 0x99, 0x37, 0x51, 0x36, 0x25, 0xc8, 0x2d, 0x21, 0xa0, 0x46, 0xe7, 0x8d, 0x97, 0x85, 0x9e, - 0x4d, 0x44, 0x46, 0x85, 0xf1, 0x38, 0x17, 0x1e, 0x2d, 0x4a, 0x05, 0x8f, 0x23, 0x29, 0xee, 0x81, - 0xe0, 0x80, 0x6d, 0x26, 0x3d, 0x54, 0x41, 0x34, 0x0e, 0x49, 0x90, 0xce, 0xf6, 0x06, 0xbf, 0x96, - 0xd6, 0xd2, 0x21, 0xf4, 0x01, 0x62, 0x9d, 0x7e, 0x38, 0x07, 0x59, 0xb0, 0x98, 0xac, 0x87, 0x72, - 0xef, 0xe3, 0x07, 0x2f, 0x26, 0x29, 0x27, 0xe2, 0x96, 0x2f, 0x60, 0x6d, 0x34, 0x8d, 0x22, 0x2d, - 0xcf, 0xb4, 0xab, 0x92, 0x69, 0xe6, 0x4b, 0xf7, 0x15, 0xb1, 0xe2, 0x55, 0x4b, 0x7b, 0x48, 0xa4, - 0x8e, 0x1c, 0x89, 0x5f, 0x81, 0xc8, 0x07, 0xb0, 0xff, 0xd1, 0x66, 0x72, 0x2e, 0xf4, 0xef, 0x5b, - 0x52, 0xf3, 0x6f, 0x47, 0xc3, 0x1d, 0xb8, 0x64, 0xb3, 0x37, 0x28, 0xae, 0xb6, 0xf1, 0x2c, 0x92, - 0x3f, 0x6e, 0xd7, 0xb8, 0x52, 0x5a, 0xe3, 0x75, 0xa8, 0x97, 0x4b, 0x50, 0xb8, 0x31, 0xbc, 0x09, - 0x9d, 0x51, 0x18, 0x49, 0x13, 0x05, 0xc5, 0x4d, 0x33, 0xf1, 0xd0, 0x0a, 0x15, 0x61, 0x98, 0xd6, - 0xf0, 0x77, 0x15, 0xb8, 0x9c, 0x7a, 0xd9, 0xd3, 0xa9, 0xd4, 0x14, 0x0b, 0xe5, 0x5c, 0x9f, 0x3a, - 0xf1, 0xb2, 0x00, 0x0f, 0x2e, 0x0d, 0xc1, 0xa3, 0x73, 0xa5, 0x43, 0x1b, 0x21, 0x3c, 0x97, 0x77, - 0x61, 0xa5, 0xd4, 0x43, 0x7b, 0x99, 0x8d, 0x56, 0xf5, 0xb2, 0xe4, 0x39, 0xe5, 0x77, 0x0f, 0x11, - 0x88, 0x0e, 0x6d, 0x41, 0x27, 0x49, 0xdb, 0x51, 0xc1, 0x80, 0xa5, 0xba, 0x1f, 0x07, 0x78, 0x72, - 0xe3, 0xe9, 0x84, 0x83, 0x39, 0x5c, 0xa8, 0xd2, 0x8c, 0xa7, 0x13, 0x8a, 0xdf, 0xac, 0x43, 0x9d, - 0xab, 0x83, 0xea, 0x04, 0xe7, 0xc6, 0xf0, 0xaf, 0xea, 0xb0, 0xb6, 0xe7, 0xcb, 0x63, 0x99, 0x8d, - 0xef, 0x79, 0xda, 0x7b, 0x10, 0x46, 0xf2, 0xc8, 0x53, 0xa7, 0xc8, 0x70, 0x34, 0xe7, 0xd4, 0xd3, - 0x27, 0x66, 0x95, 0x5a, 0x08, 0x38, 0xf0, 0xf4, 0x09, 0xaa, 0x22, 0x42, 0x8e, 0x92, 0x6c, 0x62, - 0x62, 0x6b, 0x6d, 0x87, 0xfe, 0xe3, 0x03, 0x82, 0xe4, 0xbd, 0x55, 0xf8, 0x42, 0x9a, 0xb2, 0x1a, - 0xea, 0x4d, 0x39, 0xda, 0xb7, 0xa0, 0x9b, 0x49, 0x3f, 0xc9, 0x02, 0x13, 0x30, 0xe6, 0x79, 0x76, - 0x18, 0xc6, 0xa1, 0xe2, 0x5b, 0x50, 0x64, 0x35, 0x28, 0x7c, 0xe0, 0x86, 0x36, 0x47, 0xbf, 0x92, - 0x23, 0x90, 0xf3, 0xf6, 0x02, 0xf1, 0x77, 0xa1, 0x5f, 0xd0, 0x52, 0x88, 0xdd, 0xba, 0x37, 0xdb, - 0x45, 0x08, 0x68, 0xc1, 0x5f, 0xdc, 0x3a, 0xb0, 0xbd, 0xfe, 0x0e, 0x75, 0xe2, 0x34, 0x42, 0x31, - 0x3c, 0x43, 0xc5, 0xdb, 0xd0, 0x53, 0x69, 0x14, 0x6a, 0xc3, 0x00, 0xca, 0x14, 0xdf, 0x74, 0x09, - 0xc8, 0x91, 0x70, 0xb5, 0x68, 0x0b, 0x5b, 0x3f, 0x68, 0x0b, 0xdb, 0x17, 0xb7, 0xf0, 0x7d, 0xe8, - 0xfb, 0x99, 0x0c, 0x64, 0xac, 0x43, 0x2f, 0x72, 0x95, 0x9f, 0xa4, 0x56, 0xf5, 0xae, 0x14, 0xf0, - 0x43, 0x04, 0x8b, 0x9f, 0xc1, 0x65, 0x3f, 0x89, 0xb5, 0x8c, 0x75, 0x5e, 0x9e, 0xe5, 0xc6, 0xd3, - 0xc9, 0xb1, 0xcc, 0x4c, 0xfa, 0xf9, 0x92, 0x41, 0xdb, 0x22, 0xad, 0x47, 0x84, 0x14, 0x1f, 0xc1, - 0x3a, 0x6f, 0xcf, 0x5c, 0x27, 0xce, 0x62, 0x0a, 0xda, 0xa9, 0xd9, 0x1e, 0x5b, 0xb0, 0x76, 0xe2, - 0x29, 0x37, 0x93, 0x2a, 0x0c, 0xa6, 0x5e, 0x64, 0x24, 0x84, 0xc9, 0x9d, 0xac, 0x9e, 0x78, 0xca, - 0x31, 0x18, 0x13, 0x9e, 0xa2, 0xe8, 0xf9, 0x0c, 0x2d, 0x25, 0xbd, 0xc9, 0x7d, 0x6f, 0x3b, 0x22, - 0x9b, 0xa1, 0xfe, 0xca, 0x53, 0x27, 0x57, 0xef, 0xc2, 0xfa, 0xa2, 0x0d, 0xf9, 0xbe, 0xb4, 0x4a, - 0xbb, 0x94, 0x56, 0x31, 0x25, 0x68, 0xff, 0x7d, 0x09, 0x2e, 0xd9, 0xfd, 0x26, 0xc3, 0x33, 0x67, - 0xea, 0xeb, 0xa4, 0xa3, 0xd1, 0x58, 0xcd, 0x7d, 0xf9, 0xb6, 0x03, 0x0c, 0x22, 0xc7, 0x7d, 0x13, - 0xfa, 0x86, 0xa0, 0x60, 0x7e, 0x7e, 0xcb, 0x72, 0x90, 0x0f, 0x45, 0x47, 0x80, 0xfe, 0xe0, 0x48, - 0x66, 0xb8, 0x46, 0x01, 0x55, 0x64, 0x52, 0x17, 0x62, 0x76, 0xfa, 0x83, 0x16, 0x67, 0x59, 0x4e, - 0xdc, 0x06, 0x21, 0x9f, 0x4e, 0xbd, 0x28, 0xd4, 0xe7, 0xee, 0x28, 0x94, 0x51, 0x40, 0x39, 0x3c, - 0xae, 0x29, 0xea, 0x5b, 0xcc, 0x03, 0x44, 0xec, 0x05, 0xaa, 0x34, 0x13, 0x93, 0x1a, 0xca, 0x0f, - 0x80, 0x99, 0xc9, 0x21, 0x81, 0xf7, 0x82, 0xc5, 0x67, 0xa5, 0xb1, 0xf8, 0xac, 0xbc, 0x07, 0x2b, - 0xf3, 0x7b, 0xce, 0xe9, 0x9a, 0x65, 0x35, 0xbb, 0xdf, 0x8b, 0x98, 0xb0, 0xb5, 0x90, 0x09, 0xcd, - 0xa2, 0xff, 0xcf, 0x25, 0x58, 0x37, 0x8b, 0xbe, 0x9b, 0x44, 0xd3, 0x09, 0x6a, 0xfb, 0x34, 0x8c, - 0xc7, 0x68, 0x10, 0x4c, 0x12, 0x36, 0x8b, 0x4a, 0xe2, 0x0f, 0x26, 0x49, 0x2e, 0x8b, 0x37, 0xa1, - 0x1f, 0x72, 0xcf, 0x7c, 0x5d, 0x6c, 0x15, 0xa0, 0x81, 0x9b, 0x55, 0x41, 0x2e, 0x54, 0xb1, 0x97, - 0xaa, 0x93, 0x44, 0x1b, 0x52, 0x12, 0xe2, 0xbc, 0xe6, 0xab, 0x16, 0x45, 0xd4, 0x64, 0x9d, 0xde, - 0x06, 0xe1, 0x4f, 0xb3, 0x0c, 0xcf, 0x47, 0x89, 0x9c, 0x13, 0x22, 0x7d, 0x83, 0x29, 0xa8, 0xdf, - 0x86, 0xe6, 0x24, 0x29, 0x2c, 0x92, 0x19, 0xe3, 0xd2, 0x69, 0x4c, 0x12, 0xe2, 0x90, 0xab, 0x68, - 0x35, 0x3d, 0x9d, 0x86, 0x99, 0x0c, 0xac, 0x1e, 0xb6, 0x6d, 0xa3, 0xa4, 0x4f, 0xc2, 0x20, 0x90, - 0xb1, 0x09, 0xc6, 0xb7, 0x42, 0xf5, 0x15, 0xb5, 0xa9, 0x48, 0x4e, 0x8e, 0xbc, 0x69, 0xa4, 0xdd, - 0x78, 0x1a, 0xd1, 0xa9, 0x88, 0x4c, 0xe9, 0xd6, 0x8a, 0x41, 0x3c, 0x9a, 0x46, 0x78, 0x22, 0x22, - 0xb3, 0xa5, 0xa4, 0x4b, 0x90, 0x05, 0xdd, 0x93, 0x30, 0xd6, 0x24, 0x2a, 0xda, 0xb4, 0xa5, 0x88, - 0x40, 0x26, 0xfc, 0x2a, 0x8c, 0xf5, 0xf0, 0x2f, 0x96, 0x60, 0xc3, 0x2c, 0xfc, 0xa1, 0x59, 0x00, - 0xa3, 0x9f, 0xc9, 0x63, 0xb0, 0xcb, 0x65, 0x72, 0x25, 0x55, 0x07, 0x2c, 0x68, 0x8f, 0x26, 0x5c, - 0x70, 0xd7, 0x92, 0x29, 0xe9, 0xb2, 0x7c, 0x75, 0x1b, 0xc4, 0x05, 0xbe, 0x52, 0x26, 0x66, 0xd5, - 0x9f, 0x63, 0x2c, 0x25, 0x3e, 0x85, 0x8d, 0x89, 0xd4, 0x1e, 0x1d, 0x84, 0x28, 0xf1, 0x3d, 0xea, - 0x45, 0x47, 0x9e, 0x97, 0x7b, 0xdd, 0x62, 0x1f, 0x1a, 0x24, 0x1e, 0x7a, 0x7c, 0xc7, 0xc4, 0x8b, - 0xc3, 0x91, 0x54, 0x9a, 0xec, 0x0c, 0xee, 0xc1, 0x86, 0x4f, 0xdf, 0x62, 0xd0, 0x92, 0x20, 0x6a, - 0xb2, 0x58, 0x47, 0xbc, 0x89, 0x0d, 0xa2, 0x69, 0x66, 0x72, 0x64, 0xf6, 0xae, 0x87, 0x7b, 0x15, - 0x87, 0xf1, 0x98, 0x8b, 0x83, 0x9b, 0x6c, 0x53, 0x5a, 0xe0, 0x7e, 0x12, 0xc8, 0xe1, 0x9f, 0xd7, - 0x72, 0x1e, 0x3d, 0x30, 0xf0, 0x43, 0xed, 0x69, 0xaa, 0x87, 0xcd, 0x27, 0xcf, 0x3a, 0x92, 0xd7, - 0xaa, 0x67, 0xa1, 0x5c, 0x36, 0xbb, 0x05, 0x6b, 0xb3, 0xb3, 0x65, 0xda, 0x25, 0x4e, 0x78, 0x96, - 0xa7, 0x9b, 0x97, 0xd9, 0xe6, 0xf4, 0x4c, 0x6a, 0x2a, 0x4c, 0x2d, 0x94, 0xc9, 0x3e, 0x2c, 0x16, - 0x41, 0xb9, 0x4a, 0x46, 0x5c, 0xed, 0x53, 0x9b, 0x1d, 0x55, 0x1d, 0x1a, 0x04, 0x1e, 0xcd, 0x82, - 0x3c, 0xcd, 0xa6, 0xb1, 0x0c, 0x8c, 0x4a, 0x5f, 0xc9, 0xe1, 0x07, 0x04, 0xc6, 0x09, 0xe7, 0x92, - 0xa9, 0x34, 0x74, 0x83, 0x87, 0x0e, 0x8c, 0x64, 0x2a, 0x86, 0x46, 0x1e, 0x2d, 0xe8, 0xcd, 0xd8, - 0x2c, 0x20, 0x56, 0x72, 0x6a, 0x33, 0xf6, 0xcf, 0x61, 0x90, 0xd3, 0xf2, 0xbf, 0x2b, 0x5e, 0xd0, - 0x62, 0xe5, 0x63, 0xbb, 0xd0, 0xdf, 0xcc, 0x5f, 0xf2, 0x09, 0x6c, 0xcc, 0x77, 0x34, 0x6f, 0x6a, - 0x53, 0xb7, 0xb5, 0x99, 0x6e, 0xc5, 0x3f, 0xc9, 0xf7, 0xd7, 0xf7, 0xfc, 0x13, 0xe9, 0x9e, 0x84, - 0xa6, 0xc8, 0xb4, 0xea, 0xac, 0x5a, 0xd4, 0x2e, 0x62, 0xbe, 0x0a, 0xb5, 0x5a, 0x40, 0x3f, 0x09, - 0x95, 0x32, 0x5a, 0x71, 0x96, 0x7e, 0x3f, 0x54, 0x6a, 0xf8, 0x8f, 0x01, 0xba, 0xd6, 0x52, 0xa4, - 0x3a, 0xc8, 0xdb, 0x65, 0xa3, 0xbf, 0xb3, 0xdd, 0xb7, 0xd6, 0x3b, 0x92, 0xec, 0x68, 0x9d, 0xd9, - 0xfc, 0x09, 0x3b, 0x03, 0x33, 0xf6, 0xce, 0x12, 0x19, 0x08, 0x85, 0xbd, 0xb3, 0x03, 0xab, 0x25, - 0x0b, 0xd2, 0xd5, 0x89, 0xf6, 0x22, 0xe3, 0x14, 0x94, 0x2a, 0x5a, 0x4a, 0x24, 0xce, 0x0a, 0x36, - 0xd8, 0xb6, 0x38, 0x42, 0x6a, 0x74, 0x36, 0xfc, 0x24, 0xb2, 0x85, 0x77, 0x73, 0xce, 0x06, 0x62, - 0x28, 0x57, 0x9f, 0x49, 0xf4, 0x5d, 0xd5, 0xd3, 0xc8, 0x9c, 0xa0, 0x36, 0x43, 0x0e, 0x9f, 0x46, - 0xf9, 0x04, 0xc9, 0x98, 0x6f, 0x90, 0x1f, 0x43, 0x13, 0x24, 0x2b, 0xfd, 0x43, 0xe8, 0x24, 0x59, - 0x38, 0x0e, 0x29, 0xf5, 0xc6, 0x06, 0xce, 0xfc, 0x4b, 0x80, 0x09, 0x76, 0xf1, 0x55, 0x43, 0x68, - 0x18, 0xf5, 0x7f, 0x31, 0x7f, 0x6f, 0x30, 0x68, 0x10, 0x29, 0x9d, 0x85, 0xbe, 0xc6, 0xe9, 0xf0, - 0x89, 0xe4, 0xc2, 0xac, 0x1e, 0x83, 0x0f, 0x9f, 0x46, 0x94, 0x7d, 0x7c, 0x17, 0x56, 0x7c, 0x52, - 0x17, 0x7c, 0xa0, 0x22, 0x19, 0xd3, 0x9e, 0xd6, 0x9d, 0x1e, 0x83, 0x71, 0x7e, 0x0f, 0x65, 0x6c, - 0x8a, 0xc0, 0xbc, 0x28, 0x42, 0x8f, 0x35, 0xf1, 0x02, 0x93, 0xb1, 0xef, 0x5a, 0xe0, 0xc3, 0xc4, - 0x0b, 0xc4, 0x2f, 0xe0, 0x2a, 0xe2, 0x5c, 0x39, 0x49, 0xf5, 0x39, 0xea, 0x37, 0x99, 0x85, 0xbe, - 0xeb, 0x29, 0xca, 0xe0, 0x9b, 0xc4, 0xfd, 0x06, 0x52, 0xdc, 0x47, 0x82, 0x47, 0x8c, 0xdf, 0x51, - 0xdf, 0xca, 0x2c, 0x11, 0xdf, 0x52, 0x06, 0x72, 0x91, 0xf9, 0x6e, 0x43, 0x0d, 0x6f, 0x15, 0x7b, - 0xf5, 0x12, 0x4a, 0xaa, 0x90, 0x41, 0x84, 0x63, 0x8d, 0x3e, 0xea, 0x2f, 0xbe, 0x06, 0x61, 0x15, - 0x1c, 0x71, 0xbe, 0xf6, 0xd4, 0xa9, 0xa2, 0x28, 0x44, 0x67, 0xfb, 0x8d, 0x57, 0xda, 0xa8, 0x8e, - 0xd5, 0x8c, 0x08, 0x44, 0x80, 0x12, 0x7f, 0x0a, 0xeb, 0xf9, 0x60, 0xc6, 0x96, 0xa1, 0xe1, 0x38, - 0x48, 0x71, 0xfd, 0xe2, 0x70, 0x33, 0x26, 0x90, 0x63, 0x67, 0xc2, 0x60, 0x1e, 0xf2, 0x4b, 0x58, - 0xb1, 0x43, 0xf2, 0xaa, 0xab, 0x41, 0x9f, 0x46, 0x7b, 0xf3, 0xc2, 0x68, 0x33, 0xba, 0x3d, 0xd7, - 0xcf, 0x0c, 0xc5, 0x3f, 0x9a, 0x6b, 0x72, 0xab, 0x65, 0xe8, 0x66, 0x41, 0x67, 0xfb, 0xc6, 0x85, - 0x91, 0xe6, 0x94, 0x95, 0x63, 0xa7, 0x60, 0xe1, 0xe2, 0x63, 0xb8, 0x64, 0x07, 0x4b, 0xc8, 0xc5, - 0x73, 0xc3, 0x84, 0xbc, 0x3f, 0xc1, 0x26, 0x96, 0x41, 0xb2, 0xfb, 0xb7, 0x97, 0xb0, 0xb7, 0x78, - 0xcd, 0x76, 0x61, 0x2d, 0x4c, 0x1e, 0x71, 0xfe, 0xa7, 0xd6, 0x48, 0x77, 0x0d, 0x0c, 0x09, 0xeb, - 0x65, 0xf4, 0x80, 0xed, 0xf4, 0x37, 0xa1, 0x4f, 0x55, 0xb4, 0xb8, 0xad, 0x49, 0x16, 0x84, 0xb1, - 0x17, 0x0d, 0xd6, 0x89, 0x6b, 0x96, 0x11, 0xee, 0x24, 0xcf, 0x1f, 0x33, 0x54, 0x1c, 0xc1, 0x86, - 0x7d, 0x51, 0x2e, 0x66, 0x14, 0xaa, 0x12, 0x0a, 0x7b, 0x2e, 0x5a, 0xb8, 0x19, 0x85, 0xe3, 0xd8, - 0x2d, 0x9c, 0x55, 0x43, 0xf7, 0xe0, 0xfa, 0xdc, 0xd6, 0x4e, 0xbc, 0x33, 0x77, 0x22, 0x27, 0x49, - 0x76, 0x6e, 0x14, 0xc8, 0x06, 0x09, 0xb0, 0x6b, 0x33, 0x9b, 0xb8, 0xef, 0x9d, 0xed, 0x13, 0x0d, - 0xab, 0x93, 0x2f, 0xe0, 0xf5, 0xb9, 0x51, 0xb8, 0x28, 0x55, 0xc6, 0xde, 0x71, 0x24, 0x83, 0xc1, - 0x65, 0xfa, 0x47, 0x57, 0x66, 0x86, 0x38, 0x44, 0x8a, 0xfb, 0x4c, 0x60, 0x0c, 0xba, 0x63, 0x68, - 0x53, 0x18, 0x84, 0xa4, 0x61, 0x5e, 0x20, 0x5c, 0x79, 0x75, 0x81, 0xf0, 0x87, 0xd0, 0x35, 0xd6, - 0xfe, 0xcb, 0x2a, 0x8e, 0x3b, 0x8c, 0xc7, 0x67, 0x35, 0xbc, 0x0d, 0x6d, 0x32, 0xf5, 0xe9, 0x1d, - 0xd7, 0xa1, 0xc3, 0xb7, 0x4d, 0x8e, 0xa3, 0xc4, 0x3f, 0xb5, 0xc6, 0x39, 0x81, 0xee, 0x22, 0x64, - 0x08, 0xd0, 0x7a, 0x12, 0x87, 0x49, 0xbc, 0x13, 0x45, 0xc3, 0xbf, 0x6e, 0x40, 0x1b, 0x6d, 0x02, - 0x8a, 0xdb, 0xa0, 0x5b, 0x45, 0x1b, 0x47, 0xb9, 0xdc, 0x89, 0x97, 0x9a, 0x12, 0xe8, 0x0e, 0x02, - 0x91, 0x6a, 0xdf, 0x4b, 0xe7, 0x52, 0xbd, 0x4b, 0x73, 0xa9, 0xde, 0xb7, 0xf8, 0xee, 0x13, 0xd7, - 0xbb, 0x49, 0x5b, 0x28, 0x4b, 0x03, 0xdc, 0x65, 0x10, 0xda, 0x2a, 0x44, 0xe2, 0x45, 0x64, 0xdf, - 0xa0, 0xf7, 0x14, 0x29, 0x93, 0x15, 0x26, 0xbe, 0xd9, 0x31, 0x88, 0x43, 0xc9, 0xf2, 0xb8, 0x14, - 0xac, 0xab, 0xcf, 0x07, 0xeb, 0x6e, 0x01, 0xf8, 0x49, 0x1c, 0x90, 0x09, 0x35, 0x97, 0x8d, 0xe3, - 0x94, 0x6c, 0x81, 0xfd, 0x01, 0xa1, 0xe1, 0xf7, 0xa0, 0x9f, 0x53, 0xa0, 0x85, 0xe4, 0xc7, 0xb9, - 0xff, 0x69, 0xa8, 0x1c, 0x39, 0xda, 0x8d, 0xf5, 0x7c, 0x0c, 0xb9, 0x7d, 0x21, 0x86, 0xfc, 0x92, - 0xe4, 0x3d, 0xfc, 0xe8, 0x1b, 0x24, 0x57, 0xa0, 0x45, 0x55, 0x42, 0xc1, 0x34, 0x35, 0xb2, 0xba, - 0x19, 0x2a, 0x8a, 0xf5, 0xbf, 0x2c, 0x4e, 0xdd, 0xfd, 0x7f, 0x15, 0xa7, 0xee, 0xfd, 0xb0, 0x38, - 0xf5, 0xf2, 0x0f, 0x8b, 0x53, 0xcf, 0xc5, 0x75, 0x57, 0xe6, 0xd3, 0x41, 0x2f, 0x4d, 0xbe, 0xf4, - 0x5f, 0x9a, 0x7c, 0xf9, 0x9e, 0xcc, 0xc9, 0xea, 0x2b, 0x33, 0x27, 0x3f, 0x20, 0x75, 0x23, 0xbe, - 0x2f, 0x75, 0xf3, 0x2e, 0xac, 0xe8, 0xcc, 0xf3, 0x4f, 0xd9, 0x13, 0x39, 0x95, 0xe7, 0xca, 0xa4, - 0x8a, 0x7a, 0x04, 0x46, 0x3f, 0xe4, 0x6b, 0x79, 0xae, 0x86, 0x4f, 0x00, 0xc8, 0x45, 0xa3, 0xbf, - 0xf6, 0x32, 0xde, 0xa8, 0xfc, 0xe8, 0xc2, 0x8e, 0xff, 0x5d, 0x01, 0x38, 0xf4, 0x26, 0x29, 0xc7, - 0x58, 0xc5, 0x9f, 0x40, 0x47, 0x51, 0xab, 0x9c, 0x61, 0x2f, 0x29, 0xb2, 0x82, 0xd4, 0x3c, 0xf2, - 0x45, 0x08, 0x95, 0x3f, 0x13, 0x5b, 0xf3, 0x08, 0x79, 0x11, 0x5d, 0xdd, 0x12, 0x50, 0xec, 0xeb, - 0x26, 0x2c, 0x1b, 0x82, 0x54, 0x66, 0xbe, 0x8c, 0xb9, 0x32, 0xb7, 0xe2, 0xf4, 0x18, 0x7a, 0xc0, + 0x43, 0xeb, 0x50, 0xa6, 0x5e, 0xe6, 0x21, 0x0b, 0x2c, 0x93, 0xbe, 0x2a, 0x00, 0xf8, 0x5f, 0x89, + 0x78, 0x2f, 0x30, 0x07, 0xd6, 0x36, 0x87, 0xff, 0xb4, 0x02, 0xad, 0x9d, 0xf1, 0x38, 0x93, 0x63, + 0x4f, 0x93, 0xf2, 0x4c, 0x52, 0x9a, 0x6f, 0xd5, 0x59, 0x4a, 0x52, 0x52, 0xd0, 0xf8, 0x0f, 0x78, + 0x81, 0xe8, 0x59, 0xbc, 0x09, 0xcb, 0x72, 0xf1, 0x84, 0x08, 0x2e, 0x36, 0xa0, 0xee, 0x27, 0xf1, + 0x28, 0x1c, 0x1b, 0xb5, 0x6e, 0x5a, 0xe2, 0x17, 0xd0, 0xe6, 0x27, 0xe6, 0x81, 0x1a, 0xe9, 0xb4, + 0x2b, 0xdc, 0x3d, 0x9f, 0xc1, 0x2e, 0x51, 0x20, 0x47, 0x38, 0xe0, 0xe7, 0xcf, 0xc3, 0xff, 0x50, + 0x85, 0x1a, 0xad, 0x0c, 0xee, 0x0d, 0xaa, 0x69, 0x57, 0x3e, 0xf3, 0x22, 0xbb, 0xa5, 0x08, 0xb8, + 0xff, 0xcc, 0x8b, 0xc4, 0x0d, 0xa8, 0xe1, 0x14, 0xd4, 0x82, 0x85, 0x65, 0x84, 0x78, 0x17, 0x6a, + 0xf8, 0x76, 0x35, 0x3b, 0x7b, 0x7c, 0xc7, 0xdd, 0xe5, 0xbf, 0xfa, 0x4f, 0xd7, 0x5f, 0x73, 0x18, + 0x2d, 0xde, 0x83, 0x65, 0x6f, 0x3c, 0x56, 0x74, 0x10, 0x66, 0xce, 0x62, 0x3e, 0x53, 0x87, 0x08, + 0xc4, 0x67, 0xd0, 0xe2, 0x4d, 0x47, 0xea, 0x1a, 0x51, 0x5f, 0x2e, 0x99, 0x3f, 0x65, 0x7e, 0x70, + 0x0a, 0x4a, 0xdc, 0xae, 0x50, 0x19, 0xc9, 0x42, 0xc7, 0xa1, 0xe9, 0x14, 0x00, 0xb4, 0x4f, 0xd2, + 0x4c, 0xee, 0x44, 0x51, 0xe2, 0x1f, 0x86, 0x2f, 0xa4, 0xb1, 0x66, 0x66, 0x60, 0xe2, 0x5d, 0xe8, + 0x1d, 0x30, 0xbf, 0x3a, 0x52, 0x4d, 0x23, 0xad, 0x8c, 0x85, 0x33, 0x07, 0x15, 0x5b, 0x20, 0x66, + 0x20, 0x47, 0xf4, 0xf7, 0x5b, 0x37, 0xaa, 0x9b, 0x5d, 0x67, 0x01, 0x46, 0xbc, 0x0d, 0xdd, 0x31, + 0xae, 0x74, 0x18, 0x8f, 0xdd, 0x51, 0xe4, 0xa1, 0xf1, 0x53, 0x45, 0xe3, 0xc8, 0x02, 0x1f, 0x44, + 0xde, 0x98, 0x4e, 0x48, 0x1a, 0x46, 0x91, 0x3b, 0x91, 0x13, 0x32, 0x79, 0xaa, 0x4e, 0x93, 0x00, + 0xfb, 0x72, 0x22, 0xde, 0x87, 0x55, 0x22, 0x76, 0x8f, 0xcf, 0xdd, 0x13, 0x4f, 0x9d, 0xb8, 0xa7, + 0xf2, 0x7c, 0xd0, 0x21, 0xe9, 0xd0, 0x23, 0xc4, 0xdd, 0xf3, 0xaf, 0x3c, 0x75, 0xf2, 0xb5, 0x3c, + 0x1f, 0xfe, 0xf3, 0x65, 0xa8, 0xef, 0xc5, 0x4a, 0x66, 0x1a, 0x8f, 0xaa, 0x37, 0x1a, 0x49, 0x5f, + 0x4b, 0x16, 0x91, 0xcb, 0x4e, 0xde, 0xc6, 0xd5, 0x3a, 0x4a, 0xbe, 0xc9, 0x42, 0x2d, 0x0f, 0x3f, + 0x31, 0xbc, 0x58, 0x00, 0x50, 0x28, 0x7b, 0x41, 0xe0, 0x5a, 0x6a, 0x37, 0x4b, 0x9e, 0x2b, 0x3a, + 0xb6, 0x4d, 0x67, 0xc5, 0x0b, 0x82, 0x1d, 0x03, 0x77, 0x92, 0xe7, 0x4a, 0xbc, 0x05, 0xd5, 0x4c, + 0x8e, 0x88, 0x33, 0xdb, 0xdb, 0x2b, 0xbc, 0xfb, 0x8f, 0x8f, 0xbf, 0x93, 0xbe, 0x76, 0xe4, 0xc8, + 0x41, 0x9c, 0x58, 0x87, 0x9a, 0xa7, 0x75, 0xc6, 0xbb, 0xd9, 0x72, 0xb8, 0x21, 0xb6, 0x60, 0x8d, + 0xc4, 0x83, 0x0e, 0x93, 0xd8, 0xd5, 0xde, 0x71, 0x84, 0x7a, 0x5e, 0x19, 0x95, 0xb6, 0x9a, 0xa3, + 0x8e, 0x10, 0xb3, 0x17, 0x28, 0x54, 0x82, 0xf3, 0xf4, 0xb1, 0x37, 0x91, 0x8a, 0x34, 0x5a, 0xcb, + 0x59, 0x9b, 0xed, 0xf1, 0x08, 0x51, 0xb8, 0xf4, 0x45, 0x1f, 0x14, 0x30, 0x4d, 0x3a, 0xab, 0x9d, + 0x1c, 0x88, 0xf2, 0xe7, 0x12, 0xd4, 0x43, 0xe5, 0xca, 0x38, 0x30, 0x32, 0xaf, 0x16, 0xaa, 0xfb, + 0x71, 0x20, 0x3e, 0x80, 0x16, 0xbf, 0x25, 0x90, 0x23, 0xd2, 0x48, 0xed, 0xed, 0x9e, 0x61, 0x6e, + 0x04, 0xdf, 0x93, 0x23, 0xa7, 0xa9, 0xcd, 0x13, 0x5a, 0x2b, 0x3a, 0x71, 0xe5, 0x99, 0x96, 0x59, + 0xec, 0x45, 0xb4, 0x81, 0x4d, 0x07, 0x74, 0x72, 0xdf, 0x40, 0xc4, 0x67, 0x70, 0xd9, 0x62, 0x5d, + 0xa5, 0x27, 0xda, 0x9d, 0xc6, 0xe1, 0x99, 0x1b, 0x7b, 0x71, 0x42, 0xb6, 0x68, 0xd5, 0x59, 0xb7, + 0xe8, 0x43, 0x3d, 0xd1, 0x4f, 0xe2, 0xf0, 0xec, 0x91, 0x17, 0x27, 0x62, 0x13, 0xfa, 0x79, 0x37, + 0xfd, 0x82, 0xfe, 0x30, 0x59, 0x98, 0x2d, 0xa7, 0x67, 0xe1, 0x47, 0x2f, 0xf0, 0xbf, 0xa2, 0x1a, + 0x29, 0x53, 0x26, 0xa3, 0x91, 0x92, 0xda, 0x55, 0xd2, 0x27, 0x83, 0xb2, 0xe6, 0xac, 0x15, 0xf4, + 0x8f, 0x09, 0x77, 0x28, 0xfd, 0xe1, 0xef, 0x2a, 0xd0, 0xa6, 0x23, 0xf4, 0x24, 0x0d, 0x50, 0x5a, + 0xbd, 0x0d, 0xdd, 0xd9, 0x4d, 0x67, 0xbe, 0xe9, 0x78, 0xe5, 0x1d, 0xdf, 0x80, 0xfa, 0x8e, 0x8f, + 0x8b, 0x47, 0x8c, 0xd3, 0x75, 0x4c, 0x4b, 0xfc, 0x1c, 0x56, 0xa6, 0x34, 0x8c, 0xeb, 0xeb, 0x33, + 0x37, 0x42, 0x29, 0xc7, 0x32, 0xc1, 0x70, 0x05, 0xbf, 0x63, 0x57, 0x9f, 0x39, 0xdd, 0xa9, 0x7d, + 0x7c, 0x88, 0xf2, 0xef, 0x23, 0x58, 0xcf, 0x24, 0x72, 0x8c, 0xfb, 0x42, 0x66, 0x89, 0xab, 0xe5, + 0x24, 0x4d, 0x32, 0xd2, 0x99, 0xb8, 0x8a, 0x82, 0x71, 0xdf, 0xca, 0x2c, 0x39, 0x32, 0x98, 0xe1, + 0x1b, 0x50, 0xdb, 0xc9, 0x32, 0xef, 0x9c, 0x58, 0x0b, 0x1f, 0x06, 0x15, 0x3a, 0x0d, 0xdc, 0x18, + 0xfa, 0x50, 0xdd, 0xf7, 0x52, 0x71, 0x13, 0x96, 0x26, 0x29, 0x61, 0xda, 0xdb, 0x97, 0x4a, 0x22, + 0xc4, 0x4b, 0xb7, 0xf6, 0xd3, 0xfb, 0xb1, 0xce, 0xce, 0x9d, 0xa5, 0x49, 0x7a, 0xf5, 0x33, 0x68, + 0x98, 0x26, 0x3a, 0x54, 0x78, 0xb4, 0x2a, 0xb4, 0xc2, 0xf8, 0x88, 0x2f, 0x78, 0xe6, 0x45, 0x53, + 0x6b, 0x04, 0x72, 0xe3, 0x17, 0x4b, 0x9f, 0x57, 0x86, 0xff, 0x63, 0x19, 0x9a, 0xf7, 0x64, 0x24, + 0xe9, 0xbf, 0x0f, 0xa1, 0x53, 0x3e, 0x15, 0x76, 0xdd, 0x66, 0x4e, 0xca, 0x10, 0x3a, 0xac, 0xbd, + 0xa9, 0x97, 0x34, 0xc7, 0x6e, 0x06, 0x86, 0x6a, 0x65, 0x8f, 0xcd, 0x1d, 0x3a, 0x6f, 0x5d, 0xc7, + 0x36, 0x11, 0xf3, 0xc8, 0x60, 0x96, 0x19, 0x63, 0x9a, 0xe2, 0x75, 0x80, 0x2c, 0x79, 0xee, 0x86, + 0xac, 0x42, 0x59, 0x1b, 0x35, 0xb3, 0xe4, 0xf9, 0x1e, 0x2a, 0xd1, 0xbf, 0x93, 0x63, 0xf6, 0x73, + 0x18, 0x94, 0x8e, 0x19, 0x1a, 0xdd, 0x6e, 0x18, 0xb3, 0x73, 0x61, 0x4e, 0x5c, 0x31, 0x26, 0xd9, + 0xe4, 0x7b, 0x31, 0xf9, 0x15, 0x56, 0x78, 0xb4, 0x5e, 0x21, 0x3c, 0x16, 0xca, 0x22, 0x58, 0x2c, + 0x8b, 0xee, 0x02, 0x1c, 0xca, 0xf1, 0x44, 0xc6, 0x7a, 0xdf, 0x4b, 0x07, 0x6d, 0xda, 0xf8, 0x61, + 0xb1, 0xf1, 0x76, 0xb7, 0xb6, 0x0a, 0x22, 0xe6, 0x82, 0x52, 0x2f, 0xb4, 0xac, 0x7c, 0x2f, 0x76, + 0x75, 0x36, 0x8d, 0x7d, 0x4f, 0xb3, 0xa7, 0xd8, 0x74, 0xda, 0xbe, 0x17, 0x1f, 0x19, 0x50, 0x49, + 0x60, 0x74, 0xcb, 0x02, 0xe3, 0x5d, 0x58, 0x49, 0xb3, 0x70, 0xe2, 0x65, 0xe7, 0x28, 0x9f, 0x69, + 0x33, 0xf8, 0xe8, 0x75, 0x0d, 0xf8, 0x6b, 0x79, 0xbe, 0x17, 0x9c, 0x5d, 0xfd, 0x15, 0xac, 0xcc, + 0x4d, 0xe0, 0x47, 0xf1, 0xdd, 0xbf, 0xaf, 0x42, 0xeb, 0x20, 0x93, 0x46, 0xc8, 0x5f, 0x87, 0xb6, + 0xf2, 0x4f, 0xe4, 0xc4, 0x63, 0xd9, 0xc0, 0x23, 0x00, 0x83, 0x48, 0x2e, 0xcc, 0x88, 0xb1, 0xa5, + 0xef, 0x11, 0x63, 0x7d, 0xa8, 0xb2, 0x85, 0x86, 0x87, 0x09, 0x1f, 0x0b, 0xd9, 0xbd, 0x5c, 0x96, + 0xdd, 0x37, 0xa0, 0x73, 0xe2, 0x29, 0xd7, 0x9b, 0xea, 0xc4, 0xf5, 0x93, 0x88, 0x98, 0xae, 0xe9, + 0xc0, 0x89, 0xa7, 0x76, 0xa6, 0x3a, 0xd9, 0x4d, 0x22, 0xf1, 0x06, 0x80, 0x9f, 0x44, 0x46, 0x0c, + 0x19, 0xf3, 0xb4, 0xe5, 0x27, 0x11, 0xcb, 0x1e, 0xe4, 0x4a, 0xa9, 0x74, 0x38, 0xf1, 0xcc, 0x96, + 0x1a, 0xdf, 0xb5, 0x41, 0xa2, 0x70, 0x35, 0x47, 0x39, 0xc9, 0x73, 0x76, 0x5a, 0x3f, 0x82, 0x9e, + 0x9f, 0x4c, 0x52, 0x37, 0xc5, 0x95, 0x25, 0x63, 0xa9, 0x79, 0xc1, 0xaf, 0xe8, 0x20, 0xc5, 0xc1, + 0xa9, 0x64, 0xf3, 0x6d, 0x1b, 0x56, 0xfc, 0x68, 0xaa, 0xb4, 0xcc, 0x50, 0x6b, 0xca, 0xc5, 0xae, + 0x48, 0xd7, 0x90, 0x18, 0x93, 0x6f, 0x08, 0xdd, 0x50, 0xb9, 0x49, 0x14, 0xb8, 0x2c, 0xa0, 0x0c, + 0x9f, 0xb5, 0x43, 0xf5, 0x38, 0x0a, 0x8c, 0x88, 0x64, 0x9a, 0x58, 0x3e, 0xb7, 0x34, 0x6d, 0x4b, + 0xf3, 0x48, 0x3e, 0x37, 0x34, 0x2f, 0x13, 0x68, 0x9d, 0x97, 0x0a, 0xb4, 0xff, 0xb8, 0x04, 0x8d, + 0x83, 0x44, 0xe9, 0x7b, 0x93, 0xc8, 0x1e, 0x8a, 0xca, 0x8f, 0x3d, 0x14, 0x4b, 0x8b, 0x0f, 0xc5, + 0x02, 0xb6, 0xac, 0x2e, 0x60, 0x4b, 0x54, 0x35, 0x65, 0x3a, 0x62, 0x27, 0x36, 0x7b, 0x7b, 0x05, + 0x21, 0xb1, 0xd4, 0x35, 0x34, 0xb5, 0xdc, 0x80, 0xa5, 0x18, 0x6f, 0x7d, 0x33, 0x54, 0x46, 0x82, + 0x31, 0x32, 0x24, 0xee, 0x34, 0x76, 0x58, 0x33, 0x54, 0x86, 0x5b, 0xff, 0x08, 0xae, 0xe4, 0x3d, + 0xdd, 0xe7, 0xa1, 0x3e, 0x49, 0xa6, 0xda, 0x1d, 0x91, 0xef, 0xa8, 0x8c, 0x97, 0xb2, 0x61, 0x47, + 0xfa, 0x86, 0xd1, 0xec, 0x59, 0x92, 0x59, 0x38, 0x9a, 0x46, 0x91, 0xab, 0xe5, 0x99, 0x36, 0x9b, + 0x3f, 0xe0, 0xb5, 0x31, 0xeb, 0xf6, 0x60, 0x1a, 0x45, 0x47, 0xf2, 0x4c, 0xa3, 0x82, 0x69, 0x8e, + 0x4c, 0x63, 0xf8, 0x67, 0xcb, 0x00, 0x0f, 0x13, 0xff, 0xf4, 0xc8, 0xcb, 0xc6, 0x52, 0xa3, 0xef, + 0x63, 0x65, 0xa0, 0x91, 0xd1, 0x0d, 0xcd, 0x92, 0x4f, 0x6c, 0xc3, 0x86, 0xfd, 0xff, 0xc8, 0xb9, + 0xe8, 0x87, 0xb1, 0x10, 0x33, 0x47, 0x50, 0x18, 0x2c, 0xc7, 0x08, 0x48, 0x82, 0x89, 0xcf, 0x8b, + 0xb5, 0xc5, 0x3e, 0xfa, 0x3c, 0xa5, 0xb5, 0x5d, 0x64, 0x06, 0x77, 0x8b, 0xee, 0x47, 0xe7, 0xa9, + 0xf8, 0x08, 0x2e, 0x65, 0x72, 0x94, 0x49, 0x75, 0xe2, 0x6a, 0x55, 0x7e, 0x19, 0xbb, 0x40, 0xab, + 0x06, 0x79, 0xa4, 0xf2, 0x77, 0x7d, 0x04, 0x97, 0x78, 0xa5, 0xe6, 0xa7, 0xc7, 0x12, 0x7f, 0x95, + 0x91, 0xe5, 0xd9, 0xbd, 0x01, 0x14, 0xa8, 0x64, 0x29, 0x6e, 0x6d, 0xe2, 0x88, 0x16, 0xe3, 0x38, + 0x92, 0x68, 0x03, 0xee, 0x9e, 0xa0, 0xff, 0x7f, 0x4f, 0x8e, 0xcc, 0xe2, 0x17, 0x00, 0x31, 0x84, + 0xe5, 0xfd, 0x24, 0x90, 0xb4, 0xd4, 0xbd, 0xed, 0xde, 0x16, 0x85, 0x3c, 0x71, 0x25, 0x29, 0x36, + 0x46, 0x38, 0xf1, 0x1e, 0xd0, 0x70, 0xcc, 0x7e, 0x17, 0x4f, 0x57, 0x13, 0x91, 0xc4, 0x83, 0x1f, + 0xc1, 0xa5, 0x62, 0x26, 0xae, 0xa7, 0x5d, 0x7d, 0x22, 0x49, 0x80, 0xf2, 0x01, 0x5b, 0xcd, 0x27, + 0xb5, 0xa3, 0x8f, 0x4e, 0x24, 0x0a, 0xd3, 0x4d, 0x68, 0x24, 0xc7, 0xdf, 0xb9, 0x78, 0x10, 0xda, + 0x8b, 0x0f, 0x42, 0x3d, 0x39, 0xfe, 0xce, 0x91, 0x23, 0xf1, 0xb3, 0xb2, 0xf2, 0x99, 0x5b, 0x9a, + 0x0e, 0x2d, 0xcd, 0x7a, 0x8e, 0x2f, 0xad, 0xce, 0xf0, 0x73, 0xa8, 0xe3, 0xdf, 0x79, 0x9c, 0x8a, + 0x2d, 0x68, 0x68, 0x62, 0x0f, 0x65, 0x8c, 0x85, 0xf5, 0x42, 0x67, 0x14, 0xbc, 0xe3, 0x58, 0xa2, + 0xa1, 0x03, 0x2b, 0xb9, 0x00, 0x7e, 0x12, 0x87, 0x4f, 0xa7, 0x52, 0x7c, 0x01, 0xab, 0x69, 0x26, + 0x0d, 0xdb, 0xbb, 0xd3, 0x53, 0x34, 0x81, 0xcc, 0x09, 0x5e, 0x37, 0x5c, 0x9a, 0xf7, 0x38, 0x45, + 0x0e, 0xed, 0xa5, 0x33, 0xed, 0xe1, 0xb7, 0x70, 0x39, 0xa7, 0x38, 0x94, 0x7e, 0x12, 0x07, 0x5e, + 0x76, 0x4e, 0xba, 0x72, 0x6e, 0x6c, 0xf5, 0x63, 0xc6, 0x3e, 0xa4, 0xb1, 0xff, 0x5b, 0x05, 0xda, + 0x0f, 0xa6, 0x2f, 0x5e, 0x9c, 0xf3, 0x59, 0x12, 0x1d, 0xa8, 0x3c, 0xa2, 0x01, 0x96, 0x9c, 0xca, + 0x23, 0x34, 0xe7, 0x0e, 0x4e, 0xf1, 0x5c, 0x13, 0x9f, 0xb7, 0x1c, 0xd3, 0x42, 0xc7, 0xee, 0xe0, + 0xf4, 0xe8, 0x15, 0x1c, 0xcd, 0x68, 0x74, 0x33, 0xee, 0x4e, 0xc3, 0x08, 0x8d, 0x0d, 0xc3, 0xbc, + 0x79, 0x1b, 0x5d, 0xa5, 0xbd, 0x11, 0x4f, 0xe5, 0x41, 0x96, 0x4c, 0x78, 0xb1, 0x8c, 0xc8, 0x58, + 0x80, 0x11, 0x5f, 0xc2, 0x9a, 0x09, 0x28, 0x19, 0xa9, 0xe0, 0xaa, 0x54, 0xfa, 0xc4, 0xba, 0x3f, + 0x2a, 0x08, 0x35, 0xfc, 0xdb, 0x65, 0x68, 0xa2, 0x4b, 0xf4, 0x9b, 0x24, 0x8c, 0xc5, 0x47, 0xd0, + 0xfa, 0x2e, 0x09, 0x63, 0xf6, 0x92, 0x39, 0xe8, 0xbe, 0xc6, 0x63, 0x3d, 0x4a, 0x02, 0xb9, 0x85, + 0x34, 0xe4, 0x1f, 0x37, 0xbf, 0x33, 0x4f, 0x46, 0xc8, 0x67, 0xe1, 0xf8, 0x44, 0xbb, 0x08, 0x34, + 0xb2, 0xb5, 0x1d, 0x2a, 0x07, 0x61, 0x34, 0xea, 0xeb, 0x00, 0xe4, 0x8b, 0x25, 0xb1, 0x9b, 0x9e, + 0x1a, 0xef, 0xa8, 0x89, 0x90, 0xc7, 0xf1, 0xc1, 0x29, 0x9e, 0xbd, 0x50, 0xb9, 0x26, 0x1e, 0x63, + 0x2c, 0xd9, 0x92, 0x3f, 0xfa, 0x0e, 0xf4, 0xd0, 0xca, 0x50, 0xa7, 0x61, 0xea, 0xa6, 0x59, 0x72, + 0x6c, 0x17, 0x05, 0x6d, 0x8f, 0xc3, 0xd3, 0x30, 0x3d, 0x40, 0x18, 0x29, 0x77, 0x13, 0xe5, 0x41, + 0xb1, 0xcd, 0x5a, 0x14, 0x0c, 0x08, 0xd7, 0x97, 0x42, 0x39, 0x11, 0xdb, 0xda, 0x0d, 0x52, 0xda, + 0x8d, 0x4c, 0x46, 0x64, 0x54, 0x5f, 0x81, 0x26, 0x1e, 0x06, 0x42, 0x35, 0x19, 0xe5, 0x27, 0x8c, + 0x7a, 0x1f, 0x20, 0x92, 0x23, 0xed, 0x22, 0x97, 0xb1, 0xe3, 0x3a, 0x17, 0x32, 0x41, 0xec, 0x2e, + 0x22, 0xc5, 0x07, 0xd0, 0xe6, 0x55, 0x60, 0x5a, 0xb8, 0x40, 0x0b, 0x84, 0x66, 0xe2, 0x5b, 0xd0, + 0x8e, 0x93, 0xd8, 0x95, 0x4f, 0x89, 0xda, 0x9c, 0xdb, 0x99, 0x81, 0xe3, 0x24, 0xbe, 0xff, 0x14, + 0x89, 0xc5, 0x1d, 0x33, 0x07, 0x8e, 0x1d, 0x74, 0x5e, 0x12, 0x3b, 0xa0, 0x99, 0xb0, 0x17, 0xfd, + 0xb1, 0x9d, 0x09, 0xf7, 0xe8, 0xbe, 0xa4, 0x07, 0xcf, 0x87, 0xbb, 0xdc, 0x80, 0x0e, 0xed, 0xfb, + 0xc4, 0x4b, 0x5d, 0xed, 0x8d, 0x8d, 0x35, 0x06, 0x08, 0xdb, 0xf7, 0xd2, 0x23, 0x6f, 0x2c, 0x1c, + 0xb8, 0x32, 0xc7, 0x6f, 0xc7, 0xc8, 0xba, 0xbc, 0x6a, 0x2b, 0x36, 0xf6, 0xb0, 0x98, 0xeb, 0x36, + 0x66, 0xb8, 0x8e, 0x58, 0x1e, 0x57, 0x77, 0xf8, 0xcf, 0x96, 0xa0, 0xf9, 0x30, 0x49, 0xd2, 0x9f, + 0xc8, 0x7a, 0xe5, 0x2d, 0x5d, 0x7a, 0xf9, 0x96, 0x56, 0x67, 0xb7, 0x74, 0x6e, 0xe9, 0x97, 0x7f, + 0xf8, 0xd2, 0xd7, 0x7e, 0xf4, 0xd2, 0xd7, 0x7f, 0xc2, 0xd2, 0x37, 0xe6, 0x97, 0x7e, 0xd8, 0x80, + 0xda, 0xa1, 0xd4, 0x8f, 0xd3, 0xe1, 0xbf, 0x68, 0x42, 0xeb, 0x9e, 0x0c, 0xa6, 0xbc, 0x60, 0xe5, + 0xbf, 0x5f, 0x79, 0xf9, 0xdf, 0x5f, 0x9a, 0xfd, 0xfb, 0xa8, 0x88, 0x2c, 0x47, 0x2f, 0x08, 0xa3, + 0x35, 0x2d, 0x43, 0x23, 0xeb, 0x17, 0xfc, 0x6c, 0x62, 0x51, 0x33, 0xcb, 0x94, 0xb3, 0xf3, 0xab, + 0x79, 0xa3, 0xf6, 0x93, 0x78, 0x63, 0x4e, 0x2a, 0x5c, 0x88, 0x52, 0x7d, 0xef, 0xaa, 0xcd, 0x4b, + 0x84, 0xe6, 0x05, 0x89, 0xf0, 0x10, 0xd6, 0x92, 0xd8, 0x0d, 0xa6, 0x69, 0x14, 0xa2, 0xaf, 0xe2, + 0x7a, 0xec, 0xa9, 0xb7, 0x6c, 0xbe, 0x2b, 0x67, 0xbd, 0xc7, 0xf1, 0x3d, 0x4b, 0xc4, 0xfe, 0xbb, + 0xb3, 0x9a, 0xcc, 0x83, 0x50, 0x4c, 0x05, 0xb8, 0x35, 0xa4, 0x57, 0xc9, 0x22, 0xe4, 0xc4, 0x5d, + 0x87, 0xa0, 0xbb, 0x49, 0x44, 0x9a, 0xe2, 0x73, 0x58, 0x29, 0xa8, 0x98, 0x47, 0xda, 0x2f, 0xe1, + 0x91, 0xae, 0xed, 0xc8, 0x6c, 0xf2, 0x77, 0x21, 0x05, 0x3e, 0x84, 0x35, 0x1b, 0x96, 0x30, 0xc6, + 0x01, 0xed, 0x60, 0x8f, 0x38, 0xa8, 0x6f, 0x22, 0x11, 0x64, 0x17, 0xd0, 0x16, 0xfd, 0x12, 0xd6, + 0x4b, 0xe4, 0xe8, 0x37, 0x94, 0xa5, 0x41, 0x99, 0x57, 0x56, 0xf3, 0xbe, 0xd8, 0x7c, 0xc8, 0x91, + 0xdc, 0x76, 0x20, 0x23, 0xfb, 0x22, 0xca, 0xa5, 0xd5, 0x9c, 0x56, 0x20, 0x23, 0x93, 0x58, 0xda, + 0x87, 0x77, 0xd0, 0xbb, 0x40, 0xbc, 0xef, 0xa5, 0x7a, 0x9a, 0x49, 0x37, 0x8d, 0x3c, 0x5f, 0x9e, + 0x24, 0x51, 0x20, 0xb3, 0x62, 0x72, 0xab, 0x34, 0xb9, 0xeb, 0x49, 0x14, 0xec, 0x26, 0xd1, 0x2e, + 0x53, 0x1e, 0x14, 0x84, 0x76, 0xae, 0x3b, 0xf0, 0xe6, 0x85, 0xe1, 0x50, 0x71, 0x14, 0x03, 0x09, + 0x1a, 0xe8, 0xca, 0xec, 0x40, 0x48, 0x62, 0x87, 0xf8, 0x18, 0x2e, 0xf1, 0xde, 0x31, 0x73, 0x9f, + 0x4a, 0x99, 0xba, 0x91, 0xa7, 0xf4, 0x60, 0x8d, 0x95, 0x34, 0x21, 0x89, 0x81, 0xbf, 0x96, 0x32, + 0x7d, 0xe8, 0xf1, 0x5b, 0xb9, 0x8b, 0xb1, 0xe3, 0xa9, 0xcf, 0xcc, 0xda, 0xae, 0xf3, 0x5b, 0x89, + 0x8a, 0x8d, 0x79, 0xec, 0x5c, 0x5a, 0xe4, 0x3f, 0x86, 0x6b, 0x33, 0x43, 0x4c, 0xbc, 0xec, 0xb4, + 0x30, 0x6c, 0x07, 0x97, 0x68, 0xdd, 0x2e, 0x97, 0xfa, 0xef, 0x13, 0x01, 0x8f, 0x30, 0xfc, 0xaf, + 0x35, 0xe8, 0x91, 0x1e, 0xfe, 0x83, 0xd8, 0xf8, 0x83, 0xd8, 0xf8, 0xff, 0x40, 0x6c, 0x0c, 0xff, + 0x61, 0x05, 0x1a, 0x07, 0x59, 0x12, 0x4c, 0x7d, 0xfd, 0x13, 0x39, 0x7d, 0x96, 0x83, 0xaa, 0xdf, + 0xc7, 0x41, 0xcb, 0x17, 0xd4, 0xf5, 0x5f, 0x56, 0xa0, 0x65, 0xa6, 0xf0, 0x70, 0xfb, 0x27, 0x4e, + 0xa2, 0xc8, 0x73, 0x55, 0x16, 0xe6, 0xb9, 0xbe, 0x77, 0x16, 0xc8, 0x58, 0xcf, 0x38, 0xe1, 0x9f, + 0xa4, 0x45, 0xd2, 0xab, 0xe5, 0x74, 0x18, 0xfa, 0x38, 0xa5, 0xdc, 0xd6, 0x73, 0x68, 0x91, 0xe7, + 0x44, 0x92, 0x61, 0x03, 0xea, 0x19, 0x25, 0x63, 0xcc, 0x44, 0x4d, 0xeb, 0xd5, 0xe7, 0x74, 0xe9, + 0xa7, 0x99, 0x7e, 0xff, 0xb6, 0x0a, 0x5d, 0x72, 0x63, 0x1f, 0x4c, 0x63, 0x3e, 0x09, 0x79, 0xf8, + 0xac, 0x32, 0x1b, 0x3e, 0x5b, 0xce, 0xd0, 0xdb, 0xe4, 0xd7, 0x74, 0xf8, 0x35, 0xbb, 0x49, 0x74, + 0x4f, 0x8e, 0x1c, 0xc2, 0xe0, 0x52, 0x79, 0xd9, 0x58, 0x2d, 0x4a, 0x09, 0x22, 0x1c, 0xff, 0x55, + 0xea, 0x65, 0xde, 0x44, 0xd9, 0x94, 0x20, 0xb7, 0x84, 0x80, 0x65, 0x3a, 0x6f, 0xbc, 0x2c, 0xf4, + 0x6c, 0x22, 0x32, 0x2a, 0x8c, 0xc7, 0xb9, 0xf0, 0x68, 0x52, 0x2a, 0x78, 0x1c, 0x49, 0x71, 0x0f, + 0x04, 0x07, 0x6c, 0x33, 0xe9, 0xa1, 0x0a, 0xa2, 0x71, 0x48, 0x82, 0xb4, 0xb7, 0x37, 0xf8, 0xb5, + 0xb4, 0x96, 0x0e, 0xa1, 0x0f, 0x10, 0xeb, 0xf4, 0xc3, 0x39, 0xc8, 0x82, 0xc5, 0x64, 0x3d, 0x94, + 0x7b, 0x1f, 0x3f, 0x78, 0x31, 0x49, 0x39, 0x11, 0xb7, 0x7c, 0x01, 0x6b, 0xa3, 0x69, 0x14, 0x69, + 0x79, 0xa6, 0x5d, 0x95, 0x4c, 0x33, 0x5f, 0xba, 0xaf, 0x88, 0x15, 0xaf, 0x5a, 0xda, 0x43, 0x22, + 0x75, 0xe4, 0x48, 0xfc, 0x0a, 0x44, 0x3e, 0x80, 0xfd, 0x8f, 0x36, 0x93, 0x73, 0xa1, 0x7f, 0xdf, + 0x92, 0x9a, 0x7f, 0x3b, 0x1a, 0xee, 0xc0, 0x25, 0x9b, 0xbd, 0x41, 0x71, 0xb5, 0x8d, 0x67, 0x91, + 0xfc, 0x71, 0xbb, 0xc6, 0x95, 0xd2, 0x1a, 0xaf, 0x43, 0xad, 0x5c, 0x82, 0xc2, 0x8d, 0xe1, 0x4d, + 0x68, 0x8f, 0xc2, 0x48, 0x9a, 0x28, 0x28, 0x6e, 0x9a, 0x89, 0x87, 0x56, 0xa8, 0x08, 0xc3, 0xb4, + 0x86, 0xbf, 0xab, 0xc0, 0xe5, 0xd4, 0xcb, 0x9e, 0x4e, 0xa5, 0xa6, 0x58, 0x28, 0xe7, 0xfa, 0xd4, + 0x89, 0x97, 0x05, 0x78, 0x70, 0x69, 0x08, 0x1e, 0x9d, 0x2b, 0x1d, 0x5a, 0x08, 0xe1, 0xb9, 0xbc, + 0x0b, 0x2b, 0xa5, 0x1e, 0xda, 0xcb, 0x6c, 0xb4, 0xaa, 0x9b, 0x25, 0xcf, 0x29, 0xbf, 0x7b, 0x88, + 0x40, 0x74, 0x68, 0x0b, 0x3a, 0x49, 0xda, 0x8e, 0x0a, 0x06, 0x2c, 0xd5, 0xfd, 0x38, 0xc0, 0x93, + 0x1b, 0x4f, 0x27, 0x1c, 0xcc, 0xe1, 0x42, 0x95, 0x46, 0x3c, 0x9d, 0x50, 0xfc, 0x66, 0x1d, 0x6a, + 0x5c, 0x1d, 0x54, 0x23, 0x38, 0x37, 0x86, 0x7f, 0x5d, 0x83, 0xb5, 0x3d, 0x5f, 0x1e, 0xcb, 0x6c, + 0x7c, 0xcf, 0xd3, 0xde, 0x83, 0x30, 0x92, 0x47, 0x9e, 0x3a, 0x45, 0x86, 0xa3, 0x39, 0xa7, 0x9e, + 0x3e, 0x31, 0xab, 0xd4, 0x44, 0xc0, 0x81, 0xa7, 0x4f, 0x50, 0x15, 0x11, 0x72, 0x94, 0x64, 0x13, + 0x13, 0x5b, 0x6b, 0x39, 0xf4, 0x1f, 0x1f, 0x10, 0x24, 0xef, 0xad, 0xc2, 0x17, 0xd2, 0x94, 0xd5, + 0x50, 0x6f, 0xca, 0xd1, 0xbe, 0x05, 0x9d, 0x4c, 0xfa, 0x49, 0x16, 0x98, 0x80, 0x31, 0xcf, 0xb3, + 0xcd, 0x30, 0x0e, 0x15, 0xdf, 0x82, 0x22, 0xab, 0x41, 0xe1, 0x03, 0x37, 0xb4, 0x39, 0xfa, 0x95, + 0x1c, 0x81, 0x9c, 0xb7, 0x17, 0x88, 0xbf, 0x0f, 0xfd, 0x82, 0x96, 0x42, 0xec, 0xd6, 0xbd, 0xd9, + 0x2e, 0x42, 0x40, 0x0b, 0xfe, 0xe2, 0xd6, 0x81, 0xed, 0xf5, 0xf7, 0xa8, 0x13, 0xa7, 0x11, 0x8a, + 0xe1, 0x19, 0x2a, 0xde, 0x86, 0xae, 0x4a, 0xa3, 0x50, 0x1b, 0x06, 0x50, 0xa6, 0xf8, 0xa6, 0x43, + 0x40, 0x8e, 0x84, 0xab, 0x45, 0x5b, 0xd8, 0xfc, 0x41, 0x5b, 0xd8, 0xba, 0xb8, 0x85, 0xef, 0x43, + 0xdf, 0xcf, 0x64, 0x20, 0x63, 0x1d, 0x7a, 0x91, 0xab, 0xfc, 0x24, 0xb5, 0xaa, 0x77, 0xa5, 0x80, + 0x1f, 0x22, 0x58, 0xfc, 0x0c, 0x2e, 0xfb, 0x49, 0xac, 0x65, 0xac, 0xf3, 0xf2, 0x2c, 0x37, 0x9e, + 0x4e, 0x8e, 0x65, 0x66, 0xd2, 0xcf, 0x97, 0x0c, 0xda, 0x16, 0x69, 0x3d, 0x22, 0xa4, 0xf8, 0x08, + 0xd6, 0x79, 0x7b, 0xe6, 0x3a, 0x71, 0x16, 0x53, 0xd0, 0x4e, 0xcd, 0xf6, 0xd8, 0x82, 0xb5, 0x13, + 0x4f, 0xb9, 0x99, 0x54, 0x61, 0x30, 0xf5, 0x22, 0x23, 0x21, 0x4c, 0xee, 0x64, 0xf5, 0xc4, 0x53, + 0x8e, 0xc1, 0x98, 0xf0, 0x14, 0x45, 0xcf, 0x67, 0x68, 0x29, 0xe9, 0x4d, 0xee, 0x7b, 0xcb, 0x11, + 0xd9, 0x0c, 0xf5, 0x57, 0x9e, 0x3a, 0xb9, 0x7a, 0x17, 0xd6, 0x17, 0x6d, 0xc8, 0xf7, 0xa5, 0x55, + 0x5a, 0xa5, 0xb4, 0x8a, 0x29, 0x41, 0xfb, 0xef, 0x4b, 0x70, 0xc9, 0xee, 0x37, 0x19, 0x9e, 0x39, + 0x53, 0x5f, 0x27, 0x1d, 0x8d, 0xc6, 0x6a, 0xee, 0xcb, 0xb7, 0x1c, 0x60, 0x10, 0x39, 0xee, 0x9b, + 0xd0, 0x37, 0x04, 0x05, 0xf3, 0xf3, 0x5b, 0x7a, 0x41, 0x3e, 0x14, 0x1d, 0x01, 0xfa, 0x83, 0x23, + 0x99, 0xe1, 0x1a, 0x05, 0x54, 0x91, 0x49, 0x5d, 0x88, 0xd9, 0xe9, 0x0f, 0x5a, 0x9c, 0x65, 0x39, + 0x71, 0x1b, 0x84, 0x7c, 0x3a, 0xf5, 0xa2, 0x50, 0x9f, 0xbb, 0xa3, 0x50, 0x46, 0x01, 0xe5, 0xf0, + 0xb8, 0xa6, 0xa8, 0x6f, 0x31, 0x0f, 0x10, 0xb1, 0x17, 0xa8, 0xd2, 0x4c, 0x4c, 0x6a, 0x28, 0x3f, + 0x00, 0x66, 0x26, 0x87, 0x04, 0xde, 0x0b, 0x16, 0x9f, 0x95, 0xfa, 0xe2, 0xb3, 0xf2, 0x1e, 0xac, + 0xcc, 0xef, 0x39, 0xa7, 0x6b, 0x7a, 0x6a, 0x76, 0xbf, 0x17, 0x31, 0x61, 0x73, 0x21, 0x13, 0x9a, + 0x45, 0xff, 0x9f, 0x4b, 0xb0, 0x6e, 0x16, 0x7d, 0x37, 0x89, 0xa6, 0x13, 0xd4, 0xf6, 0x69, 0x18, + 0x8f, 0xd1, 0x20, 0x98, 0x24, 0x6c, 0x16, 0x95, 0xc4, 0x1f, 0x4c, 0x92, 0x5c, 0x16, 0x6f, 0x42, + 0x3f, 0xe4, 0x9e, 0xf9, 0xba, 0xd8, 0x2a, 0x40, 0x03, 0x37, 0xab, 0x82, 0x5c, 0xa8, 0x62, 0x2f, + 0x55, 0x27, 0x89, 0x36, 0xa4, 0x24, 0xc4, 0x79, 0xcd, 0x57, 0x2d, 0x8a, 0xa8, 0xc9, 0x3a, 0xbd, + 0x0d, 0xc2, 0x9f, 0x66, 0x19, 0x9e, 0x8f, 0x12, 0x39, 0x27, 0x44, 0xfa, 0x06, 0x53, 0x50, 0xbf, + 0x0d, 0x8d, 0x49, 0x52, 0x58, 0x24, 0x33, 0xc6, 0xa5, 0x53, 0x9f, 0x24, 0xc4, 0x21, 0x57, 0xd1, + 0x6a, 0x7a, 0x3a, 0x0d, 0x33, 0x19, 0x58, 0x3d, 0x6c, 0xdb, 0x46, 0x49, 0x9f, 0x84, 0x41, 0x20, + 0x63, 0x13, 0x8c, 0x6f, 0x86, 0xea, 0x2b, 0x6a, 0x53, 0x91, 0x9c, 0x1c, 0x79, 0xd3, 0x48, 0xbb, + 0xf1, 0x34, 0xa2, 0x53, 0x11, 0x99, 0xd2, 0xad, 0x15, 0x83, 0x78, 0x34, 0x8d, 0xf0, 0x44, 0x44, + 0x66, 0x4b, 0x49, 0x97, 0x20, 0x0b, 0xba, 0x27, 0x61, 0xac, 0x49, 0x54, 0xb4, 0x68, 0x4b, 0x11, + 0x81, 0x4c, 0xf8, 0x55, 0x18, 0xeb, 0xe1, 0x5f, 0x2c, 0xc1, 0x86, 0x59, 0xf8, 0x43, 0xb3, 0x00, + 0x46, 0x3f, 0x93, 0xc7, 0x60, 0x97, 0xcb, 0xe4, 0x4a, 0xaa, 0x0e, 0x58, 0xd0, 0x1e, 0x4d, 0xb8, + 0xe0, 0xae, 0x25, 0x53, 0xd2, 0x65, 0xf9, 0xea, 0x36, 0x88, 0x0b, 0x7c, 0xa5, 0x4c, 0xcc, 0xaa, + 0x3f, 0xc7, 0x58, 0x4a, 0x7c, 0x0a, 0x1b, 0x13, 0xa9, 0x3d, 0x3a, 0x08, 0x51, 0xe2, 0x7b, 0xd4, + 0x8b, 0x8e, 0x3c, 0x2f, 0xf7, 0xba, 0xc5, 0x3e, 0x34, 0x48, 0x3c, 0xf4, 0xf8, 0x8e, 0x89, 0x17, + 0x87, 0x23, 0xa9, 0x34, 0xd9, 0x19, 0xdc, 0x83, 0x0d, 0x9f, 0xbe, 0xc5, 0xa0, 0x25, 0x41, 0xd4, + 0x64, 0xb1, 0x8e, 0x78, 0x13, 0xeb, 0x44, 0xd3, 0xc8, 0xe4, 0xc8, 0xec, 0x5d, 0x17, 0xf7, 0x2a, + 0x0e, 0xe3, 0x31, 0x17, 0x07, 0x37, 0xd8, 0xa6, 0xb4, 0xc0, 0xfd, 0x24, 0x90, 0xc3, 0x3f, 0x5f, + 0xce, 0x79, 0xf4, 0xc0, 0xc0, 0x0f, 0xb5, 0xa7, 0xa9, 0x1e, 0x36, 0x9f, 0x3c, 0xeb, 0x48, 0x5e, + 0xab, 0xae, 0x85, 0x72, 0xd9, 0xec, 0x16, 0xac, 0xcd, 0xce, 0x96, 0x69, 0x97, 0x38, 0xe1, 0x59, + 0x9e, 0x6e, 0x5e, 0x66, 0x9b, 0xd3, 0x33, 0xa9, 0xa9, 0x30, 0xb5, 0x50, 0x26, 0xfb, 0xb0, 0x58, + 0x04, 0xe5, 0x2a, 0x19, 0x71, 0xb5, 0xcf, 0xf2, 0xec, 0xa8, 0xea, 0xd0, 0x20, 0xf0, 0x68, 0x16, + 0xe4, 0x69, 0x36, 0x8d, 0x65, 0x60, 0x54, 0xfa, 0x4a, 0x0e, 0x3f, 0x20, 0x30, 0x4e, 0x38, 0x97, + 0x4c, 0xa5, 0xa1, 0xeb, 0x3c, 0x74, 0x60, 0x24, 0x53, 0x31, 0x34, 0xf2, 0x68, 0x41, 0x6f, 0xc6, + 0x66, 0x01, 0xb1, 0x92, 0x53, 0x9b, 0xb1, 0x7f, 0x0e, 0x83, 0x9c, 0x96, 0xff, 0x5d, 0xf1, 0x82, + 0x26, 0x2b, 0x1f, 0xdb, 0x85, 0xfe, 0x66, 0xfe, 0x92, 0x4f, 0x60, 0x63, 0xbe, 0xa3, 0x79, 0x53, + 0x8b, 0xba, 0xad, 0xcd, 0x74, 0x2b, 0xfe, 0x49, 0xbe, 0xbf, 0xbe, 0xe7, 0x9f, 0x48, 0xf7, 0x24, + 0x34, 0x45, 0xa6, 0x55, 0x67, 0xd5, 0xa2, 0x76, 0x11, 0xf3, 0x55, 0xa8, 0xd5, 0x02, 0xfa, 0x49, + 0xa8, 0x94, 0xd1, 0x8a, 0xb3, 0xf4, 0xfb, 0xa1, 0x52, 0xc3, 0x7f, 0x02, 0xd0, 0xb1, 0x96, 0x22, + 0xd5, 0x41, 0xde, 0x2e, 0x1b, 0xfd, 0xed, 0xed, 0xbe, 0xb5, 0xde, 0x91, 0x64, 0x47, 0xeb, 0xcc, + 0xe6, 0x4f, 0xd8, 0x19, 0x98, 0xb1, 0x77, 0x96, 0xc8, 0x40, 0x28, 0xec, 0x9d, 0x1d, 0x58, 0x2d, + 0x59, 0x90, 0xae, 0x4e, 0xb4, 0x17, 0x19, 0xa7, 0xa0, 0x54, 0xd1, 0x52, 0x22, 0x71, 0x56, 0xb0, + 0xc1, 0xb6, 0xc5, 0x11, 0x52, 0xa3, 0xb3, 0xe1, 0x27, 0x91, 0x2d, 0xbc, 0x9b, 0x73, 0x36, 0x10, + 0x43, 0xb9, 0xfa, 0x4c, 0xa2, 0xef, 0xaa, 0x9e, 0x46, 0xe6, 0x04, 0xb5, 0x18, 0x72, 0xf8, 0x34, + 0xca, 0x27, 0x48, 0xc6, 0x7c, 0x9d, 0xfc, 0x18, 0x9a, 0x20, 0x59, 0xe9, 0x1f, 0x42, 0x3b, 0xc9, + 0xc2, 0x71, 0x48, 0xa9, 0x37, 0x36, 0x70, 0xe6, 0x5f, 0x02, 0x4c, 0xb0, 0x8b, 0xaf, 0x1a, 0x42, + 0xdd, 0xa8, 0xff, 0x8b, 0xf9, 0x7b, 0x83, 0x41, 0x83, 0x48, 0xe9, 0x2c, 0xf4, 0x35, 0x4e, 0x87, + 0x4f, 0x24, 0x17, 0x66, 0x75, 0x19, 0x7c, 0xf8, 0x34, 0xa2, 0xec, 0xe3, 0xbb, 0xb0, 0xe2, 0x93, + 0xba, 0xe0, 0x03, 0x15, 0xc9, 0x98, 0xf6, 0xb4, 0xe6, 0x74, 0x19, 0x8c, 0xf3, 0x7b, 0x28, 0x63, + 0x53, 0x04, 0xe6, 0x45, 0x11, 0x7a, 0xac, 0x89, 0x17, 0x98, 0x8c, 0x7d, 0xc7, 0x02, 0x1f, 0x26, + 0x5e, 0x20, 0x7e, 0x01, 0x57, 0x11, 0xe7, 0xca, 0x49, 0xaa, 0xcf, 0x51, 0xbf, 0xc9, 0x2c, 0xf4, + 0x5d, 0x4f, 0x51, 0x06, 0xdf, 0x24, 0xee, 0x37, 0x90, 0xe2, 0x3e, 0x12, 0x3c, 0x62, 0xfc, 0x8e, + 0xfa, 0x56, 0x66, 0x89, 0xf8, 0x96, 0x32, 0x90, 0x8b, 0xcc, 0x77, 0x1b, 0x6a, 0x78, 0xab, 0xd8, + 0xab, 0x97, 0x50, 0x52, 0x85, 0x0c, 0x22, 0x1c, 0x6b, 0xf4, 0x51, 0x7f, 0xf1, 0x35, 0x08, 0xab, + 0xe0, 0x88, 0xf3, 0xb5, 0xa7, 0x4e, 0x15, 0x45, 0x21, 0xda, 0xdb, 0x6f, 0xbc, 0xd2, 0x46, 0x75, + 0xac, 0x66, 0x44, 0x20, 0x02, 0x94, 0xf8, 0x53, 0x58, 0xcf, 0x07, 0x33, 0xb6, 0x0c, 0x0d, 0xc7, + 0x41, 0x8a, 0xeb, 0x17, 0x87, 0x9b, 0x31, 0x81, 0x1c, 0x3b, 0x13, 0x06, 0xf3, 0x90, 0x5f, 0xc2, + 0x8a, 0x1d, 0x92, 0x57, 0x5d, 0x0d, 0xfa, 0x34, 0xda, 0x9b, 0x17, 0x46, 0x9b, 0xd1, 0xed, 0xb9, + 0x7e, 0x66, 0x28, 0xfe, 0xd1, 0x5c, 0x93, 0x5b, 0x2d, 0x43, 0x37, 0x0b, 0xda, 0xdb, 0x37, 0x2e, + 0x8c, 0x34, 0xa7, 0xac, 0x1c, 0x3b, 0x05, 0x0b, 0x17, 0x1f, 0xc3, 0x25, 0x3b, 0x58, 0x42, 0x2e, + 0x9e, 0x1b, 0x26, 0xe4, 0xfd, 0x09, 0x36, 0xb1, 0x0c, 0x92, 0xdd, 0xbf, 0xbd, 0x84, 0xbd, 0xc5, + 0x6b, 0xb6, 0x0b, 0x6b, 0x61, 0xf2, 0x88, 0xf3, 0x3f, 0xb5, 0x46, 0xba, 0x6b, 0x60, 0x48, 0x58, + 0x2f, 0xa3, 0x07, 0x6c, 0xa7, 0xbf, 0x09, 0x7d, 0xaa, 0xa2, 0xc5, 0x6d, 0x4d, 0xb2, 0x20, 0x8c, + 0xbd, 0x68, 0xb0, 0x4e, 0x5c, 0xd3, 0x43, 0xb8, 0x93, 0x3c, 0x7f, 0xcc, 0x50, 0x71, 0x04, 0x1b, + 0xf6, 0x45, 0xb9, 0x98, 0x51, 0xa8, 0x4a, 0x28, 0xec, 0xb9, 0x68, 0xe1, 0x66, 0x14, 0x8e, 0x63, + 0xb7, 0x70, 0x56, 0x0d, 0xdd, 0x83, 0xeb, 0x73, 0x5b, 0x3b, 0xf1, 0xce, 0xdc, 0x89, 0x9c, 0x24, + 0xd9, 0xb9, 0x51, 0x20, 0x1b, 0x24, 0xc0, 0xae, 0xcd, 0x6c, 0xe2, 0xbe, 0x77, 0xb6, 0x4f, 0x34, + 0xac, 0x4e, 0xbe, 0x80, 0xd7, 0xe7, 0x46, 0xe1, 0xa2, 0x54, 0x19, 0x7b, 0xc7, 0x91, 0x0c, 0x06, + 0x97, 0xe9, 0x1f, 0x5d, 0x99, 0x19, 0xe2, 0x10, 0x29, 0xee, 0x33, 0x81, 0x31, 0xe8, 0x8e, 0xa1, + 0x45, 0x61, 0x10, 0x92, 0x86, 0x79, 0x81, 0x70, 0xe5, 0xd5, 0x05, 0xc2, 0x1f, 0x42, 0xc7, 0x58, + 0xfb, 0x2f, 0xab, 0x38, 0x6e, 0x33, 0x1e, 0x9f, 0xd5, 0xf0, 0x36, 0xb4, 0xc8, 0xd4, 0xa7, 0x77, + 0x5c, 0x87, 0x36, 0xdf, 0x36, 0x39, 0x8e, 0x12, 0xff, 0xd4, 0x1a, 0xe7, 0x04, 0xba, 0x8b, 0x90, + 0x21, 0x40, 0xf3, 0x49, 0x1c, 0x26, 0xf1, 0x4e, 0x14, 0x0d, 0xff, 0xa6, 0x0e, 0x2d, 0xb4, 0x09, + 0x28, 0x6e, 0x83, 0x6e, 0x15, 0x6d, 0x1c, 0xe5, 0x72, 0x27, 0x5e, 0x6a, 0x4a, 0xa0, 0xdb, 0x08, + 0x44, 0xaa, 0x7d, 0x2f, 0x9d, 0x4b, 0xf5, 0x2e, 0xcd, 0xa5, 0x7a, 0xdf, 0xe2, 0xbb, 0x4f, 0x5c, + 0xef, 0x26, 0x6d, 0xa1, 0x2c, 0x0d, 0x70, 0x97, 0x41, 0x68, 0xab, 0x10, 0x89, 0x17, 0x91, 0x7d, + 0x83, 0xde, 0x53, 0xa4, 0x4c, 0x56, 0x98, 0xf8, 0x66, 0xc7, 0x20, 0x0e, 0x25, 0xcb, 0xe3, 0x52, + 0xb0, 0xae, 0x36, 0x1f, 0xac, 0xbb, 0x05, 0xe0, 0x27, 0x71, 0x40, 0x26, 0xd4, 0x5c, 0x36, 0x8e, + 0x53, 0xb2, 0x05, 0xf6, 0x07, 0x84, 0x86, 0xdf, 0x83, 0x7e, 0x4e, 0x81, 0x16, 0x92, 0x1f, 0xe7, + 0xfe, 0xa7, 0xa1, 0x72, 0xe4, 0x68, 0x37, 0xd6, 0xf3, 0x31, 0xe4, 0xd6, 0x85, 0x18, 0xf2, 0x4b, + 0x92, 0xf7, 0xf0, 0xa3, 0x6f, 0x90, 0x5c, 0x81, 0x26, 0x55, 0x09, 0x05, 0xd3, 0xd4, 0xc8, 0xea, + 0x46, 0xa8, 0x28, 0xd6, 0xff, 0xb2, 0x38, 0x75, 0xe7, 0xff, 0x55, 0x9c, 0xba, 0xfb, 0xc3, 0xe2, + 0xd4, 0xbd, 0x1f, 0x16, 0xa7, 0x9e, 0x8b, 0xeb, 0xae, 0xcc, 0xa7, 0x83, 0x5e, 0x9a, 0x7c, 0xe9, + 0xbf, 0x34, 0xf9, 0xf2, 0x3d, 0x99, 0x93, 0xd5, 0x57, 0x66, 0x4e, 0x7e, 0x40, 0xea, 0x46, 0x7c, + 0x5f, 0xea, 0xe6, 0x5d, 0x58, 0xd1, 0x99, 0xe7, 0x9f, 0xb2, 0x27, 0x72, 0x2a, 0xcf, 0x95, 0x49, + 0x15, 0x75, 0x09, 0x8c, 0x7e, 0xc8, 0xd7, 0xf2, 0x5c, 0x0d, 0x9f, 0x00, 0x90, 0x8b, 0x46, 0x7f, + 0xed, 0x65, 0xbc, 0x51, 0xf9, 0xd1, 0x85, 0x1d, 0xff, 0xbb, 0x02, 0x70, 0xe8, 0x4d, 0x52, 0x8e, + 0xb1, 0x8a, 0x3f, 0x81, 0xb6, 0xa2, 0x56, 0x39, 0xc3, 0x5e, 0x52, 0x64, 0x05, 0xa9, 0x79, 0xe4, + 0x8b, 0x10, 0x2a, 0x7f, 0x26, 0xb6, 0xe6, 0x11, 0xf2, 0x22, 0xba, 0x9a, 0x25, 0xa0, 0xd8, 0xd7, + 0x4d, 0xe8, 0x19, 0x82, 0x54, 0x66, 0xbe, 0x8c, 0xb9, 0x32, 0xb7, 0xe2, 0x74, 0x19, 0x7a, 0xc0, 0x40, 0xf1, 0x71, 0x4e, 0x66, 0x55, 0xc6, 0xc5, 0x34, 0x91, 0xe9, 0x62, 0x74, 0xc6, 0x70, 0xdb, - 0xfe, 0x15, 0x9a, 0x48, 0x0b, 0x6a, 0xf8, 0xbe, 0xfe, 0x6b, 0xa2, 0x03, 0x4d, 0x33, 0x6a, 0xbf, - 0x22, 0x7a, 0xd0, 0xa6, 0x9b, 0x37, 0x84, 0x5b, 0x1a, 0xfe, 0xd9, 0x2a, 0x74, 0xf6, 0x62, 0xa5, - 0xb3, 0x29, 0xb3, 0x70, 0x71, 0xbf, 0xa4, 0x4e, 0xf7, 0x4b, 0x4c, 0x05, 0x27, 0xff, 0x0d, 0xaa, - 0xe0, 0xfc, 0x10, 0x9a, 0xe6, 0x2a, 0x93, 0x09, 0xbc, 0x2f, 0xbc, 0x07, 0x65, 0x69, 0xc4, 0x16, - 0xb4, 0x02, 0x73, 0xc7, 0xca, 0x94, 0x11, 0x94, 0x2e, 0x3e, 0xd9, 0xdb, 0x57, 0x4e, 0x4e, 0x23, - 0xde, 0x82, 0xaa, 0x37, 0x1e, 0x1b, 0xaf, 0x77, 0xa5, 0x20, 0x25, 0x23, 0xc6, 0x41, 0x9c, 0xb8, - 0x03, 0x6d, 0x12, 0x9f, 0x54, 0x49, 0xd3, 0x98, 0x1f, 0xd3, 0x96, 0xe9, 0xb0, 0x44, 0xa5, 0x98, - 0xfd, 0x1d, 0x68, 0x47, 0x49, 0x92, 0x72, 0x87, 0xe6, 0x7c, 0x07, 0x5b, 0x5c, 0xe1, 0xb4, 0x22, - 0x5b, 0x66, 0xf1, 0x2e, 0x34, 0xd0, 0x3c, 0x4e, 0x52, 0x63, 0x56, 0x96, 0xe6, 0x41, 0x45, 0x06, - 0x4e, 0x5d, 0xe1, 0x8f, 0xd8, 0x06, 0x60, 0xfe, 0xa7, 0x91, 0xdb, 0xf3, 0xcb, 0x91, 0xe7, 0x13, - 0xf1, 0x90, 0xda, 0xd4, 0xe2, 0x5d, 0xe8, 0x73, 0xee, 0xa8, 0xd4, 0x13, 0x6c, 0xfd, 0xa1, 0xed, - 0x39, 0x9b, 0x8e, 0x74, 0x96, 0xb3, 0xd9, 0xf4, 0xe4, 0x07, 0xd0, 0x4c, 0x39, 0x79, 0x42, 0x12, - 0xa6, 0xb3, 0xbd, 0x5a, 0x74, 0x35, 0x59, 0x15, 0xc7, 0x52, 0x88, 0x5f, 0xc3, 0x32, 0xd7, 0xc9, - 0x8d, 0x4c, 0x16, 0x81, 0x22, 0x5f, 0x33, 0xb7, 0x60, 0x66, 0x92, 0x0c, 0x4e, 0x4f, 0xcf, 0xe4, - 0x1c, 0x7e, 0x09, 0xbd, 0xe2, 0xaa, 0x81, 0xef, 0xc5, 0x24, 0x77, 0x28, 0x9a, 0x6f, 0xbb, 0x97, - 0xbd, 0x15, 0xa7, 0x2b, 0xcb, 0xbe, 0xcb, 0x26, 0x34, 0x4c, 0xed, 0x66, 0x9f, 0x7a, 0x95, 0x6e, - 0x1e, 0x73, 0xb5, 0x96, 0x63, 0xf0, 0xb8, 0x96, 0x45, 0x59, 0x1a, 0x19, 0x56, 0x33, 0x6b, 0x99, - 0xd7, 0xa4, 0x39, 0xed, 0xbc, 0x1c, 0x4d, 0xdc, 0x9f, 0x2d, 0x93, 0xe3, 0x72, 0xb0, 0x35, 0xea, - 0x7a, 0x65, 0x41, 0x57, 0xae, 0x0a, 0x73, 0x56, 0xd2, 0xb9, 0x6a, 0xbb, 0xdb, 0xd0, 0x4a, 0xb2, - 0x80, 0x2a, 0x7b, 0x29, 0xd7, 0x4c, 0xeb, 0x49, 0xf1, 0x7c, 0xbe, 0xbf, 0x45, 0xc2, 0xa3, 0x99, - 0x70, 0x03, 0x0d, 0x8b, 0x34, 0x4b, 0xc8, 0x0a, 0x24, 0x11, 0x77, 0xe9, 0xa2, 0x61, 0x61, 0xf0, - 0x24, 0xe0, 0xde, 0x81, 0xa6, 0xad, 0x48, 0xdd, 0xb8, 0x40, 0x69, 0x51, 0xe2, 0x13, 0x58, 0x99, - 0x15, 0x68, 0x6a, 0x70, 0xf9, 0x02, 0xf5, 0xf2, 0x8c, 0xfc, 0x42, 0x6d, 0x5c, 0x8f, 0xc2, 0x49, - 0xa8, 0x07, 0x83, 0x0b, 0xce, 0x0f, 0x23, 0xd0, 0x3f, 0x32, 0x29, 0x82, 0x2b, 0x17, 0xfd, 0x23, - 0x93, 0x46, 0x18, 0x40, 0x33, 0x54, 0x0f, 0xc2, 0x4c, 0xe9, 0xc1, 0x55, 0xab, 0x1d, 0xa9, 0x29, - 0x36, 0xa0, 0x11, 0x2a, 0x54, 0x13, 0x83, 0x6b, 0xf6, 0xc6, 0x1f, 0x29, 0x8d, 0x5b, 0xd0, 0x30, - 0xd5, 0xba, 0x37, 0x2e, 0x9c, 0x68, 0x53, 0x13, 0xef, 0x18, 0x0a, 0xf1, 0x3e, 0x34, 0xa9, 0x54, - 0x33, 0x49, 0x07, 0x6f, 0xcd, 0x73, 0x00, 0xd7, 0x4b, 0x3a, 0x8d, 0x88, 0xeb, 0x26, 0x3f, 0x80, - 0xa6, 0x35, 0x52, 0x86, 0xf3, 0x5c, 0x6d, 0x8c, 0x15, 0xc7, 0x52, 0x88, 0x9b, 0x50, 0x9f, 0xa0, - 0x1c, 0x1b, 0xbc, 0x3d, 0x7f, 0x42, 0x59, 0xbc, 0x31, 0x56, 0xfc, 0x3d, 0xb8, 0x5a, 0x2e, 0x76, - 0xb4, 0x95, 0x90, 0x26, 0x00, 0x78, 0x93, 0xfa, 0xbe, 0xb5, 0x80, 0x55, 0x66, 0x6b, 0x26, 0x9d, - 0xcb, 0xe9, 0x4b, 0x8a, 0x29, 0x3f, 0xcb, 0xc5, 0x3d, 0x9e, 0xae, 0xc1, 0xbb, 0xb6, 0x8c, 0xf2, - 0xa2, 0xc2, 0xb0, 0x4a, 0x80, 0xf4, 0xcc, 0xe7, 0xd0, 0x1d, 0x4d, 0x5f, 0xbc, 0x38, 0xb7, 0xc1, - 0xeb, 0xf7, 0xa8, 0x5f, 0xc9, 0x05, 0x2f, 0xd5, 0x57, 0x3a, 0x9d, 0x51, 0xa9, 0xd8, 0xf2, 0x32, - 0x34, 0xfd, 0xd8, 0xf5, 0x82, 0x20, 0x1b, 0x6c, 0x72, 0x7d, 0xa5, 0x1f, 0xef, 0x04, 0x01, 0x5d, - 0x70, 0x4e, 0x52, 0x49, 0xb7, 0x09, 0xdd, 0x30, 0x18, 0xbc, 0xcf, 0x8a, 0xc7, 0x82, 0xf6, 0x02, - 0xba, 0x01, 0x6d, 0xfd, 0xd6, 0x30, 0x18, 0xdc, 0x32, 0x37, 0xa0, 0x0d, 0x68, 0x2f, 0x40, 0xc3, - 0x13, 0x8d, 0x7c, 0x0b, 0x19, 0x7c, 0xc0, 0x09, 0x81, 0x89, 0x77, 0x76, 0x60, 0x40, 0x78, 0x48, - 0x39, 0xf5, 0x45, 0x62, 0xeb, 0xf6, 0xfc, 0x21, 0xcd, 0xd3, 0xa4, 0x4e, 0x3b, 0xcc, 0x33, 0xa6, - 0x74, 0xb0, 0x49, 0x14, 0xb9, 0xd1, 0xf6, 0xe0, 0xc3, 0x8b, 0x07, 0xdb, 0x64, 0x81, 0xf1, 0x60, - 0xdb, 0x84, 0xf0, 0x36, 0x00, 0xcb, 0x2c, 0x12, 0x38, 0x5b, 0xf3, 0x7d, 0x72, 0x6f, 0xc0, 0xe1, - 0xcb, 0x08, 0x24, 0x6a, 0xb6, 0x01, 0x28, 0xfc, 0xce, 0x7d, 0xee, 0xcc, 0xf7, 0xc9, 0xad, 0x7b, - 0xa7, 0xfd, 0x2c, 0x37, 0xf4, 0xef, 0x40, 0x7b, 0x8a, 0x76, 0x3c, 0x5a, 0xd2, 0x83, 0x8f, 0xe6, - 0x99, 0xd9, 0x9a, 0xf8, 0x4e, 0x6b, 0x6a, 0x9e, 0xf0, 0x25, 0xa4, 0x7b, 0xc8, 0x0c, 0x19, 0x7c, - 0x3c, 0xff, 0x92, 0xdc, 0x0f, 0x70, 0x48, 0x45, 0xb1, 0x4b, 0xf0, 0x19, 0x74, 0x78, 0xd1, 0xb8, - 0xd3, 0xf6, 0x3c, 0x8f, 0x14, 0x76, 0x8d, 0xc3, 0xab, 0xcb, 0xdd, 0x6e, 0x42, 0xdd, 0x4b, 0xd3, - 0xe8, 0x7c, 0xf0, 0xc9, 0x3c, 0x87, 0xef, 0x20, 0xd8, 0x61, 0x2c, 0xb2, 0xd2, 0x64, 0x1a, 0xe9, - 0xd0, 0xde, 0x1f, 0xf8, 0x74, 0x9e, 0x95, 0x4a, 0x17, 0xb2, 0x9c, 0xce, 0xa4, 0x74, 0x3b, 0xeb, - 0x36, 0xb4, 0xd2, 0x44, 0x69, 0x37, 0x98, 0x44, 0x83, 0xcf, 0x2e, 0xa8, 0x11, 0xae, 0x82, 0x77, - 0x9a, 0xa9, 0xb9, 0x46, 0x30, 0x73, 0xa3, 0xf0, 0x67, 0x73, 0x37, 0x0a, 0xb7, 0xa1, 0x3b, 0x49, - 0xe2, 0x71, 0x12, 0x1c, 0xf3, 0xea, 0xff, 0xbc, 0x9c, 0x15, 0xdd, 0x47, 0x0c, 0xad, 0x7c, 0xc7, - 0x10, 0x61, 0x83, 0x7d, 0xbb, 0xdf, 0xd4, 0x5a, 0xab, 0x7d, 0xf1, 0x9b, 0x5a, 0xeb, 0x9d, 0xfe, - 0x4d, 0xa7, 0xa3, 0xe8, 0xab, 0x09, 0x34, 0xc4, 0xf0, 0x33, 0xe8, 0xee, 0xd0, 0x17, 0x23, 0x42, - 0x45, 0x72, 0xf4, 0x26, 0xd4, 0xf2, 0xfa, 0x80, 0x5c, 0x40, 0x13, 0xc5, 0x0b, 0xb9, 0x17, 0x8f, - 0x12, 0x87, 0xd0, 0xc3, 0x7f, 0x55, 0x83, 0x06, 0xe7, 0x6c, 0xbf, 0xff, 0x2e, 0xcb, 0x1b, 0x96, - 0xcb, 0xe2, 0xa2, 0x5c, 0x99, 0x19, 0x8a, 0xd0, 0xf3, 0xf5, 0x91, 0xed, 0xa2, 0xf4, 0x60, 0x1d, - 0xea, 0xec, 0x1a, 0x72, 0x44, 0x99, 0x1b, 0x74, 0xc2, 0xa6, 0xea, 0x84, 0x3e, 0x0b, 0x61, 0x72, - 0x24, 0x35, 0x07, 0x2c, 0x68, 0x2f, 0xa0, 0xd0, 0x91, 0x25, 0xa0, 0x23, 0xdc, 0x30, 0xa1, 0x61, - 0x03, 0xa4, 0x83, 0x6c, 0xcb, 0x1a, 0x9a, 0x2f, 0x29, 0x6b, 0x78, 0x13, 0x6a, 0xb1, 0xad, 0xa4, - 0xcf, 0xf1, 0x74, 0x7d, 0x9c, 0xe0, 0xe2, 0x16, 0xe4, 0x17, 0x70, 0x8c, 0x49, 0xf2, 0xf2, 0x0b, - 0x3a, 0xdb, 0xd0, 0xce, 0xbf, 0x31, 0x62, 0xac, 0x90, 0xf5, 0xad, 0xe2, 0xab, 0x23, 0x47, 0xf6, - 0xc9, 0x29, 0xc8, 0x5e, 0x9d, 0x9c, 0xef, 0xfc, 0xb4, 0xe4, 0x3c, 0xbb, 0x68, 0x7e, 0x12, 0x2b, - 0x6d, 0x82, 0x63, 0xcd, 0x50, 0xed, 0x62, 0x53, 0xfc, 0x11, 0xf4, 0x32, 0xe9, 0x3f, 0x73, 0x27, - 0x6a, 0xcc, 0xaf, 0xe8, 0x95, 0x2f, 0x01, 0x4e, 0xd4, 0xf8, 0x2b, 0x2a, 0x1c, 0x30, 0x1e, 0x53, - 0x07, 0x69, 0xf7, 0xd5, 0x98, 0x46, 0xfd, 0x00, 0x56, 0x27, 0x72, 0x72, 0x2c, 0x33, 0x75, 0x12, - 0xa6, 0x56, 0xd4, 0x2e, 0x53, 0x81, 0x43, 0xbf, 0x40, 0xf0, 0x5c, 0x86, 0xff, 0xa8, 0x02, 0x2d, - 0x5c, 0x45, 0xe4, 0x25, 0x21, 0xa0, 0x36, 0xf1, 0xd3, 0xa9, 0x31, 0x84, 0xe9, 0xd9, 0x7c, 0xb7, - 0x84, 0xb9, 0xc4, 0x7c, 0xb7, 0x84, 0xf6, 0x90, 0x53, 0x3e, 0xf4, 0xcc, 0x57, 0xd6, 0xcf, 0x29, - 0x2a, 0xc8, 0x9c, 0x61, 0x9b, 0xe2, 0x12, 0x34, 0xfc, 0x98, 0xbc, 0x61, 0x4e, 0x9d, 0xd5, 0xfd, - 0x18, 0xbd, 0x60, 0x06, 0x17, 0xd5, 0xd8, 0x75, 0x3f, 0xde, 0x0b, 0xce, 0x86, 0xff, 0xae, 0x02, - 0xab, 0x07, 0x59, 0xe2, 0x4b, 0xa5, 0x1e, 0xa2, 0x22, 0xa7, 0x34, 0x05, 0xbe, 0x91, 0xa2, 0xba, - 0x9c, 0x11, 0xa0, 0x67, 0xe4, 0x61, 0x0e, 0x55, 0xe4, 0xee, 0x46, 0xd5, 0x69, 0x13, 0x84, 0xbc, - 0x8d, 0x1c, 0x5d, 0x4a, 0x7f, 0x33, 0x9a, 0xe2, 0xc1, 0x37, 0x61, 0xb9, 0x48, 0xac, 0x94, 0x32, - 0xf5, 0xc5, 0x35, 0x57, 0x1a, 0xe5, 0x3a, 0x74, 0x4c, 0x3d, 0x07, 0x0d, 0xc3, 0x21, 0x7e, 0x60, - 0xd0, 0xa1, 0x99, 0x05, 0x0b, 0x07, 0xc2, 0x73, 0x50, 0x9f, 0xc5, 0x05, 0xa2, 0x87, 0x7f, 0x1f, - 0xfa, 0x07, 0x99, 0x4c, 0xbd, 0x4c, 0x52, 0x7d, 0x07, 0x2d, 0xf1, 0x06, 0x34, 0x22, 0x19, 0x8f, - 0x4d, 0x4a, 0xbf, 0xea, 0x98, 0x56, 0xfe, 0xc9, 0x99, 0xa5, 0xd2, 0x27, 0x67, 0x70, 0xa9, 0x33, - 0xe9, 0x99, 0x2f, 0xd3, 0xd0, 0x33, 0x1e, 0x41, 0x74, 0x19, 0xd9, 0x2f, 0x6a, 0x39, 0xdc, 0x30, - 0x77, 0xe9, 0x8e, 0xc3, 0x98, 0x6a, 0xe3, 0xe8, 0x2e, 0xdd, 0xdd, 0x30, 0x1e, 0xfe, 0x45, 0x03, - 0x3a, 0x66, 0x3d, 0xe9, 0xe5, 0xbc, 0x97, 0x95, 0x7c, 0x2f, 0xfb, 0x50, 0x55, 0x4f, 0x23, 0xb3, - 0xb9, 0xf8, 0x28, 0x3e, 0x81, 0x6a, 0x14, 0x4e, 0x8c, 0x8b, 0x73, 0x6d, 0x46, 0x5d, 0xcd, 0xee, - 0x8a, 0x61, 0x3c, 0xa4, 0x46, 0x19, 0x49, 0xf7, 0x70, 0x91, 0xc5, 0xcd, 0x4a, 0xa2, 0xea, 0x38, - 0xc3, 0x73, 0x84, 0x6b, 0xe4, 0xf9, 0x54, 0x66, 0x60, 0x85, 0x43, 0xcf, 0x69, 0x1b, 0xc8, 0x5e, - 0x20, 0x3e, 0x85, 0x56, 0x1e, 0xa8, 0xb4, 0x4e, 0x8d, 0x3e, 0x8b, 0xb7, 0x76, 0x1f, 0x1d, 0x9d, - 0xc5, 0x36, 0x12, 0x69, 0x5e, 0x96, 0x53, 0x8a, 0x5f, 0x43, 0x57, 0x49, 0xa5, 0xf8, 0xa2, 0xe4, - 0x28, 0x31, 0x42, 0xe3, 0x52, 0xd9, 0x5f, 0x21, 0x2c, 0xfe, 0x6b, 0x7b, 0x44, 0x54, 0x01, 0x12, - 0x5f, 0xc1, 0xb2, 0xed, 0x1f, 0x25, 0xe3, 0x71, 0x1e, 0x48, 0xbf, 0x76, 0x61, 0x84, 0x87, 0x84, - 0x2e, 0x8d, 0xd3, 0x53, 0x65, 0x84, 0xf8, 0x12, 0x96, 0x53, 0xde, 0x63, 0xd7, 0x94, 0x12, 0xb1, - 0xf0, 0xb9, 0x3a, 0x63, 0x5d, 0xcd, 0xf0, 0x40, 0x71, 0x95, 0xa9, 0x80, 0xab, 0x8b, 0x97, 0x86, - 0x39, 0xb3, 0x32, 0x7b, 0x69, 0x58, 0xc2, 0x86, 0xf9, 0xae, 0xc6, 0x28, 0xf3, 0xe8, 0xf2, 0x23, - 0x57, 0x70, 0xd8, 0x9a, 0xbf, 0x3b, 0x17, 0x76, 0x0c, 0x5f, 0xb8, 0xc5, 0x97, 0x63, 0x1f, 0x98, - 0x2e, 0x54, 0xe0, 0x61, 0x8a, 0x2d, 0xd6, 0xb3, 0x05, 0x28, 0xb1, 0x05, 0x6b, 0xe6, 0x35, 0xf2, - 0x4c, 0xfa, 0x53, 0x73, 0xef, 0x9b, 0x44, 0x54, 0xd7, 0x59, 0x65, 0xd4, 0x7d, 0x8b, 0xd9, 0x0b, - 0xc4, 0xe7, 0x30, 0x50, 0xda, 0xd3, 0x92, 0x26, 0x64, 0xa5, 0x64, 0x38, 0x8e, 0x93, 0x4c, 0x9a, - 0x02, 0x85, 0x8d, 0x1c, 0x6f, 0xa4, 0xe3, 0x1e, 0x61, 0xc5, 0xaf, 0xa1, 0x8f, 0x02, 0x2d, 0x0f, - 0x56, 0xbb, 0x5a, 0x19, 0x3f, 0x6d, 0xb1, 0x40, 0x5e, 0x46, 0x6a, 0xcb, 0x16, 0x47, 0xea, 0xea, - 0x97, 0x70, 0xe5, 0xa5, 0x7f, 0xee, 0xfb, 0x0a, 0x17, 0x7a, 0xe5, 0xfb, 0xa0, 0xff, 0xab, 0x0a, - 0x9d, 0x12, 0xd3, 0xd0, 0xe7, 0x99, 0x94, 0xcc, 0x6c, 0x79, 0x12, 0x3e, 0x23, 0xec, 0x24, 0x51, - 0xb6, 0xda, 0x86, 0x9e, 0x11, 0x96, 0x25, 0x79, 0xd5, 0x01, 0x3d, 0xe3, 0x56, 0x9a, 0x38, 0x80, - 0x59, 0xb8, 0x1a, 0xdf, 0x63, 0x2e, 0x80, 0x7b, 0x01, 0x7d, 0xc7, 0xc9, 0xd3, 0xde, 0xb1, 0xa7, - 0x6c, 0x9d, 0x59, 0xde, 0x46, 0x79, 0xfa, 0x4c, 0x66, 0x38, 0x17, 0x9b, 0x65, 0x35, 0x4d, 0x3c, - 0x6a, 0xb4, 0xb8, 0x2f, 0x92, 0x98, 0x33, 0xac, 0x5d, 0xa7, 0x85, 0x80, 0x6f, 0x93, 0x98, 0xba, - 0x99, 0x83, 0x65, 0x2a, 0x05, 0x6c, 0x13, 0x15, 0xcd, 0xd3, 0xa9, 0x44, 0x07, 0x20, 0xa0, 0x9b, - 0x2a, 0x6d, 0xa7, 0x49, 0x6d, 0x2e, 0x5e, 0x20, 0x4f, 0xe5, 0xb9, 0x17, 0x6a, 0x3a, 0xc1, 0xc9, - 0x54, 0x1b, 0xde, 0x5b, 0x41, 0xc4, 0x37, 0x5e, 0xa8, 0x8f, 0x18, 0x2c, 0x3e, 0x36, 0x17, 0xd0, - 0xca, 0xb4, 0x2e, 0xba, 0x59, 0x1c, 0x5f, 0x14, 0x73, 0xf4, 0x87, 0x92, 0x3e, 0xba, 0x33, 0xf1, - 0x74, 0x16, 0x9e, 0x25, 0x31, 0x1a, 0x1c, 0x3a, 0x7c, 0x26, 0x8b, 0x6f, 0x47, 0xb5, 0x9c, 0xb5, - 0x1c, 0xf9, 0x88, 0x70, 0x94, 0x92, 0x7a, 0x02, 0x9b, 0xf2, 0x2c, 0x8d, 0x42, 0x3f, 0x9c, 0xbb, - 0xfa, 0xe9, 0xfa, 0x9e, 0xd2, 0x6e, 0x26, 0xf5, 0x34, 0x8b, 0x15, 0x85, 0xce, 0x0c, 0x7b, 0xbd, - 0x6d, 0xe9, 0xcb, 0xd7, 0x41, 0x77, 0x3d, 0xa5, 0x1d, 0xa6, 0x7d, 0x34, 0x8d, 0x22, 0x5c, 0x84, - 0x3c, 0x15, 0xc6, 0x55, 0x30, 0x4d, 0xc5, 0x49, 0xb0, 0xe1, 0x7f, 0xa9, 0xc0, 0xea, 0x85, 0x03, - 0x8f, 0x4e, 0x07, 0x1e, 0x76, 0x9b, 0xb9, 0xef, 0x3a, 0x0d, 0x6c, 0xee, 0x05, 0x84, 0xd0, 0x13, - 0x6d, 0x73, 0xf6, 0x88, 0xd0, 0x13, 0x94, 0x66, 0x97, 0xa0, 0xa1, 0xcf, 0x68, 0xcb, 0x59, 0x66, - 0xd7, 0xf5, 0x19, 0xee, 0xf5, 0x0e, 0xb4, 0xa3, 0x64, 0xec, 0x46, 0xf2, 0x99, 0xe4, 0xfb, 0xf8, - 0xcb, 0xdb, 0xef, 0xbc, 0x42, 0xd2, 0x6c, 0x3d, 0x4c, 0xc6, 0x0f, 0x91, 0xd6, 0x69, 0x45, 0xe6, - 0x69, 0xf8, 0x1b, 0x68, 0x59, 0xa8, 0x68, 0x43, 0xfd, 0x9e, 0x3c, 0x9e, 0x8e, 0xfb, 0xaf, 0x89, - 0x16, 0xd4, 0xb0, 0x47, 0xbf, 0x82, 0x4f, 0xdf, 0x78, 0x59, 0xdc, 0x5f, 0x42, 0xf4, 0xfd, 0x2c, - 0x4b, 0xb2, 0x7e, 0x15, 0x1f, 0x0f, 0xbc, 0x38, 0xf4, 0xfb, 0x35, 0x7c, 0x7c, 0xe0, 0x69, 0x2f, - 0xea, 0xd7, 0x87, 0xbf, 0xab, 0x43, 0xeb, 0xc0, 0xbc, 0x5d, 0xdc, 0x83, 0x5e, 0xfe, 0x85, 0xa8, - 0xc5, 0xd1, 0xbf, 0x83, 0xf9, 0x07, 0x8a, 0xfe, 0x75, 0xd3, 0x52, 0x6b, 0xfe, 0x3b, 0x53, 0x4b, - 0x17, 0xbe, 0x33, 0xf5, 0x3a, 0x54, 0x9f, 0x66, 0xe7, 0xb3, 0x45, 0xab, 0x07, 0x91, 0x17, 0x3b, - 0x08, 0x16, 0x1f, 0x43, 0x87, 0xf2, 0x72, 0x5c, 0xa1, 0x68, 0x22, 0x66, 0xe5, 0x4f, 0xba, 0x71, - 0x39, 0x22, 0x20, 0x91, 0x31, 0x73, 0xb7, 0xa0, 0xe5, 0x9f, 0x84, 0x51, 0x90, 0xc9, 0xd8, 0x14, - 0x84, 0x8b, 0x8b, 0x53, 0x76, 0x72, 0x1a, 0xf1, 0x27, 0xd0, 0x0f, 0x8b, 0x88, 0x5f, 0x91, 0x86, - 0x9d, 0x51, 0x1b, 0xa5, 0x98, 0xa0, 0xb3, 0x52, 0x22, 0x27, 0xbb, 0xaa, 0xb8, 0xb0, 0xde, 0x2c, - 0x5f, 0x58, 0xe7, 0x2f, 0x04, 0x91, 0xf1, 0xd3, 0xca, 0xe3, 0x05, 0x68, 0xfb, 0xbc, 0x6b, 0x2c, - 0xd6, 0xf6, 0xbc, 0x83, 0x65, 0xed, 0x2d, 0x63, 0xb9, 0xbe, 0x03, 0xcb, 0x68, 0x09, 0xbb, 0x6c, - 0x40, 0xa3, 0x3a, 0x03, 0xf3, 0x7d, 0x8d, 0xa9, 0x3a, 0xb9, 0x87, 0x26, 0x34, 0x32, 0xe3, 0x4d, - 0x58, 0xb6, 0xff, 0xc5, 0x54, 0xed, 0x75, 0x4c, 0x9a, 0xd6, 0x40, 0xb9, 0x6e, 0x6f, 0x0b, 0xd6, - 0xfc, 0x13, 0x2f, 0x8e, 0x65, 0xe4, 0x1e, 0x4f, 0x47, 0x23, 0x6b, 0xbb, 0xf0, 0x67, 0x4e, 0x56, - 0x0d, 0xea, 0x2e, 0x61, 0xc8, 0x84, 0x19, 0x42, 0x2f, 0x0e, 0x23, 0xfb, 0x09, 0xb3, 0x98, 0xed, - 0xcc, 0xba, 0xd3, 0x89, 0xc3, 0x88, 0x3f, 0x5a, 0x46, 0xdf, 0x5a, 0xeb, 0x4f, 0xa7, 0x61, 0xa0, - 0x5c, 0x9d, 0xd8, 0x8f, 0x2b, 0x99, 0xd8, 0x7d, 0x29, 0x1a, 0xf6, 0x64, 0x1a, 0x06, 0x47, 0x89, - 0xf9, 0xbc, 0x52, 0x8f, 0xe8, 0x6d, 0x73, 0xf8, 0x05, 0x74, 0xcb, 0xbc, 0x83, 0xbc, 0x48, 0xe1, - 0x8a, 0xfe, 0x6b, 0x02, 0xa0, 0xf1, 0x28, 0xc9, 0x26, 0x5e, 0xd4, 0xaf, 0xe0, 0x33, 0x0b, 0xf3, - 0xfe, 0x92, 0xe8, 0x42, 0xcb, 0xba, 0xdf, 0xfd, 0xaa, 0x49, 0x88, 0xfd, 0x12, 0x5a, 0xf6, 0x9b, - 0x51, 0xf4, 0xbd, 0x9d, 0x24, 0x90, 0xec, 0x4f, 0x98, 0xea, 0x48, 0x04, 0x90, 0x2f, 0x61, 0xbf, - 0xad, 0xb7, 0x54, 0x7c, 0x5b, 0x6f, 0xf8, 0xa7, 0xd0, 0x2d, 0x4f, 0xd1, 0x86, 0x78, 0x2b, 0x45, - 0x88, 0x77, 0x41, 0x2f, 0xca, 0xda, 0x67, 0xc9, 0xc4, 0x2d, 0x99, 0xbc, 0x2d, 0x04, 0xe0, 0x6b, - 0x86, 0xff, 0xb0, 0x02, 0x75, 0x72, 0x4a, 0xc9, 0xc8, 0xc1, 0x87, 0xe2, 0x04, 0xd5, 0x9d, 0x36, - 0x41, 0xfe, 0x2f, 0x6e, 0xa3, 0xe5, 0x29, 0xbf, 0xda, 0x2b, 0x53, 0x7e, 0xb7, 0xfe, 0x75, 0x05, - 0x1a, 0xfc, 0x3d, 0x43, 0xb1, 0x0a, 0xbd, 0x27, 0xf1, 0x69, 0x9c, 0x3c, 0x8f, 0x19, 0xd0, 0x7f, - 0x4d, 0xac, 0xc1, 0x8a, 0x5d, 0x7b, 0xf3, 0xe1, 0xc4, 0x7e, 0x45, 0xf4, 0xa1, 0x4b, 0xbb, 0x6b, - 0x21, 0x4b, 0xe2, 0x75, 0x18, 0x18, 0x3b, 0xe5, 0x1e, 0x0a, 0xe3, 0x44, 0x87, 0xa3, 0x73, 0x8b, - 0xad, 0x8a, 0x15, 0xe8, 0x1c, 0xea, 0x24, 0x3d, 0x94, 0x71, 0x10, 0xc6, 0xe3, 0x7e, 0x4d, 0x0c, - 0x60, 0xdd, 0x8e, 0xca, 0xdf, 0xfc, 0x7b, 0x10, 0xc6, 0xa1, 0x3a, 0xe9, 0xd7, 0xc5, 0x35, 0xb8, - 0xbc, 0x08, 0xb3, 0xe3, 0x9f, 0xf6, 0x1b, 0x62, 0x1d, 0xfa, 0x16, 0x79, 0xd7, 0x7c, 0xbd, 0xae, - 0xdf, 0xbc, 0xf5, 0x29, 0x88, 0x8b, 0x1f, 0x0e, 0xc4, 0x77, 0x3e, 0x94, 0x63, 0xcf, 0x3f, 0xdf, - 0x8d, 0x12, 0x85, 0xac, 0xd2, 0x83, 0x76, 0x31, 0x56, 0xe5, 0xd6, 0x03, 0x68, 0xf0, 0x97, 0x1e, - 0x4b, 0xff, 0x9a, 0x01, 0xfd, 0xd7, 0xb0, 0x33, 0x2a, 0xa2, 0x30, 0x1e, 0x3f, 0x92, 0x67, 0x9a, - 0xc5, 0xe3, 0x43, 0x4f, 0xe9, 0xfe, 0x92, 0x58, 0x06, 0x30, 0x7f, 0xec, 0x7e, 0x1c, 0xf4, 0xab, - 0x77, 0x77, 0xff, 0xf2, 0xf7, 0x6f, 0x56, 0xfe, 0xea, 0xf7, 0x6f, 0x56, 0xfe, 0xf3, 0xef, 0xdf, - 0x7c, 0xed, 0xb7, 0x7f, 0xfd, 0x66, 0xe5, 0xdb, 0x8f, 0x4b, 0xdf, 0xb1, 0x34, 0xfa, 0x89, 0x6a, - 0x2f, 0xee, 0xe4, 0xca, 0xea, 0x4e, 0x7a, 0x3a, 0xbe, 0x93, 0x1e, 0xdf, 0xb1, 0xdc, 0x7f, 0xdc, - 0xa0, 0xcf, 0x53, 0x7e, 0xf2, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xff, 0x21, 0x28, 0x6a, 0x1d, - 0x53, 0x00, 0x00, + 0xfe, 0x15, 0x9a, 0x48, 0x13, 0x96, 0xf1, 0x7d, 0xfd, 0xd7, 0x44, 0x1b, 0x1a, 0x66, 0xd4, 0x7e, + 0x45, 0x74, 0xa1, 0x45, 0x37, 0x6f, 0x08, 0xb7, 0x34, 0xfc, 0xb3, 0x55, 0x68, 0xef, 0xc5, 0x4a, + 0x67, 0x53, 0x66, 0xe1, 0xe2, 0x7e, 0x49, 0x8d, 0xee, 0x97, 0x98, 0x0a, 0x4e, 0xfe, 0x1b, 0x54, + 0xc1, 0xf9, 0x21, 0x34, 0xcc, 0x55, 0x26, 0x13, 0x78, 0x5f, 0x78, 0x0f, 0xca, 0xd2, 0x88, 0x2d, + 0x68, 0x06, 0xe6, 0x8e, 0x95, 0x29, 0x23, 0x28, 0x5d, 0x7c, 0xb2, 0xb7, 0xaf, 0x9c, 0x9c, 0x46, + 0xbc, 0x05, 0x55, 0x6f, 0x3c, 0x36, 0x5e, 0xef, 0x4a, 0x41, 0x4a, 0x46, 0x8c, 0x83, 0x38, 0x71, + 0x07, 0x5a, 0x24, 0x3e, 0xa9, 0x92, 0xa6, 0x3e, 0x3f, 0xa6, 0x2d, 0xd3, 0x61, 0x89, 0x4a, 0x31, + 0xfb, 0x3b, 0xd0, 0x8a, 0x92, 0x24, 0xe5, 0x0e, 0x8d, 0xf9, 0x0e, 0xb6, 0xb8, 0xc2, 0x69, 0x46, + 0xb6, 0xcc, 0xe2, 0x5d, 0xa8, 0xa3, 0x79, 0x9c, 0xa4, 0xc6, 0xac, 0x2c, 0xcd, 0x83, 0x8a, 0x0c, + 0x9c, 0x9a, 0xc2, 0x1f, 0xb1, 0x0d, 0xc0, 0xfc, 0x4f, 0x23, 0xb7, 0xe6, 0x97, 0x23, 0xcf, 0x27, + 0xe2, 0x21, 0xb5, 0xa9, 0xc5, 0xbb, 0xd0, 0xe7, 0xdc, 0x51, 0xa9, 0x27, 0xd8, 0xfa, 0x43, 0xdb, + 0x73, 0x36, 0x1d, 0xe9, 0xf4, 0xb2, 0xd9, 0xf4, 0xe4, 0x07, 0xd0, 0x48, 0x39, 0x79, 0x42, 0x12, + 0xa6, 0xbd, 0xbd, 0x5a, 0x74, 0x35, 0x59, 0x15, 0xc7, 0x52, 0x88, 0x5f, 0x43, 0x8f, 0xeb, 0xe4, + 0x46, 0x26, 0x8b, 0x40, 0x91, 0xaf, 0x99, 0x5b, 0x30, 0x33, 0x49, 0x06, 0xa7, 0xab, 0x67, 0x72, + 0x0e, 0xbf, 0x84, 0x6e, 0x71, 0xd5, 0xc0, 0xf7, 0x62, 0x92, 0x3b, 0x14, 0xcd, 0xb7, 0xdd, 0xcb, + 0xde, 0x8a, 0xd3, 0x91, 0x65, 0xdf, 0x65, 0x13, 0xea, 0xa6, 0x76, 0xb3, 0x4f, 0xbd, 0x4a, 0x37, + 0x8f, 0xb9, 0x5a, 0xcb, 0x31, 0x78, 0x5c, 0xcb, 0xa2, 0x2c, 0x8d, 0x0c, 0xab, 0x99, 0xb5, 0xcc, + 0x6b, 0xd2, 0x9c, 0x56, 0x5e, 0x8e, 0x26, 0xee, 0xcf, 0x96, 0xc9, 0x71, 0x39, 0xd8, 0x1a, 0x75, + 0xbd, 0xb2, 0xa0, 0x2b, 0x57, 0x85, 0x39, 0x2b, 0xe9, 0x5c, 0xb5, 0xdd, 0x6d, 0x68, 0x26, 0x59, + 0x40, 0x95, 0xbd, 0x94, 0x6b, 0xa6, 0xf5, 0xa4, 0x78, 0x3e, 0xdf, 0xdf, 0x22, 0xe1, 0xd1, 0x48, + 0xb8, 0x81, 0x86, 0x45, 0x9a, 0x25, 0x64, 0x05, 0x92, 0x88, 0xbb, 0x74, 0xd1, 0xb0, 0x30, 0x78, + 0x12, 0x70, 0xef, 0x40, 0xc3, 0x56, 0xa4, 0x6e, 0x5c, 0xa0, 0xb4, 0x28, 0xf1, 0x09, 0xac, 0xcc, + 0x0a, 0x34, 0x35, 0xb8, 0x7c, 0x81, 0xba, 0x37, 0x23, 0xbf, 0x50, 0x1b, 0xd7, 0xa2, 0x70, 0x12, + 0xea, 0xc1, 0xe0, 0x82, 0xf3, 0xc3, 0x08, 0xf4, 0x8f, 0x4c, 0x8a, 0xe0, 0xca, 0x45, 0xff, 0xc8, + 0xa4, 0x11, 0x06, 0xd0, 0x08, 0xd5, 0x83, 0x30, 0x53, 0x7a, 0x70, 0xd5, 0x6a, 0x47, 0x6a, 0x8a, + 0x0d, 0xa8, 0x87, 0x0a, 0xd5, 0xc4, 0xe0, 0x9a, 0xbd, 0xf1, 0x47, 0x4a, 0xe3, 0x16, 0xd4, 0x4d, + 0xb5, 0xee, 0x8d, 0x0b, 0x27, 0xda, 0xd4, 0xc4, 0x3b, 0x86, 0x42, 0xbc, 0x0f, 0x0d, 0x2a, 0xd5, + 0x4c, 0xd2, 0xc1, 0x5b, 0xf3, 0x1c, 0xc0, 0xf5, 0x92, 0x4e, 0x3d, 0xe2, 0xba, 0xc9, 0x0f, 0xa0, + 0x61, 0x8d, 0x94, 0xe1, 0x3c, 0x57, 0x1b, 0x63, 0xc5, 0xb1, 0x14, 0xe2, 0x26, 0xd4, 0x26, 0x28, + 0xc7, 0x06, 0x6f, 0xcf, 0x9f, 0x50, 0x16, 0x6f, 0x8c, 0x15, 0xff, 0x00, 0xae, 0x96, 0x8b, 0x1d, + 0x6d, 0x25, 0xa4, 0x09, 0x00, 0xde, 0xa4, 0xbe, 0x6f, 0x2d, 0x60, 0x95, 0xd9, 0x9a, 0x49, 0xe7, + 0x72, 0xfa, 0x92, 0x62, 0xca, 0xcf, 0x72, 0x71, 0x8f, 0xa7, 0x6b, 0xf0, 0xae, 0x2d, 0xa3, 0xbc, + 0xa8, 0x30, 0xac, 0x12, 0x20, 0x3d, 0xf3, 0x39, 0x74, 0x46, 0xd3, 0x17, 0x2f, 0xce, 0x6d, 0xf0, + 0xfa, 0x3d, 0xea, 0x57, 0x72, 0xc1, 0x4b, 0xf5, 0x95, 0x4e, 0x7b, 0x54, 0x2a, 0xb6, 0xbc, 0x0c, + 0x0d, 0x3f, 0x76, 0xbd, 0x20, 0xc8, 0x06, 0x9b, 0x5c, 0x5f, 0xe9, 0xc7, 0x3b, 0x41, 0x40, 0x17, + 0x9c, 0x93, 0x54, 0xd2, 0x6d, 0x42, 0x37, 0x0c, 0x06, 0xef, 0xb3, 0xe2, 0xb1, 0xa0, 0xbd, 0x80, + 0x6e, 0x40, 0x5b, 0xbf, 0x35, 0x0c, 0x06, 0xb7, 0xcc, 0x0d, 0x68, 0x03, 0xda, 0x0b, 0xd0, 0xf0, + 0x44, 0x23, 0xdf, 0x42, 0x06, 0x1f, 0x70, 0x42, 0x60, 0xe2, 0x9d, 0x1d, 0x18, 0x10, 0x1e, 0x52, + 0x4e, 0x7d, 0x91, 0xd8, 0xba, 0x3d, 0x7f, 0x48, 0xf3, 0x34, 0xa9, 0xd3, 0x0a, 0xf3, 0x8c, 0x29, + 0x1d, 0x6c, 0x12, 0x45, 0x6e, 0xb4, 0x3d, 0xf8, 0xf0, 0xe2, 0xc1, 0x36, 0x59, 0x60, 0x3c, 0xd8, + 0x36, 0x21, 0xbc, 0x0d, 0xc0, 0x32, 0x8b, 0x04, 0xce, 0xd6, 0x7c, 0x9f, 0xdc, 0x1b, 0x70, 0xf8, + 0x32, 0x02, 0x89, 0x9a, 0x6d, 0x00, 0x0a, 0xbf, 0x73, 0x9f, 0x3b, 0xf3, 0x7d, 0x72, 0xeb, 0xde, + 0x69, 0x3d, 0xcb, 0x0d, 0xfd, 0x3b, 0xd0, 0x9a, 0xa2, 0x1d, 0x8f, 0x96, 0xf4, 0xe0, 0xa3, 0x79, + 0x66, 0xb6, 0x26, 0xbe, 0xd3, 0x9c, 0x9a, 0x27, 0x7c, 0x09, 0xe9, 0x1e, 0x32, 0x43, 0x06, 0x1f, + 0xcf, 0xbf, 0x24, 0xf7, 0x03, 0x1c, 0x52, 0x51, 0xec, 0x12, 0x7c, 0x06, 0x6d, 0x5e, 0x34, 0xee, + 0xb4, 0x3d, 0xcf, 0x23, 0x85, 0x5d, 0xe3, 0xf0, 0xea, 0x72, 0xb7, 0x9b, 0x50, 0xf3, 0xd2, 0x34, + 0x3a, 0x1f, 0x7c, 0x32, 0xcf, 0xe1, 0x3b, 0x08, 0x76, 0x18, 0x8b, 0xac, 0x34, 0x99, 0x46, 0x3a, + 0xb4, 0xf7, 0x07, 0x3e, 0x9d, 0x67, 0xa5, 0xd2, 0x85, 0x2c, 0xa7, 0x3d, 0x29, 0xdd, 0xce, 0xba, + 0x0d, 0xcd, 0x34, 0x51, 0xda, 0x0d, 0x26, 0xd1, 0xe0, 0xb3, 0x0b, 0x6a, 0x84, 0xab, 0xe0, 0x9d, + 0x46, 0x6a, 0xae, 0x11, 0xcc, 0xdc, 0x28, 0xfc, 0xd9, 0xdc, 0x8d, 0xc2, 0x6d, 0xe8, 0x4c, 0x92, + 0x78, 0x9c, 0x04, 0xc7, 0xbc, 0xfa, 0x3f, 0x2f, 0x67, 0x45, 0xf7, 0x11, 0x43, 0x2b, 0xdf, 0x36, + 0x44, 0xd8, 0x60, 0xdf, 0xee, 0x37, 0xcb, 0xcd, 0xd5, 0xbe, 0xf8, 0xcd, 0x72, 0xf3, 0x9d, 0xfe, + 0x4d, 0xa7, 0xad, 0xe8, 0xab, 0x09, 0x34, 0xc4, 0xf0, 0x33, 0xe8, 0xec, 0xd0, 0x17, 0x23, 0x42, + 0x45, 0x72, 0xf4, 0x26, 0x2c, 0xe7, 0xf5, 0x01, 0xb9, 0x80, 0x26, 0x8a, 0x17, 0x72, 0x2f, 0x1e, + 0x25, 0x0e, 0xa1, 0x87, 0xff, 0x6a, 0x19, 0xea, 0x9c, 0xb3, 0xfd, 0xfe, 0xbb, 0x2c, 0x6f, 0x58, + 0x2e, 0x8b, 0x8b, 0x72, 0x65, 0x66, 0x28, 0x42, 0xcf, 0xd7, 0x47, 0xb6, 0x8a, 0xd2, 0x83, 0x75, + 0xa8, 0xb1, 0x6b, 0xc8, 0x11, 0x65, 0x6e, 0xd0, 0x09, 0x9b, 0xaa, 0x13, 0xfa, 0x2c, 0x84, 0xc9, + 0x91, 0x2c, 0x3b, 0x60, 0x41, 0x7b, 0x01, 0x85, 0x8e, 0x2c, 0x01, 0x1d, 0xe1, 0xba, 0x09, 0x0d, + 0x1b, 0x20, 0x1d, 0x64, 0x5b, 0xd6, 0xd0, 0x78, 0x49, 0x59, 0xc3, 0x9b, 0xb0, 0x1c, 0xdb, 0x4a, + 0xfa, 0x1c, 0x4f, 0xd7, 0xc7, 0x09, 0x2e, 0x6e, 0x41, 0x7e, 0x01, 0xc7, 0x98, 0x24, 0x2f, 0xbf, + 0xa0, 0xb3, 0x0d, 0xad, 0xfc, 0x1b, 0x23, 0xc6, 0x0a, 0x59, 0xdf, 0x2a, 0xbe, 0x3a, 0x72, 0x64, + 0x9f, 0x9c, 0x82, 0xec, 0xd5, 0xc9, 0xf9, 0xf6, 0x4f, 0x4b, 0xce, 0xb3, 0x8b, 0xe6, 0x27, 0xb1, + 0xd2, 0x26, 0x38, 0xd6, 0x08, 0xd5, 0x2e, 0x36, 0xc5, 0x1f, 0x41, 0x37, 0x93, 0xfe, 0x33, 0x77, + 0xa2, 0xc6, 0xfc, 0x8a, 0x6e, 0xf9, 0x12, 0xe0, 0x44, 0x8d, 0xbf, 0xa2, 0xc2, 0x01, 0xe3, 0x31, + 0xb5, 0x91, 0x76, 0x5f, 0x8d, 0x69, 0xd4, 0x0f, 0x60, 0x75, 0x22, 0x27, 0xc7, 0x32, 0x53, 0x27, + 0x61, 0x6a, 0x45, 0x6d, 0x8f, 0x0a, 0x1c, 0xfa, 0x05, 0x82, 0xe7, 0x32, 0xfc, 0xc7, 0x15, 0x68, + 0xe2, 0x2a, 0x22, 0x2f, 0x09, 0x01, 0xcb, 0x13, 0x3f, 0x9d, 0x1a, 0x43, 0x98, 0x9e, 0xcd, 0x77, + 0x4b, 0x98, 0x4b, 0xcc, 0x77, 0x4b, 0x68, 0x0f, 0x39, 0xe5, 0x43, 0xcf, 0x7c, 0x65, 0xfd, 0x9c, + 0xa2, 0x82, 0xcc, 0x19, 0xb6, 0x29, 0x2e, 0x41, 0xdd, 0x8f, 0xc9, 0x1b, 0xe6, 0xd4, 0x59, 0xcd, + 0x8f, 0xd1, 0x0b, 0x66, 0x70, 0x51, 0x8d, 0x5d, 0xf3, 0xe3, 0xbd, 0xe0, 0x6c, 0xf8, 0xef, 0x2a, + 0xb0, 0x7a, 0x90, 0x25, 0xbe, 0x54, 0xea, 0x21, 0x2a, 0x72, 0x4a, 0x53, 0xe0, 0x1b, 0x29, 0xaa, + 0xcb, 0x19, 0x01, 0x7a, 0x46, 0x1e, 0xe6, 0x50, 0x45, 0xee, 0x6e, 0x54, 0x9d, 0x16, 0x41, 0xc8, + 0xdb, 0xc8, 0xd1, 0xa5, 0xf4, 0x37, 0xa3, 0x29, 0x1e, 0x7c, 0x13, 0x7a, 0x45, 0x62, 0xa5, 0x94, + 0xa9, 0x2f, 0xae, 0xb9, 0xd2, 0x28, 0xd7, 0xa1, 0x6d, 0xea, 0x39, 0x68, 0x18, 0x0e, 0xf1, 0x03, + 0x83, 0x0e, 0xcd, 0x2c, 0x58, 0x38, 0x10, 0x9e, 0x83, 0xfa, 0x2c, 0x2e, 0x10, 0x3d, 0xfc, 0xcb, + 0x0a, 0xf4, 0x0f, 0x32, 0x99, 0x7a, 0x99, 0xa4, 0x02, 0x0f, 0x5a, 0xe3, 0x0d, 0xa8, 0x47, 0x32, + 0x1e, 0x9b, 0x9c, 0x7e, 0xd5, 0x31, 0xad, 0xfc, 0x9b, 0x33, 0x4b, 0xa5, 0x6f, 0xce, 0xe0, 0x5a, + 0x67, 0xd2, 0x33, 0x9f, 0xa6, 0xa1, 0x67, 0x3c, 0x83, 0xe8, 0x33, 0xb2, 0x63, 0xd4, 0x74, 0xb8, + 0x61, 0x2e, 0xd3, 0x1d, 0x87, 0x31, 0x15, 0xc7, 0xd1, 0x65, 0xba, 0xbb, 0x61, 0x4c, 0x89, 0x3d, + 0x02, 0xa3, 0xf2, 0x57, 0x3a, 0x0b, 0xe3, 0x31, 0x85, 0x3a, 0x9a, 0x4e, 0x8f, 0x08, 0xbc, 0xec, + 0xfc, 0x90, 0xa0, 0xc3, 0xbf, 0xa8, 0x43, 0xdb, 0x2c, 0x3d, 0x4d, 0x93, 0xb7, 0xbd, 0x92, 0x6f, + 0x7b, 0x1f, 0xaa, 0xea, 0x69, 0x64, 0xf8, 0x00, 0x1f, 0xc5, 0x27, 0x50, 0x8d, 0xc2, 0x89, 0xf1, + 0x86, 0xae, 0xcd, 0x68, 0xb6, 0xd9, 0x0d, 0x34, 0x3c, 0x8a, 0xd4, 0x28, 0x4e, 0xe9, 0xca, 0x2e, + 0x9e, 0x06, 0xb3, 0xe8, 0xa8, 0x65, 0xce, 0xf0, 0xc8, 0xe1, 0x72, 0x7a, 0x3e, 0x55, 0x24, 0x58, + 0x39, 0xd2, 0x75, 0x5a, 0x06, 0xb2, 0x17, 0x88, 0x4f, 0xa1, 0x99, 0xc7, 0x34, 0xad, 0xff, 0xa3, + 0xcf, 0xe2, 0xad, 0xdd, 0x47, 0x47, 0x67, 0xb1, 0x0d, 0x5a, 0x9a, 0x97, 0xe5, 0x94, 0xe2, 0xd7, + 0xd0, 0x51, 0x52, 0x29, 0xbe, 0x53, 0x39, 0x4a, 0x8c, 0x7c, 0xb9, 0x54, 0x76, 0x6d, 0x08, 0x8b, + 0xff, 0xda, 0x9e, 0x26, 0x55, 0x80, 0xc4, 0x57, 0xd0, 0xb3, 0xfd, 0xa3, 0x64, 0x3c, 0xce, 0x63, + 0xee, 0xd7, 0x2e, 0x8c, 0xf0, 0x90, 0xd0, 0xa5, 0x71, 0xba, 0xaa, 0x8c, 0x10, 0x5f, 0x42, 0x2f, + 0x65, 0x6e, 0x70, 0x4d, 0xd5, 0x11, 0xcb, 0xa9, 0xab, 0x33, 0x86, 0xd8, 0x0c, 0xb7, 0x14, 0xb7, + 0x9e, 0x0a, 0xb8, 0xba, 0x78, 0xbf, 0x98, 0x93, 0x30, 0xb3, 0xf7, 0x8b, 0x25, 0x6c, 0x98, 0x4f, + 0x70, 0x8c, 0x32, 0x8f, 0xee, 0x49, 0x72, 0xb1, 0x87, 0x2d, 0x0f, 0xbc, 0x73, 0x61, 0xc7, 0xf0, + 0x85, 0x5b, 0x7c, 0x8f, 0xf6, 0x81, 0xe9, 0x42, 0xb5, 0x20, 0xa6, 0x2e, 0x63, 0x3d, 0x5b, 0x80, + 0x12, 0x5b, 0xb0, 0x66, 0x5e, 0x23, 0xcf, 0xa4, 0x3f, 0x35, 0x57, 0xc4, 0x49, 0x9a, 0x75, 0x9c, + 0x55, 0x46, 0xdd, 0xb7, 0x98, 0xbd, 0x40, 0x7c, 0x0e, 0x03, 0xa5, 0x3d, 0x2d, 0x69, 0x42, 0x56, + 0xa0, 0x86, 0xe3, 0x38, 0xc9, 0xa4, 0xa9, 0x65, 0xd8, 0xc8, 0xf1, 0x46, 0x90, 0xee, 0x11, 0x56, + 0xfc, 0x1a, 0xfa, 0x28, 0xfb, 0xf2, 0xb8, 0xb6, 0xab, 0x95, 0x71, 0xe9, 0x16, 0xcb, 0xee, 0x1e, + 0x52, 0x5b, 0xb6, 0x38, 0x52, 0x57, 0xbf, 0x84, 0x2b, 0x2f, 0xfd, 0x73, 0xdf, 0x57, 0xe3, 0xd0, + 0x2d, 0x5f, 0x1d, 0xfd, 0x5f, 0x55, 0x68, 0x97, 0x98, 0x86, 0xbe, 0xe4, 0xa4, 0x64, 0x66, 0x2b, + 0x99, 0xf0, 0x19, 0x61, 0x27, 0x89, 0xb2, 0x85, 0x39, 0xf4, 0x8c, 0xb0, 0x2c, 0xc9, 0x0b, 0x14, + 0xe8, 0x19, 0xb7, 0xd2, 0x84, 0x0c, 0xcc, 0xc2, 0x2d, 0xf3, 0x95, 0xe7, 0x02, 0xb8, 0x17, 0xd0, + 0x27, 0x9f, 0x3c, 0xed, 0x1d, 0x7b, 0xca, 0x96, 0xa4, 0xe5, 0x6d, 0x14, 0xbd, 0xcf, 0x64, 0x86, + 0x73, 0xb1, 0x09, 0x59, 0xd3, 0xc4, 0xa3, 0x46, 0x8b, 0xfb, 0x22, 0x89, 0x39, 0x19, 0xdb, 0x71, + 0x9a, 0x08, 0xf8, 0x36, 0x89, 0xa9, 0x9b, 0x39, 0x58, 0xa6, 0xa8, 0xc0, 0x36, 0x51, 0x27, 0x3d, + 0x9d, 0x4a, 0xf4, 0x15, 0x02, 0xba, 0xd4, 0xd2, 0x72, 0x1a, 0xd4, 0xe6, 0x3a, 0x07, 0x72, 0x6a, + 0x9e, 0x7b, 0xa1, 0xa6, 0x13, 0x9c, 0x4c, 0xb5, 0xe1, 0xbd, 0x15, 0x44, 0x7c, 0xe3, 0x85, 0xfa, + 0x88, 0xc1, 0xe2, 0x63, 0x73, 0x57, 0xad, 0x4c, 0xeb, 0xa2, 0x47, 0xc6, 0xa1, 0x48, 0x31, 0x47, + 0x7f, 0x28, 0xe9, 0xfb, 0x3c, 0x13, 0x4f, 0x67, 0xe1, 0x59, 0x12, 0xa3, 0x6d, 0xa2, 0xc3, 0x67, + 0xb2, 0xf8, 0xcc, 0x54, 0xd3, 0x59, 0xcb, 0x91, 0x8f, 0x08, 0x47, 0xd9, 0xab, 0x27, 0xb0, 0x29, + 0xcf, 0xd2, 0x28, 0xf4, 0xc3, 0xb9, 0x5b, 0xa2, 0xae, 0xef, 0x29, 0xed, 0x66, 0x52, 0x4f, 0xb3, + 0x58, 0x51, 0x94, 0xcd, 0xb0, 0xd7, 0xdb, 0x96, 0xbe, 0x7c, 0x73, 0x74, 0xd7, 0x53, 0xda, 0x61, + 0xda, 0x47, 0xd3, 0x28, 0xc2, 0x45, 0xc8, 0xb3, 0x66, 0x5c, 0x30, 0xd3, 0x50, 0x9c, 0x2f, 0x1b, + 0xfe, 0x97, 0x0a, 0xac, 0x5e, 0x38, 0xf0, 0xe8, 0x9f, 0xe0, 0x61, 0xb7, 0x49, 0xfe, 0x8e, 0x53, + 0xc7, 0xe6, 0x5e, 0x40, 0x08, 0x3d, 0xd1, 0x36, 0xbd, 0x8f, 0x08, 0x3d, 0x41, 0x69, 0x76, 0x09, + 0xea, 0xfa, 0x8c, 0xb6, 0x9c, 0xa5, 0x7b, 0x4d, 0x9f, 0xe1, 0x5e, 0xef, 0x40, 0x2b, 0x4a, 0xc6, + 0x6e, 0x24, 0x9f, 0x49, 0xbe, 0xba, 0xdf, 0xdb, 0x7e, 0xe7, 0x15, 0x92, 0x66, 0xeb, 0x61, 0x32, + 0x7e, 0x88, 0xb4, 0x4e, 0x33, 0x32, 0x4f, 0xc3, 0xdf, 0x40, 0xd3, 0x42, 0x45, 0x0b, 0x6a, 0xf7, + 0xe4, 0xf1, 0x74, 0xdc, 0x7f, 0x4d, 0x34, 0x61, 0x19, 0x7b, 0xf4, 0x2b, 0xf8, 0xf4, 0x8d, 0x97, + 0xc5, 0xfd, 0x25, 0x44, 0xdf, 0xcf, 0xb2, 0x24, 0xeb, 0x57, 0xf1, 0xf1, 0xc0, 0x8b, 0x43, 0xbf, + 0xbf, 0x8c, 0x8f, 0x0f, 0x3c, 0xed, 0x45, 0xfd, 0xda, 0xf0, 0x77, 0x35, 0x68, 0x1e, 0x98, 0xb7, + 0x8b, 0x7b, 0xd0, 0xcd, 0x3f, 0x26, 0xb5, 0x38, 0x50, 0x78, 0x30, 0xff, 0x40, 0x81, 0xc2, 0x4e, + 0x5a, 0x6a, 0xcd, 0x7f, 0x92, 0x6a, 0xe9, 0xc2, 0x27, 0xa9, 0x5e, 0x87, 0xea, 0xd3, 0xec, 0x7c, + 0xb6, 0xbe, 0xf5, 0x20, 0xf2, 0x62, 0x07, 0xc1, 0xe2, 0x63, 0x68, 0x53, 0x0a, 0x8f, 0x8b, 0x19, + 0x4d, 0x70, 0xad, 0xfc, 0xf5, 0x37, 0xae, 0x5c, 0x04, 0x24, 0x32, 0x16, 0xf1, 0x16, 0x34, 0xfd, + 0x93, 0x30, 0x0a, 0x32, 0x19, 0x9b, 0xda, 0x71, 0x71, 0x71, 0xca, 0x4e, 0x4e, 0x23, 0xfe, 0x04, + 0xfa, 0x61, 0x11, 0x1c, 0x2c, 0x32, 0xb6, 0x33, 0x6a, 0xa3, 0x14, 0x3e, 0x74, 0x56, 0x4a, 0xe4, + 0x64, 0x82, 0x15, 0x77, 0xdb, 0x1b, 0xe5, 0xbb, 0xed, 0xfc, 0x31, 0x21, 0xb2, 0x93, 0x9a, 0x79, + 0x68, 0x01, 0xcd, 0xa4, 0x77, 0x8d, 0x71, 0xdb, 0x9a, 0xf7, 0xc5, 0xac, 0x69, 0x66, 0x8c, 0xdc, + 0x77, 0xa0, 0x87, 0x46, 0xb3, 0xcb, 0xb6, 0x36, 0xaa, 0x33, 0x30, 0x9f, 0xe2, 0x98, 0xaa, 0x93, + 0x7b, 0x68, 0x6d, 0x23, 0x33, 0xde, 0x84, 0x9e, 0xfd, 0x2f, 0xa6, 0xc0, 0xaf, 0x6d, 0x32, 0xba, + 0x06, 0xca, 0x25, 0x7e, 0x5b, 0xb0, 0xe6, 0x9f, 0x78, 0x71, 0x2c, 0x23, 0xf7, 0x78, 0x3a, 0x1a, + 0x59, 0x33, 0x87, 0xbf, 0x88, 0xb2, 0x6a, 0x50, 0x77, 0x09, 0x43, 0xd6, 0xce, 0x10, 0xba, 0x71, + 0x18, 0xd9, 0xaf, 0x9d, 0xc5, 0x6c, 0x92, 0xd6, 0x9c, 0x76, 0x1c, 0x46, 0xfc, 0x7d, 0x33, 0xfa, + 0x2c, 0x5b, 0x7f, 0x3a, 0x0d, 0x03, 0xe5, 0xea, 0xc4, 0x7e, 0x87, 0xc9, 0x84, 0xf9, 0x4b, 0x81, + 0xb3, 0x27, 0xd3, 0x30, 0x38, 0x4a, 0xcc, 0x97, 0x98, 0xba, 0x44, 0x6f, 0x9b, 0xc3, 0x2f, 0xa0, + 0x53, 0xe6, 0x1d, 0xe4, 0x45, 0x8a, 0x6c, 0xf4, 0x5f, 0x13, 0x00, 0xf5, 0x47, 0x49, 0x36, 0xf1, + 0xa2, 0x7e, 0x05, 0x9f, 0x59, 0x98, 0xf7, 0x97, 0x44, 0x07, 0x9a, 0xd6, 0x53, 0xef, 0x57, 0x4d, + 0xee, 0xec, 0x97, 0xd0, 0xb4, 0x9f, 0x97, 0xa2, 0x4f, 0xf3, 0x24, 0x81, 0x64, 0xd7, 0xc3, 0x14, + 0x52, 0x22, 0x80, 0xdc, 0x0e, 0xfb, 0x19, 0xbe, 0xa5, 0xe2, 0x33, 0x7c, 0xc3, 0x3f, 0x85, 0x4e, + 0x79, 0x8a, 0x36, 0x1a, 0x5c, 0x29, 0xa2, 0xc1, 0x0b, 0x7a, 0x51, 0x82, 0x3f, 0x4b, 0x26, 0x6e, + 0xc9, 0x3a, 0x6e, 0x22, 0x00, 0x5f, 0x33, 0xfc, 0x47, 0x15, 0xa8, 0x91, 0xff, 0x4a, 0x46, 0x0e, + 0x3e, 0x14, 0x27, 0xa8, 0xe6, 0xb4, 0x08, 0xf2, 0x7f, 0x71, 0x71, 0x2d, 0xcf, 0x0e, 0x2e, 0xbf, + 0x32, 0x3b, 0x78, 0xeb, 0x5f, 0x57, 0xa0, 0xce, 0x9f, 0x3e, 0x14, 0xab, 0xd0, 0x7d, 0x12, 0x9f, + 0xc6, 0xc9, 0xf3, 0x98, 0x01, 0xfd, 0xd7, 0xc4, 0x1a, 0xac, 0xd8, 0xb5, 0x37, 0xdf, 0x58, 0xec, + 0x57, 0x44, 0x1f, 0x3a, 0xb4, 0xbb, 0x16, 0xb2, 0x24, 0x5e, 0x87, 0x81, 0xb1, 0x53, 0xee, 0xa1, + 0x30, 0x4e, 0x74, 0x38, 0x3a, 0xb7, 0xd8, 0xaa, 0x58, 0x81, 0xf6, 0xa1, 0x4e, 0xd2, 0x43, 0x19, + 0x07, 0x61, 0x3c, 0xee, 0x2f, 0x8b, 0x01, 0xac, 0xdb, 0x51, 0xf9, 0xf3, 0x80, 0x0f, 0xc2, 0x38, + 0x54, 0x27, 0xfd, 0x9a, 0xb8, 0x06, 0x97, 0x17, 0x61, 0x76, 0xfc, 0xd3, 0x7e, 0x5d, 0xac, 0x43, + 0xdf, 0x22, 0xef, 0x9a, 0x0f, 0xdd, 0xf5, 0x1b, 0xb7, 0x3e, 0x05, 0x71, 0xf1, 0x1b, 0x83, 0xf8, + 0xce, 0x87, 0x72, 0xec, 0xf9, 0xe7, 0xbb, 0x51, 0xa2, 0x90, 0x55, 0xba, 0xd0, 0x2a, 0xc6, 0xaa, + 0xdc, 0x7a, 0x00, 0x75, 0xfe, 0x28, 0x64, 0xe9, 0x5f, 0x33, 0xa0, 0xff, 0x1a, 0x76, 0x46, 0x45, + 0x14, 0xc6, 0xe3, 0x47, 0xf2, 0x4c, 0xb3, 0x78, 0x7c, 0xe8, 0x29, 0xdd, 0x5f, 0x12, 0x3d, 0x00, + 0xf3, 0xc7, 0xee, 0xc7, 0x41, 0xbf, 0x7a, 0x77, 0xf7, 0xaf, 0x7e, 0xff, 0x66, 0xe5, 0xaf, 0x7f, + 0xff, 0x66, 0xe5, 0x3f, 0xff, 0xfe, 0xcd, 0xd7, 0x7e, 0xfb, 0x37, 0x6f, 0x56, 0xbe, 0xfd, 0xb8, + 0xf4, 0xc9, 0x4b, 0xa3, 0x9f, 0xa8, 0x4c, 0xe3, 0x4e, 0xae, 0xac, 0xee, 0xa4, 0xa7, 0xe3, 0x3b, + 0xe9, 0xf1, 0x1d, 0xcb, 0xfd, 0xc7, 0x75, 0xfa, 0x92, 0xe5, 0x27, 0xff, 0x27, 0x00, 0x00, 0xff, + 0xff, 0xe6, 0xad, 0x56, 0xab, 0x48, 0x53, 0x00, 0x00, } func (m *Message) Marshal() (dAtA []byte, err error) { @@ -12123,6 +12132,19 @@ func (m *PrepareParamInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.IsBinaryString) > 0 { + for iNdEx := len(m.IsBinaryString) - 1; iNdEx >= 0; iNdEx-- { + i-- + if m.IsBinaryString[iNdEx] { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + } + i = encodeVarintPipeline(dAtA, i, uint64(len(m.IsBinaryString))) + i-- + dAtA[i] = 0x32 + } if len(m.IsBin) > 0 { for iNdEx := len(m.IsBin) - 1; iNdEx >= 0; iNdEx-- { i-- @@ -15141,6 +15163,9 @@ func (m *PrepareParamInfo) ProtoSize() (n int) { if len(m.IsBin) > 0 { n += 1 + sovPipeline(uint64(len(m.IsBin))) + len(m.IsBin)*1 } + if len(m.IsBinaryString) > 0 { + n += 1 + sovPipeline(uint64(len(m.IsBinaryString))) + len(m.IsBinaryString)*1 + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -30909,6 +30934,76 @@ func (m *PrepareParamInfo) Unmarshal(dAtA []byte) error { } else { return fmt.Errorf("proto: wrong wireType = %d for field IsBin", wireType) } + case 6: + if wireType == 0 { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsBinaryString = append(m.IsBinaryString, bool(v != 0)) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthPipeline + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthPipeline + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen + if elementCount != 0 && len(m.IsBinaryString) == 0 { + m.IsBinaryString = make([]bool, 0, elementCount) + } + for iNdEx < postIndex { + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPipeline + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsBinaryString = append(m.IsBinaryString, bool(v != 0)) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field IsBinaryString", wireType) + } default: iNdEx = preIndex skippy, err := skipPipeline(dAtA[iNdEx:]) diff --git a/pkg/shardservice/service_read.go b/pkg/shardservice/service_read.go index 1898d7c942629..d53e28333f42f 100644 --- a/pkg/shardservice/service_read.go +++ b/pkg/shardservice/service_read.go @@ -34,6 +34,11 @@ func hasVersionedPrepareParamMetadata(param pb.ReadParam) bool { return true } } + for _, isBinaryString := range param.Process.PrepareParams.IsBinaryString { + if isBinaryString { + return true + } + } return false } diff --git a/pkg/shardservice/service_read_test.go b/pkg/shardservice/service_read_test.go index 56bbc19bc5c50..495798aadb908 100644 --- a/pkg/shardservice/service_read_test.go +++ b/pkg/shardservice/service_read_test.go @@ -69,6 +69,10 @@ func TestValidateRemoteReadCompatibility(t *testing.T) { textParam.Process.PrepareParams.IsBin = []bool{false, false} require.NoError(t, s.validateRemoteReadCompatibility(t.Context(), target, textParam)) + binaryStringParam := textParam + binaryStringParam.Process.PrepareParams.IsBinaryString = []bool{true} + require.Error(t, s.validateRemoteReadCompatibility(t.Context(), target, binaryStringParam)) + unknown := target unknown.Replicas[0].CN = "unknown" require.Error(t, s.validateRemoteReadCompatibility(t.Context(), unknown, binaryParam)) @@ -97,6 +101,17 @@ func TestNewReadRequestUsesVersionedMethodForPrepareParamMetadata(t *testing.T) require.Equal(t, shard.Method_ShardReadV2, binaryReq.RPCMethod) s.remote.pool.ReleaseRequest(binaryReq) + binaryStringReq := s.newReadRequest( + target, + ReadRows, + shard.ReadParam{Process: pipeline.ProcessInfo{ + PrepareParams: pipeline.PrepareParamInfo{IsBinaryString: []bool{true}}, + }}, + timestamp.Timestamp{}, + ) + require.Equal(t, shard.Method_ShardReadV2, binaryStringReq.RPCMethod) + s.remote.pool.ReleaseRequest(binaryStringReq) + numericReq := s.newReadRequest( target, ReadRows, diff --git a/pkg/sql/colexec/aggexec/any2.go b/pkg/sql/colexec/aggexec/any2.go index 0456c6233bc9f..0997a8e16af89 100644 --- a/pkg/sql/colexec/aggexec/any2.go +++ b/pkg/sql/colexec/aggexec/any2.go @@ -24,6 +24,12 @@ import ( type anyExec struct { aggExec + binaryString bool +} + +func (exec *anyExec) BinaryStringState() bool { return exec.binaryString } +func (exec *anyExec) SetBinaryStringState(value bool) { + exec.binaryString = value } func (exec *anyExec) Fill(groupIndex int, row int, vectors []*vector.Vector) error { @@ -35,6 +41,7 @@ func (exec *anyExec) BulkFill(groupIndex int, vectors []*vector.Vector) error { } func (exec *anyExec) BatchFill(offset int, groups []uint64, vectors []*vector.Vector) error { + exec.binaryString = exec.binaryString || isBinaryStringVector(vectors[0]) for i, grp := range groups { if grp == GroupNotMatched { continue @@ -67,6 +74,7 @@ func (exec *anyExec) Merge(next AggFuncExec, groupIdx1, groupIdx2 int) error { func (exec *anyExec) BatchMerge(next AggFuncExec, offset int, groups []uint64) error { other := next.(*anyExec) + exec.binaryString = exec.binaryString || other.binaryString for i, grp := range groups { if grp == GroupNotMatched { continue @@ -100,6 +108,7 @@ func (exec *anyExec) Flush() ([]*vector.Vector, error) { vecs := make([]*vector.Vector, len(exec.state)) for i := range vecs { vecs[i] = exec.state[i].vecs[0] + vecs[i].SetIsBinaryString(exec.binaryString) exec.state[i].vecs[0] = nil exec.state[i].length = 0 exec.state[i].capacity = 0 diff --git a/pkg/sql/colexec/aggexec/binary_string_test.go b/pkg/sql/colexec/aggexec/binary_string_test.go new file mode 100644 index 0000000000000..9f65d739bc696 --- /dev/null +++ b/pkg/sql/colexec/aggexec/binary_string_test.go @@ -0,0 +1,161 @@ +// Copyright 2026 Matrix Origin +// +// 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 aggexec + +import ( + "testing" + + "github.com/matrixorigin/matrixone/pkg/common/mpool" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/stretchr/testify/require" +) + +func TestAggregatesPropagateBinaryStringMetadata(t *testing.T) { + mp := mpool.MustNewZero() + defer mp.Free(nil) + + input := vector.NewVec(types.T_varchar.ToType()) + require.NoError(t, vector.AppendBytes(input, []byte{0xe4, 0xbd, 0xa0}, false, mp)) + input.SetIsBinaryString(true) + defer input.Free(mp) + + tests := []struct { + name string + new func() AggFuncExec + }{ + { + name: "min", + new: func() AggFuncExec { + return makeMinMaxExec(mp, AggIdOfMin, true, types.T_varchar.ToType()) + }, + }, + { + name: "any_value", + new: func() AggFuncExec { + return makeAnyValueExec(mp, AggIdOfAny, types.T_varchar.ToType()) + }, + }, + { + name: "group_concat", + new: func() AggFuncExec { + return newGroupConcatExec(mp, multiAggInfo{ + aggID: AggIdOfGroupConcat, + argTypes: []types.Type{types.T_varchar.ToType()}, + retType: types.T_text.ToType(), + emptyNull: true, + }, ",") + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + exec := test.new() + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.Fill(0, 0, []*vector.Vector{input})) + + result, err := exec.Flush() + require.NoError(t, err) + require.Len(t, result, 1) + require.True(t, result[0].GetIsBinaryString()) + result[0].Free(mp) + exec.Free() + }) + } +} + +func TestAggregatesRecognizeMaterializedBinaryStringType(t *testing.T) { + mp := mpool.MustNewZero() + defer mp.Free(nil) + + input := vector.NewVec(types.T_varbinary.ToType()) + require.NoError(t, vector.AppendBytes(input, []byte{0xe4, 0xbd, 0xa0}, false, mp)) + defer input.Free(mp) + + tests := []struct { + name string + new func() AggFuncExec + }{ + { + name: "min", + new: func() AggFuncExec { + return makeMinMaxExec(mp, AggIdOfMin, true, types.T_varbinary.ToType()) + }, + }, + { + name: "any_value", + new: func() AggFuncExec { + return makeAnyValueExec(mp, AggIdOfAny, types.T_varbinary.ToType()) + }, + }, + { + name: "group_concat", + new: func() AggFuncExec { + return newGroupConcatExec(mp, multiAggInfo{ + aggID: AggIdOfGroupConcat, + argTypes: []types.Type{types.T_varbinary.ToType()}, + retType: types.T_text.ToType(), + emptyNull: true, + }, ",") + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + exec := test.new() + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.Fill(0, 0, []*vector.Vector{input})) + + result, err := exec.Flush() + require.NoError(t, err) + require.Len(t, result, 1) + require.True(t, result[0].GetIsBinaryString()) + result[0].Free(mp) + exec.Free() + }) + } +} + +func TestValueWindowsPropagateBinaryStringMetadata(t *testing.T) { + mp := mpool.MustNewZero() + defer mp.Free(nil) + + input := vector.NewVec(types.T_varchar.ToType()) + require.NoError(t, vector.AppendBytes(input, []byte{0xe4, 0xbd, 0xa0}, false, mp)) + input.SetIsBinaryString(true) + defer input.Free(mp) + + for _, aggID := range []int64{ + WinIdOfLag, + WinIdOfLead, + WinIdOfFirstValue, + WinIdOfLastValue, + WinIdOfNthValue, + } { + exec, err := makeValueWindowExec(mp, aggID, false, []types.Type{types.T_varchar.ToType()}) + require.NoError(t, err) + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.Fill(0, 0, []*vector.Vector{input})) + + result, err := exec.Flush() + require.NoError(t, err) + require.Len(t, result, 1) + require.True(t, result[0].GetIsBinaryString()) + result[0].Free(mp) + exec.Free() + } +} diff --git a/pkg/sql/colexec/aggexec/concat2.go b/pkg/sql/colexec/aggexec/concat2.go index 93a9d46a7c247..58cba9ee89818 100644 --- a/pkg/sql/colexec/aggexec/concat2.go +++ b/pkg/sql/colexec/aggexec/concat2.go @@ -53,6 +53,12 @@ type groupConcatExec struct { h0SpillData *os.File orderedSpillRuns [][]groupConcatSpillRun maxLen uint64 + binaryString bool +} + +func (exec *groupConcatExec) BinaryStringState() bool { return exec.binaryString } +func (exec *groupConcatExec) SetBinaryStringState(value bool) { + exec.binaryString = value } var ( @@ -195,6 +201,9 @@ func (exec *groupConcatExec) BatchFill(offset int, groups []uint64, vectors []*v len(exec.argTypes), ) } + for _, vec := range vectors[:exec.concatArgCnt] { + exec.binaryString = exec.binaryString || isBinaryStringVector(vec) + } if exec.distinct && exec.orderArgCnt == 0 { for i, grp := range groups { @@ -389,6 +398,7 @@ func (exec *groupConcatExec) Merge(next AggFuncExec, groupIdx1, groupIdx2 int) e func (exec *groupConcatExec) BatchMerge(next AggFuncExec, offset int, groups []uint64) error { other := next.(*groupConcatExec) + exec.binaryString = exec.binaryString || other.binaryString if exec.distinct && exec.orderArgCnt == 0 { if err := exec.distinctHash.merge(&other.distinctHash); err != nil { return err @@ -510,6 +520,7 @@ func (exec *groupConcatExec) FlushWithContext(ctx context.Context) (_ []*vector. }() for i, st := range exec.state { vecs[i] = vector.NewOffHeapVecWithType(exec.retType) + vecs[i].SetIsBinaryString(exec.binaryString) if err := vecs[i].PreExtend(int(st.length), exec.mp); err != nil { return nil, err } diff --git a/pkg/sql/colexec/aggexec/minmax2.go b/pkg/sql/colexec/aggexec/minmax2.go index f6b755a642fd0..e2920f39a52e6 100644 --- a/pkg/sql/colexec/aggexec/minmax2.go +++ b/pkg/sql/colexec/aggexec/minmax2.go @@ -33,9 +33,15 @@ type minMaxExecFixed[T types.FixedSizeT] struct { type minMaxExecBytes struct { aggExec - comp func([]byte, []byte) int - hasExtra bool - extra []byte + comp func([]byte, []byte) int + hasExtra bool + extra []byte + binaryString bool +} + +func (exec *minMaxExecBytes) BinaryStringState() bool { return exec.binaryString } +func (exec *minMaxExecBytes) SetBinaryStringState(value bool) { + exec.binaryString = value } func mergeMinMaxPrepareParamKind( @@ -312,6 +318,7 @@ func (exec *minMaxExecBytes) BulkFill(groupIndex int, vectors []*vector.Vector) } func (exec *minMaxExecBytes) BatchFill(offset int, groups []uint64, vectors []*vector.Vector) error { + exec.binaryString = exec.binaryString || isBinaryStringVector(vectors[0]) for i, grp := range groups { if grp == GroupNotMatched { continue @@ -359,6 +366,7 @@ func (exec *minMaxExecBytes) Merge(next AggFuncExec, groupIdx1, groupIdx2 int) e func (exec *minMaxExecBytes) BatchMerge(next AggFuncExec, offset int, groups []uint64) error { other := next.(*minMaxExecBytes) + exec.binaryString = exec.binaryString || other.binaryString for i, grp := range groups { if grp == GroupNotMatched { continue @@ -422,6 +430,7 @@ func (exec *minMaxExecBytes) Flush() ([]*vector.Vector, error) { vecs := make([]*vector.Vector, len(exec.state)) for i := range vecs { vecs[i] = exec.state[i].vecs[0] + vecs[i].SetIsBinaryString(exec.binaryString) exec.state[i].vecs[0] = nil exec.state[i].length = 0 exec.state[i].capacity = 0 diff --git a/pkg/sql/colexec/aggexec/types.go b/pkg/sql/colexec/aggexec/types.go index 57061db332ade..f1116ae4c80b2 100644 --- a/pkg/sql/colexec/aggexec/types.go +++ b/pkg/sql/colexec/aggexec/types.go @@ -33,6 +33,15 @@ const ( GroupNotMatched = 0 ) +func isBinaryStringVector(vec *vector.Vector) bool { + switch vec.GetType().Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + return true + default: + return vec.GetIsBinaryString() + } +} + // AggFuncExecExpression is the exporting structure for the aggregation information. // it is used to indicate the information of the aggregation function for the operators like 'group' or 'merge group'. type AggFuncExecExpression struct { @@ -214,6 +223,14 @@ type PrepareParamKindStateAccessor interface { RestorePrepareParamKindsFlat(kinds []vector.PrepareParamKind, mp *mpool.MPool) error } +// BinaryStringStateAccessor carries the dynamic byte-string result summary +// alongside aggregate partial state. It is optional because numeric and other +// non-string executors have no such metadata. +type BinaryStringStateAccessor interface { + BinaryStringState() bool + SetBinaryStringState(bool) +} + // indicate who implements the AggFuncExec interface. var ( _ AggFuncExec = &groupConcatExec{} diff --git a/pkg/sql/colexec/aggexec/window.go b/pkg/sql/colexec/aggexec/window.go index 857851dc2f167..303016e04416a 100644 --- a/pkg/sql/colexec/aggexec/window.go +++ b/pkg/sql/colexec/aggexec/window.go @@ -769,7 +769,8 @@ type valueWindowExec struct { currentRowPosition []int // Result vector - resultVec *vector.Vector + resultVec *vector.Vector + binaryString bool } // valueEntry stores a single value from the window frame @@ -805,6 +806,7 @@ func (exec *valueWindowExec) Fill(groupIndex int, row int, vectors []*vector.Vec } vec := vectors[0] + exec.binaryString = exec.binaryString || isBinaryStringVector(vec) entry := &valueEntry{ isNull: vec.IsNull(uint64(row)), } @@ -869,19 +871,31 @@ func (exec *valueWindowExec) SetExtraInformation(partialResult any, groupIndex i } func (exec *valueWindowExec) Flush() ([]*vector.Vector, error) { + var ( + vecs []*vector.Vector + err error + ) switch exec.singleAggInfo.aggID { case WinIdOfLag: - return exec.flushLag() + vecs, err = exec.flushLag() case WinIdOfLead: - return exec.flushLead() + vecs, err = exec.flushLead() case WinIdOfFirstValue: - return exec.flushFirstValue() + vecs, err = exec.flushFirstValue() case WinIdOfLastValue: - return exec.flushLastValue() + vecs, err = exec.flushLastValue() case WinIdOfNthValue: - return exec.flushNthValue() + vecs, err = exec.flushNthValue() + default: + return nil, moerr.NewInternalErrorNoCtx("invalid value window function") + } + if err != nil { + return nil, err + } + for _, vec := range vecs { + vec.SetIsBinaryString(exec.binaryString) } - return nil, moerr.NewInternalErrorNoCtx("invalid value window function") + return vecs, nil } func (exec *valueWindowExec) Free() { diff --git a/pkg/sql/colexec/dispatch/dispatch_test.go b/pkg/sql/colexec/dispatch/dispatch_test.go index ee317a74ed65e..323a743165927 100644 --- a/pkg/sql/colexec/dispatch/dispatch_test.go +++ b/pkg/sql/colexec/dispatch/dispatch_test.go @@ -106,6 +106,18 @@ func TestMarshalRemoteBatchPrepareParamProtocolGate(t *testing.T) { require.NoError(t, decoded.UnmarshalBinaryWithPrepareParamKinds(encoded, proc.Mp())) require.Equal(t, vector.PrepareParamInteger, decoded.Vecs[0].GetPrepareParamKindAt(0)) require.Equal(t, vector.PrepareParamNone, decoded.Vecs[0].GetPrepareParamKindAt(1)) + + bat.Vecs[0].SetIsBinaryString(true) + buf.Reset() + _, err = marshalRemoteBatch(proc, bat, buf) + require.ErrorContains(t, err, "binary-string provenance requires MORPCVersion14") + require.Empty(t, buf.Bytes()) + + runtime.SetGlobalVariables(moruntime.MOProtocolVersion, defines.MORPCVersion14) + encoded, err = marshalRemoteBatch(proc, bat, buf) + require.NoError(t, err) + require.NoError(t, decoded.UnmarshalBinaryWithPrepareParamKinds(encoded, proc.Mp())) + require.True(t, decoded.Vecs[0].GetIsBinaryString()) } func TestMarshalRemoteBatchUnknownServiceFailsClosed(t *testing.T) { diff --git a/pkg/sql/colexec/dispatch/sendfunc.go b/pkg/sql/colexec/dispatch/sendfunc.go index 23fc96a494cce..96f98049e7461 100644 --- a/pkg/sql/colexec/dispatch/sendfunc.go +++ b/pkg/sql/colexec/dispatch/sendfunc.go @@ -65,6 +65,19 @@ func prepareParamKindRemoteWireEnabled(proc *process.Process) bool { return ok && version >= defines.MORPCVersion12 } +func binaryStringRemoteWireEnabled(proc *process.Process) bool { + if proc == nil { + return false + } + rt := moruntime.ServiceRuntime(proc.GetService()) + if rt == nil { + return false + } + value, _ := rt.GetGlobalVariables(moruntime.MOProtocolVersion) + version, ok := value.(int64) + return ok && version >= defines.MORPCVersion14 +} + // marshalRemoteBatch keeps the stable Batch prefix unchanged and appends the // optional transient provenance trailer only after the shared protocol gate is // enabled. Older protocol sessions fail before writing rather than silently @@ -74,6 +87,10 @@ func marshalRemoteBatch(proc *process.Process, bat *batch.Batch, buf *bytes.Buff return nil, moerr.NewInvalidInputNoCtx("cannot marshal a nil remote batch") } wireEnabled := prepareParamKindRemoteWireEnabled(proc) + if bat.HasBinaryStringMetadata() && !binaryStringRemoteWireEnabled(proc) { + return nil, moerr.NewInvalidStateNoCtx( + "binary-string provenance requires MORPCVersion14 for remote dispatch") + } if bat.HasPrepareParamKindMetadata() && !wireEnabled { return nil, moerr.NewInvalidStateNoCtx( "prepared parameter provenance requires MORPCVersion12 for remote dispatch") diff --git a/pkg/sql/colexec/evalExpression.go b/pkg/sql/colexec/evalExpression.go index 89170374da8a3..87ea4ee734e73 100644 --- a/pkg/sql/colexec/evalExpression.go +++ b/pkg/sql/colexec/evalExpression.go @@ -432,10 +432,12 @@ func (expr *ParamExpressionExecutor) Eval(proc *process.Process, batches []*batc expr.typ, val, 1, proc.Mp(), expr.allocation, ) } else { + expr.vec.SetType(expr.typ) err = vector.SetConstBytes(expr.vec, val, 1, proc.GetMPool()) } if err == nil { expr.vec.SetIsBin(proc.GetPrepareParamIsBin(expr.pos)) + expr.vec.SetIsBinaryString(proc.GetPrepareParamIsBinaryString(expr.pos)) expr.vec.SetPrepareParamKind(proc.GetPrepareParamKind(expr.pos)) } return expr.vec, err @@ -519,6 +521,13 @@ func (expr *VarExpressionExecutor) Eval(proc *process.Process, batches []*batch. return nil, err } } + binaryString := false + if resolveBinaryString := proc.GetResolveVariableBinaryStringFunc(); resolveBinaryString != nil { + binaryString, err = resolveBinaryString(expr.name, expr.system, expr.global) + if err != nil { + return nil, err + } + } prepareParamKind := vector.PrepareParamNone if resolveKind := proc.GetResolveVariablePrepareParamKindFunc(); resolveKind != nil { prepareParamKind, err = resolveKind(expr.name, expr.system, expr.global) @@ -535,6 +544,7 @@ func (expr *VarExpressionExecutor) Eval(proc *process.Process, batches []*batch. } if err == nil { expr.null.SetIsBin(isBin) + expr.null.SetIsBinaryString(binaryString) expr.null.SetPrepareParamKind(prepareParamKind) } return expr.null, err @@ -545,6 +555,7 @@ func (expr *VarExpressionExecutor) Eval(proc *process.Process, batches []*batch. proc, expr.typ, val, expr.allocation, ) } else { + expr.vec.SetType(expr.typ) switch v := val.(type) { case []byte: err = vector.SetConstBytes(expr.vec, v, 1, proc.GetMPool()) @@ -556,6 +567,7 @@ func (expr *VarExpressionExecutor) Eval(proc *process.Process, batches []*batch. } if err == nil { expr.vec.SetIsBin(isBin) + expr.vec.SetIsBinaryString(binaryString) expr.vec.SetPrepareParamKind(prepareParamKind) } return expr.vec, err @@ -728,6 +740,7 @@ func (expr *FunctionExpressionExecutor) resetResultType(result vector.FunctionRe if vec := result.GetResultVector(); vec != nil { vec.SetType(expr.resultType) vec.SetIsBin(false) + vec.SetIsBinaryString(false) } } @@ -1056,6 +1069,7 @@ func (expr *FunctionExpressionExecutor) evalSelectedRows( selected.Reset(*parameter.GetType()) } selected.SetIsBin(parameter.GetIsBin()) + selected.SetIsBinaryString(parameter.GetIsBinaryString()) if err := selected.Union(parameter, expr.selectedRows, proc.Mp()); err != nil { return nil, err } @@ -1090,6 +1104,7 @@ func (expr *FunctionExpressionExecutor) evalSelectedRows( selectedResult := expr.selectedResult.GetResultVector() runtimeType := *selectedResult.GetType() runtimeIsBin := selectedResult.GetIsBin() + runtimeBinaryString := selectedResult.GetIsBinaryString() runtimePrepareParamKind := selectedResult.GetPrepareParamKind() if expr.fid == function.IFF || expr.fid == function.CASE || expr.fid == function.COALESCE { runtimePrepareParamKind = expr.getFlowControlPrepareParamKind() @@ -1097,8 +1112,9 @@ func (expr *FunctionExpressionExecutor) evalSelectedRows( result := expr.resultVector.GetResultVector() result.SetType(runtimeType) - result.SetIsBin(runtimeIsBin) result.ResetWithSameType() + result.SetIsBin(runtimeIsBin) + result.SetIsBinaryString(runtimeBinaryString) if expr.selectedNullResult == nil { var err error expr.selectedNullResult, err = newExpressionConstNull( @@ -1112,6 +1128,7 @@ func (expr *FunctionExpressionExecutor) evalSelectedRows( expr.selectedNullResult.SetLength(1) } expr.selectedNullResult.SetIsBin(runtimeIsBin) + expr.selectedNullResult.SetIsBinaryString(runtimeBinaryString) selectedRow := int64(0) for row := 0; row < rowCount; row++ { if selectList[row] { @@ -1492,6 +1509,7 @@ func generateConstExpressionExecutor( } if err == nil { vec.SetIsBin(con.IsBin) + vec.SetIsBinaryString(con.IsBin) } } return vec, err @@ -1599,6 +1617,7 @@ func GenerateConstListExpressionExecutor(proc *process.Process, exprs []*plan.Ex return nil, moerr.NewNYI(proc.Ctx, fmt.Sprintf("const expression %v", t.GetValue())) } vec.SetIsBin(t.IsBin) + vec.SetIsBinaryString(t.IsBin) } } return vec, nil diff --git a/pkg/sql/colexec/group/exec2.go b/pkg/sql/colexec/group/exec2.go index 8d489faf8f068..ccc54277855ce 100644 --- a/pkg/sql/colexec/group/exec2.go +++ b/pkg/sql/colexec/group/exec2.go @@ -649,6 +649,9 @@ func (group *Group) getNextIntermediateResult(proc *process.Process) (vm.CallRes prepareParamKindSummaries[i].kind, prepareParamKindSummaries[i].seen = accessor.PrepareParamKindSummaryForChunk(curr) } + if accessor, ok := ag.(aggexec.BinaryStringStateAccessor); ok && binaryStringWireEnabled(proc) { + prepareParamKindSummaries[i].binaryString = accessor.BinaryStringState() + } } if group.ctr.prepareParamKindWireV1 { if err := writePrepareParamKindTrailer(proc.Ctx, &buf, group.Aggs, diff --git a/pkg/sql/colexec/group/group_test.go b/pkg/sql/colexec/group/group_test.go index f63e3697ff099..8ac5d810fbf56 100644 --- a/pkg/sql/colexec/group/group_test.go +++ b/pkg/sql/colexec/group/group_test.go @@ -257,10 +257,11 @@ func buildPartialH0Batch(t *testing.T, proc *process.Process, values []int32) *b } type preparedPartialSpec struct { - rows int - kind vector.PrepareParamKind - allNull bool - value string + rows int + kind vector.PrepareParamKind + allNull bool + value string + binaryString bool } func buildPreparedMinPartial( @@ -287,7 +288,9 @@ func buildPreparedPartial( value = "5" } require.NoError(t, vector.AppendBytes(params, []byte(value), spec.allNull, proc.Mp())) - proc.SetPrepareParamsWithMeta(params, nil, []vector.PrepareParamKind{spec.kind}) + params.SetIsBinaryString(spec.binaryString) + proc.SetPrepareParamsWithMeta( + params, nil, []vector.PrepareParamKind{spec.kind}, []bool{spec.binaryString}) defer proc.SetPrepareParams(nil) input := batch.NewWithSize(1) @@ -307,6 +310,32 @@ func buildPreparedPartial( return cloneBatch(t, proc, partials[0]) } +func TestMergeGroupPartialCarriesBinaryStringState(t *testing.T) { + proc := testutil.NewProcess(t) + setPrepareParamKindProtocolVersion(t, proc, defines.MORPCVersion14) + t.Cleanup(func() { + require.Zero(t, proc.Mp().CurrNB()) + proc.Free() + }) + + partial := buildPreparedMinPartial(t, proc, preparedPartialSpec{ + rows: 1, value: "你", binaryString: true, + }) + child := colexec.NewMockOperator().WithBatchs([]*batch.Batch{partial}) + merge := newMergeGroupOp([]aggexec.AggFuncExecExpression{minPreparedParamAgg()}) + merge.AppendChild(child) + require.NoError(t, merge.Prepare(proc)) + outputs := collectBatches(t, merge, proc) + require.Len(t, outputs, 1) + require.True(t, outputs[0].Vecs[0].GetIsBinaryString()) + + merge.Free(proc, false, nil) + child.Free(proc, false, nil) + for _, output := range outputs { + output.Clean(proc.Mp()) + } +} + func setPrepareParamKindProtocolVersion(t *testing.T, proc *process.Process, version int64) { t.Helper() rt := moruntime.ServiceRuntime(proc.GetService()) @@ -748,10 +777,10 @@ func TestMergeGroupRejectsInvalidPrepareParamKindTrailer(t *testing.T) { { name: "unsupported version", mutate: func(extra []byte, trailerOffset int) []byte { - extra[trailerOffset+3] = 3 + extra[trailerOffset+3] = 4 return extra }, - wantErr: "unsupported aggregate prepared parameter trailer version 3", + wantErr: "unsupported aggregate prepared parameter trailer version 4", }, { name: "aggregate count mismatch", diff --git a/pkg/sql/colexec/group/helper.go b/pkg/sql/colexec/group/helper.go index 955c578a88575..6e7c675fe9f78 100644 --- a/pkg/sql/colexec/group/helper.go +++ b/pkg/sql/colexec/group/helper.go @@ -494,6 +494,10 @@ func (ctr *container) spillDataToDisk(proc *process.Process, opAnalyzer process. accessor.PrepareParamKindSummaryForSelection(fullFlags) hasPrepareParamKinds = hasPrepareParamKinds || prepareParamKindSummaries[j].seen } + if accessor, ok := ag.(aggexec.BinaryStringStateAccessor); ok { + prepareParamKindSummaries[j].binaryString = accessor.BinaryStringState() + hasPrepareParamKinds = hasPrepareParamKinds || prepareParamKindSummaries[j].binaryString + } } if hasPrepareParamKinds { if err := writePrepareParamKindTrailer(proc.Ctx, buf, ctr.aggExprs, @@ -768,6 +772,7 @@ func (ctr *container) loadSpilledData(proc *process.Process, opAnalyzer process. } } } + restoreAggregateBinaryStringStates(ctr.spillAggList, prepareParamKindSummaries) } checkMagic, err = types.ReadUint64(bufferedFile) diff --git a/pkg/sql/colexec/group/mergeGroup.go b/pkg/sql/colexec/group/mergeGroup.go index 95ec9ba7d7a63..c2f8e07c898e1 100644 --- a/pkg/sql/colexec/group/mergeGroup.go +++ b/pkg/sql/colexec/group/mergeGroup.go @@ -255,6 +255,8 @@ func (mergeGroup *MergeGroup) buildOneBatch(proc *process.Process, bat *batch.Ba } } } + restoreAggregateBinaryStringStates( + mergeGroup.ctr.spillAggList, prepareParamKindSummaries) } } diff --git a/pkg/sql/colexec/group/prepare_param_kind.go b/pkg/sql/colexec/group/prepare_param_kind.go index 490cf3755daf3..2c1aa581f756f 100644 --- a/pkg/sql/colexec/group/prepare_param_kind.go +++ b/pkg/sql/colexec/group/prepare_param_kind.go @@ -38,13 +38,17 @@ const ( // both; writers select v2 only when exact rows are present. prepareParamKindTrailerVersion = byte(1) prepareParamKindTrailerRowsVersion = byte(2) - prepareParamKindTrailerRowsMarker = byte(0x80) - prepareParamKindTrailerMaxRows = int32(1 << 24) + // Version 3 prefixes every aggregate entry with its dynamic byte-string + // summary, keeping the legacy aggregate payload itself unchanged. + prepareParamKindTrailerBinaryVersion = byte(3) + prepareParamKindTrailerRowsMarker = byte(0x80) + prepareParamKindTrailerMaxRows = int32(1 << 24) ) type prepareParamKindSummary struct { - kind vector.PrepareParamKind - seen bool + kind vector.PrepareParamKind + seen bool + binaryString bool } func prepareParamKindWireV1Enabled(proc *process.Process) bool { @@ -62,6 +66,19 @@ func prepareParamKindWireV1Enabled(proc *process.Process) bool { return ok && version >= defines.MORPCVersion12 } +func binaryStringWireEnabled(proc *process.Process) bool { + if proc == nil { + return false + } + rt := moruntime.ServiceRuntime(proc.GetService()) + if rt == nil { + return false + } + value, _ := rt.GetGlobalVariables(moruntime.MOProtocolVersion) + version, ok := value.(int64) + return ok && version >= defines.MORPCVersion14 +} + func hasPrepareParamKindPreservingAgg(aggs []aggexec.AggFuncExecExpression) bool { for i := range aggs { if aggs[i].PreservesFirstArgPrepareParamKind() { @@ -93,6 +110,12 @@ func writePrepareParamKindTrailer( if rowsVersion { version = prepareParamKindTrailerRowsVersion } + for i := range summaries { + if summaries[i].binaryString { + version = prepareParamKindTrailerBinaryVersion + break + } + } buf.WriteByte(prepareParamKindTrailerMagic0) buf.WriteByte(prepareParamKindTrailerMagic1) buf.WriteByte(prepareParamKindTrailerMagic2) @@ -100,6 +123,13 @@ func writePrepareParamKindTrailer( nAggs := int32(len(aggs)) buf.Write(types.EncodeInt32(&nAggs)) for i := range aggs { + if version == prepareParamKindTrailerBinaryVersion { + binaryString := byte(0) + if i < len(summaries) && summaries[i].binaryString { + binaryString = 1 + } + buf.WriteByte(binaryString) + } kind, seen := states.GetState(i) // Validate the execution-wide compatibility state even when a // preserving aggregate supplies a more precise per-chunk summary @@ -173,7 +203,8 @@ func readPrepareParamKindTrailer( if err != nil { return nil, nil, err } - if version != prepareParamKindTrailerVersion && version != prepareParamKindTrailerRowsVersion { + if version != prepareParamKindTrailerVersion && version != prepareParamKindTrailerRowsVersion && + version != prepareParamKindTrailerBinaryVersion { return nil, nil, moerr.NewInternalErrorf(ctx, "unsupported aggregate prepared parameter trailer version %d", version) } @@ -192,11 +223,24 @@ func readPrepareParamKindTrailer( // mixed-category partial has already been observed. summaries := make([]prepareParamKindSummary, nAggs) for i := int32(0); i < nAggs; i++ { + binaryString := false + if version == prepareParamKindTrailerBinaryVersion { + encodedBinary, err := types.ReadByte(reader) + if err != nil { + return nil, nil, err + } + if encodedBinary > 1 { + return nil, nil, moerr.NewInternalErrorf(ctx, + "invalid aggregate binary-string state %d", encodedBinary) + } + binaryString = encodedBinary == 1 + } encoded, err := types.ReadByte(reader) if err != nil { return nil, nil, err } - if version == prepareParamKindTrailerRowsVersion && encoded == prepareParamKindTrailerRowsMarker { + if (version == prepareParamKindTrailerRowsVersion || version == prepareParamKindTrailerBinaryVersion) && + encoded == prepareParamKindTrailerRowsMarker { rowCount, err := types.ReadInt32(reader) if err != nil { return nil, nil, err @@ -236,7 +280,7 @@ func readPrepareParamKindTrailer( } rows[i] = kinds kind, seen := summarizePrepareParamKinds(kinds) - summaries[i] = prepareParamKindSummary{kind: kind, seen: seen} + summaries[i] = prepareParamKindSummary{kind: kind, seen: seen, binaryString: binaryString} states.ObserveState(int(i), kind, seen) continue } @@ -245,12 +289,26 @@ func readPrepareParamKindTrailer( return nil, nil, moerr.NewInternalErrorf(ctx, "invalid aggregate prepared parameter state %d", encoded) } - summaries[i] = prepareParamKindSummary{kind: kind, seen: seen} + summaries[i] = prepareParamKindSummary{kind: kind, seen: seen, binaryString: binaryString} states.ObserveState(int(i), kind, seen) } return rows, summaries, nil } +func restoreAggregateBinaryStringStates( + aggs []aggexec.AggFuncExec, + summaries []prepareParamKindSummary, +) { + for i, agg := range aggs { + if i >= len(summaries) { + break + } + if accessor, ok := agg.(aggexec.BinaryStringStateAccessor); ok { + accessor.SetBinaryStringState(summaries[i].binaryString) + } + } +} + func prepareParamKindReaderLen(reader io.Reader) (int, bool) { type lenReader interface{ Len() int } if r, ok := reader.(lenReader); ok { diff --git a/pkg/sql/colexec/group/prepare_param_kind_test.go b/pkg/sql/colexec/group/prepare_param_kind_test.go index 37a496377a690..f49c68fc9a2f5 100644 --- a/pkg/sql/colexec/group/prepare_param_kind_test.go +++ b/pkg/sql/colexec/group/prepare_param_kind_test.go @@ -241,3 +241,23 @@ func TestPrepareParamKindStateCodec(t *testing.T) { require.Equal(t, vector.PrepareParamNone, kind) } } + +func TestPrepareParamKindTrailerCarriesBinaryStringState(t *testing.T) { + aggs := []aggexec.AggFuncExecExpression{ + aggexec.MakeAggFunctionExpression(aggexec.AggIdOfMin, false, nil, nil), + } + var states aggexec.PrepareParamKindStates + states.Reset(aggs) + var payload bytes.Buffer + require.NoError(t, writePrepareParamKindTrailer( + context.Background(), &payload, aggs, &states, nil, + []prepareParamKindSummary{{binaryString: true}}, + )) + require.Equal(t, prepareParamKindTrailerBinaryVersion, payload.Bytes()[3]) + + _, summaries, err := readPrepareParamKindTrailer( + context.Background(), bytes.NewReader(payload.Bytes()), 1, &states, []int{-1}) + require.NoError(t, err) + require.Len(t, summaries, 1) + require.True(t, summaries[0].binaryString) +} diff --git a/pkg/sql/colexec/projection/projection.go b/pkg/sql/colexec/projection/projection.go index 7793bbc37d194..a970734ec4f6c 100644 --- a/pkg/sql/colexec/projection/projection.go +++ b/pkg/sql/colexec/projection/projection.go @@ -18,6 +18,8 @@ import ( "bytes" "github.com/matrixorigin/matrixone/pkg/container/batch" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/sql/colexec" "github.com/matrixorigin/matrixone/pkg/vm" "github.com/matrixorigin/matrixone/pkg/vm/process" @@ -77,6 +79,11 @@ func (projection *Projection) Call(proc *process.Process) (vm.CallResult, error) if err != nil { return vm.CancelResult, err } + // A projection materializes its expression value for a relational + // consumer. IsBin only describes how a raw hex/bit literal is converted + // while evaluating that expression; carrying it into a derived table or + // INSERT ... SELECT would reinterpret the materialized bytes as an integer. + materializeBinaryStringVector(vec) // for projection operator, all Vectors of projectBat come from executor.Eval // and will not be modified within projection operator. so we can used the result of executor.Eval directly. // (if operator will modify vector/agg of batch, you should make a copy) @@ -90,3 +97,31 @@ func (projection *Projection) Call(proc *process.Process) (vm.CallResult, error) result.Batch = projection.ctr.buf return result, nil } + +func materializeBinaryStringVector(vec *vector.Vector) { + if !vec.GetIsBin() && !vec.GetIsBinaryString() { + return + } + vec.SetIsBin(false) + vec.SetIsBinaryString(false) + + switch vec.GetType().Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + return + case types.T_char, types.T_varchar, types.T_text: + default: + return + } + + width := 0 + for i := 0; i < vec.Length(); i++ { + if !vec.IsNull(uint64(i)) { + width = max(width, len(vec.GetBytesAt(i))) + } + } + if width > int(types.MaxVarBinaryLen) { + vec.SetType(types.T_blob.ToType()) + } else { + vec.SetType(types.New(types.T_varbinary, int32(width), 0)) + } +} diff --git a/pkg/sql/colexec/projection/projection_test.go b/pkg/sql/colexec/projection/projection_test.go index 7fcb7052f75ef..92e893adae8ed 100644 --- a/pkg/sql/colexec/projection/projection_test.go +++ b/pkg/sql/colexec/projection/projection_test.go @@ -21,6 +21,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/common/mpool" "github.com/matrixorigin/matrixone/pkg/container/batch" "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/colexec" "github.com/matrixorigin/matrixone/pkg/testutil" @@ -105,6 +106,46 @@ func TestProjection(t *testing.T) { } } +func TestProjectionMaterializesRawBinaryLiteralMetadata(t *testing.T) { + proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero()) + arg := &Projection{ProjectList: []*plan.Expr{{ + Typ: plan.Type{Id: int32(types.T_varbinary), Width: 1}, + Expr: &plan.Expr_Lit{Lit: &plan.Literal{ + IsBin: true, + Value: &plan.Literal_Sval{Sval: "1"}, + }}, + }}} + child := resetChildren(arg, proc.Mp()) + require.NoError(t, arg.Prepare(proc)) + + result, err := arg.Call(proc) + require.NoError(t, err) + require.NotNil(t, result.Batch) + require.Len(t, result.Batch.Vecs, 1) + require.False(t, result.Batch.Vecs[0].GetIsBin()) + require.False(t, result.Batch.Vecs[0].GetIsBinaryString()) + require.Equal(t, types.T_varbinary, result.Batch.Vecs[0].GetType().Oid) + + arg.Free(proc, false, nil) + child.Free(proc, false, nil) + proc.Free() +} + +func TestMaterializeBinaryStringVectorUsesStaticBinaryType(t *testing.T) { + proc := testutil.NewProcess(t) + vec, err := vector.NewConstBytes(types.T_text.ToType(), []byte("你好"), 1, proc.Mp()) + require.NoError(t, err) + defer vec.Free(proc.Mp()) + vec.SetIsBinaryString(true) + + materializeBinaryStringVector(vec) + + require.Equal(t, types.T_varbinary, vec.GetType().Oid) + require.Equal(t, int32(len("你好")), vec.GetType().Width) + require.False(t, vec.GetIsBin()) + require.False(t, vec.GetIsBinaryString()) +} + func resetChildren(arg *Projection, m *mpool.MPool) *colexec.MockOperator { bat := colexec.MakeMockBatchs(m) op := colexec.NewMockOperator().WithBatchs([]*batch.Batch{bat}) diff --git a/pkg/sql/colexec/window/value_window_test.go b/pkg/sql/colexec/window/value_window_test.go index dc10c61f71036..4c74845d07e70 100644 --- a/pkg/sql/colexec/window/value_window_test.go +++ b/pkg/sql/colexec/window/value_window_test.go @@ -214,6 +214,22 @@ func TestProcessValueFunc_NthValue(t *testing.T) { } } +func TestProcessValueFuncPropagatesBinaryStringMetadata(t *testing.T) { + tests := []string{"lag", "lead", "first_value", "last_value", "nth_value"} + for _, name := range tests { + t.Run(name, func(t *testing.T) { + mp := mpool.MustNewZero() + bat := makeVarcharBatch(mp, []string{"你", "好"}) + bat.Vecs[0].SetIsBinaryString(true) + result := runValueWindowTest(t, + makeValueWindowSpecWithName(name, int32(types.T_varchar)), bat, mp) + defer result.Free(mp) + + require.True(t, result.GetIsBinaryString()) + }) + } +} + func TestProcessValueFuncHonorsCancellation(t *testing.T) { testCases := []struct { name string @@ -917,6 +933,36 @@ func TestProcessValueFunc_LagWithDefault(t *testing.T) { require.Equal(t, int64(0), mp.CurrNB()) } +func TestProcessValueFuncBinaryDefaultPropagatesMarker(t *testing.T) { + for _, name := range []string{"lag", "lead"} { + t.Run(name, func(t *testing.T) { + mp := mpool.MustNewZero() + proc := testutil.NewProcessWithMPool(t, "", mp) + bat := makeVarcharBatch(mp, []string{"a"}) + spec := makeValueWindowSpecWithName(name, int32(types.T_varchar)) + offsetVec, err := vector.NewConstFixed(types.T_int64.ToType(), int64(1), 1, mp) + require.NoError(t, err) + defaultVec, err := vector.NewConstBytes(types.T_varchar.ToType(), []byte{0xe4, 0xbd, 0xa0}, 1, mp) + require.NoError(t, err) + defaultVec.SetIsBinaryString(true) + + ctr := &container{bat: bat, aggVecs: make([]colexec.ExprEvalVector, 1)} + ctr.aggVecs[0].Vec = []*vector.Vector{bat.Vecs[0], offsetVec, defaultVec} + result, err := ctr.processValueFunc(0, &Window{WinSpecList: []*plan.Expr{spec}}, proc) + require.NoError(t, err) + require.True(t, result.GetIsBinaryString()) + require.Equal(t, []byte{0xe4, 0xbd, 0xa0}, result.GetBytesAt(0)) + + result.Free(mp) + offsetVec.Free(mp) + defaultVec.Free(mp) + bat.Clean(mp) + proc.Free() + require.Equal(t, int64(0), mp.CurrNB()) + }) + } +} + // TestProcessValueFunc_FirstValueWithFrame tests first_value with ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING. func TestProcessValueFunc_FirstValueWithFrame(t *testing.T) { mp := mpool.MustNewZero() diff --git a/pkg/sql/compile/compile.go b/pkg/sql/compile/compile.go index 2ede75cbc56b0..12ec9fecfaede 100644 --- a/pkg/sql/compile/compile.go +++ b/pkg/sql/compile/compile.go @@ -7047,6 +7047,8 @@ func (c *Compile) runSqlWithResultAndOptions( WithLowerCaseTableNames(&lower). WithStatementOption(options). WithResolveVariableFunc(c.proc.GetResolveVariableFunc()). + WithResolveVariableIsBinFunc(c.proc.GetResolveVariableIsBinFunc()). + WithResolveVariableBinaryStringFunc(c.proc.GetResolveVariableBinaryStringFunc()). WithFrontend(c.proc.Base.IsFrontend) ctx := c.proc.Ctx diff --git a/pkg/sql/compile/compile_test.go b/pkg/sql/compile/compile_test.go index ee54408bc7621..3e2a3806940d0 100644 --- a/pkg/sql/compile/compile_test.go +++ b/pkg/sql/compile/compile_test.go @@ -83,7 +83,7 @@ func TestHasOrderedGroupConcat(t *testing.T) { require.False(t, hasOrderedGroupConcat(ordered)) } -func TestCompileRunPreservesBinaryPrepareParamAcrossRetries(t *testing.T) { +func TestCompileRunPreservesBinaryStringPrepareParamAcrossRetries(t *testing.T) { ctx := defines.AttachAccountId(context.Background(), catalog.System_Account) proc := testutil.NewProcess(t) proc.GetSessionInfo().Buf = buffer.New() @@ -110,7 +110,7 @@ func TestCompileRunPreservesBinaryPrepareParamAcrossRetries(t *testing.T) { want := []byte{'A', 'B', 0, 0} params := vector.NewVec(types.T_text.ToType()) require.NoError(t, vector.AppendBytes(params, want, false, proc.Mp())) - proc.SetOwnedPrepareParamsWithIsBin(params, []bool{true}) + proc.SetOwnedPrepareParamsWithMetadata(params, []bool{false}, []bool{true}) evaluations := 0 fill := func(bat *batch.Batch, _ *perfcounter.CounterSet) error { @@ -118,7 +118,10 @@ func TestCompileRunPreservesBinaryPrepareParamAcrossRetries(t *testing.T) { return nil } require.Len(t, bat.Vecs, 1) - require.True(t, bat.Vecs[0].GetIsBin(), "binary semantics were lost on evaluation %d", evaluations+1) + require.False(t, bat.Vecs[0].GetIsBin(), "literal numeric metadata leaked on evaluation %d", evaluations+1) + require.False(t, bat.Vecs[0].GetIsBinaryString(), "materialized metadata leaked on evaluation %d", evaluations+1) + require.Equal(t, types.T_varbinary, bat.Vecs[0].GetType().Oid, + "binary string type was lost on evaluation %d", evaluations+1) require.Equal(t, want, bat.Vecs[0].GetBytesAt(0)) evaluations++ if evaluations <= 2 { diff --git a/pkg/sql/compile/remote_expr.go b/pkg/sql/compile/remote_expr.go index d5d5a892b8cf5..8c47e5ef6ffc1 100644 --- a/pkg/sql/compile/remote_expr.go +++ b/pkg/sql/compile/remote_expr.go @@ -18,6 +18,7 @@ import ( "reflect" "github.com/matrixorigin/matrixone/pkg/container/batch" + "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/colexec" plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan" @@ -280,6 +281,14 @@ func foldVarExprsInExprInPlace(expr *plan.Expr, proc *process.Process) (bool, er return false, nil } expr.Expr = &plan.Expr_Lit{Lit: lit} + if vec.GetIsBinaryString() { + width := int32(0) + if vec.Length() > 0 && !vec.IsNull(0) { + width = int32(len(vec.GetBytesAt(0))) + } + binaryType := types.New(types.T_varbinary, width, 0) + expr.Typ = plan2.MakePlan2Type(&binaryType) + } return true, nil } return foldVarExprsInValue(reflect.ValueOf(expr.Expr), nil, proc) diff --git a/pkg/sql/compile/remote_expr_test.go b/pkg/sql/compile/remote_expr_test.go index 4b315944f1b39..41451f9f946fb 100644 --- a/pkg/sql/compile/remote_expr_test.go +++ b/pkg/sql/compile/remote_expr_test.go @@ -139,6 +139,24 @@ func TestFoldVarExprsInScope(t *testing.T) { require.Equal(t, "STRICT_TRANS_TABLES", lit.Lit.GetSval()) } +func TestFoldVarExprsInScopeMaterializesBinaryStringAsStaticType(t *testing.T) { + proc := newResolveVariableProcess(t, "\xe4\xbd\xa0") + proc.SetResolveVariableBinaryStringFunc(func(string, bool, bool) (bool, error) { + return true, nil + }) + scope := newScope(Normal) + scope.DataSource = &Source{FilterList: []*plan.Expr{makeTestVarExpr("sql_mode")}} + + folded, err := foldVarExprsInScope(scope, proc) + require.NoError(t, err) + require.True(t, folded) + expr := scope.DataSource.FilterList[0] + require.Equal(t, int32(types.T_varbinary), expr.GetTyp().Id) + require.Equal(t, int32(3), expr.GetTyp().Width) + require.False(t, expr.GetLit().GetIsBin()) + require.Equal(t, "\xe4\xbd\xa0", expr.GetLit().GetSval()) +} + func TestFoldVarExprsInScopeUsesPrivateExprCopies(t *testing.T) { shared := makeTestVarExpr("sql_mode") proc1 := newResolveVariableProcess(t, "ANSI") diff --git a/pkg/sql/compile/remoterunServer.go b/pkg/sql/compile/remoterunServer.go index b0853a8f6911c..3a304f43041a9 100644 --- a/pkg/sql/compile/remoterunServer.go +++ b/pkg/sql/compile/remoterunServer.go @@ -938,7 +938,11 @@ func (receiver *messageReceiverOnServer) newCompile() (*Compile, error) { prepareParams.GetNulls().Add(uint64(i)) } } - proc.SetOwnedPrepareParamsWithIsBin(prepareParams, prepareParamMetadata) + proc.SetOwnedPrepareParamsWithMetadata( + prepareParams, + prepareParamMetadata, + append([]bool(nil), pHelper.prepareParams.IsBinaryString...), + ) } // Carry ROW_COUNT() state so row_count() pushed down to this remote CN reads // the previous statement's affected rows instead of the default 0. diff --git a/pkg/sql/compile/remoterunServer_test.go b/pkg/sql/compile/remoterunServer_test.go index f81aa31479846..8f11d9df1157f 100644 --- a/pkg/sql/compile/remoterunServer_test.go +++ b/pkg/sql/compile/remoterunServer_test.go @@ -366,11 +366,12 @@ func TestGenerateProcessHelper_WithSnapshot(t *testing.T) { }, }, PrepareParams: pipeline.PrepareParamInfo{ - Length: 2, - Data: append([]byte(nil), params.GetData()...), - Area: append([]byte(nil), params.GetArea()...), - Nulls: []bool{false, false}, - IsBin: []bool{true, false}, + Length: 2, + Data: append([]byte(nil), params.GetData()...), + Area: append([]byte(nil), params.GetArea()...), + Nulls: []bool{false, false}, + IsBin: []bool{true, false}, + IsBinaryString: []bool{false, true}, }, } @@ -382,6 +383,7 @@ func TestGenerateProcessHelper_WithSnapshot(t *testing.T) { require.Equal(t, "test-proc-id", helper.id) require.Equal(t, catalog.System_Account, helper.accountId) require.Equal(t, []bool{true, false}, helper.prepareParams.IsBin) + require.Equal(t, []bool{false, true}, helper.prepareParams.IsBinaryString) require.Equal(t, procInfo.PrepareParams.Data, helper.prepareParams.Data) require.Equal(t, procInfo.PrepareParams.Area, helper.prepareParams.Area) require.Equal(t, int64(42), helper.affectedRows) diff --git a/pkg/sql/compile/sql_executor.go b/pkg/sql/compile/sql_executor.go index 605cb7d6b05e5..9d60c45cbf614 100644 --- a/pkg/sql/compile/sql_executor.go +++ b/pkg/sql/compile/sql_executor.go @@ -393,11 +393,15 @@ func (exec *txnExecutor) Exec( // without opening a statement, so its compile must not advance the // workspace snapshot write offset (the statement boundary). proc.SetIncrStatementDisabled(exec.opts.DisableIncrStatement()) - proc.SetResolveVariableFunc(exec.opts.ResolveVariableFunc()) - if exec.opts.ResolveVariableFunc() != nil { proc.SetResolveVariableFunc(exec.opts.ResolveVariableFunc()) } + if exec.opts.ResolveVariableIsBinFunc() != nil { + proc.SetResolveVariableIsBinFunc(exec.opts.ResolveVariableIsBinFunc()) + } + if exec.opts.ResolveVariableBinaryStringFunc() != nil { + proc.SetResolveVariableBinaryStringFunc(exec.opts.ResolveVariableBinaryStringFunc()) + } // Propagate the "is this frontend?" signal onto the proc — same // pattern as ResolveVariableFunc above. The Options default is diff --git a/pkg/sql/parsers/dialect/mysql/mysql_sql.go b/pkg/sql/parsers/dialect/mysql/mysql_sql.go index 43cc4b5ce736d..0951d38dfb806 100644 --- a/pkg/sql/parsers/dialect/mysql/mysql_sql.go +++ b/pkg/sql/parsers/dialect/mysql/mysql_sql.go @@ -1578,7 +1578,7 @@ const yyEofCode = 1 const yyErrCode = 2 const yyInitialStackSize = 16 -//line mysql_sql.y:15419 +//line mysql_sql.y:15423 //line yacctab:1 var yyExca = [...]int{ @@ -10303,7 +10303,7 @@ var yyPgo = [...]int{ 4435, } -//line mysql_sql.y:15419 +//line mysql_sql.y:15423 type yySymType struct { union interface{} id int @@ -30360,21 +30360,25 @@ yydefault: var yyLOCAL *tree.FuncExpr //line mysql_sql.y:13169 { - cn := tree.NewNumVal(yyDollar[5].str, yyDollar[5].str, false, tree.P_char) - es := yyDollar[3].exprsUnion() - es = append(es, cn) - name := tree.NewUnresolvedColName(yyDollar[1].str) + charName := tree.NewUnresolvedColName(yyDollar[1].str) + charExpr := &tree.FuncExpr{ + Func: tree.FuncName2ResolvableFunctionReference(charName), + FuncName: tree.NewCStr(yyDollar[1].str, 1), + Exprs: yyDollar[3].exprsUnion(), + } + charset := tree.NewNumVal(yyDollar[5].str, yyDollar[5].str, false, tree.P_char) + name := tree.NewUnresolvedColName("convert") yyLOCAL = &tree.FuncExpr{ Func: tree.FuncName2ResolvableFunctionReference(name), - FuncName: tree.NewCStr(yyDollar[1].str, 1), - Exprs: es, + FuncName: tree.NewCStr("convert", 1), + Exprs: tree.Exprs{charExpr, charset}, } } yyVAL.union = yyLOCAL case 1985: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13181 +//line mysql_sql.y:13185 { val := tree.NewNumVal(yyDollar[2].str, yyDollar[2].str, false, tree.P_char) name := tree.NewUnresolvedColName(yyDollar[1].str) @@ -30388,7 +30392,7 @@ yydefault: case 1986: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13191 +//line mysql_sql.y:13195 { val := tree.NewNumVal(yyDollar[2].str, yyDollar[2].str, false, tree.P_char) name := tree.NewUnresolvedColName(yyDollar[1].str) @@ -30402,7 +30406,7 @@ yydefault: case 1987: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13201 +//line mysql_sql.y:13205 { name := tree.NewUnresolvedColName(yyDollar[1].str) yyLOCAL = &tree.FuncExpr{ @@ -30415,7 +30419,7 @@ yydefault: case 1988: yyDollar = yyS[yypt-6 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13210 +//line mysql_sql.y:13214 { es := tree.Exprs{yyDollar[3].exprUnion()} es = append(es, yyDollar[5].exprUnion()) @@ -30430,7 +30434,7 @@ yydefault: case 1989: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13221 +//line mysql_sql.y:13225 { name := tree.NewUnresolvedColName(yyDollar[1].str) yyLOCAL = &tree.FuncExpr{ @@ -30443,7 +30447,7 @@ yydefault: case 1990: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13230 +//line mysql_sql.y:13234 { val := tree.NewNumVal(yyDollar[2].str, yyDollar[2].str, false, tree.P_char) name := tree.NewUnresolvedColName(yyDollar[1].str) @@ -30457,7 +30461,7 @@ yydefault: case 1991: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13240 +//line mysql_sql.y:13244 { name := tree.NewUnresolvedColName(yyDollar[1].str) yyLOCAL = &tree.FuncExpr{ @@ -30470,7 +30474,7 @@ yydefault: case 1992: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13249 +//line mysql_sql.y:13253 { name := tree.NewUnresolvedColName(yyDollar[1].str) yyLOCAL = &tree.FuncExpr{ @@ -30483,7 +30487,7 @@ yydefault: case 1993: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.FuncExpr -//line mysql_sql.y:13258 +//line mysql_sql.y:13262 { name := tree.NewUnresolvedColName(yyDollar[1].str) yyLOCAL = &tree.FuncExpr{ @@ -30496,7 +30500,7 @@ yydefault: case 1994: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13268 +//line mysql_sql.y:13272 { yyLOCAL = nil } @@ -30504,7 +30508,7 @@ yydefault: case 1995: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13272 +//line mysql_sql.y:13276 { yyLOCAL = yyDollar[1].exprUnion() } @@ -30512,7 +30516,7 @@ yydefault: case 1996: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13278 +//line mysql_sql.y:13282 { yyLOCAL = nil } @@ -30520,7 +30524,7 @@ yydefault: case 1997: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13282 +//line mysql_sql.y:13286 { ival, errStr := util.GetInt64(yyDollar[2].item) if errStr != "" { @@ -30533,18 +30537,18 @@ yydefault: yyVAL.union = yyLOCAL case 2004: yyDollar = yyS[yypt-0 : yypt+1] -//line mysql_sql.y:13301 +//line mysql_sql.y:13305 { } case 2005: yyDollar = yyS[yypt-2 : yypt+1] -//line mysql_sql.y:13303 +//line mysql_sql.y:13307 { } case 2039: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13344 +//line mysql_sql.y:13348 { name := tree.NewUnresolvedColName(yyDollar[1].str) str := strings.ToLower(yyDollar[3].str) @@ -30559,7 +30563,7 @@ yydefault: case 2040: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL tree.FuncType -//line mysql_sql.y:13356 +//line mysql_sql.y:13360 { yyLOCAL = tree.FUNC_TYPE_DEFAULT } @@ -30567,7 +30571,7 @@ yydefault: case 2041: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.FuncType -//line mysql_sql.y:13360 +//line mysql_sql.y:13364 { yyLOCAL = tree.FUNC_TYPE_DISTINCT } @@ -30575,7 +30579,7 @@ yydefault: case 2042: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.FuncType -//line mysql_sql.y:13364 +//line mysql_sql.y:13368 { yyLOCAL = tree.FUNC_TYPE_ALL } @@ -30583,7 +30587,7 @@ yydefault: case 2043: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL *tree.Tuple -//line mysql_sql.y:13370 +//line mysql_sql.y:13374 { yyLOCAL = tree.NewTuple(yyDollar[2].exprsUnion()) } @@ -30591,7 +30595,7 @@ yydefault: case 2044: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL tree.Exprs -//line mysql_sql.y:13375 +//line mysql_sql.y:13379 { yyLOCAL = nil } @@ -30599,7 +30603,7 @@ yydefault: case 2045: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Exprs -//line mysql_sql.y:13379 +//line mysql_sql.y:13383 { yyLOCAL = yyDollar[1].exprsUnion() } @@ -30607,7 +30611,7 @@ yydefault: case 2046: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Exprs -//line mysql_sql.y:13385 +//line mysql_sql.y:13389 { yyLOCAL = tree.Exprs{yyDollar[1].exprUnion()} } @@ -30615,7 +30619,7 @@ yydefault: case 2047: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Exprs -//line mysql_sql.y:13389 +//line mysql_sql.y:13393 { yyLOCAL = append(yyDollar[1].exprsUnion(), yyDollar[3].exprUnion()) } @@ -30623,7 +30627,7 @@ yydefault: case 2048: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Exprs -//line mysql_sql.y:13395 +//line mysql_sql.y:13399 { yyLOCAL = tree.Exprs{yyDollar[1].exprUnion()} } @@ -30631,7 +30635,7 @@ yydefault: case 2049: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Exprs -//line mysql_sql.y:13399 +//line mysql_sql.y:13403 { yyLOCAL = append(yyDollar[1].exprsUnion(), yyDollar[3].exprUnion()) } @@ -30639,7 +30643,7 @@ yydefault: case 2050: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13406 +//line mysql_sql.y:13410 { yyLOCAL = tree.NewAndExpr(yyDollar[1].exprUnion(), yyDollar[3].exprUnion()) } @@ -30647,7 +30651,7 @@ yydefault: case 2051: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13410 +//line mysql_sql.y:13414 { yyLOCAL = tree.NewOrExpr(yyDollar[1].exprUnion(), yyDollar[3].exprUnion()) } @@ -30655,7 +30659,7 @@ yydefault: case 2052: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13414 +//line mysql_sql.y:13418 { yyLOCAL = tree.NewXorExpr(yyDollar[1].exprUnion(), yyDollar[3].exprUnion()) } @@ -30663,7 +30667,7 @@ yydefault: case 2053: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13418 +//line mysql_sql.y:13422 { yyLOCAL = tree.NewNotExpr(yyDollar[2].exprUnion()) } @@ -30671,7 +30675,7 @@ yydefault: case 2054: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13422 +//line mysql_sql.y:13426 { yyLOCAL = yyDollar[1].exprUnion() } @@ -30679,7 +30683,7 @@ yydefault: case 2055: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13427 +//line mysql_sql.y:13431 { yyLOCAL = yyDollar[1].exprUnion() } @@ -30687,7 +30691,7 @@ yydefault: case 2056: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13431 +//line mysql_sql.y:13435 { yyLOCAL = tree.NewMaxValue() } @@ -30695,7 +30699,7 @@ yydefault: case 2057: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13437 +//line mysql_sql.y:13441 { yyLOCAL = tree.NewIsNullExpr(yyDollar[1].exprUnion()) } @@ -30703,7 +30707,7 @@ yydefault: case 2058: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13441 +//line mysql_sql.y:13445 { yyLOCAL = tree.NewIsNotNullExpr(yyDollar[1].exprUnion()) } @@ -30711,7 +30715,7 @@ yydefault: case 2059: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13445 +//line mysql_sql.y:13449 { yyLOCAL = tree.NewIsUnknownExpr(yyDollar[1].exprUnion()) } @@ -30719,7 +30723,7 @@ yydefault: case 2060: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13449 +//line mysql_sql.y:13453 { yyLOCAL = tree.NewIsNotUnknownExpr(yyDollar[1].exprUnion()) } @@ -30727,7 +30731,7 @@ yydefault: case 2061: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13453 +//line mysql_sql.y:13457 { yyLOCAL = tree.NewIsTrueExpr(yyDollar[1].exprUnion()) } @@ -30735,7 +30739,7 @@ yydefault: case 2062: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13457 +//line mysql_sql.y:13461 { yyLOCAL = tree.NewIsNotTrueExpr(yyDollar[1].exprUnion()) } @@ -30743,7 +30747,7 @@ yydefault: case 2063: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13461 +//line mysql_sql.y:13465 { yyLOCAL = tree.NewIsFalseExpr(yyDollar[1].exprUnion()) } @@ -30751,7 +30755,7 @@ yydefault: case 2064: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13465 +//line mysql_sql.y:13469 { yyLOCAL = tree.NewIsNotFalseExpr(yyDollar[1].exprUnion()) } @@ -30759,7 +30763,7 @@ yydefault: case 2065: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13469 +//line mysql_sql.y:13473 { yyLOCAL = tree.NewComparisonExpr(yyDollar[2].comparisonOpUnion(), yyDollar[1].exprUnion(), yyDollar[3].exprUnion()) } @@ -30767,7 +30771,7 @@ yydefault: case 2066: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13473 +//line mysql_sql.y:13477 { yyLOCAL = tree.NewSubqueryComparisonExpr(yyDollar[2].comparisonOpUnion(), yyDollar[3].comparisonOpUnion(), yyDollar[1].exprUnion(), yyDollar[4].subqueryUnion()) yyLOCAL = tree.NewSubqueryComparisonExpr(yyDollar[2].comparisonOpUnion(), yyDollar[3].comparisonOpUnion(), yyDollar[1].exprUnion(), yyDollar[4].subqueryUnion()) @@ -30776,7 +30780,7 @@ yydefault: case 2068: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13481 +//line mysql_sql.y:13485 { yyLOCAL = tree.NewComparisonExpr(tree.IN, yyDollar[1].exprUnion(), yyDollar[3].exprUnion()) } @@ -30784,7 +30788,7 @@ yydefault: case 2069: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13485 +//line mysql_sql.y:13489 { yyLOCAL = tree.NewComparisonExpr(tree.NOT_IN, yyDollar[1].exprUnion(), yyDollar[4].exprUnion()) } @@ -30792,7 +30796,7 @@ yydefault: case 2070: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13489 +//line mysql_sql.y:13493 { yyLOCAL = tree.NewComparisonExprWithEscape(tree.LIKE, yyDollar[1].exprUnion(), yyDollar[3].exprUnion(), yyDollar[4].exprUnion()) } @@ -30800,7 +30804,7 @@ yydefault: case 2071: yyDollar = yyS[yypt-5 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13493 +//line mysql_sql.y:13497 { yyLOCAL = tree.NewComparisonExprWithEscape(tree.NOT_LIKE, yyDollar[1].exprUnion(), yyDollar[4].exprUnion(), yyDollar[5].exprUnion()) } @@ -30808,7 +30812,7 @@ yydefault: case 2072: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13497 +//line mysql_sql.y:13501 { yyLOCAL = tree.NewComparisonExprWithEscape(tree.ILIKE, yyDollar[1].exprUnion(), yyDollar[3].exprUnion(), yyDollar[4].exprUnion()) } @@ -30816,7 +30820,7 @@ yydefault: case 2073: yyDollar = yyS[yypt-5 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13501 +//line mysql_sql.y:13505 { yyLOCAL = tree.NewComparisonExprWithEscape(tree.NOT_ILIKE, yyDollar[1].exprUnion(), yyDollar[4].exprUnion(), yyDollar[5].exprUnion()) } @@ -30824,7 +30828,7 @@ yydefault: case 2074: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13505 +//line mysql_sql.y:13509 { yyLOCAL = tree.NewComparisonExpr(tree.REG_MATCH, yyDollar[1].exprUnion(), yyDollar[3].exprUnion()) } @@ -30832,7 +30836,7 @@ yydefault: case 2075: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13509 +//line mysql_sql.y:13513 { yyLOCAL = tree.NewComparisonExpr(tree.NOT_REG_MATCH, yyDollar[1].exprUnion(), yyDollar[4].exprUnion()) } @@ -30840,7 +30844,7 @@ yydefault: case 2076: yyDollar = yyS[yypt-5 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13513 +//line mysql_sql.y:13517 { yyLOCAL = tree.NewRangeCond(false, yyDollar[1].exprUnion(), yyDollar[3].exprUnion(), yyDollar[5].exprUnion()) } @@ -30848,7 +30852,7 @@ yydefault: case 2077: yyDollar = yyS[yypt-6 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13517 +//line mysql_sql.y:13521 { yyLOCAL = tree.NewRangeCond(true, yyDollar[1].exprUnion(), yyDollar[4].exprUnion(), yyDollar[6].exprUnion()) } @@ -30856,7 +30860,7 @@ yydefault: case 2079: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13523 +//line mysql_sql.y:13527 { yyLOCAL = nil } @@ -30864,7 +30868,7 @@ yydefault: case 2080: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13527 +//line mysql_sql.y:13531 { yyLOCAL = yyDollar[2].exprUnion() } @@ -30872,7 +30876,7 @@ yydefault: case 2081: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13533 +//line mysql_sql.y:13537 { yyLOCAL = yyDollar[1].tupleUnion() } @@ -30880,7 +30884,7 @@ yydefault: case 2082: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13537 +//line mysql_sql.y:13541 { yyLOCAL = yyDollar[1].subqueryUnion() } @@ -30888,7 +30892,7 @@ yydefault: case 2083: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13544 +//line mysql_sql.y:13548 { yyLOCAL = tree.ALL } @@ -30896,7 +30900,7 @@ yydefault: case 2084: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13548 +//line mysql_sql.y:13552 { yyLOCAL = tree.ANY } @@ -30904,7 +30908,7 @@ yydefault: case 2085: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13552 +//line mysql_sql.y:13556 { yyLOCAL = tree.SOME } @@ -30912,7 +30916,7 @@ yydefault: case 2086: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13558 +//line mysql_sql.y:13562 { yyLOCAL = tree.EQUAL } @@ -30920,7 +30924,7 @@ yydefault: case 2087: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13562 +//line mysql_sql.y:13566 { yyLOCAL = tree.LESS_THAN } @@ -30928,7 +30932,7 @@ yydefault: case 2088: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13566 +//line mysql_sql.y:13570 { yyLOCAL = tree.GREAT_THAN } @@ -30936,7 +30940,7 @@ yydefault: case 2089: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13570 +//line mysql_sql.y:13574 { yyLOCAL = tree.LESS_THAN_EQUAL } @@ -30944,7 +30948,7 @@ yydefault: case 2090: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13574 +//line mysql_sql.y:13578 { yyLOCAL = tree.GREAT_THAN_EQUAL } @@ -30952,7 +30956,7 @@ yydefault: case 2091: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13578 +//line mysql_sql.y:13582 { yyLOCAL = tree.NOT_EQUAL } @@ -30960,7 +30964,7 @@ yydefault: case 2092: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ComparisonOp -//line mysql_sql.y:13582 +//line mysql_sql.y:13586 { yyLOCAL = tree.NULL_SAFE_EQUAL } @@ -30968,7 +30972,7 @@ yydefault: case 2093: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.ColumnAttribute -//line mysql_sql.y:13588 +//line mysql_sql.y:13592 { yyLOCAL = tree.NewAttributePrimaryKey() } @@ -30976,7 +30980,7 @@ yydefault: case 2094: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.ColumnAttribute -//line mysql_sql.y:13592 +//line mysql_sql.y:13596 { yyLOCAL = tree.NewAttributeUniqueKey() } @@ -30984,7 +30988,7 @@ yydefault: case 2095: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ColumnAttribute -//line mysql_sql.y:13596 +//line mysql_sql.y:13600 { yyLOCAL = tree.NewAttributeUnique() } @@ -30992,7 +30996,7 @@ yydefault: case 2096: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.ColumnAttribute -//line mysql_sql.y:13600 +//line mysql_sql.y:13604 { yyLOCAL = tree.NewAttributeKey() } @@ -31000,7 +31004,7 @@ yydefault: case 2097: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13606 +//line mysql_sql.y:13610 { str := fmt.Sprintf("%v", yyDollar[1].item) switch v := yyDollar[1].item.(type) { @@ -31017,7 +31021,7 @@ yydefault: case 2098: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13619 +//line mysql_sql.y:13623 { fval := yyDollar[1].item.(float64) yyLOCAL = tree.NewNumVal(fval, yylex.(*Lexer).scanner.LastToken, false, tree.P_float64) @@ -31026,7 +31030,7 @@ yydefault: case 2099: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13624 +//line mysql_sql.y:13628 { yyLOCAL = tree.NewNumVal(yyDollar[1].str, yyDollar[1].str, false, tree.P_decimal) } @@ -31034,7 +31038,7 @@ yydefault: case 2100: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13630 +//line mysql_sql.y:13634 { yyLOCAL = tree.NewNumVal(yyDollar[1].str, yyDollar[1].str, false, tree.P_char) } @@ -31042,7 +31046,7 @@ yydefault: case 2101: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13634 +//line mysql_sql.y:13638 { str := fmt.Sprintf("%v", yyDollar[1].item) switch v := yyDollar[1].item.(type) { @@ -31059,7 +31063,7 @@ yydefault: case 2102: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13647 +//line mysql_sql.y:13651 { fval := yyDollar[1].item.(float64) yyLOCAL = tree.NewNumVal(fval, yylex.(*Lexer).scanner.LastToken, false, tree.P_float64) @@ -31068,7 +31072,7 @@ yydefault: case 2103: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13652 +//line mysql_sql.y:13656 { yyLOCAL = tree.NewNumVal(true, "true", false, tree.P_bool) } @@ -31076,7 +31080,7 @@ yydefault: case 2104: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13656 +//line mysql_sql.y:13660 { yyLOCAL = tree.NewNumVal(false, "false", false, tree.P_bool) } @@ -31084,7 +31088,7 @@ yydefault: case 2105: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13660 +//line mysql_sql.y:13664 { yyLOCAL = tree.NewNumVal("null", "null", false, tree.P_null) } @@ -31092,7 +31096,7 @@ yydefault: case 2106: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13664 +//line mysql_sql.y:13668 { yyLOCAL = tree.NewNumVal(yyDollar[1].str, yyDollar[1].str, false, tree.P_hexnum) } @@ -31100,7 +31104,7 @@ yydefault: case 2107: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13668 +//line mysql_sql.y:13672 { yyLOCAL = tree.NewNumVal(yyDollar[2].str, yyDollar[2].str, false, tree.P_ScoreBinaryHexnum) } @@ -31108,7 +31112,7 @@ yydefault: case 2108: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13672 +//line mysql_sql.y:13676 { yyLOCAL = tree.NewNumVal(yyDollar[1].str, yyDollar[1].str, false, tree.P_decimal) } @@ -31116,7 +31120,7 @@ yydefault: case 2109: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13676 +//line mysql_sql.y:13680 { yyLOCAL = tree.NewNumVal(yyDollar[1].str, yyDollar[1].str, false, tree.P_bit) } @@ -31124,7 +31128,7 @@ yydefault: case 2110: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13680 +//line mysql_sql.y:13684 { yyLOCAL = tree.NewParamExpr(yylex.(*Lexer).GetParamIndex()) } @@ -31132,7 +31136,7 @@ yydefault: case 2111: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Expr -//line mysql_sql.y:13684 +//line mysql_sql.y:13688 { yyLOCAL = tree.NewNumVal(yyDollar[2].str, yyDollar[2].str, false, tree.P_ScoreBinary) } @@ -31140,7 +31144,7 @@ yydefault: case 2112: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13690 +//line mysql_sql.y:13694 { yyLOCAL = yyDollar[1].columnTypeUnion() yyLOCAL.InternalType.Unsigned = yyDollar[2].unsignedOptUnion() @@ -31150,7 +31154,7 @@ yydefault: case 2116: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13699 +//line mysql_sql.y:13703 { locale := "" yyLOCAL = &tree.T{ @@ -31167,7 +31171,7 @@ yydefault: case 2117: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13714 +//line mysql_sql.y:13718 { yyLOCAL = yyDollar[1].columnTypeUnion() yyLOCAL.InternalType.DisplayWith = yyDollar[2].lengthOptUnion() @@ -31176,7 +31180,7 @@ yydefault: case 2118: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13719 +//line mysql_sql.y:13723 { yyLOCAL = yyDollar[1].columnTypeUnion() } @@ -31184,7 +31188,7 @@ yydefault: case 2119: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13725 +//line mysql_sql.y:13729 { locale := "" yyLOCAL = &tree.T{ @@ -31200,7 +31204,7 @@ yydefault: case 2120: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13737 +//line mysql_sql.y:13741 { locale := "" yyLOCAL = &tree.T{ @@ -31216,7 +31220,7 @@ yydefault: case 2121: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13749 +//line mysql_sql.y:13753 { locale := "" yyLOCAL = &tree.T{ @@ -31232,7 +31236,7 @@ yydefault: case 2122: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13761 +//line mysql_sql.y:13765 { locale := "" yyLOCAL = &tree.T{ @@ -31249,7 +31253,7 @@ yydefault: case 2123: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13774 +//line mysql_sql.y:13778 { locale := "" yyLOCAL = &tree.T{ @@ -31266,7 +31270,7 @@ yydefault: case 2124: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13787 +//line mysql_sql.y:13791 { locale := "" yyLOCAL = &tree.T{ @@ -31283,7 +31287,7 @@ yydefault: case 2125: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13800 +//line mysql_sql.y:13804 { locale := "" yyLOCAL = &tree.T{ @@ -31300,7 +31304,7 @@ yydefault: case 2126: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13813 +//line mysql_sql.y:13817 { locale := "" yyLOCAL = &tree.T{ @@ -31317,7 +31321,7 @@ yydefault: case 2127: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13826 +//line mysql_sql.y:13830 { locale := "" yyLOCAL = &tree.T{ @@ -31334,7 +31338,7 @@ yydefault: case 2128: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13839 +//line mysql_sql.y:13843 { locale := "" yyLOCAL = &tree.T{ @@ -31351,7 +31355,7 @@ yydefault: case 2129: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13852 +//line mysql_sql.y:13856 { locale := "" yyLOCAL = &tree.T{ @@ -31368,7 +31372,7 @@ yydefault: case 2130: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13865 +//line mysql_sql.y:13869 { locale := "" yyLOCAL = &tree.T{ @@ -31385,7 +31389,7 @@ yydefault: case 2131: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13878 +//line mysql_sql.y:13882 { locale := "" yyLOCAL = &tree.T{ @@ -31402,7 +31406,7 @@ yydefault: case 2132: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13891 +//line mysql_sql.y:13895 { locale := "" yyLOCAL = &tree.T{ @@ -31419,7 +31423,7 @@ yydefault: case 2133: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13906 +//line mysql_sql.y:13910 { locale := "" if yyDollar[2].lengthScaleOptUnion().DisplayWith > 255 { @@ -31450,7 +31454,7 @@ yydefault: case 2134: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13933 +//line mysql_sql.y:13937 { // DOUBLE PRECISION is the SQL-standard synonym for DOUBLE (float64). locale := "" @@ -31482,7 +31486,7 @@ yydefault: case 2135: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:13961 +//line mysql_sql.y:13965 { locale := "" if yyDollar[2].lengthScaleOptUnion().DisplayWith > 255 { @@ -31527,7 +31531,7 @@ yydefault: case 2136: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14003 +//line mysql_sql.y:14007 { locale := "" if yyDollar[2].lengthScaleOptUnion().Scale != tree.NotDefineDec && yyDollar[2].lengthScaleOptUnion().Scale > yyDollar[2].lengthScaleOptUnion().DisplayWith { @@ -31579,7 +31583,7 @@ yydefault: case 2137: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14051 +//line mysql_sql.y:14055 { locale := "" if yyDollar[2].lengthScaleOptUnion().Scale != tree.NotDefineDec && yyDollar[2].lengthScaleOptUnion().Scale > yyDollar[2].lengthScaleOptUnion().DisplayWith { @@ -31631,7 +31635,7 @@ yydefault: case 2138: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14099 +//line mysql_sql.y:14103 { locale := "" width := int32(64) @@ -31656,7 +31660,7 @@ yydefault: case 2139: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14122 +//line mysql_sql.y:14126 { locale := "" yyLOCAL = &tree.T{ @@ -31672,7 +31676,7 @@ yydefault: case 2140: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14134 +//line mysql_sql.y:14138 { locale := "" if yyDollar[2].lengthOptUnion() < 0 || yyDollar[2].lengthOptUnion() > 6 { @@ -31696,7 +31700,7 @@ yydefault: case 2141: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14154 +//line mysql_sql.y:14158 { locale := "" if yyDollar[2].lengthOptUnion() < 0 || yyDollar[2].lengthOptUnion() > 6 { @@ -31720,7 +31724,7 @@ yydefault: case 2142: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14174 +//line mysql_sql.y:14178 { locale := "" if yyDollar[2].lengthOptUnion() < 0 || yyDollar[2].lengthOptUnion() > 6 { @@ -31744,7 +31748,7 @@ yydefault: case 2143: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14194 +//line mysql_sql.y:14198 { locale := "" yyLOCAL = &tree.T{ @@ -31762,7 +31766,7 @@ yydefault: case 2144: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14210 +//line mysql_sql.y:14214 { locale := "" yyLOCAL = &tree.T{ @@ -31779,7 +31783,7 @@ yydefault: case 2145: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14223 +//line mysql_sql.y:14227 { locale := "" yyLOCAL = &tree.T{ @@ -31796,7 +31800,7 @@ yydefault: case 2146: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14236 +//line mysql_sql.y:14240 { locale := "" yyLOCAL = &tree.T{ @@ -31813,7 +31817,7 @@ yydefault: case 2147: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14249 +//line mysql_sql.y:14253 { locale := "" yyLOCAL = &tree.T{ @@ -31830,7 +31834,7 @@ yydefault: case 2148: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14262 +//line mysql_sql.y:14266 { locale := "" yyLOCAL = &tree.T{ @@ -31846,7 +31850,7 @@ yydefault: case 2149: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14274 +//line mysql_sql.y:14278 { locale := "" yyLOCAL = &tree.T{ @@ -31862,7 +31866,7 @@ yydefault: case 2150: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14286 +//line mysql_sql.y:14290 { locale := "" yyLOCAL = &tree.T{ @@ -31878,7 +31882,7 @@ yydefault: case 2151: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14298 +//line mysql_sql.y:14302 { locale := "" yyLOCAL = &tree.T{ @@ -31894,7 +31898,7 @@ yydefault: case 2152: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14310 +//line mysql_sql.y:14314 { locale := "" yyLOCAL = &tree.T{ @@ -31910,7 +31914,7 @@ yydefault: case 2153: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14322 +//line mysql_sql.y:14326 { locale := "" yyLOCAL = &tree.T{ @@ -31926,7 +31930,7 @@ yydefault: case 2154: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14334 +//line mysql_sql.y:14338 { locale := "" yyLOCAL = &tree.T{ @@ -31942,7 +31946,7 @@ yydefault: case 2155: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14346 +//line mysql_sql.y:14350 { locale := "" yyLOCAL = &tree.T{ @@ -31958,7 +31962,7 @@ yydefault: case 2156: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14358 +//line mysql_sql.y:14362 { locale := "" yyLOCAL = &tree.T{ @@ -31974,7 +31978,7 @@ yydefault: case 2157: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14370 +//line mysql_sql.y:14374 { locale := "" yyLOCAL = &tree.T{ @@ -31990,7 +31994,7 @@ yydefault: case 2158: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14382 +//line mysql_sql.y:14386 { locale := "" yyLOCAL = &tree.T{ @@ -32006,7 +32010,7 @@ yydefault: case 2159: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14394 +//line mysql_sql.y:14398 { locale := "" yyLOCAL = &tree.T{ @@ -32022,7 +32026,7 @@ yydefault: case 2160: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14406 +//line mysql_sql.y:14410 { locale := "" yyLOCAL = &tree.T{ @@ -32039,7 +32043,7 @@ yydefault: case 2161: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14419 +//line mysql_sql.y:14423 { locale := "" yyLOCAL = &tree.T{ @@ -32056,7 +32060,7 @@ yydefault: case 2162: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14432 +//line mysql_sql.y:14436 { locale := "" yyLOCAL = &tree.T{ @@ -32073,7 +32077,7 @@ yydefault: case 2163: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14445 +//line mysql_sql.y:14449 { locale := "" yyLOCAL = &tree.T{ @@ -32090,7 +32094,7 @@ yydefault: case 2164: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14458 +//line mysql_sql.y:14462 { locale := "" yyLOCAL = &tree.T{ @@ -32107,7 +32111,7 @@ yydefault: case 2165: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14471 +//line mysql_sql.y:14475 { locale := "" yyLOCAL = &tree.T{ @@ -32124,7 +32128,7 @@ yydefault: case 2166: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14484 +//line mysql_sql.y:14488 { locale := "" yyLOCAL = &tree.T{ @@ -32141,7 +32145,7 @@ yydefault: case 2167: yyDollar = yyS[yypt-4 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14497 +//line mysql_sql.y:14501 { locale := "" yyLOCAL = &tree.T{ @@ -32158,7 +32162,7 @@ yydefault: case 2168: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14510 +//line mysql_sql.y:14514 { locale := "" yyLOCAL = &tree.T{ @@ -32175,7 +32179,7 @@ yydefault: case 2169: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Statement -//line mysql_sql.y:14525 +//line mysql_sql.y:14529 { yyLOCAL = &tree.Do{ Exprs: yyDollar[2].exprsUnion(), @@ -32185,7 +32189,7 @@ yydefault: case 2170: yyDollar = yyS[yypt-2 : yypt+1] var yyLOCAL tree.Statement -//line mysql_sql.y:14533 +//line mysql_sql.y:14537 { yyDollar[2].selectUnion().IsPerform = true yyLOCAL = yyDollar[2].selectUnion() @@ -32194,7 +32198,7 @@ yydefault: case 2171: yyDollar = yyS[yypt-7 : yypt+1] var yyLOCAL *tree.Select -//line mysql_sql.y:14540 +//line mysql_sql.y:14544 { yyLOCAL = &tree.Select{Select: yyDollar[1].selectStatementUnion(), TimeWindow: yyDollar[2].timeWindowUnion(), OrderBy: yyDollar[3].orderByUnion(), Limit: yyDollar[4].limitUnion(), RankOption: yyDollar[5].rankOptionUnion(), Ep: yyDollar[6].exportParmUnion(), SelectLockInfo: yyDollar[7].selectLockInfoUnion()} } @@ -32202,7 +32206,7 @@ yydefault: case 2172: yyDollar = yyS[yypt-8 : yypt+1] var yyLOCAL *tree.Select -//line mysql_sql.y:14544 +//line mysql_sql.y:14548 { yyLOCAL = &tree.Select{Select: yyDollar[2].selectStatementUnion(), TimeWindow: yyDollar[3].timeWindowUnion(), OrderBy: yyDollar[4].orderByUnion(), Limit: yyDollar[5].limitUnion(), RankOption: yyDollar[6].rankOptionUnion(), Ep: yyDollar[7].exportParmUnion(), SelectLockInfo: yyDollar[8].selectLockInfoUnion(), With: yyDollar[1].withClauseUnion()} } @@ -32210,7 +32214,7 @@ yydefault: case 2173: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.Statement -//line mysql_sql.y:14550 +//line mysql_sql.y:14554 { yyLOCAL = &tree.Declare{ Variables: yyDollar[2].strsUnion(), @@ -32222,7 +32226,7 @@ yydefault: case 2174: yyDollar = yyS[yypt-5 : yypt+1] var yyLOCAL tree.Statement -//line mysql_sql.y:14559 +//line mysql_sql.y:14563 { yyLOCAL = &tree.Declare{ Variables: yyDollar[2].strsUnion(), @@ -32234,7 +32238,7 @@ yydefault: case 2175: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL *tree.T -//line mysql_sql.y:14569 +//line mysql_sql.y:14573 { yyLOCAL = tree.NewSpatialType(yyDollar[1].str) } @@ -32242,7 +32246,7 @@ yydefault: case 2194: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL []string -//line mysql_sql.y:14597 +//line mysql_sql.y:14601 { yyLOCAL = make([]string, 0, 4) yyLOCAL = append(yyLOCAL, yyDollar[1].str) @@ -32251,7 +32255,7 @@ yydefault: case 2195: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL []string -//line mysql_sql.y:14602 +//line mysql_sql.y:14606 { yyLOCAL = append(yyDollar[1].strsUnion(), yyDollar[3].str) } @@ -32259,7 +32263,7 @@ yydefault: case 2196: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL int32 -//line mysql_sql.y:14608 +//line mysql_sql.y:14612 { yyLOCAL = 0 } @@ -32267,7 +32271,7 @@ yydefault: case 2198: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL int32 -//line mysql_sql.y:14615 +//line mysql_sql.y:14619 { yyLOCAL = 0 } @@ -32275,7 +32279,7 @@ yydefault: case 2199: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL int32 -//line mysql_sql.y:14619 +//line mysql_sql.y:14623 { yyLOCAL = int32(yyDollar[2].item.(int64)) } @@ -32283,7 +32287,7 @@ yydefault: case 2200: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL int32 -//line mysql_sql.y:14624 +//line mysql_sql.y:14628 { yyLOCAL = int32(-1) } @@ -32291,7 +32295,7 @@ yydefault: case 2201: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL int32 -//line mysql_sql.y:14628 +//line mysql_sql.y:14632 { yyLOCAL = int32(yyDollar[2].item.(int64)) } @@ -32299,7 +32303,7 @@ yydefault: case 2202: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL int32 -//line mysql_sql.y:14634 +//line mysql_sql.y:14638 { yyLOCAL = tree.GetDisplayWith(int32(yyDollar[2].item.(int64))) } @@ -32307,7 +32311,7 @@ yydefault: case 2203: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL tree.LengthScaleOpt -//line mysql_sql.y:14640 +//line mysql_sql.y:14644 { yyLOCAL = tree.LengthScaleOpt{ DisplayWith: tree.NotDefineDisplayWidth, @@ -32318,7 +32322,7 @@ yydefault: case 2204: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.LengthScaleOpt -//line mysql_sql.y:14647 +//line mysql_sql.y:14651 { yyLOCAL = tree.LengthScaleOpt{ DisplayWith: tree.GetDisplayWith(int32(yyDollar[2].item.(int64))), @@ -32329,7 +32333,7 @@ yydefault: case 2205: yyDollar = yyS[yypt-5 : yypt+1] var yyLOCAL tree.LengthScaleOpt -//line mysql_sql.y:14654 +//line mysql_sql.y:14658 { yyLOCAL = tree.LengthScaleOpt{ DisplayWith: tree.GetDisplayWith(int32(yyDollar[2].item.(int64))), @@ -32340,7 +32344,7 @@ yydefault: case 2206: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL tree.LengthScaleOpt -//line mysql_sql.y:14663 +//line mysql_sql.y:14667 { yyLOCAL = tree.LengthScaleOpt{ DisplayWith: 38, // this is the default precision for decimal @@ -32351,7 +32355,7 @@ yydefault: case 2207: yyDollar = yyS[yypt-3 : yypt+1] var yyLOCAL tree.LengthScaleOpt -//line mysql_sql.y:14670 +//line mysql_sql.y:14674 { yyLOCAL = tree.LengthScaleOpt{ DisplayWith: tree.GetDisplayWith(int32(yyDollar[2].item.(int64))), @@ -32362,7 +32366,7 @@ yydefault: case 2208: yyDollar = yyS[yypt-5 : yypt+1] var yyLOCAL tree.LengthScaleOpt -//line mysql_sql.y:14677 +//line mysql_sql.y:14681 { yyLOCAL = tree.LengthScaleOpt{ DisplayWith: tree.GetDisplayWith(int32(yyDollar[2].item.(int64))), @@ -32373,7 +32377,7 @@ yydefault: case 2209: yyDollar = yyS[yypt-0 : yypt+1] var yyLOCAL bool -//line mysql_sql.y:14686 +//line mysql_sql.y:14690 { yyLOCAL = false } @@ -32381,7 +32385,7 @@ yydefault: case 2210: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL bool -//line mysql_sql.y:14690 +//line mysql_sql.y:14694 { yyLOCAL = true } @@ -32389,33 +32393,33 @@ yydefault: case 2211: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL bool -//line mysql_sql.y:14694 +//line mysql_sql.y:14698 { yyLOCAL = false } yyVAL.union = yyLOCAL case 2212: yyDollar = yyS[yypt-0 : yypt+1] -//line mysql_sql.y:14700 +//line mysql_sql.y:14704 { } case 2213: yyDollar = yyS[yypt-1 : yypt+1] var yyLOCAL bool -//line mysql_sql.y:14702 +//line mysql_sql.y:14706 { yyLOCAL = true } yyVAL.union = yyLOCAL case 2217: yyDollar = yyS[yypt-0 : yypt+1] -//line mysql_sql.y:14712 +//line mysql_sql.y:14716 { yyVAL.str = "" } case 2218: yyDollar = yyS[yypt-1 : yypt+1] -//line mysql_sql.y:14716 +//line mysql_sql.y:14720 { yyVAL.str = string(yyDollar[1].str) } diff --git a/pkg/sql/parsers/dialect/mysql/mysql_sql.y b/pkg/sql/parsers/dialect/mysql/mysql_sql.y index e6206f21accf2..72197bc8dfd3c 100644 --- a/pkg/sql/parsers/dialect/mysql/mysql_sql.y +++ b/pkg/sql/parsers/dialect/mysql/mysql_sql.y @@ -13168,14 +13168,18 @@ function_call_keyword: } | CHAR '(' expression_list USING charset_name ')' { - cn := tree.NewNumVal($5, $5, false, tree.P_char) - es := $3 - es = append(es, cn) - name := tree.NewUnresolvedColName($1) + charName := tree.NewUnresolvedColName($1) + charExpr := &tree.FuncExpr{ + Func: tree.FuncName2ResolvableFunctionReference(charName), + FuncName: tree.NewCStr($1, 1), + Exprs: $3, + } + charset := tree.NewNumVal($5, $5, false, tree.P_char) + name := tree.NewUnresolvedColName("convert") $$ = &tree.FuncExpr{ Func: tree.FuncName2ResolvableFunctionReference(name), - FuncName: tree.NewCStr($1, 1), - Exprs: es, + FuncName: tree.NewCStr("convert", 1), + Exprs: tree.Exprs{charExpr, charset}, } } | DATE STRING diff --git a/pkg/sql/parsers/dialect/mysql/mysql_sql_test.go b/pkg/sql/parsers/dialect/mysql/mysql_sql_test.go index 12a4b807a1e6d..993035b08266a 100644 --- a/pkg/sql/parsers/dialect/mysql/mysql_sql_test.go +++ b/pkg/sql/parsers/dialect/mysql/mysql_sql_test.go @@ -416,6 +416,21 @@ func TestConvertToJSONBuildsCastExpr(t *testing.T) { require.False(t, isCast) } +func TestCharUsingCharsetBuildsConvertAroundChar(t *testing.T) { + stmt, err := ParseOne(context.Background(), "select char(65 using utf8mb4)", 1) + require.NoError(t, err) + defer stmt.Free() + + convertExpr, ok := firstSelectExpr(t, stmt).(*tree.FuncExpr) + require.True(t, ok) + require.Equal(t, "convert", convertExpr.FuncName.Compare()) + require.Len(t, convertExpr.Exprs, 2) + charExpr, ok := convertExpr.Exprs[0].(*tree.FuncExpr) + require.True(t, ok) + require.Equal(t, "char", charExpr.FuncName.Compare()) + require.Len(t, charExpr.Exprs, 1) +} + func TestParseFirstWithSQLMode(t *testing.T) { ctx := context.Background() parser := &MySQLParser{} diff --git a/pkg/sql/parsers/tree/constant.go b/pkg/sql/parsers/tree/constant.go index 87bb0b935d868..8121b2649267a 100644 --- a/pkg/sql/parsers/tree/constant.go +++ b/pkg/sql/parsers/tree/constant.go @@ -188,6 +188,10 @@ func NewNumVal[T bool | int64 | uint64 | float64 | string](val T, originString s } func (node *NumVal) Format(ctx *FmtCtx) { + if node.ValType == P_hexnum && strings.EqualFold(node.origString, "0x") { + ctx.WriteString("x''") + return + } if ctx.ModeIndependentStringLiterals() { switch node.ValType { case P_char: diff --git a/pkg/sql/plan/base_binder.go b/pkg/sql/plan/base_binder.go index be88741adc1cf..4fe760749d334 100644 --- a/pkg/sql/plan/base_binder.go +++ b/pkg/sql/plan/base_binder.go @@ -333,6 +333,10 @@ func useExplicitCastOverload(typ tree.ResolvableTypeReference) bool { switch defines.MysqlType(internal.Oid) { case defines.MYSQL_TYPE_DECIMAL, defines.MYSQL_TYPE_NEWDECIMAL: return true + case defines.MYSQL_TYPE_STRING, defines.MYSQL_TYPE_VARCHAR, + defines.MYSQL_TYPE_VAR_STRING, defines.MYSQL_TYPE_TEXT: + family := strings.ToLower(internal.FamilyString) + return !internal.Binary && family != "binary" && family != "varbinary" && family != "blob" case defines.MYSQL_TYPE_LONGLONG: family := strings.ToLower(internal.FamilyString) return family == "signed" || family == "integer" || @@ -757,6 +761,9 @@ func (b *baseBinder) bindRangeCond(astExpr *tree.RangeCond, depth int32, isRoot } func (b *baseBinder) bindUnaryExpr(astExpr *tree.UnaryExpr, depth int32, isRoot bool) (*Expr, error) { + if astExpr.Op == tree.UNARY_PLUS && isRoot && isRawBinaryLiteralAst(astExpr.Expr) { + return b.impl.BindExpr(astExpr.Expr, depth, true) + } if (astExpr.Op == tree.UNARY_PLUS || astExpr.Op == tree.UNARY_MINUS || astExpr.Op == tree.UNARY_TILDE) && b.mysqlSpecialTypeInAst(astExpr.Expr) { return b.bindWithRawMySQLSpecialTypes(func() (*Expr, error) { @@ -769,6 +776,11 @@ func (b *baseBinder) bindUnaryExpr(astExpr *tree.UnaryExpr, depth int32, isRoot return b.bindUnaryExprWithCurrentContext(astExpr, depth) } +func isRawBinaryLiteralAst(expr tree.Expr) bool { + literal, ok := unwrapParenExpr(expr).(*tree.NumVal) + return ok && (literal.ValType == tree.P_hexnum || literal.ValType == tree.P_bit) +} + func (b *baseBinder) bindUnaryExprWithCurrentContext(astExpr *tree.UnaryExpr, depth int32) (*Expr, error) { switch astExpr.Op { case tree.UNARY_MINUS: @@ -3815,7 +3827,8 @@ func BindFuncExprImplByPlanExpr(ctx context.Context, name string, args []*Expr) var argsCastType []types.Type // get function definition - fGet, err := function.GetFunctionByName(ctx, name, argsType) + lookupArgsType := binaryLiteralStringLookupTypes(name, args, argsType) + fGet, err := function.GetFunctionByName(ctx, name, lookupArgsType) if err != nil { if name == "between" { leftFn, err := BindFuncExprImplByPlanExpr(ctx, ">=", []*plan.Expr{DeepCopyExpr(args[0]), args[1]}) @@ -3837,6 +3850,7 @@ func BindFuncExprImplByPlanExpr(ctx context.Context, name string, args []*Expr) funcID = fGet.GetEncodedOverloadID() returnType = fGet.GetReturnType() argsCastType, _ = fGet.ShouldDoImplicitTypeCast() + adjustBinaryStringFunctionMetadata(name, args, &returnType) adjustControlFlowMetadata(name, args, argsType, &returnType, argsCastType) // Optimization: avoid casting columns in comparisons to preserve index usage @@ -4186,6 +4200,128 @@ func utcFunctionFSPFromPlanExpr(ctx context.Context, name string, expr *Expr) (i return int32(fsp.I64Val), nil } +func binaryLiteralStringType(expr *Expr) (types.Type, bool) { + literal := expr.GetLit() + if literal == nil || !literal.IsBin || literal.Isnull { + return types.Type{}, false + } + value, ok := literal.Value.(*plan.Literal_Sval) + if !ok { + return types.Type{}, false + } + return types.New(types.T_varbinary, int32(len(value.Sval)), 0), true +} + +func binaryLiteralStringLookupTypes(name string, args []*Expr, argTypes []types.Type) []types.Type { + lookupTypes := argTypes + cloned := false + for idx, arg := range args { + if !binaryLiteralLookupUsesArgument(name, len(args), idx) { + continue + } + binaryType, ok := binaryLiteralStringType(arg) + if !ok { + continue + } + if !cloned { + lookupTypes = append([]types.Type(nil), argTypes...) + cloned = true + } + lookupTypes[idx] = binaryType + } + return lookupTypes +} + +func binaryLiteralLookupUsesArgument(name string, argCount, idx int) bool { + switch name { + case "concat", "concat_ws", "coalesce": + return true + case "substring", "substr", "mid", "lower", "lcase", "upper", "ucase", "repeat": + return idx == 0 + case "if", "iff": + return idx == 1 || idx == 2 + case "case": + return idx%2 == 1 || argCount%2 == 1 && idx == argCount-1 + default: + return false + } +} + +func binaryStringResultUsesArgument(name string, argCount, idx int) bool { + switch name { + case "concat", "concat_ws", "coalesce", "least", "greatest": + return true + case "trim": + // The executor receives direction, trim-string, subject. + return idx == 2 + case "elt", "make_set": + return idx > 0 + case "export_set": + return idx > 0 && idx < 4 + case "if", "iff": + return idx == 1 || idx == 2 + case "case": + return idx%2 == 1 || argCount%2 == 1 && idx == argCount-1 + case "lpad", "rpad", "replace", "regexp_replace", "insert", "substring_index", "regexp_substr": + return idx == 0 + case "substring", "substr", "mid", "left", "right", "lower", "lcase", "upper", "ucase", + "repeat", "reverse", "ltrim", "rtrim", "min", "max", "any_value", "first_value", "last_value", + "nth_value": + return idx == 0 + case "lag", "lead": + return idx == 0 || idx == 2 + default: + return false + } +} + +func literalNonNegativeInt64(expr *Expr) (int64, bool) { + literal := expr.GetLit() + if literal == nil || literal.Isnull { + return 0, false + } + var value int64 + switch v := literal.Value.(type) { + case *plan.Literal_I8Val: + value = int64(v.I8Val) + case *plan.Literal_I16Val: + value = int64(v.I16Val) + case *plan.Literal_I32Val: + value = int64(v.I32Val) + case *plan.Literal_I64Val: + value = v.I64Val + case *plan.Literal_U8Val: + value = int64(v.U8Val) + case *plan.Literal_U16Val: + value = int64(v.U16Val) + case *plan.Literal_U32Val: + value = int64(v.U32Val) + case *plan.Literal_U64Val: + if v.U64Val > math.MaxInt64 { + return 0, false + } + value = int64(v.U64Val) + default: + return 0, false + } + return value, value >= 0 +} + +func adjustBinaryStringFunctionMetadata(name string, args []*Expr, returnType *types.Type) { + if name != "repeat" || len(args) != 2 || returnType.Oid != types.T_varbinary { + return + } + repeatCount, ok := literalNonNegativeInt64(args[1]) + if !ok { + return + } + width := int64(returnType.Width) * repeatCount + if width > int64(types.MaxVarBinaryLen) { + width = int64(types.MaxVarBinaryLen) + } + returnType.Width = int32(width) +} + // adjustControlFlowMetadata keeps MySQL-visible metadata for conditional // expressions precise after overload selection. The overload resolver only // sees types, whereas a literal branch has a narrower domain than its default diff --git a/pkg/sql/plan/base_binder_test.go b/pkg/sql/plan/base_binder_test.go index 29f9094957b8d..72929fae375cb 100644 --- a/pkg/sql/plan/base_binder_test.go +++ b/pkg/sql/plan/base_binder_test.go @@ -615,8 +615,17 @@ func TestBindScoreBinaryHexnumKeepsBinarySemanticsExceptNumericCast(t *testing.T plainHex := tree.NewNumVal("0x3132", "0x3132", false, tree.P_hexnum) plainHexExpr, err := binder.bindNumVal(plainHex, plan.Type{}) require.NoError(t, err) + require.Equal(t, int32(types.T_varchar), plainHexExpr.Typ.Id) + require.Equal(t, int32(2), plainHexExpr.Typ.Width) require.True(t, plainHexExpr.GetLit().GetIsBin()) + plainBit := tree.NewNumVal("0b1100001110101001", "0b1100001110101001", false, tree.P_bit) + plainBitExpr, err := binder.bindNumVal(plainBit, plan.Type{}) + require.NoError(t, err) + require.Equal(t, int32(types.T_varchar), plainBitExpr.Typ.Id) + require.Equal(t, int32(1), plainBitExpr.Typ.Width) + require.True(t, plainBitExpr.GetLit().GetIsBin()) + bitOrExpr, err := BindFuncExprImplByPlanExpr(context.Background(), "|", []*plan.Expr{rawExpr, plainHexExpr}) require.NoError(t, err) require.Equal(t, int32(types.T_varbinary), bitOrExpr.Typ.Id) @@ -627,6 +636,62 @@ func TestBindScoreBinaryHexnumKeepsBinarySemanticsExceptNumericCast(t *testing.T require.Equal(t, int32(types.T_varbinary), bitCountExpr.GetF().Args[0].Typ.Id) } +func TestRawBinaryLiteralUsesBinaryStringTypesOnlyInStringConsumers(t *testing.T) { + binder := &baseBinder{sysCtx: context.Background()} + raw, err := binder.bindNumVal( + tree.NewNumVal("0xe4bda0", "0xe4bda0", false, tree.P_hexnum), + plan.Type{}, + ) + require.NoError(t, err) + require.Equal(t, int32(types.T_varchar), raw.GetTyp().Id) + require.True(t, raw.GetLit().GetIsBin()) + + bind := func(name string, args ...*plan.Expr) *plan.Expr { + expr, bindErr := BindFuncExprImplByPlanExpr(context.Background(), name, args) + require.NoError(t, bindErr) + return expr + } + assertType := func(expr *plan.Expr, oid types.T, width int32) { + require.Equal(t, int32(oid), expr.GetTyp().Id) + require.Equal(t, width, expr.GetTyp().Width) + } + + assertType(bind("concat", raw, makePlan2StringConstExprWithType("a")), types.T_varbinary, 7) + assertType(bind("substring", raw, makePlan2Int64ConstExprWithType(1)), types.T_varbinary, 3) + assertType(bind("lower", raw), types.T_varbinary, 3) + assertType(bind("repeat", raw, makePlan2Int64ConstExprWithType(2)), types.T_varbinary, 6) + assertType(bind("coalesce", makePlan2NullConstExprWithType(), raw), types.T_varbinary, 3) + assertType(bind("if", makePlan2BoolConstExprWithType(true), raw, + makePlan2StringConstExprWithType("你好")), types.T_varbinary, 8) + assertType(bind("case", makePlan2BoolConstExprWithType(true), raw, + makePlan2StringConstExprWithType("你好")), types.T_varbinary, 8) + + assertType(bind("|", raw, makePlan2StringConstExprWithType("\x01", true)), types.T_uint64, 0) + assertType(bind("unary_tilde", raw), types.T_uint64, 0) +} + +func TestBinaryColumnComparisonDoesNotPadRawLiteral(t *testing.T) { + binder := &baseBinder{sysCtx: context.Background()} + raw, err := binder.bindNumVal( + tree.NewNumVal("0x61", "0x61", false, tree.P_hexnum), + plan.Type{}, + ) + require.NoError(t, err) + column := &plan.Expr{ + Typ: plan.Type{Id: int32(types.T_binary), Width: 4}, + Expr: &plan.Expr_Col{Col: &plan.ColRef{RelPos: 0, ColPos: 0}}, + } + + for _, args := range [][]*plan.Expr{{column, raw}, {raw, column}} { + eq, bindErr := BindFuncExprImplByPlanExpr(context.Background(), "=", args) + require.NoError(t, bindErr) + require.Len(t, eq.GetF().GetArgs(), 2) + for _, arg := range eq.GetF().GetArgs() { + require.Equal(t, int32(types.T_varchar), arg.GetTyp().Id) + } + } +} + func TestBindScoreBinaryStringUsesBinaryStringSemantics(t *testing.T) { binder := &baseBinder{sysCtx: context.Background()} binStr := tree.NewNumVal("1", "1", false, tree.P_ScoreBinary) diff --git a/pkg/sql/plan/build_ddl.go b/pkg/sql/plan/build_ddl.go index 7b7e02a9473b9..3e1ca1d79ef88 100644 --- a/pkg/sql/plan/build_ddl.go +++ b/pkg/sql/plan/build_ddl.go @@ -47,6 +47,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect/mysql" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + "github.com/matrixorigin/matrixone/pkg/sql/plan/function" "github.com/matrixorigin/matrixone/pkg/sql/util" ) @@ -286,6 +287,10 @@ func genAsSelectCols(ctx CompilerContext, stmt *tree.Select, isPrepareStmt bool) cols := make([]*plan.ColDef, len(rootNode.ProjectList)) for i, expr := range rootNode.ProjectList { typ := &expr.Typ + if binaryType, ok := ctasBinaryStringTypeInQuery(ctx, builder.qry, expr, nil); ok { + planType := makePlan2Type(&binaryType) + typ = &planType + } provenance := bindCtx.outputColumnProvenanceForProject(int32(i)) if provenance.State == ProvenanceSingleSource && provenance.Source != nil { if isEnumOrSetPlanType(&provenance.Source.Metadata.Typ) { @@ -326,6 +331,194 @@ func genAsSelectCols(ctx CompilerContext, stmt *tree.Select, isPrepareStmt bool) return cols, builder.qry, nil } +func ctasBinaryStringTypeInQuery( + ctx CompilerContext, + qry *Query, + expr *Expr, + visited map[[2]int32]bool, +) (types.Type, bool) { + if binaryType, ok := ctasBinaryStringType(ctx, expr); ok { + return binaryType, true + } + col := expr.GetCol() + if col == nil || qry == nil { + return types.Type{}, false + } + key := [2]int32{col.RelPos, col.ColPos} + if visited == nil { + visited = make(map[[2]int32]bool) + } + if visited[key] { + return types.Type{}, false + } + visited[key] = true + defer delete(visited, key) + + for _, node := range qry.Nodes { + matched := false + for _, tag := range node.BindingTags { + if tag == col.RelPos { + matched = true + break + } + } + if !matched || col.ColPos < 0 { + continue + } + position := int(col.ColPos) + for _, expressions := range [][]*Expr{node.ProjectList, node.WinSpecList} { + if position >= len(expressions) { + continue + } + if binaryType, ok := ctasBinaryStringTypeInQuery( + ctx, qry, expressions[position], visited); ok { + return binaryType, true + } + } + } + return types.Type{}, false +} + +type binaryStringVariableResolver interface { + ResolveVariableBinaryString(varName string, isSystemVar, isGlobalVar bool) (bool, error) +} + +func ctasBinaryStringType(ctx CompilerContext, expr *Expr) (types.Type, bool) { + if binaryType, ok := binaryLiteralStringType(expr); ok { + return binaryType, true + } + exprType := makeTypeByPlan2Expr(expr) + if exprType.Oid == types.T_binary || exprType.Oid == types.T_varbinary || exprType.Oid == types.T_blob { + return exprType, true + } + if variable := expr.GetV(); variable != nil { + resolver, ok := ctx.(binaryStringVariableResolver) + if !ok { + return types.Type{}, false + } + binaryString, err := resolver.ResolveVariableBinaryString( + variable.Name, variable.System, variable.Global) + if err != nil || !binaryString { + return types.Type{}, false + } + return types.T_blob.ToType(), true + } + if window := expr.GetW(); window != nil && window.WindowFunc != nil { + windowFunc := window.WindowFunc.GetF() + if windowFunc == nil || windowFunc.Func == nil { + return types.Type{}, false + } + name := strings.ToLower(windowFunc.Func.ObjName) + var resultType types.Type + found := false + for idx, arg := range windowFunc.Args { + if !binaryStringResultUsesArgument(name, len(windowFunc.Args), idx) { + continue + } + argType, ok := ctasBinaryStringType(ctx, arg) + if !ok { + continue + } + found = true + if argType.Oid == types.T_blob { + return argType, true + } + if resultType.Oid == 0 { + resultType = argType + } + } + if found { + return ctasBinaryFunctionResultType(name, windowFunc.Args, makeTypeByPlan2Expr(expr), resultType), true + } + return types.Type{}, false + } + + fn := expr.GetF() + if fn == nil || fn.Func == nil { + return types.Type{}, false + } + name := strings.ToLower(fn.Func.ObjName) + if name == "cast" { + _, overload := function.DecodeOverloadID(fn.Func.Obj) + if overload != 0 || len(fn.Args) == 0 { + return types.Type{}, false + } + return ctasBinaryStringType(ctx, fn.Args[0]) + } + if name == "char" { + return types.T_blob.ToType(), true + } + + var resultType types.Type + found := false + for idx, arg := range fn.Args { + if name != "group_concat" && !binaryStringResultUsesArgument(name, len(fn.Args), idx) { + continue + } + argType, ok := ctasBinaryStringType(ctx, arg) + if !ok { + continue + } + found = true + if argType.Oid == types.T_blob { + return argType, true + } + if resultType.Oid == types.T_any || resultType.Oid == 0 { + resultType = argType + } + } + if !found { + return types.Type{}, false + } + if name == "group_concat" { + return types.T_blob.ToType(), true + } + return ctasBinaryFunctionResultType(name, fn.Args, exprType, resultType), true +} + +func ctasBinaryFunctionResultType(name string, args []*Expr, exprType, sourceType types.Type) types.Type { + if exprType.Oid == types.T_binary || exprType.Oid == types.T_varbinary || exprType.Oid == types.T_blob { + return exprType + } + result := sourceType + if result.Oid == types.T_binary { + result.Oid = types.T_varbinary + } + if result.Oid != types.T_varbinary { + result = types.New(types.T_varbinary, sourceType.Width, 0) + } + limitWidth := func(arg int) { + if arg >= len(args) { + return + } + if width, ok := literalNonNegativeInt64(args[arg]); ok { + if width > int64(types.MaxVarBinaryLen) { + width = int64(types.MaxVarBinaryLen) + } + result.Width = int32(width) + } + } + switch name { + case "left", "right": + previous := result.Width + limitWidth(1) + if previous >= 0 && result.Width > previous { + result.Width = previous + } + case "substring", "substr", "mid": + if len(args) >= 3 { + previous := result.Width + limitWidth(2) + if previous >= 0 && result.Width > previous { + result.Width = previous + } + } + case "lpad", "rpad": + limitWidth(1) + } + return result +} + func buildCTASDefaultForView(ctx CompilerContext, typ plan.Type, nullAbility bool) (*plan.Default, error) { defaultDef := &plan.Default{NullAbility: nullAbility} if nullAbility { diff --git a/pkg/sql/plan/build_ddl_test.go b/pkg/sql/plan/build_ddl_test.go index cb8f834afd175..e9370a8caf5d9 100644 --- a/pkg/sql/plan/build_ddl_test.go +++ b/pkg/sql/plan/build_ddl_test.go @@ -925,6 +925,114 @@ func TestBuildCTASPreservesMySQLSpecialColumnTypes(t *testing.T) { require.Equal(t, int32(types.T_varchar), cols[2].Typ.GetId()) } +func TestBuildCTASMaterializesBinaryLiteralExpressionTypes(t *testing.T) { + const sql = `create table copied as select + X'e4bda0' direct_value, + concat(X'e4bda0', 'a') concat_value, + substr(X'e4bda0', 1) substr_value, + lower(X'e4bda0') lower_value, + repeat(X'e4bda0', 2) repeat_value, + if(true, X'e4bda0', '你好') if_value, + case when true then X'e4bda0' else '你好' end case_value, + coalesce(null, X'e4bda0') coalesce_value` + + stmt, err := parsers.ParseOne(t.Context(), dialect.MYSQL, sql, 1) + require.NoError(t, err) + defer stmt.Free() + p, err := BuildPlan(NewMockCompilerContext(false), stmt, false) + require.NoError(t, err) + cols := p.GetDdl().GetCreateTable().GetTableDef().GetCols() + require.GreaterOrEqual(t, len(cols), 8) + wantWidths := []int32{3, 7, 3, 3, 6, 8, 8, 3} + for i, width := range wantWidths { + require.Equal(t, int32(types.T_varbinary), cols[i].GetTyp().Id, cols[i].GetName()) + require.Equal(t, width, cols[i].GetTyp().Width, cols[i].GetName()) + } +} + +func TestBuildCTASMaterializesDynamicBinaryStringTypes(t *testing.T) { + ctx := NewMockCompilerContext(false) + ctx.ResolveVariableBinaryStringFunc = func(name string, system, global bool) (bool, error) { + return name == "u" && !system && !global, nil + } + stmt, err := parsers.ParseOne(t.Context(), dialect.MYSQL, + `create table copied as select + @u variable_value, + replace(X'e4bda0', X'bd', X'78') replace_value, + left(@u, 1) left_value, + regexp_substr(@u, '.') regexp_value, + substring_index(@u, X'61', 1) substring_index_value`, 1) + require.NoError(t, err) + defer stmt.Free() + + p, err := BuildPlan(ctx, stmt, false) + require.NoError(t, err) + cols := p.GetDdl().GetCreateTable().GetTableDef().GetCols() + require.GreaterOrEqual(t, len(cols), 5) + require.Equal(t, int32(types.T_blob), cols[0].Typ.Id) + require.Equal(t, int32(types.T_varbinary), cols[1].Typ.Id) + require.Equal(t, int32(3), cols[1].Typ.Width) + for _, idx := range []int{2, 3, 4} { + require.Equal(t, int32(types.T_blob), cols[idx].Typ.Id, cols[idx].Name) + } +} + +func TestBuildCTASIncludesLagDefaultAndSetBranchBinaryProvenance(t *testing.T) { + ctx := NewMockCompilerContext(false) + ctx.ResolveVariableBinaryStringFunc = func(name string, system, global bool) (bool, error) { + return name == "u" && !system && !global, nil + } + + for name, sql := range map[string]string{ + "lag-default": `create table copied as + select lag(n_name, 1, @u) over (order by n_nationkey) c from nation`, + "union-branch": `create table copied as + select @u c union all select '你' c`, + } { + t.Run(name, func(t *testing.T) { + stmt, err := parsers.ParseOne(t.Context(), dialect.MYSQL, sql, 1) + require.NoError(t, err) + defer stmt.Free() + p, err := BuildPlan(ctx, stmt, false) + require.NoError(t, err) + cols := p.GetDdl().GetCreateTable().GetTableDef().GetCols() + require.NotEmpty(t, cols) + require.Equal(t, int32(types.T_blob), cols[0].Typ.Id) + }) + } +} + +func TestBuildCTASUsesBinaryFunctionResultWidth(t *testing.T) { + stmt, err := parsers.ParseOne(t.Context(), dialect.MYSQL, + `create table copied as select lpad(X'61', 5, 'x') padded, +X'3132' unary_value`, 1) + require.NoError(t, err) + defer stmt.Free() + + p, err := BuildPlan(NewMockCompilerContext(false), stmt, false) + require.NoError(t, err) + cols := p.GetDdl().GetCreateTable().GetTableDef().GetCols() + require.GreaterOrEqual(t, len(cols), 2) + require.Equal(t, int32(types.T_varbinary), cols[0].Typ.Id) + require.Equal(t, int32(5), cols[0].Typ.Width) + require.Equal(t, int32(types.T_varbinary), cols[1].Typ.Id) + require.Equal(t, int32(2), cols[1].Typ.Width) +} + +func TestBuildCTASEmptyBinaryLiteralCanBeReparsed(t *testing.T) { + stmt, err := parsers.ParseOne(t.Context(), dialect.MYSQL, + `create table copied as select X'' empty_value`, 1) + require.NoError(t, err) + defer stmt.Free() + + p, err := BuildPlan(NewMockCompilerContext(false), stmt, false) + require.NoError(t, err) + create := p.GetDdl().GetCreateTable() + require.Equal(t, int32(types.T_varbinary), create.TableDef.Cols[0].Typ.Id) + require.Equal(t, int32(0), create.TableDef.Cols[0].Typ.Width) + _, err = parsers.ParseOne(t.Context(), dialect.MYSQL, create.CreateAsSelectSql, 1) + require.NoError(t, err) +} + func TestViewRebindPreservesMySQLSpecialColumnSemantics(t *testing.T) { const createViewSQL = "create view v_enum_set as select priority, flags, n_name from nation" ctx := NewMockCompilerContext(false) diff --git a/pkg/sql/plan/build_test.go b/pkg/sql/plan/build_test.go index 7e378103a27a0..87b0d6e6c651f 100644 --- a/pkg/sql/plan/build_test.go +++ b/pkg/sql/plan/build_test.go @@ -6521,6 +6521,19 @@ func TestResultColumns(t *testing.T) { } } +func TestUnionMaterializesBinaryLiteralAsVarbinary(t *testing.T) { + logicPlan, err := runOneStmt( + NewMockOptimizer(false), + t, + "select X'e4bda0' x union all select '你好' x", + ) + require.NoError(t, err) + columns := GetResultColumnsFromPlan(logicPlan) + require.Len(t, columns, 1) + require.Equal(t, int32(types.T_varbinary), columns[0].GetTyp().Id) + require.Equal(t, int32(8), columns[0].GetTyp().Width) +} + func TestResultColumns2(t *testing.T) { mock := NewMockOptimizer(true) getColumns := func(sql string) []*ColDef { diff --git a/pkg/sql/plan/explicit_cast_test.go b/pkg/sql/plan/explicit_cast_test.go index 910f2e412c3e5..167cd7c51ac35 100644 --- a/pkg/sql/plan/explicit_cast_test.go +++ b/pkg/sql/plan/explicit_cast_test.go @@ -20,6 +20,8 @@ import ( "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/defines" + "github.com/matrixorigin/matrixone/pkg/sql/parsers" + "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" "github.com/matrixorigin/matrixone/pkg/sql/plan/function" "github.com/stretchr/testify/require" @@ -46,6 +48,23 @@ func TestExplicitCastUsesDedicatedOverload(t *testing.T) { require.Equal(t, int32(1), explicitOverload) } +func TestExplicitCharacterCastsUseDedicatedOverload(t *testing.T) { + stmt, err := parsers.ParseOne(t.Context(), dialect.MYSQL, + "select cast(@u as char), cast(@u as char(10)), convert(@u, char)", 1) + require.NoError(t, err) + defer stmt.Free() + + p, err := BuildPlan(NewMockCompilerContext(false), stmt, false) + require.NoError(t, err) + projects := p.GetQuery().Nodes[p.GetQuery().Steps[0]].ProjectList + require.Len(t, projects, 3) + for idx, project := range projects { + require.Equal(t, "cast", project.GetF().GetFunc().GetObjName()) + _, overload := function.DecodeOverloadID(project.GetF().GetFunc().GetObj()) + require.Equal(t, int32(1), overload, "project %d", idx) + } +} + func TestUseExplicitCastOverload(t *testing.T) { tests := []struct { name string @@ -56,6 +75,8 @@ func TestUseExplicitCastOverload(t *testing.T) { {name: "signed integer", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_LONGLONG), FamilyString: "integer"}, want: true}, {name: "unsigned", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_LONGLONG), Unsigned: true}, want: true}, {name: "decimal", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_NEWDECIMAL), FamilyString: "decimal"}, want: true}, + {name: "char", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_STRING), FamilyString: "char"}, want: true}, + {name: "binary", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_VAR_STRING), FamilyString: "binary"}}, {name: "tinyint", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_TINY), FamilyString: "tinyint"}}, {name: "smallint", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_SHORT), FamilyString: "smallint"}}, {name: "int", typ: tree.InternalType{Oid: uint32(defines.MYSQL_TYPE_LONG), FamilyString: "int"}}, diff --git a/pkg/sql/plan/function/baseTemplate.go b/pkg/sql/plan/function/baseTemplate.go index ad6f163f554cf..4d19546ec6c90 100644 --- a/pkg/sql/plan/function/baseTemplate.go +++ b/pkg/sql/plan/function/baseTemplate.go @@ -2925,6 +2925,7 @@ func opUnaryStrToStr( rs := vector.MustFunctionResult[types.Varlena](result) p1 := vector.OptGetBytesParamFromWrapper(rs, 0, parameters[0]) rsVec := rs.GetResultVector() + defer propagateBinaryStringResultRows(parameters, rsVec, length) c1 := parameters[0].IsConst() rsNull := rsVec.GetNulls() diff --git a/pkg/sql/plan/function/binary_string.go b/pkg/sql/plan/function/binary_string.go new file mode 100644 index 0000000000000..31902159b15a4 --- /dev/null +++ b/pkg/sql/plan/function/binary_string.go @@ -0,0 +1,57 @@ +// Copyright 2026 Matrix Origin +// +// 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 function + +import ( + "strings" + + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/pb/plan" +) + +// ExpressionContainsRuntimeBinaryString reports whether evaluating expr can +// depend on binary-string metadata which a plan Literal cannot represent. +func ExpressionContainsRuntimeBinaryString(expr *plan.Expr) bool { + if expr == nil { + return false + } + if literal := expr.GetLit(); literal != nil && literal.IsBin { + return true + } + fn := expr.GetF() + if fn == nil { + return false + } + if fn.Func != nil && strings.EqualFold(fn.Func.ObjName, "bit_cast") { + return false + } + if fn.Func != nil && strings.EqualFold(fn.Func.ObjName, "cast") { + _, overload := DecodeOverloadID(fn.Func.Obj) + resultType := types.T(expr.Typ.Id) + if overload == 1 || !resultType.IsMySQLString() || + resultType == types.T_binary || resultType == types.T_varbinary || resultType == types.T_blob { + return false + } + } + if fn.Func != nil && strings.EqualFold(fn.Func.ObjName, "char") { + return true + } + for _, arg := range fn.Args { + if ExpressionContainsRuntimeBinaryString(arg) { + return true + } + } + return false +} diff --git a/pkg/sql/plan/function/func_binary.go b/pkg/sql/plan/function/func_binary.go index 4fbd6e60ae8f0..503892bc8745c 100644 --- a/pkg/sql/plan/function/func_binary.go +++ b/pkg/sql/plan/function/func_binary.go @@ -950,6 +950,18 @@ func coalesceCheck(overloads []overload, inputs []types.Type) checkResult { } return newCheckResultWithFailure(failedFunctionParametersWrong) } + if retType, ok := binaryStringCommonType(inputs); ok { + castType := make([]types.Type, len(inputs)) + for i := range castType { + castType[i] = retType + } + for i, over := range overloads { + if len(over.args) == 1 && over.args[0] == retType.Oid { + return newCheckResultWithCast(i, castType) + } + } + return newCheckResultWithFailure(failedFunctionParametersWrong) + } if result, ok := coalesceTextStringResult(overloads, inputs); ok { return result } @@ -1082,6 +1094,7 @@ func CoalesceStr(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ if err = rs.AppendBytes(v, false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), ivecs[j].GetIsBinaryStringAt(int(i))) isFill = true break } @@ -1121,6 +1134,7 @@ func concatWsCheck(overloads []overload, inputs []types.Type) checkResult { } func ConcatWs(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *process.Process, length int, selectList *FunctionSelectList) (err error) { + propagateBinaryStringResult(ivecs, result) rs := vector.MustFunctionResult[types.Varlena](result) vecs := make([]vector.FunctionParameterWrapper[types.Varlena], len(ivecs)) for i := range ivecs { @@ -4930,6 +4944,7 @@ func eltCheck(overloads []overload, inputs []types.Type) checkResult { // Elt: ELT(N, str1, str2, str3, ...) - Returns str1 if N = 1, str2 if N = 2, and so on. // Returns NULL if N is less than 1, greater than the number of strings, or NULL. func Elt(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *process.Process, length int, selectList *FunctionSelectList) error { + propagateBinaryStringResult(ivecs[1:], result) rs := vector.MustFunctionResult[types.Varlena](result) // Rest arguments are strings @@ -5052,6 +5067,7 @@ func makeSetCheck(overloads []overload, inputs []types.Type) checkResult { // MakeSet: MAKE_SET(bits, str1, str2, ...) - Returns a set value (a string containing substrings separated by ',' characters) consisting of the strings that have the corresponding bit in bits set. func MakeSet(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + propagateBinaryStringResult(ivecs[1:], result) rs := vector.MustFunctionResult[types.Varlena](result) // First argument: bits (numeric) - handle different numeric types @@ -5296,6 +5312,7 @@ func exportSetCheck(overloads []overload, inputs []types.Type) checkResult { // ExportSet: EXPORT_SET(bits, on, off[, separator[, number_of_bits]]) - Returns a string such that for every bit set in the value bits, you get an on string and for every bit not set, you get an off string. func ExportSet(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + propagateBinaryStringResult(ivecs[1:], result) rs := vector.MustFunctionResult[types.Varlena](result) // First argument: bits (numeric) - handle different numeric types @@ -6022,8 +6039,13 @@ func SubStringWith2Args(ivecs []*vector.Vector, result vector.FunctionResultWrap return err } } else { + binaryInput := ivecs[0].GetIsBinaryStringAt(int(i)) var r string - if s > 0 { + if binaryInput && s > 0 { + r = getSliceFromLeftBytes(v, s-1) + } else if binaryInput && s < 0 { + r = getSliceFromRightBytes(v, -s) + } else if s > 0 { r = getSliceFromLeft(functionUtil.QuickBytesToStr(v), s-1) } else if s < 0 { r = getSliceFromRight(functionUtil.QuickBytesToStr(v), -s) @@ -6033,11 +6055,26 @@ func SubStringWith2Args(ivecs []*vector.Vector, result vector.FunctionResultWrap if err = rs.AppendBytes(functionUtil.QuickStrToBytes(r), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } return nil } +func getSliceFromLeftBytes(s []byte, offset int64) string { + if offset > int64(len(s)) { + return "" + } + return string(s[offset:]) +} + +func getSliceFromRightBytes(s []byte, offset int64) string { + if offset > int64(len(s)) { + return "" + } + return string(s[int64(len(s))-offset:]) +} + // Cut the slice with length from left to right, starting from 0 func getSliceFromLeftWithLength(s string, offset int64, length int64) string { if offset < 0 { @@ -6071,6 +6108,24 @@ func getSliceOffsetLen(s string, offset int64, length int64) string { } } +func getSliceOffsetLenBytes(s []byte, offset int64, length int64) string { + elemSize := int64(len(s)) + if offset < 0 { + offset += elemSize + if offset < 0 { + return "" + } + } + if offset >= elemSize || length <= 0 { + return "" + } + end := offset + length + if end < offset || end > elemSize { + end = elemSize + } + return string(s[offset:end]) +} + // From right to left, cut the slice with length from 1 func getSliceFromRightWithLength(s string, offset int64, length int64) string { return getSliceOffsetLen(s, -offset, length) @@ -6092,8 +6147,13 @@ func SubStringWith3Args(ivecs []*vector.Vector, result vector.FunctionResultWrap return err } } else { + binaryInput := ivecs[0].GetIsBinaryStringAt(int(i)) var r string - if s > 0 { + if binaryInput && s > 0 { + r = getSliceOffsetLenBytes(v, s-1, l) + } else if binaryInput && s < 0 { + r = getSliceOffsetLenBytes(v, s, l) + } else if s > 0 { r = getSliceFromLeftWithLength(functionUtil.QuickBytesToStr(v), s-1, l) } else if s < 0 { r = getSliceFromRightWithLength(functionUtil.QuickBytesToStr(v), -s, l) @@ -6103,6 +6163,7 @@ func SubStringWith3Args(ivecs []*vector.Vector, result vector.FunctionResultWrap if err = rs.AppendBytes(functionUtil.QuickStrToBytes(r), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } return nil @@ -6193,6 +6254,7 @@ func SubStrIndex[T number](ivecs []*vector.Vector, result vector.FunctionResultW if err = rs.AppendBytes([]byte(r), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), ivecs[0].GetIsBinaryStringAt(int(i))) } } return nil @@ -6848,6 +6910,43 @@ func FindInSet(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc } func Instr(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) (err error) { + if ivecs[0].HasBinaryStringRows() { + valueParam := vector.GenerateFunctionStrParameter(ivecs[0]) + needleParam := vector.GenerateFunctionStrParameter(ivecs[1]) + rs := vector.MustFunctionResult[int64](result) + for row := 0; row < length; row++ { + value, null1 := valueParam.GetStrValue(uint64(row)) + needle, null2 := needleParam.GetStrValue(uint64(row)) + if null1 || null2 || selectList != nil && selectList.Contains(uint64(row)) { + if err := rs.Append(0, true); err != nil { + return err + } + continue + } + position := int64(0) + if ivecs[0].GetIsBinaryStringAt(row) { + if idx := bytes.Index(value, needle); idx >= 0 { + position = int64(idx + 1) + } + } else { + position = instr.Single(functionUtil.QuickBytesToStr(value), functionUtil.QuickBytesToStr(needle)) + } + if err := rs.Append(position, false); err != nil { + return err + } + } + return nil + } + if isBinaryStringVector(ivecs[0]) { + return opBinaryBytesBytesToFixedWithErrorCheck[int64](ivecs, result, proc, length, + func(value, needle []byte) (int64, error) { + idx := bytes.Index(value, needle) + if idx < 0 { + return 0, nil + } + return int64(idx + 1), nil + }, selectList) + } return opBinaryStrStrToFixed[int64](ivecs, result, proc, length, instr.Single, nil) } @@ -6864,16 +6963,33 @@ func Left(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *proces return err } } else { + binaryInput := ivecs[0].GetIsBinaryStringAt(int(i)) //TODO: Ignoring 4 switch cases: https://github.com/m-schen/matrixone/blob/0c480ca11b6302de26789f916a3e2faca7f79d47/pkg/sql/plan/function/builtin/binary/left.go#L38 - res := evalLeft(functionUtil.QuickBytesToStr(v1), v2) + var res string + if binaryInput { + res = evalLeftBytes(v1, v2) + } else { + res = evalLeft(functionUtil.QuickBytesToStr(v1), v2) + } if err = rs.AppendBytes(functionUtil.QuickStrToBytes(res), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } return nil } +func evalLeftBytes(str []byte, length int64) string { + if length <= 0 { + return "" + } + if length >= int64(len(str)) { + return string(str) + } + return string(str[:length]) +} + func evalLeft(str string, length int64) string { runeStr := []rune(str) leftLength := int(length) @@ -6898,15 +7014,32 @@ func Right(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *proce return err } } else { - res := evalRight(functionUtil.QuickBytesToStr(v1), v2) + binaryInput := ivecs[0].GetIsBinaryStringAt(int(i)) + var res string + if binaryInput { + res = evalRightBytes(v1, v2) + } else { + res = evalRight(functionUtil.QuickBytesToStr(v1), v2) + } if err = rs.AppendBytes(functionUtil.QuickStrToBytes(res), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } return nil } +func evalRightBytes(str []byte, length int64) string { + if length <= 0 { + return "" + } + if length >= int64(len(str)) { + return string(str) + } + return string(str[int64(len(str))-length:]) +} + func evalRight(str string, length int64) string { runeStr := []rune(str) rightLength := int(length) @@ -8360,6 +8493,7 @@ func Replace(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *pro if err = rs.AppendBytes(functionUtil.QuickStrToBytes(res), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), ivecs[0].GetIsBinaryStringAt(int(i))) } } return nil @@ -8383,6 +8517,33 @@ func Insert(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *proc return err } } else { + binaryInput := ivecs[0].GetIsBinaryStringAt(int(i)) + if binaryInput { + strLen := int64(len(v1)) + if v2 <= 0 || v2 > strLen { + if err = rs.AppendBytes(v1, false); err != nil { + return err + } + result.GetResultVector().SetIsBinaryStringAt(int(i), true) + continue + } + start := v2 - 1 + end := strLen + if v3 == 0 { + end = start + } else if v3 > 0 && v3 < strLen-start { + end = start + v3 + } + value := make([]byte, 0, int(start)+len(v4)+int(strLen-end)) + value = append(value, v1[:start]...) + value = append(value, v4...) + value = append(value, v1[end:]...) + if err = rs.AppendBytes(value, false); err != nil { + return err + } + result.GetResultVector().SetIsBinaryStringAt(int(i), true) + continue + } str := functionUtil.QuickBytesToStr(v1) pos := v2 replaceLen := v3 @@ -8399,17 +8560,17 @@ func Insert(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *proc // - Otherwise, replace replaceLen characters starting at pos with newstr // - Position is 1-based - var result string + var output string if pos <= 0 || pos > strLen { // Invalid position, return original string - result = str + output = str } else if replaceLen == 0 { // Insert without removing posIdx := int(pos - 1) // Convert to 0-based index if posIdx >= len(runes) { - result = str + newstr + output = str + newstr } else { - result = string(runes[:posIdx]) + newstr + string(runes[posIdx:]) + output = string(runes[:posIdx]) + newstr + string(runes[posIdx:]) } } else { // Replace replaceLen characters starting at pos @@ -8420,15 +8581,16 @@ func Insert(ivecs []*vector.Vector, result vector.FunctionResultWrapper, _ *proc endIdx = posIdx + int(replaceLen) } if posIdx >= len(runes) { - result = str + newstr + output = str + newstr } else { - result = string(runes[:posIdx]) + newstr + string(runes[endIdx:]) + output = string(runes[:posIdx]) + newstr + string(runes[endIdx:]) } } - if err = rs.AppendBytes(functionUtil.QuickStrToBytes(result), false); err != nil { + if err = rs.AppendBytes(functionUtil.QuickStrToBytes(output), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), false) } } return nil @@ -8468,6 +8630,7 @@ func Trim(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *pro if err = rs.AppendBytes([]byte(res), false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), ivecs[2].GetIsBinaryStringAt(int(i))) } } diff --git a/pkg/sql/plan/function/func_builtin.go b/pkg/sql/plan/function/func_builtin.go index 4b3d3ffad09ac..2cc593292453e 100644 --- a/pkg/sql/plan/function/func_builtin.go +++ b/pkg/sql/plan/function/func_builtin.go @@ -801,6 +801,7 @@ func builtInConcatCheck(_ []overload, inputs []types.Type) checkResult { } func builtInConcat(parameters []*vector.Vector, result vector.FunctionResultWrapper, _ *process.Process, length int, selectList *FunctionSelectList) error { + propagateBinaryStringResult(parameters, result) rs := vector.MustFunctionResult[types.Varlena](result) ps := make([]vector.FunctionParameterWrapper[types.Varlena], len(parameters)) for i := range ps { @@ -1109,9 +1110,10 @@ func builtInCharCheck(_ []overload, inputs []types.Type) checkResult { // - numeric types (float/decimal/bool/bit/...) : rounded to nearest integer // - string types : the leading numeric prefix is truncated to an integer // (e.g. '65.9' -> 65, not 66) - // To preserve this distinction we keep integers, cast string types to varchar - // (parsed & truncated in builtInChar), and cast every other numeric type to - // int64 (the numeric->int64 cast rounds, matching MySQL). + // To preserve this distinction we keep integers and string vectors as-is, + // and cast every other numeric type to int64 (the numeric->int64 cast rounds, + // matching MySQL). Keeping string metadata is required for hex/bit literals: + // their IsBin flag selects big-endian byte interpretation below. if len(inputs) < 1 { return newCheckResultWithFailure(failedFunctionParametersWrong) } @@ -1122,12 +1124,8 @@ func builtInCharCheck(_ []overload, inputs []types.Type) checkResult { switch { case source.Oid.IsInteger(): ret[i] = source - case source.Oid == types.T_varchar: - ret[i] = source case source.Oid.IsMySQLString(): - // char/text/blob/binary/varbinary -> varchar (truncated in builtInChar) - shouldCast = true - ret[i] = types.T_varchar.ToType() + ret[i] = source default: // float/decimal/bool/bit/... -> int64 (rounded by the cast, like MySQL) c, _ := tryToMatch([]types.Type{source}, []types.T{types.T_int64}) @@ -1145,10 +1143,11 @@ func builtInCharCheck(_ []overload, inputs []types.Type) checkResult { } func builtInChar(parameters []*vector.Vector, result vector.FunctionResultWrapper, _ *process.Process, length int, selectList *FunctionSelectList) error { + result.GetResultVector().SetIsBinaryString(true) rs := vector.MustFunctionResult[types.Varlena](result) - // After builtInCharCheck, parameters are either integer types or varchar. - // (numeric types were cast to int64, string types to varchar). + // After builtInCharCheck, parameters are either integer or string types. + // Numeric types were cast to int64; string metadata remains intact. // Each getter returns an int64 value and a null flag. type intGetter func(uint64) (int64, bool) getters := make([]intGetter, len(parameters)) @@ -1569,6 +1568,42 @@ func doRpad(src string, tgtLen int64, pad string) (string, bool) { } } +func doLpadBytes(src []byte, tgtLen int64, pad []byte) ([]byte, bool) { + if tgtLen < 0 || tgtLen > types.MaxVarcharLen { + return nil, true + } + if tgtLen <= int64(len(src)) { + return src[:tgtLen], false + } + if len(pad) == 0 { + return nil, false + } + missing := int(tgtLen) - len(src) + out := make([]byte, 0, int(tgtLen)) + out = append(out, bytes.Repeat(pad, missing/len(pad))...) + out = append(out, pad[:missing%len(pad)]...) + out = append(out, src...) + return out, false +} + +func doRpadBytes(src []byte, tgtLen int64, pad []byte) ([]byte, bool) { + if tgtLen < 0 || tgtLen > types.MaxVarcharLen { + return nil, true + } + if tgtLen <= int64(len(src)) { + return src[:tgtLen], false + } + if len(pad) == 0 { + return nil, false + } + missing := int(tgtLen) - len(src) + out := make([]byte, 0, int(tgtLen)) + out = append(out, src...) + out = append(out, bytes.Repeat(pad, missing/len(pad))...) + out = append(out, pad[:missing%len(pad)]...) + return out, false +} + func builtInRepeat(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { // repeat the string n times. repeatNTimes := func(base string, n int64) (r string, null bool) { @@ -1608,6 +1643,9 @@ func builtInRepeat(parameters []*vector.Vector, result vector.FunctionResultWrap if err != nil { return err } + if !(null1 || null2) { + result.GetResultVector().SetIsBinaryStringAt(int(i), parameters[0].GetIsBinaryStringAt(int(i))) + } } return nil } @@ -1623,11 +1661,21 @@ func builtInLpad(parameters []*vector.Vector, result vector.FunctionResultWrappe v2, null2 := p2.GetValue(i) v3, null3 := p3.GetStrValue(i) if !(null1 || null2 || null3) { - rval, shouldNull := doLpad(string(v1), v2, string(v3)) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + var rvalue []byte + var shouldNull bool + if binaryInput { + rvalue, shouldNull = doLpadBytes(v1, v2, v3) + } else { + var value string + value, shouldNull = doLpad(string(v1), v2, string(v3)) + rvalue = []byte(value) + } if !shouldNull { - if err := rs.AppendBytes([]byte(rval), false); err != nil { + if err := rs.AppendBytes(rvalue, false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) continue } } @@ -1649,11 +1697,21 @@ func builtInRpad(parameters []*vector.Vector, result vector.FunctionResultWrappe v2, null2 := p2.GetValue(i) v3, null3 := p3.GetStrValue(i) if !(null1 || null2 || null3) { - rval, shouldNull := doRpad(string(v1), v2, string(v3)) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + var rvalue []byte + var shouldNull bool + if binaryInput { + rvalue, shouldNull = doRpadBytes(v1, v2, v3) + } else { + var value string + value, shouldNull = doRpad(string(v1), v2, string(v3)) + rvalue = []byte(value) + } if !shouldNull { - if err := rs.AppendBytes([]byte(rval), false); err != nil { + if err := rs.AppendBytes(rvalue, false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) continue } } @@ -3816,17 +3874,145 @@ func isUTF8Charset(charset []byte) bool { } func builtInToUpper(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + if parameters[0].HasBinaryStringRows() { + return opUnaryBytesToBytesByBinaryRow(parameters, result, length, + func(v []byte) []byte { return v }, bytes.ToUpper, selectList) + } + if isBinaryStringVector(parameters[0]) { + err := opUnaryBytesToBytes(parameters, result, proc, length, func(v []byte) []byte { + return v + }, selectList) + if err == nil { + result.GetResultVector().SetIsBinaryString(true) + } + return err + } return opUnaryBytesToBytes(parameters, result, proc, length, func(v []byte) []byte { return bytes.ToUpper(v) }, selectList) } func builtInToLower(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + if parameters[0].HasBinaryStringRows() { + return opUnaryBytesToBytesByBinaryRow(parameters, result, length, + func(v []byte) []byte { return v }, bytes.ToLower, selectList) + } + if isBinaryStringVector(parameters[0]) { + err := opUnaryBytesToBytes(parameters, result, proc, length, func(v []byte) []byte { + return v + }, selectList) + if err == nil { + result.GetResultVector().SetIsBinaryString(true) + } + return err + } return opUnaryBytesToBytes(parameters, result, proc, length, func(v []byte) []byte { return bytes.ToLower(v) }, selectList) } +func isBinaryStringVector(vec *vector.Vector) bool { + switch vec.GetType().Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + return true + default: + return vec.GetIsBinaryString() + } +} + +func opUnaryBytesToBytesByBinaryRow( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + length int, + binaryFn func([]byte) []byte, + textFn func([]byte) []byte, + selectList *FunctionSelectList, +) error { + p := vector.GenerateFunctionStrParameter(parameters[0]) + rs := vector.MustFunctionResult[types.Varlena](result) + for row := 0; row < length; row++ { + if selectList != nil && selectList.Contains(uint64(row)) { + if err := rs.AppendBytes(nil, true); err != nil { + return err + } + continue + } + value, isNull := p.GetStrValue(uint64(row)) + if isNull { + if err := rs.AppendBytes(nil, true); err != nil { + return err + } + continue + } + binaryString := parameters[0].GetIsBinaryStringAt(row) + output := textFn(value) + if binaryString { + output = binaryFn(value) + } + if err := rs.AppendBytes(output, false); err != nil { + return err + } + result.GetResultVector().SetIsBinaryStringAt(row, binaryString) + } + return nil +} + +func opBinaryBytesBytesToFixedByBinaryRow[Tr types.FixedSizeTExceptStrType]( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + length int, + fn func([]byte, []byte, bool) (Tr, error), + selectList *FunctionSelectList, +) error { + p1 := vector.GenerateFunctionStrParameter(parameters[0]) + p2 := vector.GenerateFunctionStrParameter(parameters[1]) + rs := vector.MustFunctionResult[Tr](result) + var zero Tr + for row := 0; row < length; row++ { + v1, null1 := p1.GetStrValue(uint64(row)) + v2, null2 := p2.GetStrValue(uint64(row)) + if null1 || null2 || selectList != nil && selectList.Contains(uint64(row)) { + if err := rs.Append(zero, true); err != nil { + return err + } + continue + } + value, err := fn(v1, v2, parameters[0].GetIsBinaryStringAt(row)) + if err != nil { + return err + } + if err = rs.Append(value, false); err != nil { + return err + } + } + return nil +} + +func propagateBinaryStringResult(parameters []*vector.Vector, result vector.FunctionResultWrapper) { + for _, parameter := range parameters { + if isBinaryStringVector(parameter) { + result.GetResultVector().SetIsBinaryString(true) + return + } + } +} + +func propagateBinaryStringResultRows(parameters []*vector.Vector, result *vector.Vector, length int) { + for row := 0; row < length && row < result.Length(); row++ { + if result.IsNull(uint64(row)) { + continue + } + binaryString := false + for _, parameter := range parameters { + if parameter.GetIsBinaryStringAt(row) { + binaryString = true + break + } + } + result.SetIsBinaryStringAt(row, binaryString) + } +} + // buildInMOCU extract cu or calculate cu from parameters // example: // - select mo_cu('[6,2,3,4,5,6,2,7,8,9,10,0,11,12,13,14,1]', 134123) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index 3b163d83e6c08..31a9d0959a492 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -735,6 +735,7 @@ func leastGreatestFnVarlen( length int, selectList *FunctionSelectList, compareFn func(v1, v2 []byte) bool) error { + propagateBinaryStringResult(parameters, result) rs := vector.MustFunctionResult[types.Varlena](result) rsVec := rs.GetResultVector() rsNull := rsVec.GetNulls() diff --git a/pkg/sql/plan/function/func_builtin_regexp.go b/pkg/sql/plan/function/func_builtin_regexp.go index a833deef7f182..6067108dc2755 100644 --- a/pkg/sql/plan/function/func_builtin_regexp.go +++ b/pkg/sql/plan/function/func_builtin_regexp.go @@ -59,9 +59,20 @@ func (op *opBuiltInRegexp) likeFn(parameters []*vector.Vector, result vector.Fun p1 := vector.GenerateFunctionStrParameter(parameters[0]) p2 := vector.GenerateFunctionStrParameter(parameters[1]) rs := vector.MustFunctionResult[bool](result) + binaryInput := isBinaryStringVector(parameters[0]) + if parameters[0].HasBinaryStringRows() { + return opBinaryBytesBytesToFixedByBinaryRow[bool](parameters, result, length, + func(value, pattern []byte, binaryString bool) (bool, error) { + if binaryString { + return op.regMap.regularMatchForBinaryLikeOp( + pattern, value, []byte{DefaultEscapeChar}, true) + } + return op.regMap.regularMatchForLikeOp(pattern, value) + }, selectList) + } // optimize rule for some special case. - if parameters[1].IsConst() { + if parameters[1].IsConst() && !binaryInput { canOptimize, err := optimizeRuleForLike(p1, p2, rs, length, func(i []byte) []byte { return i }) @@ -71,6 +82,9 @@ func (op *opBuiltInRegexp) likeFn(parameters []*vector.Vector, result vector.Fun } return opBinaryBytesBytesToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 []byte) (bool, error) { + if binaryInput { + return op.regMap.regularMatchForBinaryLikeOp(v2, v1, []byte{DefaultEscapeChar}, true) + } return op.regMap.regularMatchForLikeOp(v2, v1) }, selectList) } @@ -135,7 +149,21 @@ func (op *opBuiltInRegexp) likeFnWithEscape( escape, _ = utf8.DecodeRune(escapeBytes) } } + binaryInput := isBinaryStringVector(parameters[0]) + if parameters[0].HasBinaryStringRows() { + return opBinaryBytesBytesToFixedByBinaryRow[bool](parameters[:2], result, length, + func(value, pattern []byte, binaryString bool) (bool, error) { + if binaryString { + return op.regMap.regularMatchForBinaryLikeOp(pattern, value, escapeBytes, escapeEnabled) + } + return op.regMap.regularMatchForLikeOpWithEscape( + pattern, value, escape, escapeEnabled, caseInsensitive) + }, selectList) + } return opBinaryBytesBytesToFixedWithErrorCheck[bool](parameters[:2], result, proc, length, func(value, pattern []byte) (bool, error) { + if binaryInput { + return op.regMap.regularMatchForBinaryLikeOp(pattern, value, escapeBytes, escapeEnabled) + } return op.regMap.regularMatchForLikeOpWithEscape(pattern, value, escape, escapeEnabled, caseInsensitive) }, selectList) } @@ -354,28 +382,86 @@ func optimizeRuleForLike(p1, p2 vector.FunctionParameterWrapper[types.Varlena], } func (op *opBuiltInRegexp) builtInRegMatch(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { - return opBinaryStrStrToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 string) (bool, error) { - reg, err := op.regMap.getRegularMatcherForMatch(v2) + if parameters[0].HasBinaryStringRows() { + return opBinaryBytesBytesToFixedByBinaryRow[bool](parameters, result, length, + func(v1, v2 []byte, binaryInput bool) (bool, error) { + expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) + if binaryInput { + expr, pat = binaryBytesToRegexpString(v1), binaryBytesToRegexpString(v2) + } + reg, err := op.regMap.getRegularMatcherForMatch(pat) + if err != nil { + return false, err + } + return reg.MatchString(expr), nil + }, selectList) + } + binaryInput := isBinaryStringVector(parameters[0]) + return opBinaryBytesBytesToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 []byte) (bool, error) { + expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) + if binaryInput { + expr, pat = binaryBytesToRegexpString(v1), binaryBytesToRegexpString(v2) + } + reg, err := op.regMap.getRegularMatcherForMatch(pat) if err != nil { return false, err } - return reg.MatchString(v1), nil + return reg.MatchString(expr), nil }, selectList) } func (op *opBuiltInRegexp) builtInNotRegMatch(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { - return opBinaryStrStrToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 string) (bool, error) { - reg, err := op.regMap.getRegularMatcherForMatch(v2) + if parameters[0].HasBinaryStringRows() { + return opBinaryBytesBytesToFixedByBinaryRow[bool](parameters, result, length, + func(v1, v2 []byte, binaryInput bool) (bool, error) { + expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) + if binaryInput { + expr, pat = binaryBytesToRegexpString(v1), binaryBytesToRegexpString(v2) + } + reg, err := op.regMap.getRegularMatcherForMatch(pat) + if err != nil { + return false, err + } + return !reg.MatchString(expr), nil + }, selectList) + } + binaryInput := isBinaryStringVector(parameters[0]) + return opBinaryBytesBytesToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 []byte) (bool, error) { + expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) + if binaryInput { + expr, pat = binaryBytesToRegexpString(v1), binaryBytesToRegexpString(v2) + } + reg, err := op.regMap.getRegularMatcherForMatch(pat) if err != nil { return false, err } - return !reg.MatchString(v1), nil + return !reg.MatchString(expr), nil }, selectList) } func (op *opBuiltInRegexp) builtInRegexpSubstr(parameters []*vector.Vector, result vector.FunctionResultWrapper, _ *process.Process, length int, selectList *FunctionSelectList) error { p1 := vector.GenerateFunctionStrParameter(parameters[0]) p2 := vector.GenerateFunctionStrParameter(parameters[1]) + regularSubstr := func( + expr, pat []byte, + pos, occurrence int64, + binaryInput, binaryPattern bool, + ) (bool, []byte, error) { + if binaryInput { + return op.regMap.regularSubstrBinary(pat, expr, pos, occurrence) + } + if binaryPattern { + expr = []byte(strings.Map(func(r rune) rune { + if r > unicode.MaxLatin1 { + return '?' + } + return r + }, functionUtil.QuickBytesToStr(expr))) + } + match, value, err := op.regMap.regularSubstr( + functionUtil.QuickBytesToStr(pat), functionUtil.QuickBytesToStr(expr), pos, occurrence) + return match, functionUtil.QuickStrToBytes(value), err + } rs := vector.MustFunctionResult[types.Varlena](result) switch len(parameters) { @@ -388,14 +474,18 @@ func (op *opBuiltInRegexp) builtInRegexpSubstr(parameters []*vector.Vector, resu return err } } else { - expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) - match, res, err := op.regMap.regularSubstr(pat, expr, 1, 1) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + match, res, err := regularSubstr(v1, v2, 1, 1, binaryInput, + parameters[1].GetIsBinaryStringAt(int(i))) if err != nil { return err } - if err = rs.AppendBytes(functionUtil.QuickStrToBytes(res), !match); err != nil { + if err = rs.AppendBytes(res, !match); err != nil { return err } + if match { + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) + } } } @@ -410,14 +500,18 @@ func (op *opBuiltInRegexp) builtInRegexpSubstr(parameters []*vector.Vector, resu return err } } else { - expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) - match, res, err := op.regMap.regularSubstr(pat, expr, pos, 1) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + match, res, err := regularSubstr(v1, v2, pos, 1, binaryInput, + parameters[1].GetIsBinaryStringAt(int(i))) if err != nil { return err } - if err = rs.AppendBytes(functionUtil.QuickStrToBytes(res), !match); err != nil { + if err = rs.AppendBytes(res, !match); err != nil { return err } + if match { + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) + } } } @@ -434,18 +528,20 @@ func (op *opBuiltInRegexp) builtInRegexpSubstr(parameters []*vector.Vector, resu return err } } else { - expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) - match, res, err := op.regMap.regularSubstr(pat, expr, pos, ocur) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + match, res, err := regularSubstr(v1, v2, pos, ocur, binaryInput, + parameters[1].GetIsBinaryStringAt(int(i))) if err != nil { return err } - if err = rs.AppendBytes(functionUtil.QuickStrToBytes(res), !match); err != nil { + if err = rs.AppendBytes(res, !match); err != nil { return err } + if match { + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) + } } } - return nil - } return nil } @@ -455,10 +551,37 @@ func (op *opBuiltInRegexp) builtInRegexpInstr(parameters []*vector.Vector, resul p2 := vector.GenerateFunctionStrParameter(parameters[1]) rs := vector.MustFunctionResult[int64](result) + regularInstr := func(expr, pat []byte, pos, occurrence int64, retOption int8, binaryInput bool) (int64, error) { + if binaryInput { + return op.regMap.regularInstrBinary(pat, expr, pos, occurrence, retOption) + } + return op.regMap.regularInstr(functionUtil.QuickBytesToStr(pat), functionUtil.QuickBytesToStr(expr), pos, occurrence, retOption) + } switch len(parameters) { case 2: - return opBinaryStrStrToFixedWithErrorCheck[int64](parameters, result, proc, length, func(v1, v2 string) (int64, error) { - return op.regMap.regularInstr(v2, v1, 1, 1, 0) + if parameters[0].HasBinaryStringRows() { + for i := uint64(0); i < uint64(length); i++ { + v1, null1 := p1.GetStrValue(i) + v2, null2 := p2.GetStrValue(i) + if null1 || null2 || selectList != nil && selectList.Contains(i) { + if err := rs.Append(0, true); err != nil { + return err + } + continue + } + index, err := regularInstr(v1, v2, 1, 1, 0, parameters[0].GetIsBinaryStringAt(int(i))) + if err != nil { + return err + } + if err = rs.Append(index, false); err != nil { + return err + } + } + return nil + } + binaryInput := isBinaryStringVector(parameters[0]) + return opBinaryBytesBytesToFixedWithErrorCheck[int64](parameters, result, proc, length, func(v1, v2 []byte) (int64, error) { + return regularInstr(v1, v2, 1, 1, 0, binaryInput) }, selectList) case 3: @@ -472,8 +595,7 @@ func (op *opBuiltInRegexp) builtInRegexpInstr(parameters []*vector.Vector, resul return err } } else { - expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) - index, err := op.regMap.regularInstr(pat, expr, pos, 1, 0) + index, err := regularInstr(v1, v2, pos, 1, 0, parameters[0].GetIsBinaryStringAt(int(i))) if err != nil { return err } @@ -496,8 +618,7 @@ func (op *opBuiltInRegexp) builtInRegexpInstr(parameters []*vector.Vector, resul return err } } else { - expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) - index, err := op.regMap.regularInstr(pat, expr, pos, ocur, 0) + index, err := regularInstr(v1, v2, pos, ocur, 0, parameters[0].GetIsBinaryStringAt(int(i))) if err != nil { return err } @@ -523,8 +644,7 @@ func (op *opBuiltInRegexp) builtInRegexpInstr(parameters []*vector.Vector, resul return err } } else { - expr, pat := functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v2) - index, err := op.regMap.regularInstr(pat, expr, pos, ocur, resOp) + index, err := regularInstr(v1, v2, pos, ocur, resOp, parameters[0].GetIsBinaryStringAt(int(i))) if err != nil { return err } @@ -541,10 +661,38 @@ func (op *opBuiltInRegexp) builtInRegexpLike(parameters []*vector.Vector, result p1 := vector.GenerateFunctionStrParameter(parameters[0]) p2 := vector.GenerateFunctionStrParameter(parameters[1]) rs := vector.MustFunctionResult[bool](result) + regularLike := func(expr, pat, matchType []byte, binaryInput bool) (bool, error) { + exprString, patString := functionUtil.QuickBytesToStr(expr), functionUtil.QuickBytesToStr(pat) + if binaryInput { + exprString, patString = binaryBytesToRegexpString(expr), binaryBytesToRegexpString(pat) + } + return op.regMap.regularLike(patString, exprString, functionUtil.QuickBytesToStr(matchType)) + } if len(parameters) == 2 { - return opBinaryStrStrToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 string) (bool, error) { - match, err := op.regMap.regularLike(v2, v1, "c") + if parameters[0].HasBinaryStringRows() { + for i := uint64(0); i < uint64(length); i++ { + expr, null1 := p1.GetStrValue(i) + pat, null2 := p2.GetStrValue(i) + if null1 || null2 || selectList != nil && selectList.Contains(i) { + if err := rs.Append(false, true); err != nil { + return err + } + continue + } + match, err := regularLike(expr, pat, []byte("c"), parameters[0].GetIsBinaryStringAt(int(i))) + if err != nil { + return err + } + if err = rs.Append(match, false); err != nil { + return err + } + } + return nil + } + binaryInput := isBinaryStringVector(parameters[0]) + return opBinaryBytesBytesToFixedWithErrorCheck[bool](parameters, result, proc, length, func(v1, v2 []byte) (bool, error) { + match, err := regularLike(v1, v2, []byte("c"), binaryInput) return match, err }, selectList) } else if len(parameters) == 3 { @@ -563,7 +711,7 @@ func (op *opBuiltInRegexp) builtInRegexpLike(parameters []*vector.Vector, result return err } } else { - match, err := op.regMap.regularLike(string(pat), string(expr), string(mt)) + match, err := regularLike(expr, pat, mt, parameters[0].GetIsBinaryStringAt(int(i))) if err != nil { return err } @@ -581,6 +729,13 @@ func (op *opBuiltInRegexp) builtInRegexpReplace(parameters []*vector.Vector, res p2 := vector.GenerateFunctionStrParameter(parameters[1]) // pat p3 := vector.GenerateFunctionStrParameter(parameters[2]) // repl rs := vector.MustFunctionResult[types.Varlena](result) + regularReplace := func(expr, pat, repl []byte, pos, occurrence int64, binaryInput bool) ([]byte, error) { + if binaryInput { + return op.regMap.regularReplaceBinary(pat, expr, repl, pos, occurrence) + } + value, err := op.regMap.regularReplace(functionUtil.QuickBytesToStr(pat), functionUtil.QuickBytesToStr(expr), functionUtil.QuickBytesToStr(repl), pos, occurrence) + return functionUtil.QuickStrToBytes(value), err + } if parameters[0].IsConstNull() || parameters[1].IsConstNull() || parameters[2].IsConstNull() { for i := uint64(0); i < uint64(length); i++ { @@ -602,13 +757,15 @@ func (op *opBuiltInRegexp) builtInRegexpReplace(parameters []*vector.Vector, res return err } } else { - val, err := op.regMap.regularReplace(functionUtil.QuickBytesToStr(v2), functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v3), 1, 0) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + val, err := regularReplace(v1, v2, v3, 1, 0, binaryInput) if err != nil { return err } - if err = rs.AppendBytes([]byte(val), false); err != nil { + if err = rs.AppendBytes(val, false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } @@ -624,13 +781,15 @@ func (op *opBuiltInRegexp) builtInRegexpReplace(parameters []*vector.Vector, res return err } } else { - val, err := op.regMap.regularReplace(functionUtil.QuickBytesToStr(v2), functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v3), v4, 0) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + val, err := regularReplace(v1, v2, v3, v4, 0, binaryInput) if err != nil { return err } - if err = rs.AppendBytes([]byte(val), false); err != nil { + if err = rs.AppendBytes(val, false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } @@ -648,13 +807,15 @@ func (op *opBuiltInRegexp) builtInRegexpReplace(parameters []*vector.Vector, res return err } } else { - val, err := op.regMap.regularReplace(functionUtil.QuickBytesToStr(v2), functionUtil.QuickBytesToStr(v1), functionUtil.QuickBytesToStr(v3), v4, v5) + binaryInput := parameters[0].GetIsBinaryStringAt(int(i)) + val, err := regularReplace(v1, v2, v3, v4, v5, binaryInput) if err != nil { return err } - if err = rs.AppendBytes([]byte(val), false); err != nil { + if err = rs.AppendBytes(val, false); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), binaryInput) } } } @@ -697,6 +858,61 @@ func (rs *regexpSet) regularMatchForLikeOp(pat []byte, str []byte) (match bool, return rs.regularMatchForLikeOpWithEscape(pat, str, DefaultEscapeChar, true, false) } +func binaryBytesToRegexpString(value []byte) string { + runes := make([]rune, len(value)) + for i, b := range value { + runes[i] = rune(b) + } + return string(runes) +} + +func binaryRegexpStringToBytes(value string) []byte { + runes := []rune(value) + result := make([]byte, len(runes)) + for i, r := range runes { + result[i] = byte(r) + } + return result +} + +func (rs *regexpSet) regularMatchForBinaryLikeOp( + pat, str []byte, + escape []byte, + escapeEnabled bool, +) (bool, error) { + var pattern strings.Builder + pattern.WriteString("^(?s:") + escaped := false + for i := 0; i < len(pat); i++ { + b := pat[i] + if escaped { + pattern.WriteString(regexp.QuoteMeta(binaryBytesToRegexpString([]byte{b}))) + escaped = false + continue + } + switch { + case escapeEnabled && len(escape) > 0 && bytes.HasPrefix(pat[i:], escape): + escaped = true + i += len(escape) - 1 + case b == '_': + pattern.WriteByte('.') + case b == '%': + pattern.WriteString(".*") + default: + pattern.WriteString(regexp.QuoteMeta(binaryBytesToRegexpString([]byte{b}))) + } + } + if escaped { + pattern.WriteString(regexp.QuoteMeta(binaryBytesToRegexpString(escape))) + } + pattern.WriteString(")$") + matcher, err := rs.getRegularMatcher(pattern.String()) + if err != nil { + return false, nil + } + return matcher.MatchString(binaryBytesToRegexpString(str)), nil +} + func (rs *regexpSet) regularMatchForLikeOpWithEscape( pat []byte, str []byte, @@ -789,6 +1005,95 @@ func (rs *regexpSet) regularSubstr(pat string, str string, pos, occurrence int64 return true, matches[occurrence-1], nil } +func (rs *regexpSet) regularSubstrBinary( + pat, str []byte, + pos, occurrence int64, +) (bool, []byte, error) { + if pos < 1 || pos > int64(len(str)) { + return false, nil, moerr.NewInvalidInputNoCtxf( + "regexp_substr: Index out of bounds in regular expression search. Search start position: %d, Search string length: %d", + pos, len(str)) + } + if occurrence < 1 { + return false, nil, moerr.NewInvalidInputNoCtxf( + "regexp_substr have Index out of bounds in regular expression search, return occurrence %d", occurrence) + } + matcher, err := rs.getRegularMatcher(binaryBytesToRegexpString(pat)) + if err != nil { + return false, nil, err + } + matches := matcher.FindAllString(binaryBytesToRegexpString(str[pos-1:]), -1) + if int64(len(matches)) < occurrence { + return false, nil, nil + } + return true, binaryRegexpStringToBytes(matches[occurrence-1]), nil +} + +func (rs *regexpSet) regularInstrBinary( + pat, str []byte, + pos, occurrence int64, + retOption int8, +) (int64, error) { + if pos < 1 || pos > int64(len(str)) { + return 0, moerr.NewInvalidInputNoCtxf( + "regexp_instr: Index out of bounds in regular expression search. Search start position: %d, Search string length: %d", + pos, len(str)) + } + if occurrence < 1 { + return 0, moerr.NewInvalidInputNoCtxf( + "regexp_instr have Index out of bounds in regular expression search, return occurrence %d", occurrence) + } + if retOption > 1 { + return 0, moerr.NewInvalidInputNoCtxf( + "regexp_instr have Index out of bounds in regular expression search, return option %d", retOption) + } + matcher, err := rs.getRegularMatcher(binaryBytesToRegexpString(pat)) + if err != nil { + return 0, moerr.NewInvalidArgNoCtx( + "regexp_instr have invalid regexp pattern arg", "["+string(pat)+"]") + } + value := binaryBytesToRegexpString(str[pos-1:]) + matches := matcher.FindAllStringIndex(value, -1) + if int64(len(matches)) < occurrence { + return 0, nil + } + matchIndex := matches[occurrence-1][retOption] + return int64(utf8.RuneCountInString(value[:matchIndex])) + pos, nil +} + +func (rs *regexpSet) regularReplaceBinary( + pat, str, repl []byte, + pos, occurrence int64, +) ([]byte, error) { + if pos < 1 || pos > int64(len(str)) { + return nil, moerr.NewInvalidInputNoCtxf( + "regexp_replace: Index out of bounds in regular expression search. Search start position: %d, Search string length: %d", + pos, len(str)) + } + if occurrence < 0 { + return nil, moerr.NewInvalidInputNoCtxf( + "regexp_replace have Index out of bounds in regular expression search, return occurrence %d", occurrence) + } + matcher, err := rs.getRegularMatcher(binaryBytesToRegexpString(pat)) + if err != nil { + return nil, moerr.NewInvalidArgNoCtx( + "regexp_replace have invalid regexp pattern arg", "["+string(pat)+"]") + } + prefix := binaryBytesToRegexpString(str[:pos-1]) + value := binaryBytesToRegexpString(str[pos-1:]) + replacement := binaryBytesToRegexpString(repl) + matches := matcher.FindAllStringIndex(value, -1) + if len(matches) == 0 || occurrence > int64(len(matches)) { + return append([]byte(nil), str...), nil + } + if occurrence == 0 { + return binaryRegexpStringToBytes(prefix + matcher.ReplaceAllLiteralString(value, replacement)), nil + } + match := matches[occurrence-1] + result := prefix + value[:match[0]] + replacement + value[match[1]:] + return binaryRegexpStringToBytes(result), nil +} + func (rs *regexpSet) regularReplace(pat string, str string, repl string, pos, occurrence int64) (r string, err error) { // check position if pos < 1 || pos > int64(len(str)) { diff --git a/pkg/sql/plan/function/func_builtin_regexp_test.go b/pkg/sql/plan/function/func_builtin_regexp_test.go index 8a43ce0c87965..20783d81ee3bb 100644 --- a/pkg/sql/plan/function/func_builtin_regexp_test.go +++ b/pkg/sql/plan/function/func_builtin_regexp_test.go @@ -525,3 +525,267 @@ func Test_BuiltIn_RegularSubstr(t *testing.T) { require.Equal(t, c.expected, val, i) } } + +func runBinaryStringRegexpCase( + t *testing.T, + proc *process.Process, + inputs []FunctionTestInput, + expected FunctionTestResult, + fn fEvalFn, + checkResultMarker bool, +) { + t.Helper() + tcc := NewFunctionTestCase(proc, inputs, expected, fn) + tcc.parameters[0].SetIsBinaryStringAt(0, true) + succeed, errInfo := tcc.Run() + require.True(t, succeed, errInfo) + + if checkResultMarker { + result := tcc.GetResultVectorDirectly() + require.True(t, result.GetIsBinaryStringAt(0)) + require.False(t, result.GetIsBinaryStringAt(1)) + require.False(t, result.GetIsBinaryStringAt(2)) + } +} + +func Test_BuiltIn_BinaryStringRegexpPredicatesUseSubjectRows(t *testing.T) { + proc := testutil.NewProcess(t) + subjects := []string{"你a", "你a", "你a"} + nulls := []bool{false, false, true} + + tests := []struct { + name string + inputs []FunctionTestInput + expected []bool + fn fEvalFn + }{ + { + name: "like", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"____", "____", "____"}, nil), + }, + expected: []bool{true, false, false}, + fn: newOpBuiltInRegexp().likeFn, + }, + { + name: "like with escape", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"____", "____", "____"}, nil), + NewFunctionTestConstInput(types.T_varchar.ToType(), []string{"=", "=", "="}, nil), + }, + expected: []bool{true, false, false}, + fn: newOpBuiltInRegexp().likeFn, + }, + { + name: "regexp operator", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"^....$", "^....$", "^....$"}, nil), + }, + expected: []bool{true, false, false}, + fn: newOpBuiltInRegexp().builtInRegMatch, + }, + { + name: "not regexp operator", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"^....$", "^....$", "^....$"}, nil), + }, + expected: []bool{false, true, false}, + fn: newOpBuiltInRegexp().builtInNotRegMatch, + }, + { + name: "regexp like", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"^....$", "^....$", "^....$"}, nil), + }, + expected: []bool{true, false, false}, + fn: newOpBuiltInRegexp().builtInRegexpLike, + }, + { + name: "regexp like with match type", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"^....$", "^....$", "^....$"}, nil), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"c", "c", "c"}, nil), + }, + expected: []bool{true, false, false}, + fn: newOpBuiltInRegexp().builtInRegexpLike, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + runBinaryStringRegexpCase(t, proc, test.inputs, + NewFunctionTestResult(types.T_bool.ToType(), false, test.expected, nulls), test.fn, false) + }) + } +} + +func Test_BuiltIn_BinaryStringRegexpSubstrArities(t *testing.T) { + proc := testutil.NewProcess(t) + subjects := []string{"你a", "你a", "你a"} + patterns := []string{".", ".", "."} + nulls := []bool{false, false, true} + + tests := []struct { + name string + inputs []FunctionTestInput + expected []string + }{ + { + name: "two arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + }, + expected: []string{string([]byte{0xe4}), "你", ""}, + }, + { + name: "three arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + }, + expected: []string{string([]byte{0xbd}), string([]byte{0xbd}), ""}, + }, + { + name: "four arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{1, 1, 1}, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + }, + expected: []string{string([]byte{0xbd}), "a", ""}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + runBinaryStringRegexpCase(t, proc, test.inputs, + NewFunctionTestResult(types.T_varchar.ToType(), false, test.expected, nulls), + newOpBuiltInRegexp().builtInRegexpSubstr, true) + }) + } +} + +func Test_BuiltIn_BinaryStringRegexpInstrArities(t *testing.T) { + proc := testutil.NewProcess(t) + subjects := []string{"你a", "你a", "你a"} + patterns := []string{".", ".", "."} + nulls := []bool{false, false, true} + + tests := []struct { + name string + inputs []FunctionTestInput + expected []int64 + }{ + { + name: "two arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + }, + expected: []int64{1, 1, 0}, + }, + { + name: "three arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + }, + expected: []int64{2, 2, 0}, + }, + { + name: "four arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{1, 1, 1}, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + }, + expected: []int64{2, 4, 0}, + }, + { + name: "five arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{1, 1, 1}, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + NewFunctionTestInput(types.T_int8.ToType(), []int8{1, 1, 1}, nil), + }, + expected: []int64{3, 5, 0}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tcc := NewFunctionTestCase(proc, test.inputs, + NewFunctionTestResult(types.T_int64.ToType(), false, test.expected, nulls), + newOpBuiltInRegexp().builtInRegexpInstr) + tcc.parameters[0].SetIsBinaryStringAt(0, true) + succeed, errInfo := tcc.Run() + require.True(t, succeed, errInfo) + }) + } +} + +func Test_BuiltIn_BinaryStringRegexpReplaceArities(t *testing.T) { + proc := testutil.NewProcess(t) + subjects := []string{"你a", "你a", "你a"} + patterns := []string{".", ".", "."} + replacements := []string{"x", "x", "x"} + nulls := []bool{false, false, true} + + tests := []struct { + name string + inputs []FunctionTestInput + expected []string + }{ + { + name: "three arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_varchar.ToType(), replacements, nil), + }, + expected: []string{"xxxx", "xx", ""}, + }, + { + name: "four arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_varchar.ToType(), replacements, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + }, + expected: []string{string([]byte{0xe4}) + "xxx", "xx", ""}, + }, + { + name: "five arguments", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), subjects, nulls), + NewFunctionTestInput(types.T_varchar.ToType(), patterns, nil), + NewFunctionTestInput(types.T_varchar.ToType(), replacements, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{1, 1, 1}, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{2, 2, 2}, nil), + }, + expected: []string{string([]byte{0xe4, 'x', 0xa0, 'a'}), "你x", ""}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + runBinaryStringRegexpCase(t, proc, test.inputs, + NewFunctionTestResult(types.T_varchar.ToType(), false, test.expected, nulls), + newOpBuiltInRegexp().builtInRegexpReplace, true) + }) + } +} diff --git a/pkg/sql/plan/function/func_cast.go b/pkg/sql/plan/function/func_cast.go index c37db06f51172..5381db098e194 100644 --- a/pkg/sql/plan/function/func_cast.go +++ b/pkg/sql/plan/function/func_cast.go @@ -900,7 +900,11 @@ func (m castMode) strictStringWidth() bool { } func NewCast(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { - return newCast(parameters, result, proc, length, selectList, castModeNormal, false) + err := newCast(parameters, result, proc, length, selectList, castModeNormal, false) + if err == nil && isBinaryStringVector(parameters[0]) && parameters[1].GetType().Oid.IsMySQLString() { + result.GetResultVector().SetIsBinaryString(true) + } + return err } func NewStrictCast(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { diff --git a/pkg/sql/plan/function/func_cast_test.go b/pkg/sql/plan/function/func_cast_test.go index bd6f34b507517..72cb4b1ecf4d8 100644 --- a/pkg/sql/plan/function/func_cast_test.go +++ b/pkg/sql/plan/function/func_cast_test.go @@ -2818,6 +2818,28 @@ func Test_strToUnsigned_BinaryIntroducedHexText(t *testing.T) { }) } +func TestImplicitStringCastPreservesDynamicBinarySemantics(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + input := testutil.MakeVarlenaVector( + [][]byte{{0xe4, 0xbd, 0xa0}}, nil, types.T_text.ToType(), mp) + input.SetIsBinaryString(true) + target := vector.NewConstNull(types.T_varchar.ToType(), 1, mp) + defer input.Free(mp) + defer target.Free(mp) + + run := func(t *testing.T, castFn func([]*vector.Vector, vector.FunctionResultWrapper, *process.Process, int, *FunctionSelectList) error, wantBinary bool) { + result := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer result.Free() + require.NoError(t, result.PreExtendAndReset(1)) + require.NoError(t, castFn([]*vector.Vector{input, target}, result, proc, 1, nil)) + require.Equal(t, wantBinary, result.GetResultVector().GetIsBinaryString()) + } + + t.Run("implicit cast", func(t *testing.T) { run(t, NewCast, true) }) + t.Run("explicit cast", func(t *testing.T) { run(t, NewExplicitCast, false) }) +} + func contains(slice []uint64, item uint64) bool { for _, s := range slice { if s == item { diff --git a/pkg/sql/plan/function/func_locate.go b/pkg/sql/plan/function/func_locate.go index 004e97382ba9f..1e353f2678f26 100644 --- a/pkg/sql/plan/function/func_locate.go +++ b/pkg/sql/plan/function/func_locate.go @@ -39,7 +39,16 @@ func buildInLocate2Args(parameters []*vector.Vector, result vector.FunctionResul return err } } else { - pos := Locate2Args(functionUtil.QuickBytesToStr(bytes.ToUpper(str)), functionUtil.QuickBytesToStr(bytes.ToUpper(substr))) + binaryInput := parameters[1].GetIsBinaryStringAt(int(i)) + var pos int64 + if binaryInput { + idx := bytes.Index(str, substr) + if idx >= 0 { + pos = int64(idx + 1) + } + } else { + pos = Locate2Args(functionUtil.QuickBytesToStr(bytes.ToUpper(str)), functionUtil.QuickBytesToStr(bytes.ToUpper(substr))) + } rs.AppendMustValue(pos) } } @@ -63,7 +72,18 @@ func buildInLocate3Args(parameters []*vector.Vector, result vector.FunctionResul return err } } else { - pos := Locate3Args(functionUtil.QuickBytesToStr(bytes.ToUpper(str)), functionUtil.QuickBytesToStr(bytes.ToUpper(substr)), position) + binaryInput := parameters[1].GetIsBinaryStringAt(int(i)) + var pos int64 + if binaryInput { + if position > 0 && position <= int64(len(str))+1 { + idx := bytes.Index(str[position-1:], substr) + if idx >= 0 { + pos = position + int64(idx) + } + } + } else { + pos = Locate3Args(functionUtil.QuickBytesToStr(bytes.ToUpper(str)), functionUtil.QuickBytesToStr(bytes.ToUpper(substr)), position) + } rs.AppendMustValue(pos) } } diff --git a/pkg/sql/plan/function/func_string_complex_test.go b/pkg/sql/plan/function/func_string_complex_test.go index 88877aecd6f00..4bd8150edfe1d 100644 --- a/pkg/sql/plan/function/func_string_complex_test.go +++ b/pkg/sql/plan/function/func_string_complex_test.go @@ -21,6 +21,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/testutil" + "github.com/matrixorigin/matrixone/pkg/vm/process" "github.com/stretchr/testify/require" ) @@ -126,6 +127,403 @@ func Test_BuiltInConcat(t *testing.T) { } } +func TestBinaryStringFunctionsPreserveDynamicSemantics(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + binary := testutil.MakeVarlenaVector( + [][]byte{[]byte("AB"), {0xe4, 0xbd, 0xa0}}, nil, types.T_varchar.ToType(), mp) + binary.SetIsBinaryString(true) + defer binary.Free(mp) + + lowerResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer lowerResult.Free() + require.NoError(t, lowerResult.PreExtendAndReset(binary.Length())) + require.NoError(t, builtInToLower([]*vector.Vector{binary}, lowerResult, proc, binary.Length(), nil)) + require.False(t, lowerResult.GetResultVector().GetIsBin()) + require.True(t, lowerResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{"AB", string([]byte{0xe4, 0xbd, 0xa0})}, + vector.InefficientMustStrCol(lowerResult.GetResultVector())) + + starts := testutil.MakeInt64Vector([]int64{2, 2}, nil, mp) + lens := testutil.MakeInt64Vector([]int64{1, 1}, nil, mp) + defer starts.Free(mp) + defer lens.Free(mp) + substringResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer substringResult.Free() + require.NoError(t, substringResult.PreExtendAndReset(binary.Length())) + require.NoError(t, SubStringWith3Args( + []*vector.Vector{binary, starts, lens}, substringResult, proc, binary.Length(), nil)) + require.False(t, substringResult.GetResultVector().GetIsBin()) + require.True(t, substringResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{"B", string([]byte{0xbd})}, + vector.InefficientMustStrCol(substringResult.GetResultVector())) + + repeatCounts := testutil.MakeInt64Vector([]int64{2, 2}, nil, mp) + defer repeatCounts.Free(mp) + repeatResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer repeatResult.Free() + require.NoError(t, repeatResult.PreExtendAndReset(binary.Length())) + require.NoError(t, builtInRepeat( + []*vector.Vector{binary, repeatCounts}, repeatResult, proc, binary.Length(), nil)) + require.False(t, repeatResult.GetResultVector().GetIsBin()) + require.True(t, repeatResult.GetResultVector().GetIsBinaryString()) +} + +func TestBinaryStringFunctionsUseByteSemantics(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + binary := testutil.MakeVarlenaVector( + [][]byte{{0xe4, 0xbd, 0xa0, 0x61}, {0xff, 0x61}}, nil, types.T_varchar.ToType(), mp) + binary.SetIsBinaryString(true) + defer binary.Free(mp) + + lengths := testutil.MakeInt64Vector([]int64{1, 1}, nil, mp) + defer lengths.Free(mp) + leftResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer leftResult.Free() + require.NoError(t, leftResult.PreExtendAndReset(binary.Length())) + require.NoError(t, Left([]*vector.Vector{binary, lengths}, leftResult, proc, binary.Length(), nil)) + require.True(t, leftResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{string([]byte{0xe4}), string([]byte{0xff})}, + vector.InefficientMustStrCol(leftResult.GetResultVector())) + + reverseResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer reverseResult.Free() + require.NoError(t, reverseResult.PreExtendAndReset(binary.Length())) + require.NoError(t, Reverse([]*vector.Vector{binary}, reverseResult, proc, binary.Length(), nil)) + require.True(t, reverseResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{string([]byte{0x61, 0xa0, 0xbd, 0xe4}), string([]byte{0x61, 0xff})}, + vector.InefficientMustStrCol(reverseResult.GetResultVector())) + + ordResult := vector.NewFunctionResultWrapper(types.T_int64.ToType(), mp) + defer ordResult.Free() + require.NoError(t, ordResult.PreExtendAndReset(binary.Length())) + require.NoError(t, Ord([]*vector.Vector{binary}, ordResult, proc, binary.Length(), nil)) + require.Equal(t, []int64{0xe4, 0xff}, vector.MustFixedColWithTypeCheck[int64](ordResult.GetResultVector())) + + needle := testutil.MakeVarlenaVector([][]byte{{0xbd}, {0x61}}, nil, types.T_varchar.ToType(), mp) + needle.SetIsBinaryString(true) + defer needle.Free(mp) + instrResult := vector.NewFunctionResultWrapper(types.T_int64.ToType(), mp) + defer instrResult.Free() + require.NoError(t, instrResult.PreExtendAndReset(binary.Length())) + require.NoError(t, Instr([]*vector.Vector{binary, needle}, instrResult, proc, binary.Length(), nil)) + require.Equal(t, []int64{2, 2}, vector.MustFixedColWithTypeCheck[int64](instrResult.GetResultVector())) + + locateResult := vector.NewFunctionResultWrapper(types.T_int64.ToType(), mp) + defer locateResult.Free() + require.NoError(t, locateResult.PreExtendAndReset(binary.Length())) + require.NoError(t, buildInLocate2Args( + []*vector.Vector{needle, binary}, locateResult, proc, binary.Length(), nil)) + require.Equal(t, []int64{2, 2}, vector.MustFixedColWithTypeCheck[int64](locateResult.GetResultVector())) + + pad := testutil.MakeVarlenaVector([][]byte{[]byte("x"), []byte("x")}, nil, types.T_varchar.ToType(), mp) + defer pad.Free(mp) + padLengths := testutil.MakeInt64Vector([]int64{5, 5}, nil, mp) + defer padLengths.Free(mp) + for name, fn := range map[string]func([]*vector.Vector, vector.FunctionResultWrapper, *process.Process, int, *FunctionSelectList) error{ + "lpad": builtInLpad, + "rpad": builtInRpad, + } { + t.Run(name, func(t *testing.T) { + result := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer result.Free() + require.NoError(t, result.PreExtendAndReset(binary.Length())) + require.NoError(t, fn( + []*vector.Vector{binary, padLengths, pad}, result, proc, binary.Length(), nil)) + require.True(t, result.GetResultVector().GetIsBinaryString()) + values := vector.InefficientMustStrCol(result.GetResultVector()) + if name == "lpad" { + require.Equal(t, []string{ + string([]byte{'x', 0xe4, 0xbd, 0xa0, 0x61}), + string([]byte{'x', 'x', 'x', 0xff, 0x61}), + }, values) + } else { + require.Equal(t, []string{ + string([]byte{0xe4, 0xbd, 0xa0, 0x61, 'x'}), + string([]byte{0xff, 0x61, 'x', 'x', 'x'}), + }, values) + } + }) + } + + patterns := testutil.MakeVarlenaVector( + [][]byte{[]byte("____"), []byte("__")}, nil, types.T_varchar.ToType(), mp) + defer patterns.Free(mp) + likeResult := vector.NewFunctionResultWrapper(types.T_bool.ToType(), mp) + defer likeResult.Free() + require.NoError(t, likeResult.PreExtendAndReset(binary.Length())) + require.NoError(t, newOpBuiltInRegexp().likeFn( + []*vector.Vector{binary, patterns}, likeResult, proc, binary.Length(), nil)) + require.Equal(t, []bool{true, true}, vector.MustFixedColWithTypeCheck[bool](likeResult.GetResultVector())) + + escape := testutil.MakeScalarVarchar("=", binary.Length(), mp) + defer escape.Free(mp) + likeEscapeResult := vector.NewFunctionResultWrapper(types.T_bool.ToType(), mp) + defer likeEscapeResult.Free() + require.NoError(t, likeEscapeResult.PreExtendAndReset(binary.Length())) + require.NoError(t, newOpBuiltInRegexp().likeFn( + []*vector.Vector{binary, patterns, escape}, likeEscapeResult, proc, binary.Length(), nil)) + require.Equal(t, []bool{true, true}, vector.MustFixedColWithTypeCheck[bool](likeEscapeResult.GetResultVector())) + + dot := testutil.MakeVarlenaVector([][]byte{[]byte("."), []byte(".")}, nil, types.T_varchar.ToType(), mp) + defer dot.Free(mp) + regexpResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer regexpResult.Free() + require.NoError(t, regexpResult.PreExtendAndReset(binary.Length())) + require.NoError(t, newOpBuiltInRegexp().builtInRegexpSubstr( + []*vector.Vector{binary, dot}, regexpResult, proc, binary.Length(), nil)) + require.True(t, regexpResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{string([]byte{0xe4}), string([]byte{0xff})}, + vector.InefficientMustStrCol(regexpResult.GetResultVector())) + + regPatterns := testutil.MakeVarlenaVector( + [][]byte{[]byte("^....$"), []byte("^..$")}, nil, types.T_varchar.ToType(), mp) + defer regPatterns.Free(mp) + regMatchResult := vector.NewFunctionResultWrapper(types.T_bool.ToType(), mp) + defer regMatchResult.Free() + require.NoError(t, regMatchResult.PreExtendAndReset(binary.Length())) + require.NoError(t, newOpBuiltInRegexp().builtInRegMatch( + []*vector.Vector{binary, regPatterns}, regMatchResult, proc, binary.Length(), nil)) + require.Equal(t, []bool{true, true}, vector.MustFixedColWithTypeCheck[bool](regMatchResult.GetResultVector())) + + occurrences := testutil.MakeInt64Vector([]int64{2, 2}, nil, mp) + defer occurrences.Free(mp) + positions := testutil.MakeInt64Vector([]int64{1, 1}, nil, mp) + defer positions.Free(mp) + regexpInstrResult := vector.NewFunctionResultWrapper(types.T_int64.ToType(), mp) + defer regexpInstrResult.Free() + require.NoError(t, regexpInstrResult.PreExtendAndReset(binary.Length())) + require.NoError(t, newOpBuiltInRegexp().builtInRegexpInstr( + []*vector.Vector{binary, dot, positions, occurrences}, regexpInstrResult, proc, binary.Length(), nil)) + require.Equal(t, []int64{2, 2}, vector.MustFixedColWithTypeCheck[int64](regexpInstrResult.GetResultVector())) + + replacements := testutil.MakeVarlenaVector( + [][]byte{[]byte("x"), []byte("x")}, nil, types.T_varchar.ToType(), mp) + defer replacements.Free(mp) + regexpReplaceResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer regexpReplaceResult.Free() + require.NoError(t, regexpReplaceResult.PreExtendAndReset(binary.Length())) + require.NoError(t, newOpBuiltInRegexp().builtInRegexpReplace( + []*vector.Vector{binary, dot, replacements}, regexpReplaceResult, proc, binary.Length(), nil)) + require.True(t, regexpReplaceResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{"xxxx", "xx"}, vector.InefficientMustStrCol(regexpReplaceResult.GetResultVector())) + + delimiters := testutil.MakeVarlenaVector( + [][]byte{[]byte("a"), []byte("a")}, nil, types.T_varchar.ToType(), mp) + defer delimiters.Free(mp) + counts := testutil.MakeInt64Vector([]int64{1, 1}, nil, mp) + defer counts.Free(mp) + substrIndexResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer substrIndexResult.Free() + require.NoError(t, substrIndexResult.PreExtendAndReset(binary.Length())) + require.NoError(t, SubStrIndex[int64]( + []*vector.Vector{binary, delimiters, counts}, substrIndexResult, proc, binary.Length(), nil)) + require.True(t, substrIndexResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{string([]byte{0xe4, 0xbd, 0xa0}), string([]byte{0xff})}, + vector.InefficientMustStrCol(substrIndexResult.GetResultVector())) + + insertResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer insertResult.Free() + require.NoError(t, insertResult.PreExtendAndReset(binary.Length())) + require.NoError(t, Insert( + []*vector.Vector{binary, occurrences, positions, replacements}, insertResult, proc, binary.Length(), nil)) + require.True(t, insertResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []string{string([]byte{0xe4, 'x', 0xa0, 0x61}), string([]byte{0xff, 'x'})}, + vector.InefficientMustStrCol(insertResult.GetResultVector())) +} + +func TestBinaryStringScalarResultsPropagateMetadata(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + binary := testutil.MakeVarlenaVector( + [][]byte{{0xe4, 0xbd, 0xa0}}, nil, types.T_varchar.ToType(), mp) + binary.SetIsBinaryString(true) + defer binary.Free(mp) + text := testutil.MakeVarlenaVector([][]byte{[]byte("x")}, nil, types.T_varchar.ToType(), mp) + defer text.Free(mp) + + tests := []struct { + name string + fn func([]*vector.Vector, vector.FunctionResultWrapper, *process.Process, int, *FunctionSelectList) error + params []*vector.Vector + }{ + {name: "substring_index", fn: SubStrIndex[int64], params: []*vector.Vector{ + binary, text, testutil.MakeInt64Vector([]int64{1}, nil, mp), + }}, + {name: "replace", fn: Replace, params: []*vector.Vector{binary, text, text}}, + {name: "insert", fn: Insert, params: []*vector.Vector{ + binary, testutil.MakeInt64Vector([]int64{1}, nil, mp), testutil.MakeInt64Vector([]int64{1}, nil, mp), text, + }}, + {name: "make_set", fn: MakeSet, params: []*vector.Vector{ + testutil.MakeInt64Vector([]int64{1}, nil, mp), binary, + }}, + {name: "export_set", fn: ExportSet, params: []*vector.Vector{ + testutil.MakeInt64Vector([]int64{1}, nil, mp), binary, text, text, testutil.MakeInt64Vector([]int64{1}, nil, mp), + }}, + {name: "ltrim", fn: Ltrim, params: []*vector.Vector{binary}}, + {name: "rtrim", fn: Rtrim, params: []*vector.Vector{binary}}, + {name: "elt", fn: Elt, params: []*vector.Vector{ + testutil.MakeInt64Vector([]int64{1}, nil, mp), binary, + }}, + {name: "trim", fn: Trim, params: []*vector.Vector{ + testutil.MakeVarlenaVector([][]byte{[]byte("both")}, nil, types.T_varchar.ToType(), mp), + text, binary, + }}, + } + defer tests[0].params[2].Free(mp) + defer tests[2].params[1].Free(mp) + defer tests[2].params[2].Free(mp) + defer tests[3].params[0].Free(mp) + defer tests[4].params[0].Free(mp) + defer tests[4].params[4].Free(mp) + defer tests[7].params[0].Free(mp) + defer tests[8].params[0].Free(mp) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer result.Free() + require.NoError(t, result.PreExtendAndReset(1)) + require.NoError(t, test.fn(test.params, result, proc, 1, nil)) + require.True(t, result.GetResultVector().GetIsBinaryString()) + }) + } + + leastResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer leastResult.Free() + require.NoError(t, leastResult.PreExtendAndReset(1)) + require.NoError(t, leastFn([]*vector.Vector{binary, binary}, leastResult, proc, 1, nil)) + require.True(t, leastResult.GetResultVector().GetIsBinaryString()) + + charInput := testutil.MakeInt64Vector([]int64{0xe4bda0}, nil, mp) + defer charInput.Free(mp) + charResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer charResult.Free() + require.NoError(t, charResult.PreExtendAndReset(1)) + require.NoError(t, builtInChar( + []*vector.Vector{charInput}, charResult, proc, 1, nil)) + require.True(t, charResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []byte{0xe4, 0xbd, 0xa0}, charResult.GetResultVector().GetBytesAt(0)) + + charset := testutil.MakeScalarVarchar("utf8mb4", 1, mp) + defer charset.Free(mp) + convertResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer convertResult.Free() + require.NoError(t, convertResult.PreExtendAndReset(1)) + require.NoError(t, builtInConvertUsingCharset( + []*vector.Vector{charResult.GetResultVector(), charset}, convertResult, proc, 1, nil)) + require.False(t, convertResult.GetResultVector().GetIsBinaryString()) + require.Equal(t, []byte{0xe4, 0xbd, 0xa0}, convertResult.GetResultVector().GetBytesAt(0)) +} + +func TestBinaryAuxiliaryArgumentsDoNotChangeSubjectSemantics(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + subject := testutil.MakeVarlenaVector( + [][]byte{[]byte("你x")}, nil, types.T_varchar.ToType(), mp) + auxiliary := testutil.MakeVarlenaVector( + [][]byte{[]byte("x")}, nil, types.T_varchar.ToType(), mp) + auxiliary.SetIsBinaryString(true) + defer subject.Free(mp) + defer auxiliary.Free(mp) + + instrResult := vector.NewFunctionResultWrapper(types.T_int64.ToType(), mp) + defer instrResult.Free() + require.NoError(t, instrResult.PreExtendAndReset(1)) + require.NoError(t, Instr([]*vector.Vector{subject, auxiliary}, instrResult, proc, 1, nil)) + require.Equal(t, int64(2), vector.GetFixedAtWithTypeCheck[int64](instrResult.GetResultVector(), 0)) + + locateResult := vector.NewFunctionResultWrapper(types.T_int64.ToType(), mp) + defer locateResult.Free() + require.NoError(t, locateResult.PreExtendAndReset(1)) + require.NoError(t, buildInLocate2Args( + []*vector.Vector{auxiliary, subject}, locateResult, proc, 1, nil)) + require.Equal(t, int64(2), vector.GetFixedAtWithTypeCheck[int64](locateResult.GetResultVector(), 0)) + + padLength := testutil.MakeInt64Vector([]int64{4}, nil, mp) + defer padLength.Free(mp) + padResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer padResult.Free() + require.NoError(t, padResult.PreExtendAndReset(1)) + require.NoError(t, builtInLpad( + []*vector.Vector{subject, padLength, auxiliary}, padResult, proc, 1, nil)) + require.Equal(t, "xx你x", string(padResult.GetResultVector().GetBytesAt(0))) + require.False(t, padResult.GetResultVector().GetIsBinaryString()) + + position := testutil.MakeInt64Vector([]int64{2}, nil, mp) + replaceLength := testutil.MakeInt64Vector([]int64{1}, nil, mp) + replacement := testutil.MakeVarlenaVector( + [][]byte{[]byte("z")}, nil, types.T_varchar.ToType(), mp) + replacement.SetIsBinaryString(true) + defer position.Free(mp) + defer replaceLength.Free(mp) + defer replacement.Free(mp) + insertResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer insertResult.Free() + require.NoError(t, insertResult.PreExtendAndReset(1)) + require.NoError(t, Insert([]*vector.Vector{ + subject, position, replaceLength, replacement, + }, insertResult, proc, 1, nil)) + require.Equal(t, "你z", string(insertResult.GetResultVector().GetBytesAt(0))) + require.False(t, insertResult.GetResultVector().GetIsBinaryString()) + + regexpSubject := testutil.MakeVarlenaVector( + [][]byte{[]byte("你")}, nil, types.T_varchar.ToType(), mp) + pattern := testutil.MakeVarlenaVector( + [][]byte{[]byte(".")}, nil, types.T_varchar.ToType(), mp) + pattern.SetIsBinaryString(true) + defer regexpSubject.Free(mp) + defer pattern.Free(mp) + regexpResult := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer regexpResult.Free() + require.NoError(t, regexpResult.PreExtendAndReset(1)) + require.NoError(t, newOpBuiltInRegexp().builtInRegexpSubstr( + []*vector.Vector{regexpSubject, pattern}, regexpResult, proc, 1, nil)) + require.Equal(t, []byte{'?'}, regexpResult.GetResultVector().GetBytesAt(0)) + require.False(t, regexpResult.GetResultVector().GetIsBinaryString()) +} + +func TestCoalescePreservesSelectedRowBinarySemantics(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + text := testutil.MakeVarlenaVector( + [][]byte{[]byte("你"), nil}, []uint64{1}, types.T_varchar.ToType(), mp) + fallback := testutil.MakeVarlenaVector( + [][]byte{[]byte("你"), []byte("你")}, nil, types.T_varchar.ToType(), mp) + fallback.SetIsBinaryString(true) + defer text.Free(mp) + defer fallback.Free(mp) + + coalesced := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer coalesced.Free() + require.NoError(t, coalesced.PreExtendAndReset(2)) + require.NoError(t, CoalesceStr( + []*vector.Vector{text, fallback}, coalesced, proc, 2, nil)) + require.False(t, coalesced.GetResultVector().GetIsBinaryStringAt(0)) + require.True(t, coalesced.GetResultVector().GetIsBinaryStringAt(1)) + + lengths := vector.NewFunctionResultWrapper(types.T_uint64.ToType(), mp) + defer lengths.Free() + require.NoError(t, lengths.PreExtendAndReset(2)) + require.NoError(t, LengthUTF8( + []*vector.Vector{coalesced.GetResultVector()}, lengths, proc, 2, nil)) + require.Equal(t, []uint64{1, 3}, + vector.MustFixedColWithTypeCheck[uint64](lengths.GetResultVector())) + + ones := testutil.MakeInt64Vector([]int64{1, 1}, nil, mp) + defer ones.Free(mp) + left := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer left.Free() + require.NoError(t, left.PreExtendAndReset(2)) + require.NoError(t, Left( + []*vector.Vector{coalesced.GetResultVector(), ones}, left, proc, 2, nil)) + require.Equal(t, [][]byte{[]byte("你"), {0xe4}}, + vector.InefficientMustBytesCol(left.GetResultVector())) + require.False(t, left.GetResultVector().GetIsBinaryStringAt(0)) + require.True(t, left.GetResultVector().GetIsBinaryStringAt(1)) +} + // Test_ConcatWs tests CONCAT_WS function (concat with separator) // This is a complex function with conditional logic for NULL handling func Test_ConcatWs(t *testing.T) { @@ -894,15 +1292,12 @@ func Test_BuiltInCharCheck(t *testing.T) { require.Equal(t, succeedMatched, got.status) } - // other string types (char/text/blob): cast to varchar + // other string types retain their metadata { got := builtInCharCheck(nil, []types.Type{ types.T_char.ToType(), types.T_text.ToType(), types.T_blob.ToType(), }) - require.Equal(t, succeedWithCast, got.status) - for _, ft := range got.finalType { - require.Equal(t, types.T_varchar, ft.Oid) - } + require.Equal(t, succeedMatched, got.status) } // numeric types (float/decimal): cast to int64 diff --git a/pkg/sql/plan/function/func_unary.go b/pkg/sql/plan/function/func_unary.go index f9c59f806cdbf..cb7345331fac7 100644 --- a/pkg/sql/plan/function/func_unary.go +++ b/pkg/sql/plan/function/func_unary.go @@ -466,6 +466,38 @@ func OrdString(val []byte) int64 { } func Ord(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) (err error) { + if ivecs[0].HasBinaryStringRows() { + p := vector.GenerateFunctionStrParameter(ivecs[0]) + rs := vector.MustFunctionResult[int64](result) + for row := 0; row < length; row++ { + value, isNull := p.GetStrValue(uint64(row)) + if isNull || selectList != nil && selectList.Contains(uint64(row)) { + if err := rs.Append(0, true); err != nil { + return err + } + continue + } + ord := OrdString(value) + if ivecs[0].GetIsBinaryStringAt(row) { + ord = 0 + if len(value) > 0 { + ord = int64(value[0]) + } + } + if err := rs.Append(ord, false); err != nil { + return err + } + } + return nil + } + if isBinaryStringVector(ivecs[0]) { + return opUnaryBytesToFixed[int64](ivecs, result, proc, length, func(v []byte) int64 { + if len(v) == 0 { + return 0 + } + return int64(v[0]) + }, selectList) + } return opUnaryBytesToFixed[int64](ivecs, result, proc, length, func(v []byte) int64 { return OrdString(v) }, selectList) @@ -6057,6 +6089,27 @@ func strLength(xs string) int64 { } func LengthUTF8(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + if isBinaryStringVector(ivecs[0]) { + p := vector.GenerateFunctionStrParameter(ivecs[0]) + rs := vector.MustFunctionResult[uint64](result) + for row := 0; row < length; row++ { + value, isNull := p.GetStrValue(uint64(row)) + if isNull || selectList != nil && selectList.Contains(uint64(row)) { + if err := rs.Append(0, true); err != nil { + return err + } + continue + } + valueLength := strLengthUTF8(value) + if ivecs[0].GetIsBinaryStringAt(row) { + valueLength = strLengthBinary(value) + } + if err := rs.Append(valueLength, false); err != nil { + return err + } + } + return nil + } return opUnaryBytesToFixed[uint64](ivecs, result, proc, length, strLengthUTF8, selectList) } @@ -6095,6 +6148,32 @@ func rtrim(xs string) string { } func Reverse(ivecs []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + if ivecs[0].HasBinaryStringRows() { + return opUnaryBytesToBytesByBinaryRow(ivecs, result, length, + func(v []byte) []byte { + out := bytes.Clone(v) + for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 { + out[i], out[j] = out[j], out[i] + } + return out + }, + func(v []byte) []byte { return []byte(reverse(functionUtil.QuickBytesToStr(v))) }, + selectList, + ) + } + if isBinaryStringVector(ivecs[0]) { + err := opUnaryBytesToBytes(ivecs, result, proc, length, func(v []byte) []byte { + out := bytes.Clone(v) + for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 { + out[i], out[j] = out[j], out[i] + } + return out + }, selectList) + if err == nil { + result.GetResultVector().SetIsBinaryString(true) + } + return err + } return opUnaryStrToStr(ivecs, result, proc, length, reverse, selectList) } diff --git a/pkg/sql/plan/function/func_unary_test.go b/pkg/sql/plan/function/func_unary_test.go index ae988601f5d45..e854786392f45 100644 --- a/pkg/sql/plan/function/func_unary_test.go +++ b/pkg/sql/plan/function/func_unary_test.go @@ -4899,6 +4899,32 @@ func TestLengthUTF8(t *testing.T) { } } +func TestLengthUTF8SeparatesBinaryStringFromLiteralNumericMetadata(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + input := testutil.MakeVarlenaVector( + [][]byte{[]byte("你好"), {}, {0xff, 0xfe, 0xfd}}, + nil, + types.T_varchar.ToType(), + mp, + ) + defer input.Free(mp) + input.SetIsBinaryString(true) + + result := vector.NewFunctionResultWrapper(types.T_uint64.ToType(), mp) + defer result.Free() + require.NoError(t, result.PreExtendAndReset(input.Length())) + + require.NoError(t, LengthUTF8([]*vector.Vector{input}, result, proc, input.Length(), nil)) + require.Equal(t, []uint64{6, 0, 3}, vector.MustFixedColNoTypeCheck[uint64](result.GetResultVector())) + + input.SetIsBinaryString(false) + input.SetIsBin(true) + require.NoError(t, result.PreExtendAndReset(input.Length())) + require.NoError(t, LengthUTF8([]*vector.Vector{input}, result, proc, input.Length(), nil)) + require.Equal(t, []uint64{2, 0, 3}, vector.MustFixedColNoTypeCheck[uint64](result.GetResultVector())) +} + func TestLengthBinary(t *testing.T) { proc := testutil.NewProcess(t) for _, typ := range []types.Type{ diff --git a/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index 3621e30a4ca3d..9761b3c8beab2 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -16,6 +16,7 @@ package function import ( "fmt" + "unicode/utf8" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/container/types" @@ -27,6 +28,50 @@ import ( "github.com/matrixorigin/matrixone/pkg/vm/process" ) +func binaryStringTransformReturnType(parameters []types.Type) types.Type { + if len(parameters) == 0 { + return types.T_varbinary.ToType() + } + ret := parameters[0] + switch ret.Oid { + case types.T_binary: + ret.Oid = types.T_varbinary + case types.T_blob: + return types.T_blob.ToType() + } + return ret +} + +func concatReturnType(parameters []types.Type) types.Type { + hasBinary := false + unbounded := false + width := int64(0) + for _, parameter := range parameters { + switch parameter.Oid { + case types.T_binary, types.T_varbinary: + hasBinary = true + width += int64(parameter.Width) + case types.T_blob: + hasBinary = true + unbounded = true + case types.T_text: + unbounded = true + case types.T_char, types.T_varchar: + width += int64(parameter.Width) * utf8.UTFMax + } + } + if !hasBinary { + return types.T_varchar.ToType() + } + if unbounded { + return types.T_blob.ToType() + } + if width > int64(types.MaxVarBinaryLen) { + width = int64(types.MaxVarBinaryLen) + } + return types.New(types.T_varbinary, int32(width), 0) +} + func jsonConstructorSupportsType(oid types.T) bool { switch oid { case types.T_any, @@ -295,14 +340,7 @@ var supportedStringBuiltIns = []FuncNew{ Overloads: []overload{ { overloadId: 0, - retType: func(parameters []types.Type) types.Type { - for _, p := range parameters { - if p.Oid == types.T_binary || p.Oid == types.T_varbinary || p.Oid == types.T_blob { - return types.T_blob.ToType() - } - } - return types.T_varchar.ToType() - }, + retType: concatReturnType, newOp: func() executeLogicOfOverload { return builtInConcat }, @@ -2814,6 +2852,26 @@ var supportedStringBuiltIns = []FuncNew{ return builtInRepeat }, }, + { + overloadId: 1, + args: []types.T{types.T_binary, types.T_int64}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return builtInRepeat }, + }, + { + overloadId: 2, + args: []types.T{types.T_varbinary, types.T_int64}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return builtInRepeat }, + }, + { + overloadId: 3, + args: []types.T{types.T_blob, types.T_int64}, + retType: func(parameters []types.Type) types.Type { + return types.T_blob.ToType() + }, + newOp: func() executeLogicOfOverload { return builtInRepeat }, + }, }, }, @@ -3740,6 +3798,46 @@ var supportedStringBuiltIns = []FuncNew{ return SubStringWith2Args }, }, + { + overloadId: 7, + args: []types.T{types.T_binary, types.T_int64}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return SubStringWith2Args }, + }, + { + overloadId: 8, + args: []types.T{types.T_varbinary, types.T_int64}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return SubStringWith2Args }, + }, + { + overloadId: 9, + args: []types.T{types.T_binary, types.T_int64, types.T_int64}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return SubStringWith3Args }, + }, + { + overloadId: 10, + args: []types.T{types.T_varbinary, types.T_int64, types.T_int64}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return SubStringWith3Args }, + }, + { + overloadId: 11, + args: []types.T{types.T_blob, types.T_int64}, + retType: func(parameters []types.Type) types.Type { + return types.T_blob.ToType() + }, + newOp: func() executeLogicOfOverload { return SubStringWith2Args }, + }, + { + overloadId: 12, + args: []types.T{types.T_blob, types.T_int64, types.T_int64}, + retType: func(parameters []types.Type) types.Type { + return types.T_blob.ToType() + }, + newOp: func() executeLogicOfOverload { return SubStringWith3Args }, + }, }, }, @@ -4047,6 +4145,24 @@ var supportedStringBuiltIns = []FuncNew{ return builtInToLower }, }, + { + overloadId: 1, + args: []types.T{types.T_binary}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return builtInToLower }, + }, + { + overloadId: 2, + args: []types.T{types.T_varbinary}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return builtInToLower }, + }, + { + overloadId: 3, + args: []types.T{types.T_blob}, + retType: func(parameters []types.Type) types.Type { return parameters[0] }, + newOp: func() executeLogicOfOverload { return builtInToLower }, + }, }, }, @@ -4068,6 +4184,24 @@ var supportedStringBuiltIns = []FuncNew{ return builtInToUpper }, }, + { + overloadId: 1, + args: []types.T{types.T_binary}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return builtInToUpper }, + }, + { + overloadId: 2, + args: []types.T{types.T_varbinary}, + retType: binaryStringTransformReturnType, + newOp: func() executeLogicOfOverload { return builtInToUpper }, + }, + { + overloadId: 3, + args: []types.T{types.T_blob}, + retType: func(parameters []types.Type) types.Type { return parameters[0] }, + newOp: func() executeLogicOfOverload { return builtInToUpper }, + }, }, }, @@ -11249,8 +11383,6 @@ func makeTimeReturnType(parameters []types.Type) types.Type { } func isMakeTimeTextType(oid types.T) bool { - // Binary inputs must take the numeric cast path so hex/bit literal byte - // semantics are consumed before function-expression evaluation clears IsBin. switch oid { case types.T_binary, types.T_varbinary, types.T_blob: return false diff --git a/pkg/sql/plan/function/list_operator.go b/pkg/sql/plan/function/list_operator.go index 99452cc08eba1..716ba0e62de2c 100644 --- a/pkg/sql/plan/function/list_operator.go +++ b/pkg/sql/plan/function/list_operator.go @@ -2764,6 +2764,22 @@ var supportedOperators = []FuncNew{ return CoalesceStr }, }, + { + overloadId: 29, + args: []types.T{types.T_binary}, + retType: func(parameters []types.Type) types.Type { + return parameters[0] + }, + newOp: func() executeLogicOfOverload { return CoalesceStr }, + }, + { + overloadId: 30, + args: []types.T{types.T_varbinary}, + retType: func(parameters []types.Type) types.Type { + return parameters[0] + }, + newOp: func() executeLogicOfOverload { return CoalesceStr }, + }, }, }, diff --git a/pkg/sql/plan/function/operatorSet.go b/pkg/sql/plan/function/operatorSet.go index be9f1f8687402..1e9066cb0e522 100644 --- a/pkg/sql/plan/function/operatorSet.go +++ b/pkg/sql/plan/function/operatorSet.go @@ -94,6 +94,7 @@ func signedUnsignedIntegerCommonType(source []types.Type) (types.Type, bool) { func binaryStringCommonType(source []types.Type) (types.Type, bool) { hasBinary := false + hasBlob := false sameFixedBinary := true hasFixedBinary := false fixedBinaryWidth := int32(0) @@ -122,6 +123,10 @@ func binaryStringCommonType(source []types.Type) (types.Type, bool) { if typ.Width > width { width = typ.Width } + case types.T_blob: + hasBinary = true + hasBlob = true + sameFixedBinary = false case types.T_char, types.T_varchar: sameFixedBinary = false // Character widths count runes, while VARBINARY widths count bytes. @@ -134,6 +139,13 @@ func binaryStringCommonType(source []types.Type) (types.Type, bool) { if byteWidth > width { width = byteWidth } + case types.T_text: + sameFixedBinary = false + // TEXT has no useful bounded Width. Keep the binary result wide + // enough for any value that a VARBINARY result can represent. + if int32(types.MaxVarBinaryLen) > width { + width = int32(types.MaxVarBinaryLen) + } default: return types.Type{}, false } @@ -141,6 +153,9 @@ func binaryStringCommonType(source []types.Type) (types.Type, bool) { if !hasBinary { return types.Type{}, false } + if hasBlob { + return types.T_blob.ToType(), true + } if sameFixedBinary { return types.New(types.T_binary, fixedBinaryWidth, 0), true } @@ -506,6 +521,7 @@ func strCaseFn(vecs []*vector.Vector, result vector.FunctionResultWrapper, _ *pr if err := rs.AppendBytes(ys[j].GetStrValue(i)); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), vecs[2*j+1].GetIsBinaryStringAt(int(i))) matchElse = false break } @@ -514,6 +530,7 @@ func strCaseFn(vecs []*vector.Vector, result vector.FunctionResultWrapper, _ *pr if err := rs.AppendBytes(z.GetStrValue(i)); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), vecs[len(vecs)-1].GetIsBinaryStringAt(int(i))) } } } else { @@ -524,6 +541,7 @@ func strCaseFn(vecs []*vector.Vector, result vector.FunctionResultWrapper, _ *pr if err := rs.AppendBytes(ys[j].GetStrValue(i)); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), vecs[2*j+1].GetIsBinaryStringAt(int(i))) matchElse = false break } @@ -837,10 +855,12 @@ func strIffFn(vecs []*vector.Vector, result vector.FunctionResultWrapper, proc * if err := rs.AppendBytes(p2.GetStrValue(i)); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), vecs[1].GetIsBinaryStringAt(int(i))) } else { if err := rs.AppendBytes(p3.GetStrValue(i)); err != nil { return err } + result.GetResultVector().SetIsBinaryStringAt(int(i), vecs[2].GetIsBinaryStringAt(int(i))) } } return nil diff --git a/pkg/sql/plan/function/operatorSet_test.go b/pkg/sql/plan/function/operatorSet_test.go index a06503a85dc84..d83ce4c2bf05a 100644 --- a/pkg/sql/plan/function/operatorSet_test.go +++ b/pkg/sql/plan/function/operatorSet_test.go @@ -1018,6 +1018,25 @@ func TestBinaryStringCommonTypePreservesSameFixedBinary(t *testing.T) { wantOid: types.T_varbinary, wantWidth: 4, }, + { + name: "blob dominates character string", + source: []types.Type{ + types.T_blob.ToType(), + types.New(types.T_varchar, 4, 0), + }, + wantOK: true, + wantOid: types.T_blob, + }, + { + name: "text uses maximum varbinary width", + source: []types.Type{ + types.New(types.T_varbinary, 3, 0), + types.T_text.ToType(), + }, + wantOK: true, + wantOid: types.T_varbinary, + wantWidth: int32(types.MaxVarBinaryLen), + }, { name: "only null is not binary", source: []types.Type{types.T_any.ToType()}, @@ -1363,6 +1382,43 @@ func Test_CoalesceCheck_TextStringBranchesStayText(t *testing.T) { } } +func Test_CoalesceCheck_BinaryStringDominatesCharacterString(t *testing.T) { + overloads := []overload{ + {args: []types.T{types.T_varchar}}, + {args: []types.T{types.T_varbinary}}, + } + inputs := []types.Type{ + types.New(types.T_varbinary, 3, 0), + types.New(types.T_varchar, 2, 0), + } + result := coalesceCheck(overloads, inputs) + require.Equal(t, succeedWithCast, result.status) + require.Equal(t, 1, result.idx) + require.Len(t, result.finalType, len(inputs)) + for _, typ := range result.finalType { + require.Equal(t, types.T_varbinary, typ.Oid) + require.Equal(t, int32(8), typ.Width) + } +} + +func Test_CoalesceCheck_BlobDominatesCharacterString(t *testing.T) { + overloads := []overload{ + {args: []types.T{types.T_varchar}}, + {args: []types.T{types.T_blob}}, + } + inputs := []types.Type{ + types.T_blob.ToType(), + types.New(types.T_varchar, 2, 0), + } + result := coalesceCheck(overloads, inputs) + require.Equal(t, succeedWithCast, result.status) + require.Equal(t, 1, result.idx) + require.Len(t, result.finalType, len(inputs)) + for _, typ := range result.finalType { + require.Equal(t, types.T_blob, typ.Oid) + } +} + // issue #24565: COALESCE over decimal branches with different scales must align // scale/width across all branches, otherwise the result inherits the first // branch's scale while carrying another branch's raw value (magnified result). @@ -1494,6 +1550,29 @@ func Test_CaseFn_VarBinaryExecution(t *testing.T) { require.True(t, succeed, tc.info, info) } +func TestStrCaseFnPreservesDynamicBinarySemantics(t *testing.T) { + proc := testutil.NewProcess(t) + mp := proc.Mp() + condition := newVectorByType(mp, types.T_bool.ToType(), []bool{true, false}, nil) + binaryBranch := newVectorByType(mp, types.T_varchar.ToType(), []string{"a", "a"}, nil) + binaryBranch.SetIsBinaryString(true) + textBranch := newVectorByType(mp, types.T_varchar.ToType(), []string{"bc", "bc"}, nil) + defer condition.Free(mp) + defer binaryBranch.Free(mp) + defer textBranch.Free(mp) + + result := vector.NewFunctionResultWrapper(types.T_varchar.ToType(), mp) + defer result.Free() + require.NoError(t, result.PreExtendAndReset(2)) + require.NoError(t, strCaseFn( + []*vector.Vector{condition, binaryBranch, textBranch}, result, proc, 2, nil)) + require.False(t, result.GetResultVector().GetIsBin()) + require.True(t, result.GetResultVector().GetIsBinaryString()) + require.True(t, result.GetResultVector().GetIsBinaryStringAt(0)) + require.False(t, result.GetResultVector().GetIsBinaryStringAt(1)) + require.Equal(t, []string{"a", "bc"}, vector.InefficientMustStrCol(result.GetResultVector())) +} + func Test_IffFn_Decimal256Execution(t *testing.T) { proc := testutil.NewProcess(t) d1, err := types.ParseDecimal256("123456789012345678901234567890123456789", 76, 0) diff --git a/pkg/sql/plan/mock.go b/pkg/sql/plan/mock.go index 8d5464da34cd2..1c3d73cabd5ab 100644 --- a/pkg/sql/plan/mock.go +++ b/pkg/sql/plan/mock.go @@ -51,14 +51,15 @@ type MockCompilerContext struct { ctx context.Context // Add function fields for test overrides - GetAccountNameFunc func() string - GetAccountIdFunc func() (uint32, error) - DatabaseExistsFunc func(string, *Snapshot) bool - GetDatabaseIdFunc func(string, *Snapshot) (uint64, error) - ResolveAccountIdsFunc func([]string) ([]uint32, error) - ResolveFunc func(string, string, *Snapshot) (*ObjectRef, *TableDef) - ResolveVariableFunc func(string, bool, bool) (interface{}, error) - GetProcessFunc func() *process.Process + GetAccountNameFunc func() string + GetAccountIdFunc func() (uint32, error) + DatabaseExistsFunc func(string, *Snapshot) bool + GetDatabaseIdFunc func(string, *Snapshot) (uint64, error) + ResolveAccountIdsFunc func([]string) ([]uint32, error) + ResolveFunc func(string, string, *Snapshot) (*ObjectRef, *TableDef) + ResolveVariableFunc func(string, bool, bool) (interface{}, error) + ResolveVariableBinaryStringFunc func(string, bool, bool) (bool, error) + GetProcessFunc func() *process.Process } func (m *MockCompilerContext) GetLowerCaseTableNames() int64 { @@ -145,6 +146,13 @@ func (m *MockCompilerContext) ResolveVariable(varName string, isSystemVar, isGlo return nil, moerr.NewInternalError(m.ctx, "var not found") } +func (m *MockCompilerContext) ResolveVariableBinaryString(varName string, isSystemVar, isGlobalVar bool) (bool, error) { + if m.ResolveVariableBinaryStringFunc != nil { + return m.ResolveVariableBinaryStringFunc(varName, isSystemVar, isGlobalVar) + } + return false, nil +} + type col struct { Name string Id types.T diff --git a/pkg/sql/plan/query_builder.go b/pkg/sql/plan/query_builder.go index 1a61fc54aac56..2ef8ceb80e690 100644 --- a/pkg/sql/plan/query_builder.go +++ b/pkg/sql/plan/query_builder.go @@ -3501,7 +3501,11 @@ func (builder *QueryBuilder) buildUnionWithResultLen( } for i, expr := range subCtx.results { - projectTypList[i][idx] = makeTypeByPlan2Expr(expr) + if binaryType, ok := ctasBinaryStringType(builder.compCtx, expr); ok { + projectTypList[i][idx] = binaryType + } else { + projectTypList[i][idx] = makeTypeByPlan2Expr(expr) + } } subCtxList[idx] = subCtx nodes[idx] = nodeID @@ -3535,6 +3539,17 @@ func (builder *QueryBuilder) buildUnionWithResultLen( } if len(tmpArgsType) > 0 { + setBinaryString := false + setBinaryType := types.Type{} + for _, typ := range argsType { + switch typ.Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + setBinaryString = true + if typ.Oid == types.T_blob || setBinaryType.Oid == 0 { + setBinaryType = typ + } + } + } fGet, err := function.GetFunctionByName(builder.GetContext(), "coalesce", tmpArgsType) if err != nil { return 0, moerr.NewParseErrorf(builder.GetContext(), "the %d column cann't cast to a same type", columnIdx) @@ -3561,6 +3576,15 @@ func (builder *QueryBuilder) buildUnionWithResultLen( } else { targetArgType = argsCastType[0] } + if setBinaryString { + // Preserve the common expression width chosen above. A binary + // branch changes the set column's string category, not the width + // required to hold values from every branch. + targetArgType.Oid = setBinaryType.Oid + if setBinaryType.Oid == types.T_binary { + targetArgType.Oid = types.T_varbinary + } + } preserveGroupingBinary := distinct && groupingOrderResolve != nil && (tmpArgsType[0].Oid == types.T_binary || tmpArgsType[0].Oid == types.T_varbinary) @@ -3577,8 +3601,6 @@ func (builder *QueryBuilder) buildUnionWithResultLen( targetArgType.Width = typ.Width } } - } else if targetArgType.Oid == types.T_binary || targetArgType.Oid == types.T_varbinary { - targetArgType = types.T_blob.ToType() } targetType = makePlan2Type(&targetArgType) @@ -8289,6 +8311,9 @@ func (builder *QueryBuilder) appendProjectionNode( } ctx.projects[i] = proj } + if err = materializeBinaryLiteralProjects(builder.GetContext(), ctx.projects); err != nil { + return + } nodeID = builder.appendNode(&plan.Node{ NodeType: plan.Node_PROJECT, @@ -8314,6 +8339,9 @@ func (builder *QueryBuilder) appendGroupingSetDistinctProjectionNode( } ctx.projects[i] = proj } + if err = materializeBinaryLiteralProjects(builder.GetContext(), ctx.projects); err != nil { + return + } originalProjects := ctx.projects materializedTag := builder.genNewBindTag() @@ -8351,6 +8379,19 @@ func (builder *QueryBuilder) appendGroupingSetDistinctProjectionNode( return } +func materializeBinaryLiteralProjects(ctx context.Context, projects []*plan.Expr) error { + for i, project := range projects { + if binaryType, ok := binaryLiteralStringType(project); ok { + cast, err := appendCastBeforeExpr(ctx, project, makePlan2Type(&binaryType)) + if err != nil { + return err + } + projects[i] = cast + } + } + return nil +} + func (builder *QueryBuilder) appendDistinctNode(ctx *BindContext, nodeID int32) int32 { return builder.appendNode(&plan.Node{ NodeType: plan.Node_DISTINCT, diff --git a/pkg/sql/plan/query_builder_test.go b/pkg/sql/plan/query_builder_test.go index 9da338364f857..46752fdf32409 100644 --- a/pkg/sql/plan/query_builder_test.go +++ b/pkg/sql/plan/query_builder_test.go @@ -4824,6 +4824,23 @@ func TestBaseBinder_bindRangeCond(t *testing.T) { } } +func TestBaseBinderBindVarbinaryUnhexRange(t *testing.T) { + builder, bindCtx := genBuilderAndCtxWithColumnType(types.T_varbinary, "b") + whereBinder := NewWhereBinder(builder, bindCtx) + stmts, err := parsers.Parse(context.TODO(), dialect.MYSQL, + "select * from bind_select where b between unhex('41') and unhex('41')", 1) + require.NoError(t, err) + rangeCond := stmts[0].(*tree.Select).Select.(*tree.SelectClause).Where.Expr.(*tree.RangeCond) + expr, err := whereBinder.bindRangeCond(rangeCond, 0, false) + require.NoError(t, err) + require.Equal(t, "between", expr.GetF().Func.ObjName) + for _, bound := range expr.GetF().Args[1:] { + require.Equal(t, int32(types.T_varbinary), bound.Typ.Id) + require.NotNil(t, bound.GetLit()) + require.False(t, bound.GetLit().IsBin) + } +} + // TestBaseBinder_baseBindExpr tests baseBindExpr with various expression types func TestBaseBinder_baseBindExpr(t *testing.T) { builder, bindCtx := genBuilderAndCtx() diff --git a/pkg/sql/plan/rule/constant_fold.go b/pkg/sql/plan/rule/constant_fold.go index dc42fa0173ba3..c38f038a855c0 100644 --- a/pkg/sql/plan/rule/constant_fold.go +++ b/pkg/sql/plan/rule/constant_fold.go @@ -211,12 +211,22 @@ func (r *ConstantFold) constantFold(expr *plan.Expr, proc *process.Process) *pla if IsDivisionByZeroConstant(fn) { return expr } + if function.ExpressionContainsRuntimeBinaryString(expr) { + return expr + } vec, free, err := colexec.GetReadonlyResultFromExpression(proc, expr, []*batch.Batch{r.bat}) if err != nil { return expr } defer free() + // binaryString is runtime string metadata and cannot be represented by + // Literal.IsBin without also changing raw hex/bit numeric semantics. + resultType := types.T(expr.Typ.Id) + staticBinaryResult := resultType == types.T_binary || resultType == types.T_varbinary || resultType == types.T_blob + if vec.GetIsBinaryString() && !staticBinaryResult { + return expr + } if isVec { data, err := vec.MarshalBinary() diff --git a/pkg/sql/plan/utils.go b/pkg/sql/plan/utils.go index 1117f507b9cd8..c0a478763d553 100644 --- a/pkg/sql/plan/utils.go +++ b/pkg/sql/plan/utils.go @@ -1525,12 +1525,24 @@ func ConstantFold(bat *batch.Batch, expr *plan.Expr, proc *process.Process, varA if rule.IsDivisionByZeroConstant(fn) { return expr, nil } + if function.ExpressionContainsRuntimeBinaryString(expr) { + return expr, nil + } vec, free, err := colexec.GetReadonlyResultFromExpression(proc, expr, []*batch.Batch{bat}) if err != nil { return nil, err } defer free() + // A static binary result type already preserves byte-string semantics in the + // plan, so it is safe to fold. Dynamic binaryString metadata on a textual + // result still cannot be represented by Literal.IsBin without also changing + // raw hex/bit numeric semantics. + resultType := types.T(expr.Typ.Id) + staticBinaryResult := resultType == types.T_binary || resultType == types.T_varbinary || resultType == types.T_blob + if vec.GetIsBinaryString() && !staticBinaryResult { + return expr, nil + } if isVec { data, err := vec.MarshalBinary() @@ -3145,17 +3157,20 @@ func FillValuesOfParamsInPlan(ctx context.Context, preparePlan *Plan, paramVals } type ParamValue struct { - Value any - IsBin bool + Value any + IsBin bool + BinaryString bool } func replaceParamVals(ctx context.Context, plan0 *Plan, paramVals []any) error { params := make([]*Expr, len(paramVals)) for i, val := range paramVals { isBin := false + binaryString := false if param, ok := val.(ParamValue); ok { val = param.Value isBin = param.IsBin + binaryString = param.BinaryString } if val == nil { pc := &plan.Literal{ @@ -3175,6 +3190,14 @@ func replaceParamVals(ctx context.Context, plan0 *Plan, paramVals []any) error { Lit: pc, }, } + if binaryString { + binaryType := types.New( + types.T_varbinary, + int32(len(fmt.Sprintf("%v", val))), + 0, + ) + params[i].Typ = makePlan2Type(&binaryType) + } } } paramRule := NewResetParamRefRule(ctx, params) diff --git a/pkg/sql/plan/utils_test.go b/pkg/sql/plan/utils_test.go index c4a4841e7b841..04dfed2e6beb0 100644 --- a/pkg/sql/plan/utils_test.go +++ b/pkg/sql/plan/utils_test.go @@ -23,11 +23,14 @@ import ( "path/filepath" "testing" + "github.com/matrixorigin/matrixone/pkg/container/batch" "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/fileservice" "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + "github.com/matrixorigin/matrixone/pkg/sql/plan/function" "github.com/matrixorigin/matrixone/pkg/stage" + "github.com/matrixorigin/matrixone/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1639,5 +1642,43 @@ func TestInitInfileOrStageParam_NonStageFallsThrough(t *testing.T) { assert.Equal(t, "parquet", param.Format) } +func TestConstantFoldKeepsBinaryStringMetadataProducingFunction(t *testing.T) { + proc := testutil.NewProcess(t) + f, err := function.GetFunctionByName(context.Background(), "char", + []types.Type{types.T_int64.ToType(), types.T_int64.ToType(), types.T_int64.ToType()}) + require.NoError(t, err) + args := make([]*plan.Expr, 3) + for i, value := range []int64{228, 189, 160} { + args[i] = &plan.Expr{ + Typ: plan.Type{Id: int32(types.T_int64)}, + Expr: &plan.Expr_Lit{Lit: &plan.Literal{ + Value: &plan.Literal_I64Val{I64Val: value}, + }}, + } + } + expr := &plan.Expr{ + Typ: plan.Type{Id: int32(types.T_varchar)}, + Expr: &plan.Expr_F{F: &plan.Function{ + Func: &plan.ObjectRef{Obj: f.GetEncodedOverloadID(), ObjName: "char"}, + Args: args, + }}, + } + lengthFn, err := function.GetFunctionByName(context.Background(), "char_length", + []types.Type{types.T_varchar.ToType()}) + require.NoError(t, err) + expr = &plan.Expr{ + Typ: plan.Type{Id: int32(types.T_uint64)}, + Expr: &plan.Expr_F{F: &plan.Function{ + Func: &plan.ObjectRef{Obj: lengthFn.GetEncodedOverloadID(), ObjName: "char_length"}, + Args: []*plan.Expr{expr}, + }}, + } + + folded, err := ConstantFold(batch.EmptyForConstFoldBatch, expr, proc, false, true) + require.NoError(t, err) + require.NotNil(t, folded.GetF()) + require.NotNil(t, folded.GetF().Args[0].GetF()) +} + // Avoid unused import warning when some branches of types are not directly referenced. var _ = types.T_int32 diff --git a/pkg/sql/plan/visit_plan_rule.go b/pkg/sql/plan/visit_plan_rule.go index 68141afba0e8e..ecb297de062ce 100644 --- a/pkg/sql/plan/visit_plan_rule.go +++ b/pkg/sql/plan/visit_plan_rule.go @@ -471,10 +471,12 @@ func (rule *ResetParamRefRule) applyExpr(e *plan.Expr) (*plan.Expr, error) { if int(exprImpl.P.Pos) >= len(rule.params) { return nil, moerr.NewInternalErrorf(context.TODO(), "get prepare params error, index %d not exists", int(exprImpl.P.Pos)) } - return &plan.Expr{ - Typ: e.Typ, - Expr: rule.params[int(exprImpl.P.Pos)].Expr, - }, nil + param := rule.params[int(exprImpl.P.Pos)] + typ := e.Typ + if param.Typ.Id != 0 { + typ = param.Typ + } + return &plan.Expr{Typ: typ, Expr: param.Expr}, nil case *plan.Expr_List: for i, arg := range exprImpl.List.List { exprImpl.List.List[i], err = rule.ApplyExpr(arg) diff --git a/pkg/tests/issues/issue_25295_test.go b/pkg/tests/issues/issue_25295_test.go new file mode 100644 index 0000000000000..d5f7f1562bf8c --- /dev/null +++ b/pkg/tests/issues/issue_25295_test.go @@ -0,0 +1,95 @@ +// Copyright 2021 - 2026 Matrix Origin +// +// 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 issues + +import ( + "context" + "database/sql" + "fmt" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + + "github.com/matrixorigin/matrixone/pkg/embed" +) + +func TestIssue25295BinaryProtocolParameterKinds(t *testing.T) { + embed.RunBaseClusterTests(t, func(c embed.Cluster) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + cn, err := c.GetCNService(0) + require.NoError(t, err) + port := cn.GetServiceConfig().CN.Frontend.Port + db, err := sql.Open("mysql", fmt.Sprintf( + "dump:111@tcp(127.0.0.1:%d)/?interpolateParams=false", port)) + require.NoError(t, err) + defer db.Close() + + conn, err := db.Conn(ctx) + require.NoError(t, err) + defer conn.Close() + lengthStmt, err := conn.PrepareContext(ctx, "select char_length(?)") + require.NoError(t, err) + defer lengthStmt.Close() + + lengthTests := []struct { + name string + value any + wantLength sql.NullInt64 + }{ + // go-sql-driver encodes both []byte and string as MYSQL_TYPE_STRING, + // so the server must apply the same text semantics to both. + {name: "byte slice utf8", value: []byte("你好"), wantLength: sql.NullInt64{Int64: 2, Valid: true}}, + {name: "text utf8", value: "你好", wantLength: sql.NullInt64{Int64: 2, Valid: true}}, + {name: "invalid utf8 bytes", value: []byte{0xff, 0xfe}, wantLength: sql.NullInt64{Int64: 2, Valid: true}}, + {name: "empty bytes", value: []byte{}, wantLength: sql.NullInt64{Valid: true}}, + {name: "numeric bytes", value: []byte("1"), wantLength: sql.NullInt64{Int64: 1, Valid: true}}, + {name: "numeric text", value: "1", wantLength: sql.NullInt64{Int64: 1, Valid: true}}, + {name: "null", value: nil, wantLength: sql.NullInt64{}}, + } + + for _, test := range lengthTests { + t.Run(test.name, func(t *testing.T) { + var gotLength sql.NullInt64 + err := lengthStmt.QueryRowContext(ctx, test.value).Scan(&gotLength) + require.NoError(t, err) + require.Equal(t, test.wantLength, gotLength) + }) + } + + numericStmt, err := conn.PrepareContext(ctx, "select ? + 0") + require.NoError(t, err) + defer numericStmt.Close() + for _, test := range []struct { + name string + value any + wantNumber sql.NullInt64 + }{ + {name: "numeric bytes", value: []byte("1"), wantNumber: sql.NullInt64{Int64: 1, Valid: true}}, + {name: "numeric text", value: "1", wantNumber: sql.NullInt64{Int64: 1, Valid: true}}, + {name: "null", value: nil, wantNumber: sql.NullInt64{}}, + } { + t.Run("numeric "+test.name, func(t *testing.T) { + var gotNumber sql.NullInt64 + err := numericStmt.QueryRowContext(ctx, test.value).Scan(&gotNumber) + require.NoError(t, err) + require.Equal(t, test.wantNumber, gotNumber) + }) + } + }) +} diff --git a/pkg/util/executor/options.go b/pkg/util/executor/options.go index b8751041a725a..068d58d57a8c4 100644 --- a/pkg/util/executor/options.go +++ b/pkg/util/executor/options.go @@ -329,6 +329,24 @@ func (opts Options) ResolveVariableFunc() func(varName string, isSystemVar, isGl return opts.resolveVariableFunc } +func (opts Options) WithResolveVariableIsBinFunc(fn func(varName string, isSystemVar, isGlobalVar bool) (bool, error)) Options { + opts.resolveVariableIsBinFunc = fn + return opts +} + +func (opts Options) ResolveVariableIsBinFunc() func(varName string, isSystemVar, isGlobalVar bool) (bool, error) { + return opts.resolveVariableIsBinFunc +} + +func (opts Options) WithResolveVariableBinaryStringFunc(fn func(varName string, isSystemVar, isGlobalVar bool) (bool, error)) Options { + opts.resolveVariableBinaryStringFunc = fn + return opts +} + +func (opts Options) ResolveVariableBinaryStringFunc() func(varName string, isSystemVar, isGlobalVar bool) (bool, error) { + return opts.resolveVariableBinaryStringFunc +} + // WithFrontend marks the SQL execution as a frontend session-bound // invocation (b=true) versus a background / internal one (b=false). // Consumed by pkg/sql/compile/sql_executor.go's NewTopProcess which diff --git a/pkg/util/executor/options_test.go b/pkg/util/executor/options_test.go index a5bcd30b5b691..5b91fe0d9d7ca 100644 --- a/pkg/util/executor/options_test.go +++ b/pkg/util/executor/options_test.go @@ -49,6 +49,21 @@ func TestOptionsLockWaitTimeout(t *testing.T) { require.Len(t, opts.ExtraTxnOptions(), 2) } +func TestOptionsPreserveVariableMetadataResolvers(t *testing.T) { + isBin := func(string, bool, bool) (bool, error) { return true, nil } + binaryString := func(string, bool, bool) (bool, error) { return true, nil } + opts := Options{}. + WithResolveVariableIsBinFunc(isBin). + WithResolveVariableBinaryStringFunc(binaryString) + + gotIsBin, err := opts.ResolveVariableIsBinFunc()("v", false, false) + require.NoError(t, err) + require.True(t, gotIsBin) + gotBinaryString, err := opts.ResolveVariableBinaryStringFunc()("v", false, false) + require.NoError(t, err) + require.True(t, gotBinaryString) +} + func TestStatementOptionParamsPreserveNulls(t *testing.T) { mp := mpool.MustNewZero() vec := StatementOption{}. diff --git a/pkg/util/executor/types.go b/pkg/util/executor/types.go index c1c2bc49cdec6..ead208166d1eb 100644 --- a/pkg/util/executor/types.go +++ b/pkg/util/executor/types.go @@ -50,29 +50,31 @@ type TxnExecutor interface { // Options execute options. type Options struct { - disableIncrStatement bool - txnOp client.TxnOperator - database string - accountID uint32 - hasAccountID bool - minCommittedTS timestamp.Timestamp - innerTxn bool - waitCommittedLogApplied bool - timeZone *time.Location - statementOptions StatementOption - txnOpts []client.TxnOption - enableTrace bool - lower *int64 - streaming bool - stream_chan chan Result - error_chan chan error - sql string - forceRebuildPlan bool - resolveVariableFunc func(varName string, isSystemVar, isGlobalVar bool) (interface{}, error) - adjustTableExtraFunc func(*api.SchemaExtra) error - keepTxnAlive bool - lockWaitTimeout time.Duration - lockWaitTimeoutSet bool + disableIncrStatement bool + txnOp client.TxnOperator + database string + accountID uint32 + hasAccountID bool + minCommittedTS timestamp.Timestamp + innerTxn bool + waitCommittedLogApplied bool + timeZone *time.Location + statementOptions StatementOption + txnOpts []client.TxnOption + enableTrace bool + lower *int64 + streaming bool + stream_chan chan Result + error_chan chan error + sql string + forceRebuildPlan bool + resolveVariableFunc func(varName string, isSystemVar, isGlobalVar bool) (interface{}, error) + resolveVariableIsBinFunc func(varName string, isSystemVar, isGlobalVar bool) (bool, error) + resolveVariableBinaryStringFunc func(varName string, isSystemVar, isGlobalVar bool) (bool, error) + adjustTableExtraFunc func(*api.SchemaExtra) error + keepTxnAlive bool + lockWaitTimeout time.Duration + lockWaitTimeoutSet bool // isFrontend records whether the caller is a frontend // session-bound invocation. Go zero value (false) means // background: every caller of the internal SQL executor is diff --git a/pkg/vm/process/process.go b/pkg/vm/process/process.go index ad8eed0f086f7..20b34bd185135 100644 --- a/pkg/vm/process/process.go +++ b/pkg/vm/process/process.go @@ -153,12 +153,21 @@ func (proc *Process) GetPrepareParams() *vector.Vector { // SetPrepareParams borrows prepareParams. The caller remains responsible for releasing it. func (proc *Process) SetPrepareParams(prepareParams *vector.Vector) { - proc.setPrepareParams(prepareParams, nil, false) + proc.setPrepareParams(prepareParams, nil, nil, false) } // SetPrepareParamsWithIsBin borrows prepareParams. The caller remains responsible for releasing it. func (proc *Process) SetPrepareParamsWithIsBin(prepareParams *vector.Vector, isBin []bool) { - proc.setPrepareParams(prepareParams, isBin, false) + proc.setPrepareParams(prepareParams, isBin, nil, false) +} + +// SetPrepareParamsWithMetadata borrows prepareParams and keeps literal numeric +// metadata separate from binary-string metadata. +func (proc *Process) SetPrepareParamsWithMetadata( + prepareParams *vector.Vector, + isBin, binaryString []bool, +) { + proc.setPrepareParams(prepareParams, isBin, binaryString, false) } // SetPrepareParamsWithMeta borrows prepareParams and carries per-parameter @@ -169,13 +178,27 @@ func (proc *Process) SetPrepareParamsWithMeta( prepareParams *vector.Vector, isBin []bool, kinds []vector.PrepareParamKind, + binaryString ...[]bool, ) { - proc.setPrepareParams(prepareParams, prepareParamMetadata(prepareParams, isBin, kinds), false) + var binary []bool + if len(binaryString) > 0 { + binary = binaryString[0] + } + proc.setPrepareParams(prepareParams, prepareParamMetadata(prepareParams, isBin, kinds), binary, false) } // SetOwnedPrepareParamsWithIsBin transfers prepareParams to proc. Replacing or freeing proc releases it. func (proc *Process) SetOwnedPrepareParamsWithIsBin(prepareParams *vector.Vector, isBin []bool) { - proc.setPrepareParams(prepareParams, isBin, true) + proc.setPrepareParams(prepareParams, isBin, nil, true) +} + +// SetOwnedPrepareParamsWithMetadata transfers prepareParams to proc and keeps +// literal numeric metadata separate from binary-string metadata. +func (proc *Process) SetOwnedPrepareParamsWithMetadata( + prepareParams *vector.Vector, + isBin, binaryString []bool, +) { + proc.setPrepareParams(prepareParams, isBin, binaryString, true) } // SetOwnedPrepareParamsWithMeta transfers prepareParams to proc and preserves @@ -184,8 +207,13 @@ func (proc *Process) SetOwnedPrepareParamsWithMeta( prepareParams *vector.Vector, isBin []bool, kinds []vector.PrepareParamKind, + binaryString ...[]bool, ) { - proc.setPrepareParams(prepareParams, prepareParamMetadata(prepareParams, isBin, kinds), true) + var binary []bool + if len(binaryString) > 0 { + binary = binaryString[0] + } + proc.setPrepareParams(prepareParams, prepareParamMetadata(prepareParams, isBin, kinds), binary, true) } func prepareParamMetadata( @@ -302,7 +330,11 @@ func prepareParamProtocolVersion(service string) int64 { } } -func (proc *Process) setPrepareParams(prepareParams *vector.Vector, isBin []bool, owned bool) { +func (proc *Process) setPrepareParams( + prepareParams *vector.Vector, + isBin, binaryString []bool, + owned bool, +) { if proc.Base.prepareParams == prepareParams && proc.Base.prepareParamsOwned { owned = true } @@ -311,6 +343,7 @@ func (proc *Process) setPrepareParams(prepareParams *vector.Vector, isBin []bool } proc.Base.prepareParams = prepareParams proc.Base.prepareParamsIsBin = isBin + proc.Base.prepareParamsBinaryString = binaryString proc.Base.prepareParamsOwned = owned && prepareParams != nil } @@ -319,6 +352,7 @@ func (proc *Process) setPrepareParams(prepareParams *vector.Vector, isBin []bool type PrepareParamsState struct { prepareParams *vector.Vector isBin []bool + binaryString []bool owned bool } @@ -329,10 +363,12 @@ func (proc *Process) DetachPrepareParams() PrepareParamsState { state := PrepareParamsState{ prepareParams: proc.Base.prepareParams, isBin: proc.Base.prepareParamsIsBin, + binaryString: proc.Base.prepareParamsBinaryString, owned: proc.Base.prepareParamsOwned, } proc.Base.prepareParams = nil proc.Base.prepareParamsIsBin = nil + proc.Base.prepareParamsBinaryString = nil proc.Base.prepareParamsOwned = false return state } @@ -341,13 +377,13 @@ func (proc *Process) DetachPrepareParams() PrepareParamsState { // their ownership back to proc. It lets nested work use the parameters while // Process.Free releases only resources owned by that nested work. func (proc *Process) BorrowPrepareParams(state PrepareParamsState) { - proc.setPrepareParams(state.prepareParams, state.isBin, false) + proc.setPrepareParams(state.prepareParams, state.isBin, state.binaryString, false) } // RestorePrepareParams restores state previously returned by // DetachPrepareParams. func (proc *Process) RestorePrepareParams(state PrepareParamsState) { - proc.setPrepareParams(state.prepareParams, state.isBin, state.owned) + proc.setPrepareParams(state.prepareParams, state.isBin, state.binaryString, state.owned) } func (proc *Process) OperatorOutofMemory(size int64) bool { diff --git a/pkg/vm/process/process2.go b/pkg/vm/process/process2.go index 79ea4df526c70..a76ffa7fe23f6 100644 --- a/pkg/vm/process/process2.go +++ b/pkg/vm/process/process2.go @@ -248,7 +248,7 @@ func (proc *Process) Free() { proc.Base.cteMemoryBudget = nil } proc.Base.cteMemoryBudgetMu.Unlock() - proc.setPrepareParams(nil, nil, false) + proc.setPrepareParams(nil, nil, nil, false) } type QueryBaseContext struct { diff --git a/pkg/vm/process/process2_test.go b/pkg/vm/process/process2_test.go index 570e25e48fc3e..937fe8ec7be68 100644 --- a/pkg/vm/process/process2_test.go +++ b/pkg/vm/process/process2_test.go @@ -201,16 +201,18 @@ func TestDetachAndRestorePrepareParams(t *testing.T) { proc := &Process{Base: &BaseProcess{mp: mpool.MustNewZero()}} params := vector.NewVec(types.T_text.ToType()) require.NoError(t, vector.AppendBytes(params, []byte("binary"), false, proc.Mp())) - proc.SetOwnedPrepareParamsWithIsBin(params, []bool{true}) + proc.SetOwnedPrepareParamsWithMetadata(params, []bool{true}, []bool{true}) state := proc.DetachPrepareParams() require.Nil(t, proc.GetPrepareParams()) require.False(t, proc.GetPrepareParamIsBin(0)) + require.False(t, proc.GetPrepareParamIsBinaryString(0)) require.Equal(t, 1, params.Length(), "detach must not release owned params") proc.BorrowPrepareParams(state) require.Same(t, params, proc.GetPrepareParams()) require.True(t, proc.GetPrepareParamIsBin(0)) + require.True(t, proc.GetPrepareParamIsBinaryString(0)) require.False(t, proc.Base.prepareParamsOwned) proc.Free() @@ -219,6 +221,7 @@ func TestDetachAndRestorePrepareParams(t *testing.T) { proc.RestorePrepareParams(state) require.Same(t, params, proc.GetPrepareParams()) require.True(t, proc.GetPrepareParamIsBin(0)) + require.True(t, proc.GetPrepareParamIsBinaryString(0)) require.True(t, proc.Base.prepareParamsOwned) proc.Free() diff --git a/pkg/vm/process/process_codec.go b/pkg/vm/process/process_codec.go index ecf0e9de4210e..e5bd085cd1b51 100644 --- a/pkg/vm/process/process_codec.go +++ b/pkg/vm/process/process_codec.go @@ -20,6 +20,7 @@ import ( "time" "github.com/google/uuid" + "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/common/mpool" "github.com/matrixorigin/matrixone/pkg/common/runtime" "github.com/matrixorigin/matrixone/pkg/container/types" @@ -79,6 +80,23 @@ func (proc *Process) BuildProcessInfo( vec := proc.GetPrepareParams() if vec != nil { + protocolVersion := int64(0) + if rt := runtime.ServiceRuntime(proc.GetService()); rt != nil { + if value, ok := rt.GetGlobalVariables(runtime.MOProtocolVersion); ok { + protocolVersion, _ = value.(int64) + } + } + hasBinaryString := false + for _, binaryString := range proc.Base.prepareParamsBinaryString { + hasBinaryString = hasBinaryString || binaryString + } + if hasBinaryString && protocolVersion < defines.MORPCVersion14 { + return procInfo, moerr.NewNotSupportedf( + proc.Ctx, + "binary string prepared parameters require protocol version %d", + defines.MORPCVersion14, + ) + } procInfo.PrepareParams.Length = int64(vec.Length()) procInfo.PrepareParams.Data = make([]byte, 0, len(vec.GetData())) procInfo.PrepareParams.Data = append(procInfo.PrepareParams.Data, vec.GetData()...) @@ -97,6 +115,10 @@ func (proc *Process) BuildProcessInfo( return procInfo, err } procInfo.PrepareParams.IsBin = metadata + if hasBinaryString { + procInfo.PrepareParams.IsBinaryString = append( + []bool(nil), proc.Base.prepareParamsBinaryString...) + } } } { // session info @@ -282,7 +304,11 @@ func (c *codecService) Decode( prepareParams.GetNulls().Add(uint64(i)) } } - proc.SetOwnedPrepareParamsWithIsBin(prepareParams, prepareParamMetadata) + proc.SetOwnedPrepareParamsWithMetadata( + prepareParams, + prepareParamMetadata, + value.PrepareParams.IsBinaryString, + ) } return proc, nil } diff --git a/pkg/vm/process/process_codec_test.go b/pkg/vm/process/process_codec_test.go index d1f419a618fd7..ae4855f75b4b1 100644 --- a/pkg/vm/process/process_codec_test.go +++ b/pkg/vm/process/process_codec_test.go @@ -104,7 +104,7 @@ func newCodecTestProcess(t *testing.T) (*Process, client.TxnOperator) { vec := vector.NewVec(types.T_text.ToType()) require.NoError(t, vector.AppendBytes(vec, []byte("a"), false, proc.Mp())) require.NoError(t, vector.AppendBytes(vec, []byte("b"), true, proc.Mp())) - proc.SetPrepareParamsWithIsBin(vec, []bool{true, false}) + proc.SetPrepareParamsWithMetadata(vec, []bool{true, false}, []bool{false, true}) proc.SetAffectedRows(42) proc.SetPlanSnapshotTS(timestamp.Timestamp{PhysicalTime: 123, LogicalTime: 4}) return proc, txnOp @@ -319,6 +319,7 @@ func TestBuildProcessInfoAndMockProcessInfoWithPro(t *testing.T) { require.Equal(t, int64(2), info.PrepareParams.Length) require.Equal(t, []bool{false, true}, info.PrepareParams.Nulls) require.Equal(t, []bool{true, false}, info.PrepareParams.IsBin) + require.Equal(t, []bool{false, true}, info.PrepareParams.IsBinaryString) require.Equal(t, int64(42), info.AffectedRows) require.True(t, info.StatementRuntimeIgnore) require.Equal(t, ×tamp.Timestamp{PhysicalTime: 123, LogicalTime: 4}, info.PlanSnapshotTs) @@ -351,6 +352,48 @@ func TestBuildProcessInfoAndMockProcessInfoWithPro(t *testing.T) { require.Equal(t, "UTC", proc.Base.SessionInfo.TimeZone.String()) } +func TestBuildProcessInfoGatesBinaryStringMetadataByProtocolVersion(t *testing.T) { + proc, _ := newCodecTestProcess(t) + serviceRuntime := rt.ServiceRuntime(proc.GetService()) + original, hadOriginal := serviceRuntime.GetGlobalVariables(rt.MOProtocolVersion) + defer func() { + if hadOriginal { + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, original) + } else { + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, defines.MORPCLatestVersion) + } + proc.Free() + }() + + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, defines.MORPCVersion10) + _, err := proc.BuildProcessInfo("select ?") + require.Error(t, err) + + proc.SetPrepareParamsWithMetadata(proc.GetPrepareParams(), []bool{false, false}, []bool{false, false}) + info, err := proc.BuildProcessInfo("select ?") + require.NoError(t, err) + require.Empty(t, info.PrepareParams.IsBinaryString) + + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, defines.MORPCVersion11) + proc.SetPrepareParamsWithMetadata(proc.GetPrepareParams(), []bool{false, false}, []bool{false, true}) + _, err = proc.BuildProcessInfo("select ?") + require.Error(t, err) + + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, defines.MORPCVersion12) + _, err = proc.BuildProcessInfo("select ?") + require.Error(t, err) + + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, defines.MORPCVersion13) + _, err = proc.BuildProcessInfo("select ?") + require.Error(t, err) + + serviceRuntime.SetGlobalVariables(rt.MOProtocolVersion, defines.MORPCVersion14) + proc.SetPrepareParamsWithMetadata(proc.GetPrepareParams(), []bool{false, false}, []bool{false, true}) + info, err = proc.BuildProcessInfo("select ?") + require.NoError(t, err) + require.Equal(t, []bool{false, true}, info.PrepareParams.IsBinaryString) +} + func TestCodecServiceEncodeDecodeAndLookup(t *testing.T) { proc, _ := newCodecTestProcess(t) decodedTxn := fakeCodecTxnOperator{} @@ -487,6 +530,7 @@ func TestCodecServiceDecodesLegacyPrepareParamsWithoutBinaryFlags(t *testing.T) info, err := proc.BuildProcessInfo("select ?") require.NoError(t, err) info.PrepareParams.IsBin = nil + info.PrepareParams.IsBinaryString = nil // An old coordinator does not send the new field. Protobuf decodes that // absence as false, preserving the prior strict-mode behavior remotely. info.StatementRuntimeIgnore = false @@ -496,6 +540,7 @@ func TestCodecServiceDecodesLegacyPrepareParamsWithoutBinaryFlags(t *testing.T) legacyInfo := pipeline.ProcessInfo{} require.NoError(t, legacyInfo.Unmarshal(payload)) require.Empty(t, legacyInfo.PrepareParams.IsBin) + require.Empty(t, legacyInfo.PrepareParams.IsBinaryString) svc := NewCodecService(fakeCodecTxnClient{op: fakeCodecTxnOperator{}}, nil, nil, nil, nil, nil, nil, nil) decodedProc, err := svc.Decode(context.Background(), legacyInfo) diff --git a/pkg/vm/process/types.go b/pkg/vm/process/types.go index 765f819081def..c9d993d977c9f 100644 --- a/pkg/vm/process/types.go +++ b/pkg/vm/process/types.go @@ -368,9 +368,11 @@ type BaseProcess struct { Aicm *defines.AutoIncrCacheManager resolveVariableFunc func(varName string, isSystemVar, isGlobalVar bool) (interface{}, error) resolveVariableIsBinFunc func(varName string, isSystemVar, isGlobalVar bool) (bool, error) + resolveVariableBinaryStringFunc func(varName string, isSystemVar, isGlobalVar bool) (bool, error) resolveVariablePrepareParamKindFunc func(varName string, isSystemVar, isGlobalVar bool) (vector.PrepareParamKind, error) prepareParams *vector.Vector prepareParamsIsBin []bool + prepareParamsBinaryString []bool prepareParamsOwned bool QueryClient qclient.QueryClient Hakeeper logservice.CNHAKeeperClient @@ -572,6 +574,10 @@ func (proc *Process) getPrepareParamMeta(i, section int) bool { proc.Base.prepareParamsIsBin[offset] } +func (proc *Process) GetPrepareParamIsBinaryString(i int) bool { + return i >= 0 && i < len(proc.Base.prepareParamsBinaryString) && proc.Base.prepareParamsBinaryString[i] +} + // SetIncrStatementDisabled marks this process (and every child process // sharing its BaseProcess) as running internal SQL that must not advance the // workspace snapshot write offset. See BaseProcess.incrStatementDisabled. @@ -601,6 +607,14 @@ func (proc *Process) GetResolveVariableIsBinFunc() func(varName string, isSystem return proc.Base.resolveVariableIsBinFunc } +func (proc *Process) SetResolveVariableBinaryStringFunc(f func(varName string, isSystemVar, isGlobalVar bool) (bool, error)) { + proc.Base.resolveVariableBinaryStringFunc = f +} + +func (proc *Process) GetResolveVariableBinaryStringFunc() func(varName string, isSystemVar, isGlobalVar bool) (bool, error) { + return proc.Base.resolveVariableBinaryStringFunc +} + func (proc *Process) SetResolveVariablePrepareParamKindFunc( f func(varName string, isSystemVar, isGlobalVar bool) (vector.PrepareParamKind, error), ) { diff --git a/proto/pipeline.proto b/proto/pipeline.proto index 1a8df67633ac1..237558018c224 100644 --- a/proto/pipeline.proto +++ b/proto/pipeline.proto @@ -643,6 +643,7 @@ message PrepareParamInfo { bytes area = 3; repeated bool nulls = 4; repeated bool is_bin = 5; + repeated bool is_binary_string = 6; } message ProcessInfo { diff --git a/test/distributed/cases/dtype/binary_string.result b/test/distributed/cases/dtype/binary_string.result new file mode 100644 index 0000000000000..053bb02f6db83 --- /dev/null +++ b/test/distributed/cases/dtype/binary_string.result @@ -0,0 +1,62 @@ +set @binary_string = X'e4bda0'; +select hex(left(X'e4bda061', 1)), hex(reverse(X'e4bda061')), +hex(lpad(X'e4bda061', 5, X'78')), hex(rpad(X'e4bda061', 5, X'78')), +ord(X'e4bda061'), instr(X'e4bda061', X'bd'), locate(X'bd', X'e4bda061'), +X'e4bda061' like '____', hex(regexp_substr(X'e4bda061', '.')), +hex(left(X'ff61', 1)); +➤ hex(left(0xe4bda061, 1))[12,65535,0] ¦ hex(reverse(0xe4bda061))[12,65535,0] ¦ hex(lpad(0xe4bda061, 5, 0x78))[12,65535,0] ¦ hex(rpad(0xe4bda061, 5, 0x78))[12,65535,0] ¦ ord(0xe4bda061)[-5,64,0] ¦ instr(0xe4bda061, 0xbd)[-5,64,0] ¦ locate(0xbd, 0xe4bda061)[-5,64,0] ¦ 0xe4bda061 like ____[-7,1,0] ¦ hex(regexp_substr(0xe4bda061, .))[12,65535,0] ¦ hex(left(0xff61, 1))[12,65535,0] 𝄀 +E4 ¦ 61A0BDE4 ¦ 78E4BDA061 ¦ E4BDA06178 ¦ 228 ¦ 2 ¦ 2 ¦ 1 ¦ E4 ¦ FF +select char_length(replace(@binary_string, X'bd', X'78')), +char_length(trim(@binary_string)), char_length(ltrim(@binary_string)), +char_length(rtrim(@binary_string)), char_length(elt(1, @binary_string)), +char_length(char(228, 189, 160)); +➤ char_length(replace(@binary_string, 0xbd, 0x78))[-5,64,0] ¦ char_length(trim(@binary_string))[-5,64,0] ¦ char_length(ltrim(@binary_string))[-5,64,0] ¦ char_length(rtrim(@binary_string))[-5,64,0] ¦ char_length(elt(1, @binary_string))[-5,64,0] ¦ char_length(char(228, 189, 160))[-5,64,0] 𝄀 +3 ¦ 3 ¦ 3 ¦ 3 ¦ 3 ¦ 3 +select char_length(min(v)), char_length(max(v)), char_length(any_value(v)) +from (select @binary_string v) s; +➤ char_length(min(v))[-5,64,0] ¦ char_length(max(v))[-5,64,0] ¦ char_length(any_value(v))[-5,64,0] 𝄀 +3 ¦ 3 ¦ 3 +select char_length(group_concat(v separator '')) +from (select @binary_string v union all select @binary_string) s; +➤ char_length(group_concat(v separator ))[-5,64,0] 𝄀 +6 +select char_length(first_value(v) over ()), char_length(last_value(v) over ()), +char_length(nth_value(v, 1) over ()), char_length(lag(v, 0) over ()), +char_length(lead(v, 0) over ()) +from (select @binary_string v) s; +➤ char_length(first_value(v) over ())[-5,64,0] ¦ char_length(last_value(v) over ())[-5,64,0] ¦ char_length(nth_value(v, 1) over ())[-5,64,0] ¦ char_length(lag(v, 0) over ())[-5,64,0] ¦ char_length(lead(v, 0) over ())[-5,64,0] 𝄀 +3 ¦ 3 ¦ 3 ¦ 3 ¦ 3 +select char_length(cast(@binary_string as char)), +char_length(cast(@binary_string as char(10))), +char_length(convert(@binary_string, char)), +char_length(convert(@binary_string using utf8mb4)); +➤ char_length(cast(@binary_string as varchar))[-5,64,0] ¦ char_length(cast(@binary_string as char(10)))[-5,64,0] ¦ char_length(cast(@binary_string as varchar))[-5,64,0] ¦ char_length(convert(@binary_string, utf8mb4))[-5,64,0] 𝄀 +1 ¦ 1 ¦ 1 ¦ 1 +drop table if exists binary_string_ctas_var; +drop table if exists binary_string_ctas_expr; +drop table if exists binary_string_ctas_empty; +create table binary_string_ctas_var as select @binary_string c; +create table binary_string_ctas_expr as select replace(X'e4bda0', X'bd', X'78') c; +create table binary_string_ctas_empty as select X'' c; +select table_name, data_type, character_maximum_length +from information_schema.columns +where table_schema = database() +and table_name in ('binary_string_ctas_var', 'binary_string_ctas_expr', 'binary_string_ctas_empty') +and column_name = 'c' +order by table_name; +➤ table_name[12,256,0] ¦ data_type[12,65535,0] ¦ character_maximum_length[-5,64,0] 𝄀 +binary_string_ctas_empty ¦ varbinary ¦ 0 𝄀 +binary_string_ctas_expr ¦ varbinary ¦ 3 𝄀 +binary_string_ctas_var ¦ blob ¦ 0 +select char_length(c), hex(c) from binary_string_ctas_var; +➤ char_length(c)[-5,64,0] ¦ hex(c)[12,65535,0] 𝄀 +3 ¦ E4BDA0 +select char_length(c), hex(c) from binary_string_ctas_expr; +➤ char_length(c)[-5,64,0] ¦ hex(c)[12,65535,0] 𝄀 +3 ¦ E478A0 +select char_length(c) from binary_string_ctas_empty; +➤ char_length(c)[-5,64,0] 𝄀 +0 +drop table binary_string_ctas_var; +drop table binary_string_ctas_expr; +drop table binary_string_ctas_empty; diff --git a/test/distributed/cases/dtype/binary_string.sql b/test/distributed/cases/dtype/binary_string.sql new file mode 100644 index 0000000000000..6e7a1e47ea8c0 --- /dev/null +++ b/test/distributed/cases/dtype/binary_string.sql @@ -0,0 +1,51 @@ +-- Binary-string expressions use byte semantics and keep their runtime metadata. +set @binary_string = X'e4bda0'; + +select hex(left(X'e4bda061', 1)), hex(reverse(X'e4bda061')), + hex(lpad(X'e4bda061', 5, X'78')), hex(rpad(X'e4bda061', 5, X'78')), + ord(X'e4bda061'), instr(X'e4bda061', X'bd'), locate(X'bd', X'e4bda061'), + X'e4bda061' like '____', hex(regexp_substr(X'e4bda061', '.')), + hex(left(X'ff61', 1)); + +select char_length(replace(@binary_string, X'bd', X'78')), + char_length(trim(@binary_string)), char_length(ltrim(@binary_string)), + char_length(rtrim(@binary_string)), char_length(elt(1, @binary_string)), + char_length(char(228, 189, 160)); + +select char_length(min(v)), char_length(max(v)), char_length(any_value(v)) +from (select @binary_string v) s; + +select char_length(group_concat(v separator '')) +from (select @binary_string v union all select @binary_string) s; + +select char_length(first_value(v) over ()), char_length(last_value(v) over ()), + char_length(nth_value(v, 1) over ()), char_length(lag(v, 0) over ()), + char_length(lead(v, 0) over ()) +from (select @binary_string v) s; + +select char_length(cast(@binary_string as char)), + char_length(cast(@binary_string as char(10))), + char_length(convert(@binary_string, char)), + char_length(convert(@binary_string using utf8mb4)); + +drop table if exists binary_string_ctas_var; +drop table if exists binary_string_ctas_expr; +drop table if exists binary_string_ctas_empty; +create table binary_string_ctas_var as select @binary_string c; +create table binary_string_ctas_expr as select replace(X'e4bda0', X'bd', X'78') c; +create table binary_string_ctas_empty as select X'' c; + +select table_name, data_type, character_maximum_length +from information_schema.columns +where table_schema = database() + and table_name in ('binary_string_ctas_var', 'binary_string_ctas_expr', 'binary_string_ctas_empty') + and column_name = 'c' +order by table_name; + +select char_length(c), hex(c) from binary_string_ctas_var; +select char_length(c), hex(c) from binary_string_ctas_expr; +select char_length(c) from binary_string_ctas_empty; + +drop table binary_string_ctas_var; +drop table binary_string_ctas_expr; +drop table binary_string_ctas_empty; diff --git a/test/distributed/cases/dtype/text_blob.result b/test/distributed/cases/dtype/text_blob.result index 69bb981abdb87..d8902c1d7bf65 100644 --- a/test/distributed/cases/dtype/text_blob.result +++ b/test/distributed/cases/dtype/text_blob.result @@ -178,7 +178,7 @@ length(b1) length(b2) length(b3) 8 null null select substring(b3,5),substr(b2,-3,2) from blob_01; substring(b3, 5) substr(b2, -3, 2) -3432234234 +3432234234 æ– -01-01 co null null select count(b1) from blob_01; diff --git a/test/distributed/cases/function/func_binary_string_semantics.result b/test/distributed/cases/function/func_binary_string_semantics.result new file mode 100644 index 0000000000000..62e867a8bdb27 --- /dev/null +++ b/test/distributed/cases/function/func_binary_string_semantics.result @@ -0,0 +1,68 @@ +DROP DATABASE IF EXISTS binary_string_semantics; +CREATE DATABASE binary_string_semantics; +USE binary_string_semantics; +SET @b = X'e4bda061'; +SELECT HEX(SUBSTRING_INDEX(@b, X'61', 1)), CHAR_LENGTH(SUBSTRING_INDEX(@b, X'61', 1)); +➤ HEX(SUBSTRING_INDEX(@b, 0x61, 1))[12,65535,0] ¦ CHAR_LENGTH(SUBSTRING_INDEX(@b, 0x61, 1))[-5,64,0] 𝄀 +E4BDA0 ¦ 3 +SELECT HEX(MAKE_SET(1, @b)), CHAR_LENGTH(MAKE_SET(1, @b)); +➤ HEX(MAKE_SET(1, @b))[12,65535,0] ¦ CHAR_LENGTH(MAKE_SET(1, @b))[-5,64,0] 𝄀 +E4BDA061 ¦ 4 +SELECT HEX(EXPORT_SET(1, @b, '', '', 1)), CHAR_LENGTH(EXPORT_SET(1, @b, '', '', 1)); +➤ HEX(EXPORT_SET(1, @b, , , 1))[12,65535,0] ¦ CHAR_LENGTH(EXPORT_SET(1, @b, , , 1))[-5,64,0] 𝄀 +E4BDA061 ¦ 4 +SELECT HEX(LEAST(@b, @b)), CHAR_LENGTH(LEAST(@b, @b)), +HEX(GREATEST(@b, @b)), CHAR_LENGTH(GREATEST(@b, @b)); +➤ HEX(LEAST(@b, @b))[12,65535,0] ¦ CHAR_LENGTH(LEAST(@b, @b))[-5,64,0] ¦ HEX(GREATEST(@b, @b))[12,65535,0] ¦ CHAR_LENGTH(GREATEST(@b, @b))[-5,64,0] 𝄀 +E4BDA061 ¦ 4 ¦ E4BDA061 ¦ 4 +SELECT HEX(INSERT(@b, 2, 1, X'78')), CHAR_LENGTH(INSERT(@b, 2, 1, X'78')); +➤ HEX(INSERT(@b, 2, 1, 0x78))[12,65535,0] ¦ CHAR_LENGTH(INSERT(@b, 2, 1, 0x78))[-5,64,0] 𝄀 +E478A061 ¦ 4 +SELECT REGEXP_LIKE(@b, '^....$'), @b REGEXP '^....$', REGEXP_INSTR(@b, '.', 1, 2), +HEX(REGEXP_REPLACE(@b, '.', 'x')), CHAR_LENGTH(REGEXP_REPLACE(@b, 'z', 'z')); +➤ REGEXP_LIKE(@b, ^....$)[-7,1,0] ¦ @b reg_match ^....$[-7,1,0] ¦ REGEXP_INSTR(@b, ., 1, 2)[-5,64,0] ¦ HEX(REGEXP_REPLACE(@b, ., x))[12,65535,0] ¦ CHAR_LENGTH(REGEXP_REPLACE(@b, z, z))[-5,64,0] 𝄀 +1 ¦ 1 ¦ 2 ¦ 78787878 ¦ 4 +SELECT @b LIKE '____' ESCAPE '='; +➤ @b like ____ escape =[-7,1,0] 𝄀 +1 +SELECT HEX(x), CHAR_LENGTH(x), OCTET_LENGTH(x) +FROM (SELECT LAG(v, 1, @b) OVER (ORDER BY id) x FROM (SELECT 1 id, 'a' v) s) q; +➤ HEX(x)[12,65535,0] ¦ CHAR_LENGTH(x)[-5,64,0] ¦ OCTET_LENGTH(x)[-5,64,0] 𝄀 +E4BDA0 ¦ 3 ¦ 3 +SELECT HEX(x), CHAR_LENGTH(x), OCTET_LENGTH(x) +FROM (SELECT LEAD(v, 1, @b) OVER (ORDER BY id) x FROM (SELECT 1 id, 'a' v) s) q; +➤ HEX(x)[12,65535,0] ¦ CHAR_LENGTH(x)[-5,64,0] ¦ OCTET_LENGTH(x)[-5,64,0] 𝄀 +E4BDA0 ¦ 3 ¦ 3 +SELECT HEX(CHAR(65 USING utf8mb4)), CHAR_LENGTH(CHAR(65 USING utf8mb4)), +OCTET_LENGTH(CHAR(65 USING utf8mb4)); +➤ HEX(convert(CHAR(65), utf8mb4))[12,65535,0] ¦ CHAR_LENGTH(convert(CHAR(65), utf8mb4))[-5,64,0] ¦ OCTET_LENGTH(convert(CHAR(65), utf8mb4))[-5,64,0] 𝄀 +41 ¦ 1 ¦ 1 +SET @b = X'e4bda0'; +CREATE TABLE binary_left_ctas AS SELECT LEFT(@b, 1) c; +CREATE TABLE binary_regexp_ctas AS SELECT REGEXP_SUBSTR(@b, '.') c; +CREATE TABLE binary_lpad_ctas AS SELECT LPAD(X'61', 5, 'x') c; +CREATE TABLE binary_unary_ctas AS SELECT +X'3132' c; +SELECT table_name, column_name, column_type, is_nullable +FROM information_schema.columns +WHERE table_schema = 'binary_string_semantics' +AND table_name IN ('binary_left_ctas', 'binary_regexp_ctas', 'binary_lpad_ctas', 'binary_unary_ctas') +AND column_name NOT LIKE '__mo%' +ORDER BY table_name, ordinal_position; +➤ table_name[12,-1,0] ¦ column_name[12,-1,0] ¦ column_type[12,-1,0] ¦ is_nullable[12,-1,0] 𝄀 +binary_left_ctas ¦ c ¦ BLOB(0) ¦ YES 𝄀 +binary_lpad_ctas ¦ c ¦ VARBINARY(5) ¦ NO 𝄀 +binary_regexp_ctas ¦ c ¦ BLOB(0) ¦ YES 𝄀 +binary_unary_ctas ¦ c ¦ VARBINARY(2) ¦ NO +SELECT HEX(c), CHAR_LENGTH(c) FROM binary_left_ctas; +➤ HEX(c)[12,65535,0] ¦ CHAR_LENGTH(c)[-5,64,0] 𝄀 +E4 ¦ 1 +SELECT HEX(c), CHAR_LENGTH(c) FROM binary_regexp_ctas; +➤ HEX(c)[12,65535,0] ¦ CHAR_LENGTH(c)[-5,64,0] 𝄀 +E4 ¦ 1 +SELECT HEX(c), CHAR_LENGTH(c) FROM binary_lpad_ctas; +➤ HEX(c)[12,65535,0] ¦ CHAR_LENGTH(c)[-5,64,0] 𝄀 +7878787861 ¦ 5 +SELECT HEX(c), CHAR_LENGTH(c), c + 0 FROM binary_unary_ctas; +➤ HEX(c)[12,65535,0] ¦ CHAR_LENGTH(c)[-5,64,0] ¦ c + 0[-5,64,0] 𝄀 +3132 ¦ 2 ¦ 12 +DROP DATABASE binary_string_semantics; diff --git a/test/distributed/cases/function/func_binary_string_semantics.test b/test/distributed/cases/function/func_binary_string_semantics.test new file mode 100644 index 0000000000000..b8e9e073fd31a --- /dev/null +++ b/test/distributed/cases/function/func_binary_string_semantics.test @@ -0,0 +1,41 @@ +DROP DATABASE IF EXISTS binary_string_semantics; +CREATE DATABASE binary_string_semantics; +USE binary_string_semantics; + +SET @b = X'e4bda061'; +SELECT HEX(SUBSTRING_INDEX(@b, X'61', 1)), CHAR_LENGTH(SUBSTRING_INDEX(@b, X'61', 1)); +SELECT HEX(MAKE_SET(1, @b)), CHAR_LENGTH(MAKE_SET(1, @b)); +SELECT HEX(EXPORT_SET(1, @b, '', '', 1)), CHAR_LENGTH(EXPORT_SET(1, @b, '', '', 1)); +SELECT HEX(LEAST(@b, @b)), CHAR_LENGTH(LEAST(@b, @b)), + HEX(GREATEST(@b, @b)), CHAR_LENGTH(GREATEST(@b, @b)); +SELECT HEX(INSERT(@b, 2, 1, X'78')), CHAR_LENGTH(INSERT(@b, 2, 1, X'78')); + +SELECT REGEXP_LIKE(@b, '^....$'), @b REGEXP '^....$', REGEXP_INSTR(@b, '.', 1, 2), + HEX(REGEXP_REPLACE(@b, '.', 'x')), CHAR_LENGTH(REGEXP_REPLACE(@b, 'z', 'z')); +SELECT @b LIKE '____' ESCAPE '='; + +SELECT HEX(x), CHAR_LENGTH(x), OCTET_LENGTH(x) +FROM (SELECT LAG(v, 1, @b) OVER (ORDER BY id) x FROM (SELECT 1 id, 'a' v) s) q; +SELECT HEX(x), CHAR_LENGTH(x), OCTET_LENGTH(x) +FROM (SELECT LEAD(v, 1, @b) OVER (ORDER BY id) x FROM (SELECT 1 id, 'a' v) s) q; + +SELECT HEX(CHAR(65 USING utf8mb4)), CHAR_LENGTH(CHAR(65 USING utf8mb4)), + OCTET_LENGTH(CHAR(65 USING utf8mb4)); + +SET @b = X'e4bda0'; +CREATE TABLE binary_left_ctas AS SELECT LEFT(@b, 1) c; +CREATE TABLE binary_regexp_ctas AS SELECT REGEXP_SUBSTR(@b, '.') c; +CREATE TABLE binary_lpad_ctas AS SELECT LPAD(X'61', 5, 'x') c; +CREATE TABLE binary_unary_ctas AS SELECT +X'3132' c; +SELECT table_name, column_name, column_type, is_nullable +FROM information_schema.columns +WHERE table_schema = 'binary_string_semantics' + AND table_name IN ('binary_left_ctas', 'binary_regexp_ctas', 'binary_lpad_ctas', 'binary_unary_ctas') + AND column_name NOT LIKE '__mo%' +ORDER BY table_name, ordinal_position; +SELECT HEX(c), CHAR_LENGTH(c) FROM binary_left_ctas; +SELECT HEX(c), CHAR_LENGTH(c) FROM binary_regexp_ctas; +SELECT HEX(c), CHAR_LENGTH(c) FROM binary_lpad_ctas; +SELECT HEX(c), CHAR_LENGTH(c), c + 0 FROM binary_unary_ctas; + +DROP DATABASE binary_string_semantics; diff --git a/test/distributed/cases/function/func_string_char_length.result b/test/distributed/cases/function/func_string_char_length.result index ad4de05e8e48f..43afe20bcb192 100644 --- a/test/distributed/cases/function/func_string_char_length.result +++ b/test/distributed/cases/function/func_string_char_length.result @@ -15,6 +15,170 @@ CHAR_LENGTH(_binary '你好'), CHARACTER_LENGTH(_binary '你好'); LENGTH(_binary '你好') CHAR_LENGTH(_binary '你好') CHARACTER_LENGTH(_binary '你好') 6 6 6 +SELECT LENGTH(X'e4bda0'), +CHAR_LENGTH(X'e4bda0'), +CHARACTER_LENGTH(X'e4bda0'); +LENGTH(X'e4bda0') CHAR_LENGTH(X'e4bda0') CHARACTER_LENGTH(X'e4bda0') +3 3 3 +SELECT CHAR_LENGTH(0xE4BDA0), +CHAR_LENGTH(B'111001001011110110100000'), +CHAR_LENGTH(X''), +CHAR_LENGTH(B''); +CHAR_LENGTH(0xE4BDA0) CHAR_LENGTH(B'111001001011110110100000') CHAR_LENGTH(X'') CHAR_LENGTH(B'') +3 3 0 0 +SELECT CHAR_LENGTH(_binary X'e4bda0'), +CHAR_LENGTH(_binary '你好'), +CHAR_LENGTH('你好'); +CHAR_LENGTH(_binary X'e4bda0') CHAR_LENGTH(_binary '你好') CHAR_LENGTH('你好') +3 6 2 +SELECT CHAR_LENGTH(CAST(X'e4bda0' AS CHAR)), +CHAR_LENGTH(CAST(X'e4bda0' AS BINARY)); +CHAR_LENGTH(CAST(X'e4bda0' AS CHAR)) CHAR_LENGTH(CAST(X'e4bda0' AS BINARY)) +1 3 +SELECT CHAR_LENGTH(CONCAT(X'e4bda0', 'a')), +CHAR_LENGTH(SUBSTR(X'e4bda0', 1)), +CHAR_LENGTH(LOWER(X'e4bda0')), +CHAR_LENGTH(REPEAT(X'e4bda0', 2)), +CHAR_LENGTH(COALESCE(NULL, X'e4bda0')); +CHAR_LENGTH(CONCAT(0xe4bda0, a)) CHAR_LENGTH(SUBSTR(0xe4bda0, 1)) CHAR_LENGTH(LOWER(0xe4bda0)) CHAR_LENGTH(REPEAT(0xe4bda0, 2)) CHAR_LENGTH(COALESCE(null, 0xe4bda0)) +4 3 3 6 3 +SELECT HEX(SUBSTR(X'e4bda0', 2, 1)), +HEX(LOWER(X'4142')), +HEX(UPPER(X'6162')); +HEX(SUBSTR(0xe4bda0, 2, 1)) HEX(LOWER(0x4142)) HEX(UPPER(0x6162)) +BD 4142 6162 +CREATE TABLE binary_literal_ids (id INT); +INSERT INTO binary_literal_ids VALUES (1), (2); +SELECT id, +CHAR_LENGTH(IF(id = 1, X'e4bda0', '你好')), +CHAR_LENGTH(CASE WHEN id = 1 THEN X'e4bda0' ELSE '你好' END) +FROM binary_literal_ids ORDER BY id; +id CHAR_LENGTH(IF(id = 1, 0xe4bda0, 你好)) CHAR_LENGTH(case when id = 1 then 0xe4bda0 else 你好 end) +1 3 3 +2 6 6 +DROP TABLE binary_literal_ids; +SET @binary_literal_v = X'e4bda0'; +SELECT CHAR_LENGTH(@binary_literal_v); +CHAR_LENGTH(@binary_literal_v) +3 +SELECT CHAR_LENGTH(CONCAT(@binary_literal_v, 'a')), +CHAR_LENGTH(SUBSTR(@binary_literal_v, 1)), +CHAR_LENGTH(LOWER(@binary_literal_v)), +CHAR_LENGTH(REPEAT(@binary_literal_v, 2)), +CHAR_LENGTH(IF(TRUE, @binary_literal_v, '你好')), +CHAR_LENGTH(CASE WHEN TRUE THEN @binary_literal_v ELSE '你好' END), +CHAR_LENGTH(COALESCE(NULL, @binary_literal_v)); +CHAR_LENGTH(CONCAT(@binary_literal_v, a)) CHAR_LENGTH(SUBSTR(@binary_literal_v, 1)) CHAR_LENGTH(LOWER(@binary_literal_v)) CHAR_LENGTH(REPEAT(@binary_literal_v, 2)) CHAR_LENGTH(IF(true, @binary_literal_v, 你好)) CHAR_LENGTH(case when true then @binary_literal_v else 你好 end) CHAR_LENGTH(COALESCE(null, @binary_literal_v)) +4 3 3 6 3 3 3 +SET @binary_numeric_v = X'3132'; +SELECT @binary_numeric_v + 0; +@binary_numeric_v + 0 +12 +PREPARE binary_literal_stmt FROM 'SELECT CHAR_LENGTH(?)'; +SET @binary_literal_v = X'e4bda0'; +EXECUTE binary_literal_stmt USING @binary_literal_v; +CHAR_LENGTH(?) +3 +SET @binary_literal_v = '你好'; +EXECUTE binary_literal_stmt USING @binary_literal_v; +CHAR_LENGTH(?) +2 +SET @binary_literal_v = X'e4bda0'; +EXECUTE binary_literal_stmt USING @binary_literal_v; +CHAR_LENGTH(?) +3 +DEALLOCATE PREPARE binary_literal_stmt; +PREPARE binary_numeric_stmt FROM 'SELECT ? + 0'; +SET @binary_numeric_v = X'31'; +EXECUTE binary_numeric_stmt USING @binary_numeric_v; +? + 0 +1 +SET @binary_numeric_v = '2'; +EXECUTE binary_numeric_stmt USING @binary_numeric_v; +? + 0 +2 +SET @binary_numeric_v = X'31'; +EXECUTE binary_numeric_stmt USING @binary_numeric_v; +? + 0 +1 +DEALLOCATE PREPARE binary_numeric_stmt; +CREATE TABLE binary_literal_bigint (id INT AUTO_INCREMENT PRIMARY KEY, v BIGINT); +INSERT INTO binary_literal_bigint(v) VALUES (X'31'); +SET @binary_numeric_v = X'31'; +INSERT INTO binary_literal_bigint(v) VALUES (@binary_numeric_v); +SET @binary_numeric_v = '2'; +INSERT INTO binary_literal_bigint(v) VALUES (@binary_numeric_v); +SET @binary_numeric_v = X'31'; +INSERT INTO binary_literal_bigint(v) VALUES (@binary_numeric_v); +SELECT v FROM binary_literal_bigint ORDER BY id; +v +49 +1 +2 +1 +DROP TABLE binary_literal_bigint; +CREATE TABLE binary_literal_ctas AS SELECT X'e4bda0' x; +SELECT DATA_TYPE, CHARACTER_MAXIMUM_LENGTH +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'binary_literal_ctas' AND COLUMN_NAME = 'x'; +DATA_TYPE CHARACTER_MAXIMUM_LENGTH +varbinary 3 +SELECT CHAR_LENGTH(x) FROM binary_literal_ctas; +CHAR_LENGTH(x) +3 +SELECT tag, CHAR_LENGTH(x) +FROM ( +SELECT 1 tag, X'e4bda0' x +UNION ALL +SELECT 2 tag, '你好' x +) u ORDER BY tag; +tag CHAR_LENGTH(x) +1 3 +2 6 +SELECT x + 0, CHAR_LENGTH(x) FROM (SELECT X'31' x) d; +x + 0 CHAR_LENGTH(x) +1 1 +DROP TABLE binary_literal_ctas; +CREATE TABLE binary_literal_nested_ctas AS SELECT +CONCAT(X'e4bda0', 'a') concat_value, +SUBSTR(X'e4bda0', 1) substr_value, +LOWER(X'e4bda0') lower_value, +REPEAT(X'e4bda0', 2) repeat_value, +IF(TRUE, X'e4bda0', '你好') if_value, +CASE WHEN TRUE THEN X'e4bda0' ELSE '你好' END case_value, +COALESCE(NULL, X'e4bda0') coalesce_value; +SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'binary_literal_nested_ctas' +AND COLUMN_NAME != '__mo_fake_pk_col' +ORDER BY ORDINAL_POSITION; +COLUMN_NAME DATA_TYPE CHARACTER_MAXIMUM_LENGTH +concat_value varbinary 7 +substr_value varbinary 3 +lower_value varbinary 3 +repeat_value varbinary 6 +if_value varbinary 8 +case_value varbinary 8 +coalesce_value varbinary 3 +SELECT CHAR_LENGTH(concat_value), CHAR_LENGTH(substr_value), CHAR_LENGTH(lower_value), +CHAR_LENGTH(repeat_value), CHAR_LENGTH(if_value), CHAR_LENGTH(case_value), +CHAR_LENGTH(coalesce_value) +FROM binary_literal_nested_ctas; +CHAR_LENGTH(concat_value) CHAR_LENGTH(substr_value) CHAR_LENGTH(lower_value) CHAR_LENGTH(repeat_value) CHAR_LENGTH(if_value) CHAR_LENGTH(case_value) CHAR_LENGTH(coalesce_value) +4 3 3 6 3 3 3 +DROP TABLE binary_literal_nested_ctas; +SELECT X'41' + 0, +B'1000001' + 0, +CAST(X'3132' AS UNSIGNED), +CAST(B'11000100110010' AS UNSIGNED), +HEX(CHAR(X'41')); +0x41 + 0 0b1000001 + 0 cast(0x3132 as unsigned) cast(0b11000100110010 as unsigned) HEX(CHAR(0x41)) +65 65 12594 12594 41 +SELECT X'40' | X'01', +~X'31', +1.0 * (X'312E35' | X'312E35'); +0x40 | 0x01 ~0x31 1.0 * (0x312E35 | 0x312E35) +65 18446744073709551566 3223093.0 CREATE TABLE binary_char_length ( b BINARY(8), vb VARBINARY(8), @@ -26,6 +190,15 @@ FROM binary_char_length; CHAR_LENGTH(b) CHAR_LENGTH(vb) CHAR_LENGTH(bl) 8 6 6 DROP TABLE binary_char_length; +CREATE TABLE binary_padding_compare (b BINARY(4)); +INSERT INTO binary_padding_compare VALUES ('a'); +SELECT b = X'61', X'61' = b FROM binary_padding_compare; +b = 0x61 0x61 = b +0 0 +SELECT COUNT(*) FROM binary_padding_compare WHERE b = X'61'; +COUNT(*) +0 +DROP TABLE binary_padding_compare; SELECT CHAR_LENGTH(NULL); CHAR_LENGTH(NULL) null diff --git a/test/distributed/cases/function/func_string_char_length.test b/test/distributed/cases/function/func_string_char_length.test index 993f736899aa7..c5659d87e11ef 100644 --- a/test/distributed/cases/function/func_string_char_length.test +++ b/test/distributed/cases/function/func_string_char_length.test @@ -8,6 +8,120 @@ SELECT CHAR_LENGTH("Español"); SELECT LENGTH(_binary '你好'), CHAR_LENGTH(_binary '你好'), CHARACTER_LENGTH(_binary '你好'); +SELECT LENGTH(X'e4bda0'), + CHAR_LENGTH(X'e4bda0'), + CHARACTER_LENGTH(X'e4bda0'); +SELECT CHAR_LENGTH(0xE4BDA0), + CHAR_LENGTH(B'111001001011110110100000'), + CHAR_LENGTH(X''), + CHAR_LENGTH(B''); +SELECT CHAR_LENGTH(_binary X'e4bda0'), + CHAR_LENGTH(_binary '你好'), + CHAR_LENGTH('你好'); +SELECT CHAR_LENGTH(CAST(X'e4bda0' AS CHAR)), + CHAR_LENGTH(CAST(X'e4bda0' AS BINARY)); + +# binary literal propagation through functions and control flow +SELECT CHAR_LENGTH(CONCAT(X'e4bda0', 'a')), + CHAR_LENGTH(SUBSTR(X'e4bda0', 1)), + CHAR_LENGTH(LOWER(X'e4bda0')), + CHAR_LENGTH(REPEAT(X'e4bda0', 2)), + CHAR_LENGTH(COALESCE(NULL, X'e4bda0')); +SELECT HEX(SUBSTR(X'e4bda0', 2, 1)), + HEX(LOWER(X'4142')), + HEX(UPPER(X'6162')); +CREATE TABLE binary_literal_ids (id INT); +INSERT INTO binary_literal_ids VALUES (1), (2); +SELECT id, + CHAR_LENGTH(IF(id = 1, X'e4bda0', '你好')), + CHAR_LENGTH(CASE WHEN id = 1 THEN X'e4bda0' ELSE '你好' END) +FROM binary_literal_ids ORDER BY id; +DROP TABLE binary_literal_ids; + +# user variables and text PREPARE preserve binary metadata across reuse +SET @binary_literal_v = X'e4bda0'; +SELECT CHAR_LENGTH(@binary_literal_v); +SELECT CHAR_LENGTH(CONCAT(@binary_literal_v, 'a')), + CHAR_LENGTH(SUBSTR(@binary_literal_v, 1)), + CHAR_LENGTH(LOWER(@binary_literal_v)), + CHAR_LENGTH(REPEAT(@binary_literal_v, 2)), + CHAR_LENGTH(IF(TRUE, @binary_literal_v, '你好')), + CHAR_LENGTH(CASE WHEN TRUE THEN @binary_literal_v ELSE '你好' END), + CHAR_LENGTH(COALESCE(NULL, @binary_literal_v)); +SET @binary_numeric_v = X'3132'; +SELECT @binary_numeric_v + 0; +PREPARE binary_literal_stmt FROM 'SELECT CHAR_LENGTH(?)'; +SET @binary_literal_v = X'e4bda0'; +EXECUTE binary_literal_stmt USING @binary_literal_v; +SET @binary_literal_v = '你好'; +EXECUTE binary_literal_stmt USING @binary_literal_v; +SET @binary_literal_v = X'e4bda0'; +EXECUTE binary_literal_stmt USING @binary_literal_v; +DEALLOCATE PREPARE binary_literal_stmt; + +PREPARE binary_numeric_stmt FROM 'SELECT ? + 0'; +SET @binary_numeric_v = X'31'; +EXECUTE binary_numeric_stmt USING @binary_numeric_v; +SET @binary_numeric_v = '2'; +EXECUTE binary_numeric_stmt USING @binary_numeric_v; +SET @binary_numeric_v = X'31'; +EXECUTE binary_numeric_stmt USING @binary_numeric_v; +DEALLOCATE PREPARE binary_numeric_stmt; + +CREATE TABLE binary_literal_bigint (id INT AUTO_INCREMENT PRIMARY KEY, v BIGINT); +INSERT INTO binary_literal_bigint(v) VALUES (X'31'); +SET @binary_numeric_v = X'31'; +INSERT INTO binary_literal_bigint(v) VALUES (@binary_numeric_v); +SET @binary_numeric_v = '2'; +INSERT INTO binary_literal_bigint(v) VALUES (@binary_numeric_v); +SET @binary_numeric_v = X'31'; +INSERT INTO binary_literal_bigint(v) VALUES (@binary_numeric_v); +SELECT v FROM binary_literal_bigint ORDER BY id; +DROP TABLE binary_literal_bigint; + +# materialization keeps a binary string contract +CREATE TABLE binary_literal_ctas AS SELECT X'e4bda0' x; +SELECT DATA_TYPE, CHARACTER_MAXIMUM_LENGTH +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'binary_literal_ctas' AND COLUMN_NAME = 'x'; +SELECT CHAR_LENGTH(x) FROM binary_literal_ctas; +SELECT tag, CHAR_LENGTH(x) +FROM ( + SELECT 1 tag, X'e4bda0' x + UNION ALL + SELECT 2 tag, '你好' x +) u ORDER BY tag; +SELECT x + 0, CHAR_LENGTH(x) FROM (SELECT X'31' x) d; +DROP TABLE binary_literal_ctas; + +CREATE TABLE binary_literal_nested_ctas AS SELECT + CONCAT(X'e4bda0', 'a') concat_value, + SUBSTR(X'e4bda0', 1) substr_value, + LOWER(X'e4bda0') lower_value, + REPEAT(X'e4bda0', 2) repeat_value, + IF(TRUE, X'e4bda0', '你好') if_value, + CASE WHEN TRUE THEN X'e4bda0' ELSE '你好' END case_value, + COALESCE(NULL, X'e4bda0') coalesce_value; +SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'binary_literal_nested_ctas' + AND COLUMN_NAME != '__mo_fake_pk_col' +ORDER BY ORDINAL_POSITION; +SELECT CHAR_LENGTH(concat_value), CHAR_LENGTH(substr_value), CHAR_LENGTH(lower_value), + CHAR_LENGTH(repeat_value), CHAR_LENGTH(if_value), CHAR_LENGTH(case_value), + CHAR_LENGTH(coalesce_value) +FROM binary_literal_nested_ctas; +DROP TABLE binary_literal_nested_ctas; + +# numeric and explicit cast contexts keep their existing semantics +SELECT X'41' + 0, + B'1000001' + 0, + CAST(X'3132' AS UNSIGNED), + CAST(B'11000100110010' AS UNSIGNED), + HEX(CHAR(X'41')); +SELECT X'40' | X'01', + ~X'31', + 1.0 * (X'312E35' | X'312E35'); CREATE TABLE binary_char_length ( b BINARY(8), vb VARBINARY(8), @@ -18,6 +132,12 @@ SELECT CHAR_LENGTH(b), CHAR_LENGTH(vb), CHAR_LENGTH(bl) FROM binary_char_length; DROP TABLE binary_char_length; +CREATE TABLE binary_padding_compare (b BINARY(4)); +INSERT INTO binary_padding_compare VALUES ('a'); +SELECT b = X'61', X'61' = b FROM binary_padding_compare; +SELECT COUNT(*) FROM binary_padding_compare WHERE b = X'61'; +DROP TABLE binary_padding_compare; + #NULL SELECT CHAR_LENGTH(NULL);