diff --git a/common/coords.go b/common/coords.go index 1efcf7b..6cdb714 100644 --- a/common/coords.go +++ b/common/coords.go @@ -21,6 +21,10 @@ type ( X, Y, Z int16 } + Cuboid struct { + P1, P2 Coord + } + KeyTransformer func(int64) int64 KeyEncoder func(int64) ([]byte, error) KeyDecoder func([]byte) (int64, error) @@ -29,6 +33,12 @@ type ( KeyJoiner func(Coord) int64 ) +func (cub Cuboid) Contains(c Coord) bool { + return c.X >= cub.P1.X && c.X <= cub.P2.X && + c.Y >= cub.P1.Y && c.Y <= cub.P2.Y && + c.Z >= cub.P1.Z && c.Z <= cub.P2.Z +} + func (c Coord) String() string { return fmt.Sprintf("(%d, %d, %d)", c.X, c.Y, c.Z) } @@ -204,3 +214,23 @@ func TranscodeInterleavedToPlain(key []byte) ([]byte, error) { return EncodeStringToBytes(TransformInterleavedToPlain(pos)) } } + +// For correctness checks only. +func NaiveBigMin(minz, maxz, zcode int64) int64 { + c1, c2 := InterleavedToCoord(minz), InterleavedToCoord(maxz) + + cand := minz + + for x := c1.X; x <= c2.X; x++ { + for y := c1.Y; y <= c2.Y; y++ { + for z := c1.Z; z <= c2.Z; z++ { + z := CoordToInterleaved(Coord{X: x, Y: y, Z: z}) + if z > zcode && z < cand { + cand = z + } + } + } + } + + return cand +} diff --git a/sqlite.go b/sqlite.go index eb67ab8..103544b 100644 --- a/sqlite.go +++ b/sqlite.go @@ -23,7 +23,7 @@ const ( insertSql = "INSERT INTO blocks (pos, data) VALUES (?, ?)" countSql = "SELECT count(*) FROM blocks" keysSql = "SELECT pos FROM blocks" - rangeSql = "SELECT pos, data FROM blocks WHERE pos BETWEEN ? AND ?" + rangeSql = "SELECT pos, data FROM blocks WHERE pos BETWEEN ? AND ? ORDER BY pos" ) type SqliteBackend struct { @@ -283,15 +283,13 @@ func (ss *SqliteSession) AllKeys(hash []byte, done chan struct{}) (keys chan []b return } -func (ss *SqliteSession) SpatialQuery(hash, first, second []byte, done chan struct{}) (blocks chan Block, err error) { +func (ss *SqliteSession) SpatialQuery(hash, first, second []byte, done chan struct{}) (chan Block, error) { - // No implementation for the interleaved case, yet. if ss.backend.interleaved { - err = ErrNotImplemented - return + return ss.interleavedSpatialQuery(first, second, done) } - return ss.PlainSpatialQuery(first, second, done) + return ss.plainSpatialQuery(first, second, done) } func order(a, b int64) (int64, int64) { @@ -301,7 +299,79 @@ func order(a, b int64) (int64, int64) { return b, a } -func (ss *SqliteSession) PlainSpatialQuery(first, second []byte, done chan struct{}) (blocks chan Block, err error) { +func (ss *SqliteSession) interleavedSpatialQuery(first, second []byte, done chan struct{}) (blocks chan Block, err error) { + var ( + firstKey int64 + secondKey int64 + ) + if firstKey, err = common.DecodeStringFromBytes(first); err != nil { + return + } + if secondKey, err = common.DecodeStringFromBytes(second); err != nil { + return + } + c1 := common.PlainToCoord(firstKey) + c2 := common.PlainToCoord(secondKey) + c1, c2 = common.MinCoord(c1, c2), common.MaxCoord(c1, c2) + + blocks = make(chan Block) + + globalLock.RLock() + + go func() { + defer close(blocks) + defer globalLock.RUnlock() + zmin, zmax := common.CoordToInterleaved(c1), common.CoordToInterleaved(c2) + // Should not be necessary. + zmin, zmax = order(zmin, zmax) + cub := common.Cuboid{P1: c1, P2: c2} + rangeStmt := ss.txStmt(ss.backend.rangeStmt) + OUTER: + for { + var ( + err error + rows *sql.Rows + ) + if rows, err = rangeStmt.Query(zmin, zmax); err != nil { + return + } + for rows.Next() { + var zcode int64 + var data []byte + if err = rows.Scan(&zcode, &data); err != nil { + log.Printf("Error in range query: %s", err) + break + } + c := common.InterleavedToCoord(zcode) + if cub.Contains(c) { + var encodedKey []byte + if encodedKey, err = common.EncodeStringToBytes(common.CoordToPlain(c)); err != nil { + log.Printf("Key encoding failed: %s", err) + break + } + select { + case blocks <- Block{Key: encodedKey, Data: data}: + case <-done: + rows.Close() + return + } + } else { // Left the cuboid + rows.Close() + zmin = common.NaiveBigMin(zmin, zmax, zcode) + continue OUTER + } + } + if err = rows.Err(); err != nil { + log.Printf("Error in range query: %s", err) + } + rows.Close() + } + }() + + return +} + +func (ss *SqliteSession) plainSpatialQuery(first, second []byte, done chan struct{}) (blocks chan Block, err error) { var ( firstKey int64 @@ -317,10 +387,10 @@ func (ss *SqliteSession) PlainSpatialQuery(first, second []byte, done chan struc c2 := common.PlainToCoord(secondKey) c1, c2 = common.MinCoord(c1, c2), common.MaxCoord(c1, c2) - globalLock.RLock() - blocks = make(chan Block) + globalLock.RLock() + go func() { defer globalLock.RUnlock() defer close(blocks)