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 }