github.com/apache/arrow/go/v15@v15.0.1/arrow/compute/exec/kernel.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one
     2  // or more contributor license agreements.  See the NOTICE file
     3  // distributed with this work for additional information
     4  // regarding copyright ownership.  The ASF licenses this file
     5  // to you under the Apache License, Version 2.0 (the
     6  // "License"); you may not use this file except in compliance
     7  // with the License.  You may obtain a copy of the License at
     8  //
     9  // http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  //go:build go1.18
    18  
    19  package exec
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"hash/maphash"
    25  	"strings"
    26  
    27  	"github.com/apache/arrow/go/v15/arrow"
    28  	"github.com/apache/arrow/go/v15/arrow/bitutil"
    29  	"github.com/apache/arrow/go/v15/arrow/internal/debug"
    30  	"github.com/apache/arrow/go/v15/arrow/memory"
    31  	"golang.org/x/exp/slices"
    32  )
    33  
    34  var hashSeed = maphash.MakeSeed()
    35  
    36  type ctxAllocKey struct{}
    37  
    38  // WithAllocator returns a new context with the provided allocator
    39  // embedded into the context.
    40  func WithAllocator(ctx context.Context, mem memory.Allocator) context.Context {
    41  	return context.WithValue(ctx, ctxAllocKey{}, mem)
    42  }
    43  
    44  // GetAllocator retrieves the allocator from the context, or returns
    45  // memory.DefaultAllocator if there was no allocator in the provided
    46  // context.
    47  func GetAllocator(ctx context.Context) memory.Allocator {
    48  	mem, ok := ctx.Value(ctxAllocKey{}).(memory.Allocator)
    49  	if !ok {
    50  		return memory.DefaultAllocator
    51  	}
    52  	return mem
    53  }
    54  
    55  // Kernel defines the minimum interface required for the basic execution
    56  // kernel. It will grow as the implementation requires.
    57  type Kernel interface {
    58  	GetInitFn() KernelInitFn
    59  	GetSig() *KernelSignature
    60  }
    61  
    62  // NonAggKernel builds on the base Kernel interface for
    63  // non aggregate execution kernels. Specifically this will
    64  // represent Scalar and Vector kernels.
    65  type NonAggKernel interface {
    66  	Kernel
    67  	Exec(*KernelCtx, *ExecSpan, *ExecResult) error
    68  	GetNullHandling() NullHandling
    69  	GetMemAlloc() MemAlloc
    70  	CanFillSlices() bool
    71  }
    72  
    73  // KernelCtx is a small struct holding the context for a kernel execution
    74  // consisting of a pointer to the kernel, initialized state (if needed)
    75  // and the context for this execution.
    76  type KernelCtx struct {
    77  	Ctx    context.Context
    78  	Kernel Kernel
    79  	State  KernelState
    80  }
    81  
    82  func (k *KernelCtx) Allocate(bufsize int) *memory.Buffer {
    83  	buf := memory.NewResizableBuffer(GetAllocator(k.Ctx))
    84  	buf.Resize(bufsize)
    85  	return buf
    86  }
    87  
    88  func (k *KernelCtx) AllocateBitmap(nbits int64) *memory.Buffer {
    89  	nbytes := bitutil.BytesForBits(nbits)
    90  	return k.Allocate(int(nbytes))
    91  }
    92  
    93  // TypeMatcher define an interface for matching Input or Output types
    94  // for execution kernels. There are multiple implementations of this
    95  // interface provided by this package.
    96  type TypeMatcher interface {
    97  	fmt.Stringer
    98  	Matches(typ arrow.DataType) bool
    99  	Equals(other TypeMatcher) bool
   100  }
   101  
   102  type sameTypeIDMatcher struct {
   103  	accepted arrow.Type
   104  }
   105  
   106  func (s sameTypeIDMatcher) Matches(typ arrow.DataType) bool { return s.accepted == typ.ID() }
   107  func (s sameTypeIDMatcher) Equals(other TypeMatcher) bool {
   108  	if s == other {
   109  		return true
   110  	}
   111  
   112  	o, ok := other.(*sameTypeIDMatcher)
   113  	if !ok {
   114  		return false
   115  	}
   116  
   117  	return s.accepted == o.accepted
   118  }
   119  
   120  func (s sameTypeIDMatcher) String() string {
   121  	return "Type::" + s.accepted.String()
   122  }
   123  
   124  // SameTypeID returns a type matcher which will match
   125  // any DataType that uses the same arrow.Type ID as the one
   126  // passed in here.
   127  func SameTypeID(id arrow.Type) TypeMatcher { return &sameTypeIDMatcher{id} }
   128  
   129  type timeUnitMatcher struct {
   130  	id   arrow.Type
   131  	unit arrow.TimeUnit
   132  }
   133  
   134  func (s timeUnitMatcher) Matches(typ arrow.DataType) bool {
   135  	if typ.ID() != s.id {
   136  		return false
   137  	}
   138  	return s.unit == typ.(arrow.TemporalWithUnit).TimeUnit()
   139  }
   140  
   141  func (s timeUnitMatcher) String() string {
   142  	return strings.ToLower(s.id.String()) + "(" + s.unit.String() + ")"
   143  }
   144  
   145  func (s *timeUnitMatcher) Equals(other TypeMatcher) bool {
   146  	if s == other {
   147  		return true
   148  	}
   149  
   150  	o, ok := other.(*timeUnitMatcher)
   151  	if !ok {
   152  		return false
   153  	}
   154  	return o.id == s.id && o.unit == s.unit
   155  }
   156  
   157  // TimestampTypeUnit returns a TypeMatcher that will match only
   158  // a Timestamp datatype with the specified TimeUnit.
   159  func TimestampTypeUnit(unit arrow.TimeUnit) TypeMatcher {
   160  	return &timeUnitMatcher{arrow.TIMESTAMP, unit}
   161  }
   162  
   163  // Time32TypeUnit returns a TypeMatcher that will match only
   164  // a Time32 datatype with the specified TimeUnit.
   165  func Time32TypeUnit(unit arrow.TimeUnit) TypeMatcher {
   166  	return &timeUnitMatcher{arrow.TIME32, unit}
   167  }
   168  
   169  // Time64TypeUnit returns a TypeMatcher that will match only
   170  // a Time64 datatype with the specified TimeUnit.
   171  func Time64TypeUnit(unit arrow.TimeUnit) TypeMatcher {
   172  	return &timeUnitMatcher{arrow.TIME64, unit}
   173  }
   174  
   175  // DurationTypeUnit returns a TypeMatcher that will match only
   176  // a Duration datatype with the specified TimeUnit.
   177  func DurationTypeUnit(unit arrow.TimeUnit) TypeMatcher {
   178  	return &timeUnitMatcher{arrow.DURATION, unit}
   179  }
   180  
   181  type integerMatcher struct{}
   182  
   183  func (integerMatcher) String() string                  { return "integer" }
   184  func (integerMatcher) Matches(typ arrow.DataType) bool { return arrow.IsInteger(typ.ID()) }
   185  func (integerMatcher) Equals(other TypeMatcher) bool {
   186  	_, ok := other.(integerMatcher)
   187  	return ok
   188  }
   189  
   190  type binaryLikeMatcher struct{}
   191  
   192  func (binaryLikeMatcher) String() string                  { return "binary-like" }
   193  func (binaryLikeMatcher) Matches(typ arrow.DataType) bool { return arrow.IsBinaryLike(typ.ID()) }
   194  func (binaryLikeMatcher) Equals(other TypeMatcher) bool {
   195  	_, ok := other.(binaryLikeMatcher)
   196  	return ok
   197  }
   198  
   199  type largeBinaryLikeMatcher struct{}
   200  
   201  func (largeBinaryLikeMatcher) String() string { return "large-binary-like" }
   202  func (largeBinaryLikeMatcher) Matches(typ arrow.DataType) bool {
   203  	return arrow.IsLargeBinaryLike(typ.ID())
   204  }
   205  func (largeBinaryLikeMatcher) Equals(other TypeMatcher) bool {
   206  	_, ok := other.(largeBinaryLikeMatcher)
   207  	return ok
   208  }
   209  
   210  type fsbLikeMatcher struct{}
   211  
   212  func (fsbLikeMatcher) String() string                  { return "fixed-size-binary-like" }
   213  func (fsbLikeMatcher) Matches(typ arrow.DataType) bool { return arrow.IsFixedSizeBinary(typ.ID()) }
   214  func (fsbLikeMatcher) Equals(other TypeMatcher) bool {
   215  	_, ok := other.(fsbLikeMatcher)
   216  	return ok
   217  }
   218  
   219  // Integer returns a TypeMatcher which will match any integral type like int8 or uint16
   220  func Integer() TypeMatcher { return integerMatcher{} }
   221  
   222  // BinaryLike returns a TypeMatcher that will match Binary or String
   223  func BinaryLike() TypeMatcher { return binaryLikeMatcher{} }
   224  
   225  // LargeBinaryLike returns a TypeMatcher which will match LargeBinary or LargeString
   226  func LargeBinaryLike() TypeMatcher { return largeBinaryLikeMatcher{} }
   227  
   228  // FixedSizeBinaryLike returns a TypeMatcher that will match FixedSizeBinary
   229  // or Decimal128/256
   230  func FixedSizeBinaryLike() TypeMatcher { return fsbLikeMatcher{} }
   231  
   232  type primitiveMatcher struct{}
   233  
   234  func (primitiveMatcher) String() string                  { return "primitive" }
   235  func (primitiveMatcher) Matches(typ arrow.DataType) bool { return arrow.IsPrimitive(typ.ID()) }
   236  func (primitiveMatcher) Equals(other TypeMatcher) bool {
   237  	_, ok := other.(primitiveMatcher)
   238  	return ok
   239  }
   240  
   241  // Primitive returns a TypeMatcher that will match any type that arrow.IsPrimitive
   242  // returns true for.
   243  func Primitive() TypeMatcher { return primitiveMatcher{} }
   244  
   245  type reeMatcher struct {
   246  	runEndsMatcher TypeMatcher
   247  	encodedMatcher TypeMatcher
   248  }
   249  
   250  func (r reeMatcher) Matches(typ arrow.DataType) bool {
   251  	if typ.ID() != arrow.RUN_END_ENCODED {
   252  		return false
   253  	}
   254  
   255  	dt := typ.(*arrow.RunEndEncodedType)
   256  	return r.runEndsMatcher.Matches(dt.RunEnds()) && r.encodedMatcher.Matches(dt.Encoded())
   257  }
   258  
   259  func (r reeMatcher) Equals(other TypeMatcher) bool {
   260  	o, ok := other.(reeMatcher)
   261  	if !ok {
   262  		return false
   263  	}
   264  	return r.runEndsMatcher.Equals(o.runEndsMatcher) && r.encodedMatcher.Equals(o.encodedMatcher)
   265  }
   266  
   267  func (r reeMatcher) String() string {
   268  	return "run_end_encoded(run_ends=" + r.runEndsMatcher.String() + ", values=" + r.encodedMatcher.String() + ")"
   269  }
   270  
   271  // RunEndEncoded returns a matcher which matches a RunEndEncoded
   272  // type whose encoded type is matched by the passed in matcher.
   273  func RunEndEncoded(runEndsMatcher, encodedMatcher TypeMatcher) TypeMatcher {
   274  	return reeMatcher{
   275  		runEndsMatcher: runEndsMatcher,
   276  		encodedMatcher: encodedMatcher}
   277  }
   278  
   279  // InputKind is an enum representing the type of Input matching
   280  // that will be done. Either accepting any type, an exact specific type
   281  // or using a TypeMatcher.
   282  type InputKind int8
   283  
   284  const (
   285  	InputAny InputKind = iota
   286  	InputExact
   287  	InputUseMatcher
   288  )
   289  
   290  // InputType is used for type checking arguments passed to a kernel
   291  // and stored within a KernelSignature. The type-checking rule can
   292  // be supplied either with an exact DataType instance or a custom
   293  // TypeMatcher.
   294  type InputType struct {
   295  	Kind    InputKind
   296  	Type    arrow.DataType
   297  	Matcher TypeMatcher
   298  }
   299  
   300  func NewExactInput(dt arrow.DataType) InputType { return InputType{Kind: InputExact, Type: dt} }
   301  func NewMatchedInput(match TypeMatcher) InputType {
   302  	return InputType{Kind: InputUseMatcher, Matcher: match}
   303  }
   304  func NewIDInput(id arrow.Type) InputType { return NewMatchedInput(SameTypeID(id)) }
   305  
   306  func (it InputType) MatchID() arrow.Type {
   307  	switch it.Kind {
   308  	case InputExact:
   309  		return it.Type.ID()
   310  	case InputUseMatcher:
   311  		if idMatch, ok := it.Matcher.(*sameTypeIDMatcher); ok {
   312  			return idMatch.accepted
   313  		}
   314  	}
   315  	debug.Assert(false, "MatchID called on non-id matching InputType")
   316  	return -1
   317  }
   318  
   319  func (it InputType) String() string {
   320  	switch it.Kind {
   321  	case InputAny:
   322  		return "any"
   323  	case InputUseMatcher:
   324  		return it.Matcher.String()
   325  	case InputExact:
   326  		return it.Type.String()
   327  	}
   328  	return ""
   329  }
   330  
   331  func (it *InputType) Equals(other *InputType) bool {
   332  	if it == other {
   333  		return true
   334  	}
   335  
   336  	if it.Kind != other.Kind {
   337  		return false
   338  	}
   339  
   340  	switch it.Kind {
   341  	case InputAny:
   342  		return true
   343  	case InputExact:
   344  		return arrow.TypeEqual(it.Type, other.Type)
   345  	case InputUseMatcher:
   346  		return it.Matcher.Equals(other.Matcher)
   347  	default:
   348  		return false
   349  	}
   350  }
   351  
   352  func (it InputType) Hash() uint64 {
   353  	var h maphash.Hash
   354  
   355  	h.SetSeed(hashSeed)
   356  	result := HashCombine(h.Sum64(), uint64(it.Kind))
   357  	switch it.Kind {
   358  	case InputExact:
   359  		result = HashCombine(result, arrow.HashType(hashSeed, it.Type))
   360  	}
   361  	return result
   362  }
   363  
   364  func (it InputType) Matches(dt arrow.DataType) bool {
   365  	switch it.Kind {
   366  	case InputExact:
   367  		return arrow.TypeEqual(it.Type, dt)
   368  	case InputUseMatcher:
   369  		return it.Matcher.Matches(dt)
   370  	case InputAny:
   371  		return true
   372  	default:
   373  		debug.Assert(false, "invalid InputKind")
   374  		return true
   375  	}
   376  }
   377  
   378  // ResolveKind defines the way that a particular OutputType resolves
   379  // its type. Either it has a fixed type to resolve to or it contains
   380  // a Resolver which will compute the resolved type based on
   381  // the input types.
   382  type ResolveKind int8
   383  
   384  const (
   385  	ResolveFixed ResolveKind = iota
   386  	ResolveComputed
   387  )
   388  
   389  // TypeResolver is simply a function that takes a KernelCtx and a list of input types
   390  // and returns the resolved type or an error.
   391  type TypeResolver = func(*KernelCtx, []arrow.DataType) (arrow.DataType, error)
   392  
   393  type OutputType struct {
   394  	Kind     ResolveKind
   395  	Type     arrow.DataType
   396  	Resolver TypeResolver
   397  }
   398  
   399  func NewOutputType(dt arrow.DataType) OutputType {
   400  	return OutputType{Kind: ResolveFixed, Type: dt}
   401  }
   402  
   403  func NewComputedOutputType(resolver TypeResolver) OutputType {
   404  	return OutputType{Kind: ResolveComputed, Resolver: resolver}
   405  }
   406  
   407  func (o OutputType) String() string {
   408  	if o.Kind == ResolveFixed {
   409  		return o.Type.String()
   410  	}
   411  	return "computed"
   412  }
   413  
   414  func (o OutputType) Resolve(ctx *KernelCtx, types []arrow.DataType) (arrow.DataType, error) {
   415  	switch o.Kind {
   416  	case ResolveFixed:
   417  		return o.Type, nil
   418  	}
   419  
   420  	return o.Resolver(ctx, types)
   421  }
   422  
   423  // NullHandling is an enum representing how a particular Kernel
   424  // wants the executor to handle nulls.
   425  type NullHandling int8
   426  
   427  const (
   428  	// Compute the output validity bitmap by intersection the validity
   429  	// bitmaps of the arguments using bitwise-and operations. This means
   430  	// that values in the output are valid/non-null only if the corresponding
   431  	// values in all input arguments were valid/non-null. Kernels generally
   432  	// do not have to touch the bitmap afterwards, but a kernel's exec function
   433  	// is permitted to alter the bitmap after the null intersection is computed
   434  	// if necessary.
   435  	NullIntersection NullHandling = iota
   436  	// Kernel expects a pre-allocated buffer to write the result bitmap
   437  	// into.
   438  	NullComputedPrealloc
   439  	// Kernel will allocate and set the validity bitmap of the output
   440  	NullComputedNoPrealloc
   441  	// kernel output is never null and a validity bitmap doesn't need to
   442  	// be allocated
   443  	NullNoOutput
   444  )
   445  
   446  // MemAlloc is the preference for preallocating memory of fixed-width
   447  // type outputs during kernel execution.
   448  type MemAlloc int8
   449  
   450  const (
   451  	// For data types that support pre-allocation (fixed-width), the
   452  	// kernel expects to be provided a pre-allocated buffer to write into.
   453  	// Non-fixed-width types must always allocate their own buffers.
   454  	// The allocation is made for the same length as the execution batch,
   455  	// so vector kernels yielding differently sized outputs should not
   456  	// use this.
   457  	//
   458  	// It is valid for the data to not be preallocated but the validity
   459  	// bitmap is (or is computed using intersection).
   460  	//
   461  	// For variable-size output types like Binary or String, or for nested
   462  	// types, this option has no effect.
   463  	MemPrealloc MemAlloc = iota
   464  	// The kernel is responsible for allocating its own data buffer
   465  	// for fixed-width output types.
   466  	MemNoPrealloc
   467  )
   468  
   469  type KernelState any
   470  
   471  // KernelInitArgs are the arguments required to initialize an Kernel's
   472  // state using the input types and any options.
   473  type KernelInitArgs struct {
   474  	Kernel Kernel
   475  	Inputs []arrow.DataType
   476  	// Options are opaque and specific to the Kernel being initialized,
   477  	// may be nil if the kernel doesn't require options.
   478  	Options any
   479  }
   480  
   481  // KernelInitFn is any function that receives a KernelCtx and initialization
   482  // arguments and returns the initialized state or an error.
   483  type KernelInitFn = func(*KernelCtx, KernelInitArgs) (KernelState, error)
   484  
   485  // KernelSignature holds the input and output types for a kernel.
   486  //
   487  // Variable argument functions with a minimum of N arguments should pass
   488  // up to N input types to be used to validate for invocation. The first
   489  // N-1 types will be matched against the first N-1 arguments and the last
   490  // type will be matched against the remaining arguments.
   491  type KernelSignature struct {
   492  	InputTypes []InputType
   493  	OutType    OutputType
   494  	IsVarArgs  bool
   495  
   496  	// store the hashcode after it is computed so we don't
   497  	// need to recompute it
   498  	hashCode uint64
   499  }
   500  
   501  func (k KernelSignature) String() string {
   502  	var b strings.Builder
   503  	if k.IsVarArgs {
   504  		b.WriteString("varargs[")
   505  	} else {
   506  		b.WriteByte('(')
   507  	}
   508  
   509  	for i, t := range k.InputTypes {
   510  		if i != 0 {
   511  			b.WriteString(", ")
   512  		}
   513  		b.WriteString(t.String())
   514  	}
   515  	if k.IsVarArgs {
   516  		b.WriteString("*]")
   517  	} else {
   518  		b.WriteByte(')')
   519  	}
   520  
   521  	b.WriteString(" -> ")
   522  	b.WriteString(k.OutType.String())
   523  	return b.String()
   524  }
   525  
   526  func (k KernelSignature) Equals(other KernelSignature) bool {
   527  	if k.IsVarArgs != other.IsVarArgs {
   528  		return false
   529  	}
   530  
   531  	return slices.EqualFunc(k.InputTypes, other.InputTypes, func(e1, e2 InputType) bool {
   532  		return e1.Equals(&e2)
   533  	})
   534  }
   535  
   536  func (k *KernelSignature) Hash() uint64 {
   537  	if k.hashCode != 0 {
   538  		return k.hashCode
   539  	}
   540  
   541  	var h maphash.Hash
   542  	h.SetSeed(hashSeed)
   543  	result := h.Sum64()
   544  	for _, typ := range k.InputTypes {
   545  		result = HashCombine(result, typ.Hash())
   546  	}
   547  	k.hashCode = result
   548  	return result
   549  }
   550  
   551  func (k KernelSignature) MatchesInputs(types []arrow.DataType) bool {
   552  	switch k.IsVarArgs {
   553  	case true:
   554  		// check that it has enough to match at least the non-vararg types
   555  		if len(types) < (len(k.InputTypes) - 1) {
   556  			return false
   557  		}
   558  
   559  		for i, t := range types {
   560  			if !k.InputTypes[Min(i, len(k.InputTypes)-1)].Matches(t) {
   561  				return false
   562  			}
   563  		}
   564  	case false:
   565  		if len(types) != len(k.InputTypes) {
   566  			return false
   567  		}
   568  		for i, t := range types {
   569  			if !k.InputTypes[i].Matches(t) {
   570  				return false
   571  			}
   572  		}
   573  	}
   574  	return true
   575  }
   576  
   577  // ArrayKernelExec is an alias definition for a kernel's execution function.
   578  //
   579  // This is used for both stateless and stateful kernels. If a kernel
   580  // depends on some execution state, it can be accessed from the KernelCtx
   581  // object, which also contains the context.Context object which can be
   582  // used for shortcircuiting by checking context.Done / context.Err.
   583  // This allows kernels to control handling timeouts or cancellation of
   584  // computation.
   585  type ArrayKernelExec = func(*KernelCtx, *ExecSpan, *ExecResult) error
   586  
   587  type kernel struct {
   588  	Init           KernelInitFn
   589  	Signature      *KernelSignature
   590  	Data           KernelState
   591  	Parallelizable bool
   592  }
   593  
   594  func (k kernel) GetInitFn() KernelInitFn  { return k.Init }
   595  func (k kernel) GetSig() *KernelSignature { return k.Signature }
   596  
   597  // A ScalarKernel is the kernel implementation for a Scalar Function.
   598  // In addition to the members found in the base Kernel, it contains
   599  // the null handling and memory pre-allocation preferences.
   600  type ScalarKernel struct {
   601  	kernel
   602  
   603  	ExecFn             ArrayKernelExec
   604  	CanWriteIntoSlices bool
   605  	NullHandling       NullHandling
   606  	MemAlloc           MemAlloc
   607  }
   608  
   609  // NewScalarKernel constructs a new kernel for scalar execution, constructing
   610  // a KernelSignature with the provided input types and output type, and using
   611  // the passed in execution implementation and initialization function.
   612  func NewScalarKernel(in []InputType, out OutputType, exec ArrayKernelExec, init KernelInitFn) ScalarKernel {
   613  	return NewScalarKernelWithSig(&KernelSignature{
   614  		InputTypes: in,
   615  		OutType:    out,
   616  	}, exec, init)
   617  }
   618  
   619  // NewScalarKernelWithSig is a convenience when you already have a signature
   620  // to use for constructing a kernel. It's equivalent to passing the components
   621  // of the signature (input and output types) to NewScalarKernel.
   622  func NewScalarKernelWithSig(sig *KernelSignature, exec ArrayKernelExec, init KernelInitFn) ScalarKernel {
   623  	return ScalarKernel{
   624  		kernel:             kernel{Signature: sig, Init: init, Parallelizable: true},
   625  		ExecFn:             exec,
   626  		CanWriteIntoSlices: true,
   627  		NullHandling:       NullIntersection,
   628  		MemAlloc:           MemPrealloc,
   629  	}
   630  }
   631  
   632  func (s *ScalarKernel) Exec(ctx *KernelCtx, sp *ExecSpan, out *ExecResult) error {
   633  	return s.ExecFn(ctx, sp, out)
   634  }
   635  
   636  func (s ScalarKernel) GetNullHandling() NullHandling { return s.NullHandling }
   637  func (s ScalarKernel) GetMemAlloc() MemAlloc         { return s.MemAlloc }
   638  func (s ScalarKernel) CanFillSlices() bool           { return s.CanWriteIntoSlices }
   639  
   640  // ChunkedExec is the signature for executing a stateful vector kernel
   641  // against a ChunkedArray input. It is optional
   642  type ChunkedExec func(*KernelCtx, []*arrow.Chunked, *ExecResult) ([]*ExecResult, error)
   643  
   644  // FinalizeFunc is an optional finalizer function for any postprocessing
   645  // that may need to be done on data before returning it
   646  type FinalizeFunc func(*KernelCtx, []*ArraySpan) ([]*ArraySpan, error)
   647  
   648  // VectorKernel is a structure for implementations of vector functions.
   649  // It can optionally contain a finalizer function, the null handling
   650  // and memory pre-allocation preferences (different defaults from
   651  // scalar kernels when using NewVectorKernel), and other execution related
   652  // options.
   653  type VectorKernel struct {
   654  	kernel
   655  
   656  	ExecFn              ArrayKernelExec
   657  	ExecChunked         ChunkedExec
   658  	Finalize            FinalizeFunc
   659  	NullHandling        NullHandling
   660  	MemAlloc            MemAlloc
   661  	CanWriteIntoSlices  bool
   662  	CanExecuteChunkWise bool
   663  	OutputChunked       bool
   664  }
   665  
   666  // NewVectorKernel constructs a new kernel for execution of vector functions,
   667  // which take into account more than just the individual scalar values
   668  // of its input. Output of a vector kernel may be a different length
   669  // than its inputs.
   670  func NewVectorKernel(inTypes []InputType, outType OutputType, exec ArrayKernelExec, init KernelInitFn) VectorKernel {
   671  	return NewVectorKernelWithSig(&KernelSignature{
   672  		InputTypes: inTypes, OutType: outType}, exec, init)
   673  }
   674  
   675  // NewVectorKernelWithSig is a convenience function for creating a kernel
   676  // when you already have a signature constructed.
   677  func NewVectorKernelWithSig(sig *KernelSignature, exec ArrayKernelExec, init KernelInitFn) VectorKernel {
   678  	return VectorKernel{
   679  		kernel:              kernel{Signature: sig, Init: init, Parallelizable: true},
   680  		ExecFn:              exec,
   681  		CanWriteIntoSlices:  true,
   682  		CanExecuteChunkWise: true,
   683  		OutputChunked:       true,
   684  		NullHandling:        NullComputedNoPrealloc,
   685  		MemAlloc:            MemNoPrealloc,
   686  	}
   687  }
   688  
   689  func (s *VectorKernel) Exec(ctx *KernelCtx, sp *ExecSpan, out *ExecResult) error {
   690  	return s.ExecFn(ctx, sp, out)
   691  }
   692  
   693  func (s VectorKernel) GetNullHandling() NullHandling { return s.NullHandling }
   694  func (s VectorKernel) GetMemAlloc() MemAlloc         { return s.MemAlloc }
   695  func (s VectorKernel) CanFillSlices() bool           { return s.CanWriteIntoSlices }