Rename triple entities were relevant
This commit is contained in:
parent
ddf8849e60
commit
443a091b72
62 changed files with 664 additions and 664 deletions
|
|
@ -21,16 +21,16 @@ import (
|
|||
|
||||
type AllIterator struct {
|
||||
iterator.Int64
|
||||
ts *TripleStore
|
||||
qs *QuadStore
|
||||
}
|
||||
|
||||
type NodesAllIterator AllIterator
|
||||
type QuadsAllIterator AllIterator
|
||||
|
||||
func NewMemstoreNodesAllIterator(ts *TripleStore) *NodesAllIterator {
|
||||
func NewMemstoreNodesAllIterator(qs *QuadStore) *NodesAllIterator {
|
||||
var out NodesAllIterator
|
||||
out.Int64 = *iterator.NewInt64(1, ts.idCounter-1)
|
||||
out.ts = ts
|
||||
out.Int64 = *iterator.NewInt64(1, qs.idCounter-1)
|
||||
out.qs = qs
|
||||
return &out
|
||||
}
|
||||
|
||||
|
|
@ -43,17 +43,17 @@ func (it *NodesAllIterator) Next() bool {
|
|||
if !it.Int64.Next() {
|
||||
return false
|
||||
}
|
||||
_, ok := it.ts.revIdMap[it.Int64.Result().(int64)]
|
||||
_, ok := it.qs.revIdMap[it.Int64.Result().(int64)]
|
||||
if !ok {
|
||||
return it.Next()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func NewMemstoreQuadsAllIterator(ts *TripleStore) *QuadsAllIterator {
|
||||
func NewMemstoreQuadsAllIterator(qs *QuadStore) *QuadsAllIterator {
|
||||
var out QuadsAllIterator
|
||||
out.Int64 = *iterator.NewInt64(1, ts.quadIdCounter-1)
|
||||
out.ts = ts
|
||||
out.Int64 = *iterator.NewInt64(1, qs.quadIdCounter-1)
|
||||
out.qs = qs
|
||||
return &out
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ func (qit *QuadsAllIterator) Next() bool {
|
|||
out := qit.Int64.Next()
|
||||
if out {
|
||||
i64 := qit.Int64.Result().(int64)
|
||||
if qit.ts.log[i64].DeletedBy != 0 || qit.ts.log[i64].Action == graph.Delete {
|
||||
if qit.qs.log[i64].DeletedBy != 0 || qit.qs.log[i64].Action == graph.Delete {
|
||||
return qit.Next()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
type Iterator struct {
|
||||
uid uint64
|
||||
ts *TripleStore
|
||||
qs *QuadStore
|
||||
tags graph.Tagger
|
||||
tree *b.Tree
|
||||
iter *b.Enumerator
|
||||
|
|
@ -38,14 +38,14 @@ func cmp(a, b int64) int {
|
|||
return int(a - b)
|
||||
}
|
||||
|
||||
func NewIterator(tree *b.Tree, data string, ts *TripleStore) *Iterator {
|
||||
func NewIterator(tree *b.Tree, data string, qs *QuadStore) *Iterator {
|
||||
iter, err := tree.SeekFirst()
|
||||
if err != nil {
|
||||
iter = nil
|
||||
}
|
||||
return &Iterator{
|
||||
uid: iterator.NextUID(),
|
||||
ts: ts,
|
||||
qs: qs,
|
||||
tree: tree,
|
||||
iter: iter,
|
||||
data: data,
|
||||
|
|
@ -96,7 +96,7 @@ func (it *Iterator) Clone() graph.Iterator {
|
|||
|
||||
m := &Iterator{
|
||||
uid: iterator.NextUID(),
|
||||
ts: it.ts,
|
||||
qs: it.qs,
|
||||
tree: it.tree,
|
||||
iter: iter,
|
||||
data: it.data,
|
||||
|
|
@ -109,7 +109,7 @@ func (it *Iterator) Clone() graph.Iterator {
|
|||
func (it *Iterator) Close() {}
|
||||
|
||||
func (it *Iterator) checkValid(index int64) bool {
|
||||
return it.ts.log[index].DeletedBy == 0
|
||||
return it.qs.log[index].DeletedBy == 0
|
||||
}
|
||||
|
||||
func (it *Iterator) Next() bool {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
graph.RegisterTripleStore("memstore", false, func(string, graph.Options) (graph.TripleStore, error) {
|
||||
return newTripleStore(), nil
|
||||
graph.RegisterQuadStore("memstore", false, func(string, graph.Options) (graph.QuadStore, error) {
|
||||
return newQuadStore(), nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ type LogEntry struct {
|
|||
DeletedBy int64
|
||||
}
|
||||
|
||||
type TripleStore struct {
|
||||
type QuadStore struct {
|
||||
idCounter int64
|
||||
quadIdCounter int64
|
||||
idMap map[string]int64
|
||||
|
|
@ -80,8 +80,8 @@ type TripleStore struct {
|
|||
// vip_index map[string]map[int64]map[string]map[int64]*b.Tree
|
||||
}
|
||||
|
||||
func newTripleStore() *TripleStore {
|
||||
return &TripleStore{
|
||||
func newQuadStore() *QuadStore {
|
||||
return &QuadStore{
|
||||
idMap: make(map[string]int64),
|
||||
revIdMap: make(map[int64]string),
|
||||
|
||||
|
|
@ -94,13 +94,13 @@ func newTripleStore() *TripleStore {
|
|||
}
|
||||
}
|
||||
|
||||
func (ts *TripleStore) ApplyDeltas(deltas []graph.Delta) error {
|
||||
func (qs *QuadStore) ApplyDeltas(deltas []graph.Delta) error {
|
||||
for _, d := range deltas {
|
||||
var err error
|
||||
if d.Action == graph.Add {
|
||||
err = ts.AddDelta(d)
|
||||
err = qs.AddDelta(d)
|
||||
} else {
|
||||
err = ts.RemoveDelta(d)
|
||||
err = qs.RemoveDelta(d)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -111,7 +111,7 @@ func (ts *TripleStore) ApplyDeltas(deltas []graph.Delta) error {
|
|||
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
|
||||
func (ts *TripleStore) indexOf(t quad.Quad) (int64, bool) {
|
||||
func (qs *QuadStore) indexOf(t quad.Quad) (int64, bool) {
|
||||
min := maxInt
|
||||
var tree *b.Tree
|
||||
for d := quad.Subject; d <= quad.Label; d++ {
|
||||
|
|
@ -119,12 +119,12 @@ func (ts *TripleStore) indexOf(t quad.Quad) (int64, bool) {
|
|||
if d == quad.Label && sid == "" {
|
||||
continue
|
||||
}
|
||||
id, ok := ts.idMap[sid]
|
||||
id, ok := qs.idMap[sid]
|
||||
// If we've never heard about a node, it must not exist
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
index, ok := ts.index.Get(d, id)
|
||||
index, ok := qs.index.Get(d, id)
|
||||
if !ok {
|
||||
// If it's never been indexed in this direction, it can't exist.
|
||||
return 0, false
|
||||
|
|
@ -133,35 +133,35 @@ func (ts *TripleStore) indexOf(t quad.Quad) (int64, bool) {
|
|||
min, tree = l, index
|
||||
}
|
||||
}
|
||||
it := NewIterator(tree, "", ts)
|
||||
it := NewIterator(tree, "", qs)
|
||||
|
||||
for it.Next() {
|
||||
val := it.Result()
|
||||
if t == ts.log[val.(int64)].Quad {
|
||||
if t == qs.log[val.(int64)].Quad {
|
||||
return val.(int64), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (ts *TripleStore) AddDelta(d graph.Delta) error {
|
||||
if _, exists := ts.indexOf(d.Quad); exists {
|
||||
func (qs *QuadStore) AddDelta(d graph.Delta) error {
|
||||
if _, exists := qs.indexOf(d.Quad); exists {
|
||||
return graph.ErrQuadExists
|
||||
}
|
||||
qid := ts.quadIdCounter
|
||||
ts.log = append(ts.log, LogEntry{Delta: d})
|
||||
ts.size++
|
||||
ts.quadIdCounter++
|
||||
qid := qs.quadIdCounter
|
||||
qs.log = append(qs.log, LogEntry{Delta: d})
|
||||
qs.size++
|
||||
qs.quadIdCounter++
|
||||
|
||||
for dir := quad.Subject; dir <= quad.Label; dir++ {
|
||||
sid := d.Quad.Get(dir)
|
||||
if dir == quad.Label && sid == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := ts.idMap[sid]; !ok {
|
||||
ts.idMap[sid] = ts.idCounter
|
||||
ts.revIdMap[ts.idCounter] = sid
|
||||
ts.idCounter++
|
||||
if _, ok := qs.idMap[sid]; !ok {
|
||||
qs.idMap[sid] = qs.idCounter
|
||||
qs.revIdMap[qs.idCounter] = sid
|
||||
qs.idCounter++
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,8 +169,8 @@ func (ts *TripleStore) AddDelta(d graph.Delta) error {
|
|||
if dir == quad.Label && d.Quad.Get(dir) == "" {
|
||||
continue
|
||||
}
|
||||
id := ts.idMap[d.Quad.Get(dir)]
|
||||
tree := ts.index.Tree(dir, id)
|
||||
id := qs.idMap[d.Quad.Get(dir)]
|
||||
tree := qs.index.Tree(dir, id)
|
||||
tree.Set(qid, struct{}{})
|
||||
}
|
||||
|
||||
|
|
@ -178,43 +178,43 @@ func (ts *TripleStore) AddDelta(d graph.Delta) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ts *TripleStore) RemoveDelta(d graph.Delta) error {
|
||||
prevQuadID, exists := ts.indexOf(d.Quad)
|
||||
func (qs *QuadStore) RemoveDelta(d graph.Delta) error {
|
||||
prevQuadID, exists := qs.indexOf(d.Quad)
|
||||
if !exists {
|
||||
return graph.ErrQuadNotExist
|
||||
}
|
||||
|
||||
quadID := ts.quadIdCounter
|
||||
ts.log = append(ts.log, LogEntry{Delta: d})
|
||||
ts.log[prevQuadID].DeletedBy = quadID
|
||||
ts.size--
|
||||
ts.quadIdCounter++
|
||||
quadID := qs.quadIdCounter
|
||||
qs.log = append(qs.log, LogEntry{Delta: d})
|
||||
qs.log[prevQuadID].DeletedBy = quadID
|
||||
qs.size--
|
||||
qs.quadIdCounter++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TripleStore) Quad(index graph.Value) quad.Quad {
|
||||
return ts.log[index.(int64)].Quad
|
||||
func (qs *QuadStore) Quad(index graph.Value) quad.Quad {
|
||||
return qs.log[index.(int64)].Quad
|
||||
}
|
||||
|
||||
func (ts *TripleStore) TripleIterator(d quad.Direction, value graph.Value) graph.Iterator {
|
||||
index, ok := ts.index.Get(d, value.(int64))
|
||||
func (qs *QuadStore) QuadIterator(d quad.Direction, value graph.Value) graph.Iterator {
|
||||
index, ok := qs.index.Get(d, value.(int64))
|
||||
data := fmt.Sprintf("dir:%s val:%d", d, value.(int64))
|
||||
if ok {
|
||||
return NewIterator(index, data, ts)
|
||||
return NewIterator(index, data, qs)
|
||||
}
|
||||
return &iterator.Null{}
|
||||
}
|
||||
|
||||
func (ts *TripleStore) Horizon() int64 {
|
||||
return ts.log[len(ts.log)-1].ID
|
||||
func (qs *QuadStore) Horizon() int64 {
|
||||
return qs.log[len(qs.log)-1].ID
|
||||
}
|
||||
|
||||
func (ts *TripleStore) Size() int64 {
|
||||
return ts.size
|
||||
func (qs *QuadStore) Size() int64 {
|
||||
return qs.size
|
||||
}
|
||||
|
||||
func (ts *TripleStore) DebugPrint() {
|
||||
for i, l := range ts.log {
|
||||
func (qs *QuadStore) DebugPrint() {
|
||||
for i, l := range qs.log {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
|
@ -222,29 +222,29 @@ func (ts *TripleStore) DebugPrint() {
|
|||
}
|
||||
}
|
||||
|
||||
func (ts *TripleStore) ValueOf(name string) graph.Value {
|
||||
return ts.idMap[name]
|
||||
func (qs *QuadStore) ValueOf(name string) graph.Value {
|
||||
return qs.idMap[name]
|
||||
}
|
||||
|
||||
func (ts *TripleStore) NameOf(id graph.Value) string {
|
||||
return ts.revIdMap[id.(int64)]
|
||||
func (qs *QuadStore) NameOf(id graph.Value) string {
|
||||
return qs.revIdMap[id.(int64)]
|
||||
}
|
||||
|
||||
func (ts *TripleStore) TriplesAllIterator() graph.Iterator {
|
||||
return NewMemstoreQuadsAllIterator(ts)
|
||||
func (qs *QuadStore) QuadsAllIterator() graph.Iterator {
|
||||
return NewMemstoreQuadsAllIterator(qs)
|
||||
}
|
||||
|
||||
func (ts *TripleStore) FixedIterator() graph.FixedIterator {
|
||||
func (qs *QuadStore) FixedIterator() graph.FixedIterator {
|
||||
return iterator.NewFixedIteratorWithCompare(iterator.BasicEquality)
|
||||
}
|
||||
|
||||
func (ts *TripleStore) TripleDirection(val graph.Value, d quad.Direction) graph.Value {
|
||||
name := ts.Quad(val).Get(d)
|
||||
return ts.ValueOf(name)
|
||||
func (qs *QuadStore) QuadDirection(val graph.Value, d quad.Direction) graph.Value {
|
||||
name := qs.Quad(val).Get(d)
|
||||
return qs.ValueOf(name)
|
||||
}
|
||||
|
||||
func (ts *TripleStore) NodesAllIterator() graph.Iterator {
|
||||
return NewMemstoreNodesAllIterator(ts)
|
||||
func (qs *QuadStore) NodesAllIterator() graph.Iterator {
|
||||
return NewMemstoreNodesAllIterator(qs)
|
||||
}
|
||||
|
||||
func (ts *TripleStore) Close() {}
|
||||
func (qs *QuadStore) Close() {}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ import (
|
|||
"github.com/google/cayley/graph/iterator"
|
||||
)
|
||||
|
||||
func (ts *TripleStore) OptimizeIterator(it graph.Iterator) (graph.Iterator, bool) {
|
||||
func (qs *QuadStore) OptimizeIterator(it graph.Iterator) (graph.Iterator, bool) {
|
||||
switch it.Type() {
|
||||
case graph.LinksTo:
|
||||
return ts.optimizeLinksTo(it.(*iterator.LinksTo))
|
||||
return qs.optimizeLinksTo(it.(*iterator.LinksTo))
|
||||
|
||||
}
|
||||
return it, false
|
||||
}
|
||||
|
||||
func (ts *TripleStore) optimizeLinksTo(it *iterator.LinksTo) (graph.Iterator, bool) {
|
||||
func (qs *QuadStore) optimizeLinksTo(it *iterator.LinksTo) (graph.Iterator, bool) {
|
||||
subs := it.SubIterators()
|
||||
if len(subs) != 1 {
|
||||
return it, false
|
||||
|
|
@ -41,7 +41,7 @@ func (ts *TripleStore) optimizeLinksTo(it *iterator.LinksTo) (graph.Iterator, bo
|
|||
panic("unexpected size during optimize")
|
||||
}
|
||||
val := primary.Result()
|
||||
newIt := ts.TripleIterator(it.Direction(), val)
|
||||
newIt := qs.QuadIterator(it.Direction(), val)
|
||||
nt := newIt.Tagger()
|
||||
nt.CopyFrom(it)
|
||||
for _, tag := range primary.Tagger().Tags() {
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ var simpleGraph = []quad.Quad{
|
|||
{"G", "status", "cool", "status_graph"},
|
||||
}
|
||||
|
||||
func makeTestStore(data []quad.Quad) (*TripleStore, graph.QuadWriter, []pair) {
|
||||
func makeTestStore(data []quad.Quad) (*QuadStore, graph.QuadWriter, []pair) {
|
||||
seen := make(map[string]struct{})
|
||||
ts := newTripleStore()
|
||||
qs := newQuadStore()
|
||||
var (
|
||||
val int64
|
||||
ind []pair
|
||||
)
|
||||
writer, _ := writer.NewSingleReplication(ts, nil)
|
||||
writer, _ := writer.NewSingleReplication(qs, nil)
|
||||
for _, t := range data {
|
||||
for _, qp := range []string{t.Subject, t.Predicate, t.Object, t.Label} {
|
||||
if _, ok := seen[qp]; !ok && qp != "" {
|
||||
|
|
@ -71,7 +71,7 @@ func makeTestStore(data []quad.Quad) (*TripleStore, graph.QuadWriter, []pair) {
|
|||
|
||||
writer.AddQuad(t)
|
||||
}
|
||||
return ts, writer, ind
|
||||
return qs, writer, ind
|
||||
}
|
||||
|
||||
type pair struct {
|
||||
|
|
@ -80,12 +80,12 @@ type pair struct {
|
|||
}
|
||||
|
||||
func TestMemstore(t *testing.T) {
|
||||
ts, _, index := makeTestStore(simpleGraph)
|
||||
if size := ts.Size(); size != int64(len(simpleGraph)) {
|
||||
t.Errorf("Triple store has unexpected size, got:%d expected %d", size, len(simpleGraph))
|
||||
qs, _, index := makeTestStore(simpleGraph)
|
||||
if size := qs.Size(); size != int64(len(simpleGraph)) {
|
||||
t.Errorf("Quad store has unexpected size, got:%d expected %d", size, len(simpleGraph))
|
||||
}
|
||||
for _, test := range index {
|
||||
v := ts.ValueOf(test.query)
|
||||
v := qs.ValueOf(test.query)
|
||||
switch v := v.(type) {
|
||||
default:
|
||||
t.Errorf("ValueOf(%q) returned unexpected type, got:%T expected int64", test.query, v)
|
||||
|
|
@ -98,21 +98,21 @@ func TestMemstore(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIteratorsAndNextResultOrderA(t *testing.T) {
|
||||
ts, _, _ := makeTestStore(simpleGraph)
|
||||
qs, _, _ := makeTestStore(simpleGraph)
|
||||
|
||||
fixed := ts.FixedIterator()
|
||||
fixed.Add(ts.ValueOf("C"))
|
||||
fixed := qs.FixedIterator()
|
||||
fixed.Add(qs.ValueOf("C"))
|
||||
|
||||
fixed2 := ts.FixedIterator()
|
||||
fixed2.Add(ts.ValueOf("follows"))
|
||||
fixed2 := qs.FixedIterator()
|
||||
fixed2.Add(qs.ValueOf("follows"))
|
||||
|
||||
all := ts.NodesAllIterator()
|
||||
all := qs.NodesAllIterator()
|
||||
|
||||
innerAnd := iterator.NewAnd()
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed2, quad.Predicate))
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, all, quad.Object))
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(qs, fixed2, quad.Predicate))
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(qs, all, quad.Object))
|
||||
|
||||
hasa := iterator.NewHasA(ts, innerAnd, quad.Subject)
|
||||
hasa := iterator.NewHasA(qs, innerAnd, quad.Subject)
|
||||
outerAnd := iterator.NewAnd()
|
||||
outerAnd.AddSubIterator(fixed)
|
||||
outerAnd.AddSubIterator(hasa)
|
||||
|
|
@ -121,8 +121,8 @@ func TestIteratorsAndNextResultOrderA(t *testing.T) {
|
|||
t.Error("Expected one matching subtree")
|
||||
}
|
||||
val := outerAnd.Result()
|
||||
if ts.NameOf(val) != "C" {
|
||||
t.Errorf("Matching subtree should be %s, got %s", "barak", ts.NameOf(val))
|
||||
if qs.NameOf(val) != "C" {
|
||||
t.Errorf("Matching subtree should be %s, got %s", "barak", qs.NameOf(val))
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -130,7 +130,7 @@ func TestIteratorsAndNextResultOrderA(t *testing.T) {
|
|||
expect = []string{"B", "D"}
|
||||
)
|
||||
for {
|
||||
got = append(got, ts.NameOf(all.Result()))
|
||||
got = append(got, qs.NameOf(all.Result()))
|
||||
if !outerAnd.NextPath() {
|
||||
break
|
||||
}
|
||||
|
|
@ -147,12 +147,12 @@ func TestIteratorsAndNextResultOrderA(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLinksToOptimization(t *testing.T) {
|
||||
ts, _, _ := makeTestStore(simpleGraph)
|
||||
qs, _, _ := makeTestStore(simpleGraph)
|
||||
|
||||
fixed := ts.FixedIterator()
|
||||
fixed.Add(ts.ValueOf("cool"))
|
||||
fixed := qs.FixedIterator()
|
||||
fixed.Add(qs.ValueOf("cool"))
|
||||
|
||||
lto := iterator.NewLinksTo(ts, fixed, quad.Object)
|
||||
lto := iterator.NewLinksTo(qs, fixed, quad.Object)
|
||||
lto.Tagger().Add("foo")
|
||||
|
||||
newIt, changed := lto.Optimize()
|
||||
|
|
@ -174,22 +174,22 @@ func TestLinksToOptimization(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemoveTriple(t *testing.T) {
|
||||
ts, w, _ := makeTestStore(simpleGraph)
|
||||
func TestRemoveQuad(t *testing.T) {
|
||||
qs, w, _ := makeTestStore(simpleGraph)
|
||||
|
||||
w.RemoveQuad(quad.Quad{"E", "follows", "F", ""})
|
||||
|
||||
fixed := ts.FixedIterator()
|
||||
fixed.Add(ts.ValueOf("E"))
|
||||
fixed := qs.FixedIterator()
|
||||
fixed.Add(qs.ValueOf("E"))
|
||||
|
||||
fixed2 := ts.FixedIterator()
|
||||
fixed2.Add(ts.ValueOf("follows"))
|
||||
fixed2 := qs.FixedIterator()
|
||||
fixed2.Add(qs.ValueOf("follows"))
|
||||
|
||||
innerAnd := iterator.NewAnd()
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed, quad.Subject))
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed2, quad.Predicate))
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(qs, fixed, quad.Subject))
|
||||
innerAnd.AddSubIterator(iterator.NewLinksTo(qs, fixed2, quad.Predicate))
|
||||
|
||||
hasa := iterator.NewHasA(ts, innerAnd, quad.Object)
|
||||
hasa := iterator.NewHasA(qs, innerAnd, quad.Object)
|
||||
|
||||
newIt, _ := hasa.Optimize()
|
||||
if graph.Next(newIt) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue