github.com/cayleygraph/cayley@v0.7.7/graph/sql/shape_test.go (about) 1 package sql 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/cayleygraph/cayley/graph" 8 "github.com/cayleygraph/cayley/graph/iterator" 9 "github.com/cayleygraph/cayley/graph/shape" 10 "github.com/cayleygraph/quad" 11 "github.com/stretchr/testify/require" 12 ) 13 14 type stringVal string 15 16 func (s stringVal) Key() interface{} { 17 return string(s) 18 } 19 20 func (s stringVal) SQLValue() interface{} { 21 return string(s) 22 } 23 24 func sVal(s string) stringVal { 25 return stringVal(s) 26 } 27 28 func sVals(arr ...string) []Value { 29 out := make([]Value, 0, len(arr)) 30 for _, s := range arr { 31 out = append(out, sVal(s)) 32 } 33 return out 34 } 35 36 var shapeCases = []struct { 37 skip bool 38 name string 39 s shape.Shape 40 qu string 41 args []Value 42 }{ 43 { 44 name: "all nodes", 45 s: shape.AllNodes{}, 46 qu: `SELECT hash AS ` + tagNode + ` FROM nodes`, 47 }, 48 { 49 name: "lookup iri", 50 s: shape.Lookup{quad.IRI("a")}, 51 qu: `SELECT hash AS ` + tagNode + ` FROM nodes WHERE hash = $1`, 52 args: []Value{HashOf(quad.IRI("a"))}, 53 }, 54 { 55 name: "gt iri", 56 s: shape.Filter{ 57 From: shape.AllNodes{}, 58 Filters: []shape.ValueFilter{ 59 shape.Comparison{Op: iterator.CompareGT, Val: quad.IRI("a")}, 60 }, 61 }, 62 qu: `SELECT hash AS ` + tagNode + ` FROM nodes WHERE value_string > $1 AND iri IS true`, 63 args: []Value{StringVal("a")}, 64 }, 65 { 66 name: "gt string", 67 s: shape.Filter{ 68 From: shape.AllNodes{}, 69 Filters: []shape.ValueFilter{ 70 shape.Comparison{Op: iterator.CompareGT, Val: quad.String("a")}, 71 }, 72 }, 73 qu: `SELECT hash AS ` + tagNode + ` FROM nodes WHERE value_string > $1 AND iri IS NULL AND bnode IS NULL AND datatype IS NULL AND language IS NULL`, 74 args: []Value{StringVal("a")}, 75 }, 76 { 77 name: "gt typed string", 78 s: shape.Filter{ 79 From: shape.AllNodes{}, 80 Filters: []shape.ValueFilter{ 81 shape.Comparison{Op: iterator.CompareGT, Val: quad.TypedString{Value: "a", Type: "A"}}, 82 }, 83 }, 84 qu: `SELECT hash AS ` + tagNode + ` FROM nodes WHERE value_string > $1 AND datatype = $2`, 85 args: []Value{StringVal("a"), StringVal("A")}, 86 }, 87 { 88 name: "lookup int", 89 s: shape.Filter{ 90 From: shape.AllNodes{}, 91 Filters: []shape.ValueFilter{ 92 shape.Comparison{Op: iterator.CompareGT, Val: quad.Int(42)}, 93 }, 94 }, 95 qu: `SELECT hash AS ` + tagNode + ` FROM nodes WHERE value_int > $1`, 96 args: []Value{IntVal(42)}, 97 }, 98 { 99 name: "all quads", 100 s: shape.Quads{}, 101 qu: `SELECT t_1.subject_hash AS __subject, t_1.predicate_hash AS __predicate, t_1.object_hash AS __object, t_1.label_hash AS __label 102 FROM quads AS t_1`, 103 }, 104 { 105 name: "limit quads and skip first", 106 s: shape.Page{From: shape.Quads{}, Limit: 100, Skip: 1}, 107 qu: `SELECT t_1.subject_hash AS __subject, t_1.predicate_hash AS __predicate, t_1.object_hash AS __object, t_1.label_hash AS __label 108 FROM quads AS t_1 109 LIMIT 100 110 OFFSET 1`, 111 }, 112 { 113 name: "quads with subject and predicate", 114 s: shape.Quads{ 115 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 116 {Dir: quad.Predicate, Values: shape.Fixed{sVal("p")}}, 117 }, 118 qu: `SELECT t_1.subject_hash AS __subject, t_1.predicate_hash AS __predicate, t_1.object_hash AS __object, t_1.label_hash AS __label 119 FROM quads AS t_1 120 WHERE t_1.subject_hash = $1 AND t_1.predicate_hash = $2`, 121 args: sVals("s", "p"), 122 }, 123 { 124 name: "quad actions", 125 s: shape.QuadsAction{ 126 Result: quad.Subject, 127 Save: map[quad.Direction][]string{ 128 quad.Object: {"o1", "o2"}, 129 quad.Label: {"l 1"}, 130 }, 131 Filter: map[quad.Direction]graph.Ref{ 132 quad.Predicate: sVal("p"), 133 }, 134 }, 135 qu: `SELECT subject_hash AS ` + tagNode + `, object_hash AS o1, object_hash AS o2, label_hash AS "l 1" 136 FROM quads 137 WHERE predicate_hash = $1`, 138 args: sVals("p"), 139 }, 140 { 141 name: "quad actions and save", 142 s: shape.Save{ 143 Tags: []string{"sub"}, 144 From: shape.QuadsAction{ 145 Result: quad.Subject, 146 Save: map[quad.Direction][]string{ 147 quad.Object: {"o1", "o2"}, 148 quad.Label: {"l 1"}, 149 }, 150 Filter: map[quad.Direction]graph.Ref{ 151 quad.Predicate: sVal("p"), 152 }, 153 }, 154 }, 155 qu: `SELECT subject_hash AS sub, subject_hash AS ` + tagNode + `, object_hash AS o1, object_hash AS o2, label_hash AS "l 1" 156 FROM quads 157 WHERE predicate_hash = $1`, 158 args: sVals("p"), 159 }, 160 { 161 name: "quads with subquery", 162 s: shape.Quads{ 163 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 164 { 165 Dir: quad.Predicate, 166 Values: shape.QuadsAction{ 167 Result: quad.Subject, 168 Filter: map[quad.Direction]graph.Ref{ 169 quad.Predicate: sVal("p"), 170 }, 171 }, 172 }, 173 }, 174 qu: `SELECT t_1.subject_hash AS __subject, t_1.predicate_hash AS __predicate, t_1.object_hash AS __object, t_1.label_hash AS __label 175 FROM quads AS t_1, (SELECT subject_hash AS ` + tagNode + ` FROM quads WHERE predicate_hash = $1) AS t_2 176 WHERE t_1.subject_hash = $2 AND t_1.predicate_hash = t_2.` + tagNode, 177 args: sVals("p", "s"), 178 }, 179 { 180 name: "quads with subquery (inner tags)", 181 s: shape.Quads{ 182 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 183 { 184 Dir: quad.Predicate, 185 Values: shape.Save{ 186 Tags: []string{"pred"}, 187 From: shape.QuadsAction{ 188 Result: quad.Subject, 189 Save: map[quad.Direction][]string{ 190 quad.Object: {"ob"}, 191 }, 192 Filter: map[quad.Direction]graph.Ref{ 193 quad.Predicate: sVal("p"), 194 }, 195 }, 196 }, 197 }, 198 }, 199 qu: `SELECT t_1.subject_hash AS __subject, t_1.predicate_hash AS __predicate, t_1.object_hash AS __object, t_1.label_hash AS __label, t_2.subject_hash AS pred, t_2.object_hash AS ob 200 FROM quads AS t_1, quads AS t_2 201 WHERE t_1.subject_hash = $1 AND t_2.predicate_hash = $2 AND t_1.predicate_hash = t_2.subject_hash`, 202 args: sVals("s", "p"), 203 }, 204 { 205 name: "quads with subquery (limit)", 206 s: shape.Quads{ 207 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 208 { 209 Dir: quad.Predicate, 210 Values: shape.Page{ 211 Limit: 10, 212 From: shape.QuadsAction{ 213 Result: quad.Subject, 214 Filter: map[quad.Direction]graph.Ref{ 215 quad.Predicate: sVal("p"), 216 }, 217 }, 218 }, 219 }, 220 }, 221 qu: `SELECT t_1.subject_hash AS __subject, t_1.predicate_hash AS __predicate, t_1.object_hash AS __object, t_1.label_hash AS __label 222 FROM quads AS t_1, (SELECT subject_hash AS ` + tagNode + ` FROM quads WHERE predicate_hash = $1 LIMIT 10) AS t_2 223 WHERE t_1.subject_hash = $2 AND t_1.predicate_hash = t_2.` + tagNode, 224 args: sVals("p", "s"), 225 }, 226 { 227 skip: true, // TODO 228 name: "quads with subquery (inner tags + limit)", 229 s: shape.Quads{ 230 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 231 { 232 Dir: quad.Predicate, 233 Values: shape.Save{ 234 Tags: []string{"pred"}, 235 From: shape.Page{ 236 Limit: 10, 237 From: shape.QuadsAction{ 238 Result: quad.Subject, 239 Save: map[quad.Direction][]string{ 240 quad.Object: {"ob"}, 241 }, 242 Filter: map[quad.Direction]graph.Ref{ 243 quad.Predicate: sVal("p"), 244 }, 245 }, 246 }, 247 }, 248 }, 249 }, 250 qu: ``, 251 args: []Value{}, 252 }, 253 { 254 name: "nodes from quads", 255 s: shape.NodesFrom{ 256 Dir: quad.Object, 257 Quads: shape.Quads{ 258 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 259 { 260 Dir: quad.Predicate, 261 Values: shape.QuadsAction{ 262 Result: quad.Subject, 263 Save: map[quad.Direction][]string{ 264 quad.Object: {"ob"}, 265 }, 266 Filter: map[quad.Direction]graph.Ref{ 267 quad.Predicate: sVal("p"), 268 }, 269 }, 270 }, 271 }, 272 }, 273 qu: `SELECT t_1.object_hash AS ` + tagNode + `, t_2.object_hash AS ob 274 FROM quads AS t_1, quads AS t_2 275 WHERE t_1.subject_hash = $1 AND t_2.predicate_hash = $2 AND t_1.predicate_hash = t_2.subject_hash`, 276 args: sVals("s", "p"), 277 }, 278 { 279 name: "intersect selects", 280 s: shape.Intersect{ 281 shape.Save{ 282 Tags: []string{"sub"}, 283 From: shape.QuadsAction{ 284 Result: quad.Subject, 285 Save: map[quad.Direction][]string{ 286 quad.Object: {"o1"}, 287 quad.Label: {"l 1"}, 288 }, 289 Filter: map[quad.Direction]graph.Ref{ 290 quad.Predicate: sVal("p1"), 291 }, 292 }, 293 }, 294 shape.NodesFrom{ 295 Dir: quad.Object, 296 Quads: shape.Quads{ 297 {Dir: quad.Subject, Values: shape.Fixed{sVal("s")}}, 298 { 299 Dir: quad.Predicate, 300 Values: shape.QuadsAction{ 301 Result: quad.Subject, 302 Save: map[quad.Direction][]string{ 303 quad.Object: {"ob"}, 304 }, 305 Filter: map[quad.Direction]graph.Ref{ 306 quad.Predicate: sVal("p2"), 307 }, 308 }, 309 }, 310 }, 311 }, 312 }, 313 qu: `SELECT t_3.subject_hash AS sub, t_3.subject_hash AS __node, t_3.object_hash AS o1, t_3.label_hash AS "l 1", t_2.object_hash AS ob 314 FROM quads AS t_3, quads AS t_1, quads AS t_2 315 WHERE t_3.predicate_hash = $1 AND t_1.subject_hash = $2 AND t_2.predicate_hash = $3 AND t_1.predicate_hash = t_2.subject_hash AND t_3.subject_hash = t_1.object_hash`, 316 args: sVals("p1", "s", "p2"), 317 }, 318 { 319 name: "deep shape", 320 s: shape.NodesFrom{ 321 Dir: quad.Object, 322 Quads: shape.Quads{ 323 shape.QuadFilter{Dir: quad.Predicate, Values: shape.Fixed{sVal("s")}}, 324 shape.QuadFilter{ 325 Dir: quad.Subject, 326 Values: shape.NodesFrom{ 327 Dir: quad.Subject, 328 Quads: shape.Quads{ 329 shape.QuadFilter{Dir: quad.Predicate, Values: shape.Fixed{sVal("s")}}, 330 shape.QuadFilter{ 331 Dir: quad.Object, 332 Values: shape.NodesFrom{ 333 Dir: quad.Subject, 334 Quads: shape.Quads{ 335 shape.QuadFilter{Dir: quad.Predicate, Values: shape.Fixed{sVal("a")}}, 336 shape.QuadFilter{ 337 Dir: quad.Object, 338 Values: shape.QuadsAction{ 339 Result: quad.Subject, 340 Filter: map[quad.Direction]graph.Ref{ 341 quad.Predicate: sVal("n"), 342 quad.Object: sVal("k"), 343 }, 344 }, 345 }, 346 }, 347 }, 348 }, 349 }, 350 }, 351 }, 352 }, 353 }, 354 qu: `SELECT t_5.object_hash AS __node FROM quads AS t_5, (SELECT t_3.subject_hash AS __node FROM quads AS t_3, (SELECT t_1.subject_hash AS __node FROM quads AS t_1, (SELECT subject_hash AS __node FROM quads WHERE predicate_hash = $1 AND object_hash = $2) AS t_2 WHERE t_1.predicate_hash = $3 AND t_1.object_hash = t_2.__node) AS t_4 WHERE t_3.predicate_hash = $4 AND t_3.object_hash = t_4.__node) AS t_6 WHERE t_5.predicate_hash = $5 AND t_5.subject_hash = t_6.__node`, 355 args: sVals("n", "k", "a", "s", "s"), 356 }, 357 } 358 359 func TestSQLShapes(t *testing.T) { 360 dialect := DefaultDialect 361 dialect.Placeholder = func(i int) string { 362 return fmt.Sprintf("$%d", i) 363 } 364 for _, c := range shapeCases { 365 t.Run(c.name, func(t *testing.T) { 366 opt := NewOptimizer() 367 s, ok := c.s.Optimize(opt) 368 if c.skip { 369 t.Skipf("%#v", s) 370 } 371 require.True(t, ok, "%#v", s) 372 sq, ok := s.(Shape) 373 require.True(t, ok, "%#v", s) 374 b := NewBuilder(dialect) 375 require.Equal(t, c.qu, sq.SQL(b), "%#v", sq) 376 require.Equal(t, c.args, sq.Args()) 377 }) 378 } 379 }