vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/distinctV3.go (about)

     1  /*
     2  Copyright 2020 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package engine
    18  
    19  import (
    20  	"context"
    21  
    22  	"vitess.io/vitess/go/mysql/collations"
    23  	"vitess.io/vitess/go/sqltypes"
    24  	querypb "vitess.io/vitess/go/vt/proto/query"
    25  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    26  )
    27  
    28  // DistinctV3 Primitive is used to uniqueify results
    29  // It does not always work, and should be removed once the V3 planner has been removed
    30  var _ Primitive = (*DistinctV3)(nil)
    31  
    32  // Distinct Primitive is used to uniqueify results
    33  type DistinctV3 struct {
    34  	Source Primitive
    35  }
    36  
    37  type row = []sqltypes.Value
    38  
    39  type probeTableV3 struct {
    40  	m map[evalengine.HashCode][]row
    41  }
    42  
    43  func (pt *probeTableV3) exists(inputRow row) (bool, error) {
    44  	// calculate hashcode from all column values in the input row
    45  	code := evalengine.HashCode(17)
    46  	for _, value := range inputRow {
    47  		hashcode, err := evalengine.NullsafeHashcode(value, collations.Unknown, value.Type())
    48  		if err != nil {
    49  			return false, err
    50  		}
    51  		code = code*31 + hashcode
    52  	}
    53  
    54  	existingRows, found := pt.m[code]
    55  	if !found {
    56  		// nothing with this hash code found, we can be sure it's a not seen row
    57  		pt.m[code] = []row{inputRow}
    58  		return false, nil
    59  	}
    60  
    61  	// we found something in the map - still need to check all individual values
    62  	// so we don't just fall for a hash collision
    63  	for _, existingRow := range existingRows {
    64  		exists, err := equalV3(existingRow, inputRow)
    65  		if err != nil {
    66  			return false, err
    67  		}
    68  		if exists {
    69  			return true, nil
    70  		}
    71  	}
    72  
    73  	pt.m[code] = append(existingRows, inputRow)
    74  
    75  	return false, nil
    76  }
    77  
    78  func equalV3(a, b []sqltypes.Value) (bool, error) {
    79  	for i, aVal := range a {
    80  		cmp, err := evalengine.NullsafeCompare(aVal, b[i], collations.Unknown)
    81  		if err != nil {
    82  			return false, err
    83  		}
    84  		if cmp != 0 {
    85  			return false, nil
    86  		}
    87  	}
    88  	return true, nil
    89  }
    90  
    91  func newProbeTableV3() *probeTableV3 {
    92  	return &probeTableV3{m: map[evalengine.HashCode][]row{}}
    93  }
    94  
    95  // TryExecute implements the Primitive interface
    96  func (d *DistinctV3) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    97  	input, err := vcursor.ExecutePrimitive(ctx, d.Source, bindVars, wantfields)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	result := &sqltypes.Result{
   103  		Fields:   input.Fields,
   104  		InsertID: input.InsertID,
   105  	}
   106  
   107  	pt := newProbeTableV3()
   108  
   109  	for _, row := range input.Rows {
   110  		exists, err := pt.exists(row)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  		if !exists {
   115  			result.Rows = append(result.Rows, row)
   116  		}
   117  	}
   118  
   119  	return result, err
   120  }
   121  
   122  // TryStreamExecute implements the Primitive interface
   123  func (d *DistinctV3) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
   124  	pt := newProbeTableV3()
   125  
   126  	err := vcursor.StreamExecutePrimitive(ctx, d.Source, bindVars, wantfields, func(input *sqltypes.Result) error {
   127  		result := &sqltypes.Result{
   128  			Fields:   input.Fields,
   129  			InsertID: input.InsertID,
   130  		}
   131  		for _, row := range input.Rows {
   132  			exists, err := pt.exists(row)
   133  			if err != nil {
   134  				return err
   135  			}
   136  			if !exists {
   137  				result.Rows = append(result.Rows, row)
   138  			}
   139  		}
   140  		return callback(result)
   141  	})
   142  
   143  	return err
   144  }
   145  
   146  // RouteType implements the Primitive interface
   147  func (d *DistinctV3) RouteType() string {
   148  	return d.Source.RouteType()
   149  }
   150  
   151  // GetKeyspaceName implements the Primitive interface
   152  func (d *DistinctV3) GetKeyspaceName() string {
   153  	return d.Source.GetKeyspaceName()
   154  }
   155  
   156  // GetTableName implements the Primitive interface
   157  func (d *DistinctV3) GetTableName() string {
   158  	return d.Source.GetTableName()
   159  }
   160  
   161  // GetFields implements the Primitive interface
   162  func (d *DistinctV3) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
   163  	return d.Source.GetFields(ctx, vcursor, bindVars)
   164  }
   165  
   166  // NeedsTransaction implements the Primitive interface
   167  func (d *DistinctV3) NeedsTransaction() bool {
   168  	return d.Source.NeedsTransaction()
   169  }
   170  
   171  // Inputs implements the Primitive interface
   172  func (d *DistinctV3) Inputs() []Primitive {
   173  	return []Primitive{d.Source}
   174  }
   175  
   176  func (d *DistinctV3) description() PrimitiveDescription {
   177  	return PrimitiveDescription{
   178  		OperatorType: "Distinct",
   179  	}
   180  }