github.com/cayleygraph/cayley@v0.7.7/graph/sql/shape.go (about)

     1  // Copyright 2017 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sql
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/cayleygraph/cayley/graph"
    23  	"github.com/cayleygraph/cayley/graph/iterator"
    24  	"github.com/cayleygraph/cayley/graph/shape"
    25  	"github.com/cayleygraph/quad"
    26  )
    27  
    28  var DefaultDialect = QueryDialect{
    29  	FieldQuote: func(s string) string {
    30  		return strconv.Quote(s)
    31  	},
    32  	Placeholder: func(_ int) string {
    33  		return "?"
    34  	},
    35  }
    36  
    37  type QueryDialect struct {
    38  	RegexpOp    CmpOp
    39  	FieldQuote  func(string) string
    40  	Placeholder func(int) string
    41  }
    42  
    43  func NewBuilder(d QueryDialect) *Builder {
    44  	return &Builder{d: d}
    45  }
    46  
    47  type Builder struct {
    48  	d  QueryDialect
    49  	pi int
    50  }
    51  
    52  func needQuotes(s string) bool {
    53  	for i, r := range s {
    54  		if (r < 'a' || r > 'z') && r != '_' && (i == 0 || r < '0' || r > '9') {
    55  			return true
    56  		}
    57  	}
    58  	return false
    59  }
    60  func (b *Builder) EscapeField(s string) string {
    61  	if !needQuotes(s) {
    62  		return s
    63  	}
    64  	return b.d.FieldQuote(s)
    65  }
    66  func (b *Builder) Placeholder() string {
    67  	b.pi++
    68  	return b.d.Placeholder(b.pi)
    69  }
    70  
    71  const (
    72  	tagPref = "__"
    73  	tagNode = tagPref + "node"
    74  )
    75  
    76  func dirField(d quad.Direction) string {
    77  	return d.String() + "_hash"
    78  }
    79  
    80  func dirTag(d quad.Direction) string {
    81  	return tagPref + d.String()
    82  }
    83  
    84  type Value interface {
    85  	SQLValue() interface{}
    86  }
    87  
    88  type Shape interface {
    89  	SQL(b *Builder) string
    90  	Args() []Value
    91  	Columns() []string
    92  }
    93  
    94  func AllNodes() Select {
    95  	return Nodes(nil, nil)
    96  }
    97  
    98  func Nodes(where []Where, params []Value) Select {
    99  	return Select{
   100  		Fields: []Field{
   101  			{Name: "hash", Alias: tagNode},
   102  		},
   103  		From: []Source{
   104  			Table{Name: "nodes"},
   105  		},
   106  		Where:  where,
   107  		Params: params,
   108  	}
   109  }
   110  
   111  func AllQuads(alias string) Select {
   112  	sel := Select{
   113  		From: []Source{
   114  			Table{Name: "quads", Alias: alias},
   115  		},
   116  	}
   117  	for _, d := range quad.Directions {
   118  		sel.Fields = append(sel.Fields, Field{
   119  			Table: alias,
   120  			Name:  dirField(d),
   121  			Alias: dirTag(d),
   122  		})
   123  	}
   124  	return sel
   125  }
   126  
   127  type FieldName struct {
   128  	Name  string
   129  	Table string
   130  }
   131  
   132  func (FieldName) isExpr() {}
   133  func (f FieldName) SQL(b *Builder) string {
   134  	name := b.EscapeField(f.Name)
   135  	if f.Table != "" {
   136  		name = f.Table + "." + name
   137  	}
   138  	return name
   139  }
   140  
   141  type Field struct {
   142  	Name  string
   143  	Raw   bool // do not quote Name
   144  	Alias string
   145  	Table string
   146  }
   147  
   148  func (f Field) SQL(b *Builder) string {
   149  	name := f.Name
   150  	if !f.Raw {
   151  		name = b.EscapeField(name)
   152  	}
   153  	if f.Table != "" {
   154  		name = f.Table + "." + name
   155  	}
   156  	if f.Alias == "" {
   157  		return name
   158  	}
   159  	return name + " AS " + b.EscapeField(f.Alias)
   160  }
   161  func (f Field) NameOrAlias() string {
   162  	if f.Alias != "" {
   163  		return f.Alias
   164  	}
   165  	return f.Name
   166  }
   167  
   168  type Source interface {
   169  	SQL(b *Builder) string
   170  	Args() []Value
   171  	isSource()
   172  }
   173  
   174  type Table struct {
   175  	Name  string
   176  	Alias string
   177  }
   178  
   179  func (Table) isSource() {}
   180  
   181  type Subquery struct {
   182  	Query Select
   183  	Alias string
   184  }
   185  
   186  func (Subquery) isSource() {}
   187  func (s Subquery) SQL(b *Builder) string {
   188  	q := "(" + s.Query.SQL(b) + ")"
   189  	if s.Alias != "" {
   190  		q += " AS " + b.EscapeField(s.Alias)
   191  	}
   192  	return q
   193  }
   194  func (s Subquery) Args() []Value {
   195  	return s.Query.Args()
   196  }
   197  
   198  func (f Table) SQL(b *Builder) string {
   199  	if f.Alias == "" {
   200  		return f.Name
   201  	}
   202  	return f.Name + " AS " + b.EscapeField(f.Alias)
   203  }
   204  
   205  func (f Table) Args() []Value {
   206  	return nil
   207  }
   208  func (f Table) NameSQL() string {
   209  	if f.Alias != "" {
   210  		return f.Alias
   211  	}
   212  	return f.Name
   213  }
   214  
   215  type CmpOp string
   216  
   217  const (
   218  	OpEqual  = CmpOp("=")
   219  	OpGT     = CmpOp(">")
   220  	OpGTE    = CmpOp(">=")
   221  	OpLT     = CmpOp("<")
   222  	OpLTE    = CmpOp("<=")
   223  	OpIsNull = CmpOp("IS NULL")
   224  	OpIsTrue = CmpOp("IS true")
   225  )
   226  
   227  type Expr interface {
   228  	isExpr()
   229  	SQL(b *Builder) string
   230  }
   231  
   232  type Placeholder struct{}
   233  
   234  func (Placeholder) isExpr() {}
   235  
   236  func (Placeholder) SQL(b *Builder) string {
   237  	return b.Placeholder()
   238  }
   239  
   240  type Where struct {
   241  	Field string
   242  	Table string
   243  	Op    CmpOp
   244  	Value Expr
   245  }
   246  
   247  func (w Where) SQL(b *Builder) string {
   248  	name := w.Field
   249  	if w.Table != "" {
   250  		name = w.Table + "." + b.EscapeField(name)
   251  	}
   252  	parts := []string{name, string(w.Op)}
   253  	if w.Value != nil {
   254  		parts = append(parts, w.Value.SQL(b))
   255  	}
   256  	return strings.Join(parts, " ")
   257  }
   258  
   259  var _ Shape = Select{}
   260  
   261  // Select is a simplified representation of SQL SELECT query.
   262  type Select struct {
   263  	Fields []Field
   264  	From   []Source
   265  	Where  []Where
   266  	Params []Value
   267  	Limit  int64
   268  	Offset int64
   269  
   270  	// TODO(dennwc): this field in unexported because we don't want it to a be a part of the API
   271  	//               however, it's necessary to make NodesFrom optimizations to work with SQL
   272  	nextPath bool
   273  }
   274  
   275  func (s Select) Clone() Select {
   276  	s.Fields = append([]Field{}, s.Fields...)
   277  	s.From = append([]Source{}, s.From...)
   278  	s.Where = append([]Where{}, s.Where...)
   279  	s.Params = append([]Value{}, s.Params...)
   280  	return s
   281  }
   282  
   283  func (s Select) isAll() bool {
   284  	return len(s.From) == 1 && len(s.Where) == 0 && len(s.Params) == 0 && !s.onlyAsSubquery()
   285  }
   286  
   287  // onlyAsSubquery indicates that query cannot be merged into existing SELECT because of some specific properties of query.
   288  // An example of such properties might be LIMIT, DISTINCT, etc.
   289  func (s Select) onlyAsSubquery() bool {
   290  	return s.Limit > 0 || s.Offset > 0
   291  }
   292  
   293  func (s Select) Columns() []string {
   294  	names := make([]string, 0, len(s.Fields))
   295  	for _, f := range s.Fields {
   296  		name := f.Alias
   297  		if name == "" {
   298  			name = f.Name
   299  		}
   300  		names = append(names, name)
   301  	}
   302  	return names
   303  }
   304  
   305  func (s Select) BuildIterator(qs graph.QuadStore) graph.Iterator {
   306  	sq, ok := qs.(*QuadStore)
   307  	if !ok {
   308  		return iterator.NewError(fmt.Errorf("not a SQL quadstore: %T", qs))
   309  	}
   310  	return sq.NewIterator(s)
   311  }
   312  
   313  func (s Select) Optimize(r shape.Optimizer) (shape.Shape, bool) {
   314  	// TODO: call optimize on sub-tables? but what if it decides to de-optimize our SQL shape?
   315  	return s, false
   316  }
   317  
   318  func (s *Select) AppendParam(o Value) Expr {
   319  	s.Params = append(s.Params, o)
   320  	return Placeholder{}
   321  }
   322  
   323  func (s *Select) WhereEq(tbl, field string, v Value) {
   324  	s.Where = append(s.Where, Where{
   325  		Table: tbl,
   326  		Field: field,
   327  		Op:    OpEqual,
   328  		Value: s.AppendParam(v),
   329  	})
   330  }
   331  
   332  func (s Select) SQL(b *Builder) string {
   333  	var parts []string
   334  
   335  	var fields []string
   336  	for _, f := range s.Fields {
   337  		fields = append(fields, f.SQL(b))
   338  	}
   339  	parts = append(parts, "SELECT "+strings.Join(fields, ", "))
   340  
   341  	var tables []string
   342  	for _, t := range s.From {
   343  		tables = append(tables, t.SQL(b))
   344  	}
   345  	parts = append(parts, "FROM "+strings.Join(tables, ", "))
   346  
   347  	if len(s.Where) != 0 {
   348  		var wheres []string
   349  		for _, w := range s.Where {
   350  			wheres = append(wheres, w.SQL(b))
   351  		}
   352  		parts = append(parts, "WHERE "+strings.Join(wheres, " AND "))
   353  	}
   354  	if s.Limit > 0 {
   355  		parts = append(parts, "LIMIT "+strconv.FormatInt(s.Limit, 10))
   356  	}
   357  	if s.Offset > 0 {
   358  		parts = append(parts, "OFFSET "+strconv.FormatInt(s.Offset, 10))
   359  	}
   360  	sep := " "
   361  	if len(fields) > 1 {
   362  		sep = "\n\t"
   363  	}
   364  	return strings.Join(parts, sep)
   365  }
   366  func (s Select) Args() []Value {
   367  	var args []Value
   368  	// first add args for FROM subqueries
   369  	for _, q := range s.From {
   370  		args = append(args, q.Args()...)
   371  	}
   372  	// and add params for WHERE
   373  	args = append(args, s.Params...)
   374  	return args
   375  }