https://github.com/google/cayley
Raw File
Tip revision: 3091e673e8d9a945a71e286ca90f5e478e3ac503 authored by Denys Smirnov on 03 March 2018, 18:40:57 UTC
schema: allow to customize behavior; resolves #694
Tip revision: 3091e67
value_comparison.go
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// 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 iterator

// "Value Comparison" is a unary operator -- a filter across the values in the
// relevant subiterator.
//
// This is hugely useful for things like label, but value ranges in general
// come up from time to time. At *worst* we're as big as our underlying iterator.
// At best, we're the null iterator.
//
// This is ripe for backend-side optimization. If you can run a value iterator,
// from a sorted set -- some sort of value index, then go for it.
//
// In MQL terms, this is the [{"age>=": 21}] concept.

import (
	"context"
	"fmt"
	"time"

	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad"
)

type Operator int

func (op Operator) String() string {
	switch op {
	case CompareLT:
		return "<"
	case CompareLTE:
		return "<="
	case CompareGT:
		return ">"
	case CompareGTE:
		return ">="
	default:
		return fmt.Sprintf("op(%d)", int(op))
	}
}

const (
	CompareLT Operator = iota
	CompareLTE
	CompareGT
	CompareGTE
	// Why no Equals? Because that's usually an AndIterator.
)

var _ graph.Iterator = &Comparison{}

type Comparison struct {
	uid    uint64
	tags   graph.Tagger
	subIt  graph.Iterator
	op     Operator
	val    quad.Value
	qs     graph.QuadStore
	result graph.Value
	err    error
}

func NewComparison(sub graph.Iterator, op Operator, val quad.Value, qs graph.QuadStore) *Comparison {
	return &Comparison{
		uid:   NextUID(),
		subIt: sub,
		op:    op,
		val:   val,
		qs:    qs,
	}
}

func (it *Comparison) UID() uint64 {
	return it.uid
}

func (it *Comparison) Value() quad.Value  { return it.val }
func (it *Comparison) Operator() Operator { return it.op }

// Here's the non-boilerplate part of the ValueComparison iterator. Given a value
// and our operator, determine whether or not we meet the requirement.
func (it *Comparison) doComparison(val graph.Value) bool {
	qval := it.qs.NameOf(val)
	switch cVal := it.val.(type) {
	case quad.Int:
		if cVal2, ok := qval.(quad.Int); ok {
			return RunIntOp(cVal2, it.op, cVal)
		}
		return false
	case quad.Float:
		if cVal2, ok := qval.(quad.Float); ok {
			return RunFloatOp(cVal2, it.op, cVal)
		}
		return false
	case quad.String:
		if cVal2, ok := qval.(quad.String); ok {
			return RunStrOp(string(cVal2), it.op, string(cVal))
		}
		return false
	case quad.BNode:
		if cVal2, ok := qval.(quad.BNode); ok {
			return RunStrOp(string(cVal2), it.op, string(cVal))
		}
		return false
	case quad.IRI:
		if cVal2, ok := qval.(quad.IRI); ok {
			return RunStrOp(string(cVal2), it.op, string(cVal))
		}
		return false
	case quad.Time:
		if cVal2, ok := qval.(quad.Time); ok {
			return RunTimeOp(time.Time(cVal2), it.op, time.Time(cVal))
		}
		return false
	default:
		return RunStrOp(quad.StringOf(qval), it.op, quad.StringOf(it.val))
	}
}

func (it *Comparison) Close() error {
	return it.subIt.Close()
}

func RunIntOp(a quad.Int, op Operator, b quad.Int) bool {
	switch op {
	case CompareLT:
		return a < b
	case CompareLTE:
		return a <= b
	case CompareGT:
		return a > b
	case CompareGTE:
		return a >= b
	default:
		panic("Unknown operator type")
	}
}

func RunFloatOp(a quad.Float, op Operator, b quad.Float) bool {
	switch op {
	case CompareLT:
		return a < b
	case CompareLTE:
		return a <= b
	case CompareGT:
		return a > b
	case CompareGTE:
		return a >= b
	default:
		panic("Unknown operator type")
	}
}

func RunStrOp(a string, op Operator, b string) bool {
	switch op {
	case CompareLT:
		return a < b
	case CompareLTE:
		return a <= b
	case CompareGT:
		return a > b
	case CompareGTE:
		return a >= b
	default:
		panic("Unknown operator type")
	}
}

func RunTimeOp(a time.Time, op Operator, b time.Time) bool {
	switch op {
	case CompareLT:
		return a.Before(b)
	case CompareLTE:
		return !a.After(b)
	case CompareGT:
		return a.After(b)
	case CompareGTE:
		return !a.Before(b)
	default:
		panic("Unknown operator type")
	}
}

func (it *Comparison) Reset() {
	it.subIt.Reset()
	it.err = nil
	it.result = nil
}

func (it *Comparison) Tagger() *graph.Tagger {
	return &it.tags
}

func (it *Comparison) Clone() graph.Iterator {
	out := NewComparison(it.subIt.Clone(), it.op, it.val, it.qs)
	out.tags.CopyFrom(it)
	return out
}

func (it *Comparison) Next(ctx context.Context) bool {
	for it.subIt.Next(ctx) {
		val := it.subIt.Result()
		if it.doComparison(val) {
			it.result = val
			return true
		}
	}
	it.err = it.subIt.Err()
	return false
}

func (it *Comparison) Err() error {
	return it.err
}

func (it *Comparison) Result() graph.Value {
	return it.result
}

func (it *Comparison) NextPath(ctx context.Context) bool {
	for {
		hasNext := it.subIt.NextPath(ctx)
		if !hasNext {
			it.err = it.subIt.Err()
			return false
		}
		if it.doComparison(it.subIt.Result()) {
			break
		}
	}
	it.result = it.subIt.Result()
	return true
}

func (it *Comparison) SubIterators() []graph.Iterator {
	return []graph.Iterator{it.subIt}
}

func (it *Comparison) Contains(ctx context.Context, val graph.Value) bool {
	if !it.doComparison(val) {
		return false
	}
	ok := it.subIt.Contains(ctx, val)
	if !ok {
		it.err = it.subIt.Err()
	}
	return ok
}

// If we failed the check, then the subiterator should not contribute to the result
// set. Otherwise, go ahead and tag it.
func (it *Comparison) TagResults(dst map[string]graph.Value) {
	it.tags.TagResult(dst, it.Result())

	it.subIt.TagResults(dst)
}

// Registers the value-comparison iterator.
func (it *Comparison) Type() graph.Type { return graph.Comparison }

func (it *Comparison) String() string {
	return fmt.Sprintf("Comparison(%v, %v)", it.op, it.val)
}

// There's nothing to optimize, locally, for a value-comparison iterator.
// Replace the underlying iterator if need be.
// potentially replace it.
func (it *Comparison) Optimize() (graph.Iterator, bool) {
	newSub, changed := it.subIt.Optimize()
	if changed {
		it.subIt.Close()
		it.subIt = newSub
	}
	return it, false
}

// We're only as expensive as our subiterator.
// Again, optimized value comparison iterators should do better.
func (it *Comparison) Stats() graph.IteratorStats {
	return it.subIt.Stats()
}

func (it *Comparison) Size() (int64, bool) {
	sz, _ := it.subIt.Size()
	return sz / 2, false
}
back to top