Repository: TileDB-Inc/TileDB-Py

Issue:
Subselection on attributes
We need to expose the ability to subselect on attributes before indexing.  Example API: `A.attrs["a1", "a2"][10:20]`


Discussion:

Resolving patch:
diff --git a/examples/reading_dense_layouts.py b/examples/reading_dense_layouts.py
new file mode 100644
--- /dev/null
+++ b/examples/reading_dense_layouts.py
@@ -0,0 +1,98 @@
+# reading_dense_layouts.py
+#
+# LICENSE
+#
+# The MIT License
+#
+# Copyright (c) 2018 TileDB, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# DESCRIPTION
+#
+# This is a part of the TileDB quickstart tutorial:
+#   https://docs.tiledb.io/en/latest/reading.html
+#
+# When run, this program will create a simple 2D dense array, write some data
+# to it, and read a slice of the data back in the layout of the user's choice
+# (passed as an argument to the program: "row", "col", or "global").
+#
+
+import numpy as np
+import sys
+import tiledb
+
+# Name of the array to create.
+array_name = "reading_dense_layouts"
+
+
+def create_array():
+    # Create a TileDB context
+    ctx = tiledb.Ctx()
+
+    # Check if the array already exists.
+    if tiledb.object_type(ctx, array_name) == "array":
+        print("Array already exists.")
+        sys.exit(0)
+
+    # The array will be 4x4 with dimensions "rows" and "cols", with domain [1,4].
+    dom = tiledb.Domain(ctx,
+                        tiledb.Dim(ctx, name="rows", domain=(1, 4), tile=2, dtype=np.int32),
+                        tiledb.Dim(ctx, name="cols", domain=(1, 4), tile=2, dtype=np.int32))
+
+    # The array will be dense with a single attribute "a" so each (i,j) cell can store an integer.
+    schema = tiledb.ArraySchema(ctx, domain=dom, sparse=False,
+                                attrs=[tiledb.Attr(ctx, name="a", dtype=np.int32)])
+
+    # Create the (empty) array on disk.
+    tiledb.DenseArray.create(array_name, schema)
+
+
+def write_array():
+    ctx = tiledb.Ctx()
+    # Open the array and write to it.
+    with tiledb.DenseArray(ctx, array_name, mode='w') as A:
+        data = np.array(([1, 2, 3, 4],
+                         [5, 6, 7, 8],
+                         [9, 10, 11, 12],
+                         [13, 14, 15, 16]))
+        A[:] = data
+
+
+def read_array():
+    ctx = tiledb.Ctx()
+
+    # Open the array and read from it.
+    with tiledb.DenseArray(ctx, array_name, mode='r') as A:
+        # Get non-empty domain
+        print(A.nonempty_domain())
+
+        # Slice only rows 1, 2 and cols 2, 3, 4.
+        data = A.query(attrs=["a"], coords=True)[1:3, 2:5]
+        a_vals = data["a"]
+        coords = data["coords"]
+
+        for i in range(coords.shape[0]):
+            for j in range(coords.shape[1]):
+                print("Cell {} has data {}".format(coords[i, j], a_vals[i, j]))
+
+
+create_array()
+write_array()
+read_array()


Test patch:
diff --git a/tiledb/tests/test_libtiledb.py b/tiledb/tests/test_libtiledb.py
--- a/tiledb/tests/test_libtiledb.py
+++ b/tiledb/tests/test_libtiledb.py
@@ -350,7 +350,7 @@ def test_array_create(self):
 
         # load array in readonly mode
         array = tiledb.libtiledb.Array(ctx, self.path("foo"), mode='r')
-        self.assertTrue(array.opened)
+        self.assertTrue(array.isopen)
         self.assertEqual(array.schema, schema)
         self.assertEqual(array.mode, 'r')
 
@@ -361,10 +361,15 @@ def test_array_create(self):
         array.consolidate()
 
         array.reopen()
-        self.assertTrue(array.opened)
+        self.assertTrue(array.isopen)
 
         array.close()
-        self.assertEqual(array.opened, False)
+        self.assertEqual(array.isopen, False)
+
+        with self.assertRaises(tiledb.TileDBError):
+            # cannot get schema from closed array
+            array.schema
+
         with self.assertRaises(tiledb.TileDBError):
             # cannot re-open a closed array
             array.reopen()
@@ -644,7 +649,8 @@ def test_ncell_attributes(self):
     def test_multiple_attributes(self):
         ctx = tiledb.Ctx()
         dom = tiledb.Domain(ctx,
-                       tiledb.Dim(ctx, domain=(0, 7), tile=8, dtype=int))
+                       tiledb.Dim(ctx, domain=(0, 1), tile=1, dtype=int),
+                       tiledb.Dim(ctx, domain=(0, 3), tile=4, dtype=int))
         attr_int = tiledb.Attr(ctx, "ints", dtype=int)
         attr_float = tiledb.Attr(ctx, "floats", dtype=float)
         schema = tiledb.ArraySchema(ctx,
@@ -652,8 +658,10 @@ def test_multiple_attributes(self):
                                     attrs=(attr_int, attr_float))
         tiledb.DenseArray.create(self.path("foo"), schema)
 
-        V_ints = np.array([0, 1, 2, 3, 4, 6, 7, 5])
-        V_floats = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 6.0, 7.0, 5.0])
+        V_ints = np.array([[0, 1, 2, 3,],
+                           [4, 6, 7, 5]])
+        V_floats = np.array([[0.0, 1.0, 2.0, 3.0,],
+                             [4.0, 6.0, 7.0, 5.0]])
 
         V = {"ints": V_ints, "floats": V_floats}
         with tiledb.DenseArray(ctx, self.path("foo"), mode='w') as T:
@@ -664,6 +672,26 @@ def test_multiple_attributes(self):
             assert_array_equal(V["ints"], R["ints"])
             assert_array_equal(V["floats"], R["floats"])
 
+            R = T.query(attrs=("ints",))[1:3]
+            assert_array_equal(V["ints"][1:3], R["ints"])
+
+            R = T.query(attrs=("floats",), order='F')[:]
+            self.assertTrue(R["floats"].flags.f_contiguous)
+
+            R = T.query(attrs=("ints",), coords=True)[0, 0:3]
+            self.assertTrue("coords" in R)
+            assert_array_equal(R["coords"],
+                               np.array([(0, 0), (0, 1), (0, 2)],
+                                        dtype=[('__dim_0', '<i8'),
+                                               ('__dim_1', '<i8')]))
+
+            # Global order returns results as a linear buffer
+            R = T.query(attrs=("ints",), order='G')[:]
+            self.assertEqual(R["ints"].shape, (8,))
+
+            with self.assertRaises(tiledb.TileDBError):
+                T.query(attrs=("unknown"))[:]
+
         with tiledb.DenseArray(ctx, self.path("foo"), mode='w') as T:
             # check error ncells length
             V["ints"] = V["ints"][1:2].copy()
@@ -818,6 +846,11 @@ def test_subarray(self):
             # retrieve just valid coordinates in subarray T[40:60]
             assert_array_equal(T[40:61]["coords"]["x"], [50, 60])
 
+            #TODO: dropping coords with one anon value returns just an array
+            res = T.query(coords=False)[40:61]
+            assert_array_equal(res[""], [1.0, 2.0])
+            self.assertEqual(("coords" in res), False)
+
 
 class DenseIndexing(DiskTestCase):