1 /**
2  * @file   attribute.h
3  *
4  * @author Ravi Gaddipati
5  *
6  * @section LICENSE
7  *
8  * The MIT License
9  *
10  * @copyright Copyright (c) 2017-2021 TileDB, Inc.
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a copy
13  * of this software and associated documentation files (the "Software"), to deal
14  * in the Software without restriction, including without limitation the rights
15  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16  * copies of the Software, and to permit persons to whom the Software is
17  * furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included in
20  * all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28  * THE SOFTWARE.
29  *
30  * @section DESCRIPTION
31  *
32  * This file declares the C++ API for the TileDB Attribute object.
33  */
34 
35 #ifndef TILEDB_CPP_API_ATTRIBUTE_H
36 #define TILEDB_CPP_API_ATTRIBUTE_H
37 
38 #include "context.h"
39 #include "deleter.h"
40 #include "exception.h"
41 #include "filter_list.h"
42 #include "object.h"
43 #include "tiledb.h"
44 #include "type.h"
45 
46 #include <array>
47 #include <functional>
48 #include <memory>
49 #include <type_traits>
50 
51 namespace tiledb {
52 
53 /**
54  * Describes an attribute of an Array cell.
55  *
56  * @details
57  * An attribute specifies a name and datatype for a particular value in each
58  * array cell. There are 3 supported attribute types:
59  *
60  * - Fundamental types, such as `char`, `int`, `double`, `uint64_t`, etc..
61  * - Fixed sized arrays: `T[N]` or `std::array<T, N>`, where T is a fundamental
62  * type
63  * - Variable length data: `std::string`, `std::vector<T>` where T is a
64  * fundamental type
65  *
66  * Fixed-size array types using POD types like `std::array<T, N>` are internally
67  * converted to byte-array attributes. E.g. an attribute of type
68  * `std::array<float, 3>` will be created as an attribute of type `TILEDB_CHAR`
69  * with cell_val_num `sizeof(std::array<float, 3>)`.
70  *
71  * Therefore, for fixed-length attributes it is recommended to use C-style
72  * arrays instead, e.g. `float[3]` instead of `std::array<float, 3>`.
73  *
74  * **Example:**
75  *
76  * @code{.cpp}
77  * tiledb::Context ctx;
78  * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
79  * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
80  * auto a3 = tiledb::Attribute::create<float[3]>(ctx, "a3");
81  *
82  * // Change compression scheme
83  * tiledb::FilterList filters(ctx);
84  * filters.add_filter({ctx, TILEDB_FILTER_BZIP2});
85  * a1.set_filter_list(filters);
86  *
87  * // Add attributes to a schema
88  * tiledb::ArraySchema schema(ctx, TILEDB_DENSE);
89  * schema.add_attributes(a1, a2, a3);
90  * @endcode
91  */
92 class Attribute {
93  public:
94   /* ********************************* */
95   /*     CONSTRUCTORS & DESTRUCTORS    */
96   /* ********************************* */
97 
Attribute(const Context & ctx,tiledb_attribute_t * attr)98   Attribute(const Context& ctx, tiledb_attribute_t* attr)
99       : ctx_(ctx) {
100     attr_ = std::shared_ptr<tiledb_attribute_t>(attr, deleter_);
101   }
102 
103   /**
104    * Construct an attribute with a name and enumerated type. `cell_val_num` will
105    * be set to 1.
106    *
107    * @param ctx TileDB context
108    * @param name Name of attribute
109    * @param type Enumerated type of attribute
110    */
Attribute(const Context & ctx,const std::string & name,tiledb_datatype_t type)111   Attribute(const Context& ctx, const std::string& name, tiledb_datatype_t type)
112       : ctx_(ctx) {
113     init_from_type(name, type);
114   }
115 
116   /** Construct an attribute with an enumerated type and given filter list. */
Attribute(const Context & ctx,const std::string & name,tiledb_datatype_t type,const FilterList & filter_list)117   Attribute(
118       const Context& ctx,
119       const std::string& name,
120       tiledb_datatype_t type,
121       const FilterList& filter_list)
122       : ctx_(ctx) {
123     init_from_type(name, type);
124     set_filter_list(filter_list);
125   }
126 
127   Attribute(const Attribute&) = default;
128   Attribute(Attribute&&) = default;
129   Attribute& operator=(const Attribute&) = default;
130   Attribute& operator=(Attribute&&) = default;
131 
132   /* ********************************* */
133   /*                API                */
134   /* ********************************* */
135 
136   /** Returns the name of the attribute. */
name()137   std::string name() const {
138     auto& ctx = ctx_.get();
139     const char* name;
140     ctx.handle_error(
141         tiledb_attribute_get_name(ctx.ptr().get(), attr_.get(), &name));
142     return name;
143   }
144 
145   /** Returns the attribute datatype. */
type()146   tiledb_datatype_t type() const {
147     auto& ctx = ctx_.get();
148     tiledb_datatype_t type;
149     ctx.handle_error(
150         tiledb_attribute_get_type(ctx.ptr().get(), attr_.get(), &type));
151     return type;
152   }
153 
154   /**
155    * Returns the size (in bytes) of one cell on this attribute. For
156    * variable-sized attributes returns TILEDB_VAR_NUM.
157    *
158    * **Example:**
159    * @code{.cpp}
160    * tiledb::Context ctx;
161    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
162    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
163    * auto a3 = tiledb::Attribute::create<float[3]>(ctx, "a3");
164    * auto a4 = tiledb::Attribute::create<std::array<float, 3>>(ctx, "a4");
165    * a1.cell_size();    // Returns sizeof(int)
166    * a2.cell_size();    // Variable sized attribute, returns TILEDB_VAR_NUM
167    * a3.cell_size();    // Returns 3 * sizeof(float)
168    * a4.cell_size();    // Stored as byte array, returns sizeof(char).
169    * @endcode
170    */
cell_size()171   uint64_t cell_size() const {
172     auto& ctx = ctx_.get();
173     uint64_t cell_size;
174     ctx.handle_error(tiledb_attribute_get_cell_size(
175         ctx.ptr().get(), attr_.get(), &cell_size));
176     return cell_size;
177   }
178 
179   /**
180    * Returns number of values of one cell on this attribute. For variable-sized
181    * attributes returns TILEDB_VAR_NUM.
182    *
183    * **Example:**
184    * @code{.cpp}
185    * tiledb::Context ctx;
186    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
187    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
188    * auto a3 = tiledb::Attribute::create<float[3]>(ctx, "a3");
189    * auto a4 = tiledb::Attribute::create<std::array<float, 3>>(ctx, "a4");
190    * a1.cell_val_num();   // Returns 1
191    * a2.cell_val_num();   // Variable sized attribute, returns TILEDB_VAR_NUM
192    * a3.cell_val_num();   // Returns 3
193    * a4.cell_val_num();   // Stored as byte array, returns
194    *                         sizeof(std::array<float, 3>).
195    * @endcode
196    */
cell_val_num()197   unsigned cell_val_num() const {
198     auto& ctx = ctx_.get();
199     unsigned num;
200     ctx.handle_error(
201         tiledb_attribute_get_cell_val_num(ctx.ptr().get(), attr_.get(), &num));
202     return num;
203   }
204 
205   /**
206    * Sets the number of attribute values per cell. This is inferred from
207    * the type parameter of the `Attribute::create<T>()` function, but can also
208    * be set manually.
209    *
210    * **Example:**
211    * @code{.cpp}
212    * // a1 and a2 are equivalent:
213    * auto a1 = Attribute::create<std::vector<int>>(...);
214    * auto a2 = Attribute::create<int>(...);
215    * a2.set_cell_val_num(TILEDB_VAR_NUM);
216    * @endcode
217    *
218    * @param num Cell val number to set.
219    * @return Reference to this Attribute
220    */
set_cell_val_num(unsigned num)221   Attribute& set_cell_val_num(unsigned num) {
222     auto& ctx = ctx_.get();
223     ctx.handle_error(
224         tiledb_attribute_set_cell_val_num(ctx.ptr().get(), attr_.get(), num));
225     return *this;
226   }
227 
228   /**
229    * Sets the default fill value for the input attribute. This value will
230    * be used for the input attribute whenever querying (1) an empty cell in
231    * a dense array, or (2) a non-empty cell (in either dense or sparse array)
232    * when values on the input attribute are missing (e.g., if the user writes
233    * a subset of the attributes in a write operation).
234    *
235    * Applicable to var-sized attributes.
236    *
237    * **Example:**
238    *
239    * @code{.c}
240    * tiledb::Context ctx;
241    *
242    * // Fixed-sized attribute
243    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
244    * int32_t value = 0;
245    * uint64_t size = sizeof(value);
246    * a1.set_fill_value(&value, size);
247    *
248    * // Var-sized attribute
249    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
250    * std::string value("null");
251    * a2.set_fill_value(value.c_str(), value.size());
252    * @endcode
253    *
254    * @param value The fill value to set.
255    * @param size The fill value size in bytes.
256    *
257    * @note A call to `cell_val_num` sets the fill value
258    *     of the attribute to its default. Therefore, make sure you invoke
259    *     `set_fill_value` after deciding on the number
260    *     of values this attribute will hold in each cell.
261    *
262    * @note For fixed-sized attributes, the input `size` should be equal
263    *     to the cell size.
264    */
set_fill_value(const void * value,uint64_t size)265   Attribute& set_fill_value(const void* value, uint64_t size) {
266     auto& ctx = ctx_.get();
267     ctx.handle_error(tiledb_attribute_set_fill_value(
268         ctx.ptr().get(), attr_.get(), value, size));
269     return *this;
270   }
271 
272   /**
273    * Gets the default fill value for the input attribute. This value will
274    * be used for the input attribute whenever querying (1) an empty cell in
275    * a dense array, or (2) a non-empty cell (in either dense or sparse array)
276    * when values on the input attribute are missing (e.g., if the user writes
277    * a subset of the attributes in a write operation).
278    *
279    * Applicable to both fixed-sized and var-sized attributes.
280    *
281    * **Example:**
282    *
283    * @code{.c}
284    * // Fixed-sized attribute
285    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
286    * const int32_t* value;
287    * uint64_t size;
288    * a1.get_fill_value(&value, &size);
289    *
290    * // Var-sized attribute
291    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
292    * const char* value;
293    * uint64_t size;
294    * a2.get_fill_value(&value, &size);
295    * @endcode
296    *
297    * @param value A pointer to the fill value to get.
298    * @param size The size of the fill value to get.
299    */
get_fill_value(const void ** value,uint64_t * size)300   void get_fill_value(const void** value, uint64_t* size) {
301     auto& ctx = ctx_.get();
302     ctx.handle_error(tiledb_attribute_get_fill_value(
303         ctx.ptr().get(), attr_.get(), value, size));
304   }
305 
306   /**
307    * Sets the default fill value for the input, nullable attribute. This value
308    * will be used for the input attribute whenever querying (1) an empty cell in
309    * a dense array, or (2) a non-empty cell (in either dense or sparse array)
310    * when values on the input attribute are missing (e.g., if the user writes
311    * a subset of the attributes in a write operation).
312    *
313    * Applicable to var-sized attributes.
314    *
315    * **Example:**
316    *
317    * @code{.c}
318    * tiledb::Context ctx;
319    *
320    * // Fixed-sized attribute
321    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
322    * a1.set_nullable(true);
323    * int32_t value = 0;
324    * uint64_t size = sizeof(value);
325    * uint8_t valid = 0;
326    * a1.set_fill_value(&value, size, valid);
327    *
328    * // Var-sized attribute
329    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
330    * a2.set_nullable(true);
331    * std::string value("null");
332    * uint8_t valid = 0;
333    * a2.set_fill_value(value.c_str(), value.size(), valid);
334    * @endcode
335    *
336    * @param value The fill value to set.
337    * @param size The fill value size in bytes.
338    * @param valid The validity fill value, zero for a null value and
339    *     non-zero for a valid attribute.
340    *
341    * @note A call to `cell_val_num` sets the fill value
342    *     of the attribute to its default. Therefore, make sure you invoke
343    *     `set_fill_value` after deciding on the number
344    *     of values this attribute will hold in each cell.
345    *
346    * @note For fixed-sized attributes, the input `size` should be equal
347    *     to the cell size.
348    */
set_fill_value(const void * value,uint64_t size,uint8_t valid)349   Attribute& set_fill_value(const void* value, uint64_t size, uint8_t valid) {
350     auto& ctx = ctx_.get();
351     ctx.handle_error(tiledb_attribute_set_fill_value_nullable(
352         ctx.ptr().get(), attr_.get(), value, size, valid));
353     return *this;
354   }
355 
356   /**
357    * Gets the default fill value for the input attribute. This value will
358    * be used for the input attribute whenever querying (1) an empty cell in
359    * a dense array, or (2) a non-empty cell (in either dense or sparse array)
360    * when values on the input attribute are missing (e.g., if the user writes
361    * a subset of the attributes in a write operation).
362    *
363    * Applicable to both fixed-sized and var-sized attributes.
364    *
365    * **Example:**
366    *
367    * @code{.c}
368    * // Fixed-sized attribute
369    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
370    * a1.set_nullable(true);
371    * const int32_t* value;
372    * uint64_t size;
373    * uint8_t valid;
374    * a1.get_fill_value(&value, &size, &valid);
375    *
376    * // Var-sized attribute
377    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
378    * a2.set_nullable(true);
379    * const char* value;
380    * uint64_t size;
381    * uint8_t valid;
382    * a2.get_fill_value(&value, &size, &valid);
383    * @endcode
384    *
385    * @param value A pointer to the fill value to get.
386    * @param size The size of the fill value to get.
387    * @param valid The fill value validity to get.
388    */
get_fill_value(const void ** value,uint64_t * size,uint8_t * valid)389   void get_fill_value(const void** value, uint64_t* size, uint8_t* valid) {
390     auto& ctx = ctx_.get();
391     ctx.handle_error(tiledb_attribute_get_fill_value_nullable(
392         ctx.ptr().get(), attr_.get(), value, size, valid));
393   }
394 
395   /** Check if attribute is variable sized. **/
variable_sized()396   bool variable_sized() const {
397     return cell_val_num() == TILEDB_VAR_NUM;
398   }
399 
400   /**
401    * Returns a copy of the FilterList of the attribute.
402    * To change the filter list, use `set_filter_list()`.
403    *
404    * @return Copy of the attribute FilterList.
405    */
filter_list()406   FilterList filter_list() const {
407     auto& ctx = ctx_.get();
408     tiledb_filter_list_t* filter_list;
409     ctx.handle_error(tiledb_attribute_get_filter_list(
410         ctx.ptr().get(), attr_.get(), &filter_list));
411     return FilterList(ctx, filter_list);
412   }
413 
414   /**
415    * Sets the attribute filter list, which is an ordered list of filters that
416    * will be used to process and/or transform the attribute data (such as
417    * compression).
418    *
419    * @param filter_list Filter list to set
420    * @return Reference to this Attribute
421    */
set_filter_list(const FilterList & filter_list)422   Attribute& set_filter_list(const FilterList& filter_list) {
423     auto& ctx = ctx_.get();
424     ctx.handle_error(tiledb_attribute_set_filter_list(
425         ctx.ptr().get(), attr_.get(), filter_list.ptr().get()));
426     return *this;
427   }
428 
429   /**
430    * Sets the nullability of an attribute.
431    *
432    * **Example:**
433    * @code{.cpp}
434    * auto a1 = Attribute::create<int>(...);
435    * a1.set_nullable(true);
436    * @endcode
437    *
438    * @param nullable Whether the attribute is nullable.
439    * @return Reference to this Attribute
440    */
set_nullable(bool nullable)441   Attribute& set_nullable(bool nullable) {
442     auto& ctx = ctx_.get();
443     ctx.handle_error(tiledb_attribute_set_nullable(
444         ctx.ptr().get(), attr_.get(), static_cast<uint8_t>(nullable)));
445     return *this;
446   }
447 
448   /**
449    * Gets the nullability of an attribute.
450    *
451    * **Example:**
452    * @code{.cpp}
453    * auto a1 = Attribute::create<int>(...);
454    * auto nullable = a1.nullable();
455    * @endcode
456    *
457    * @return Whether the attribute is nullable.
458    */
nullable()459   bool nullable() const {
460     auto& ctx = ctx_.get();
461     uint8_t nullable;
462     ctx.handle_error(
463         tiledb_attribute_get_nullable(ctx.ptr().get(), attr_.get(), &nullable));
464     return static_cast<bool>(nullable);
465   }
466 
467   /** Returns the C TileDB attribute object pointer. */
ptr()468   std::shared_ptr<tiledb_attribute_t> ptr() const {
469     return attr_;
470   }
471 
472   /**
473    * Dumps information about the attribute in an ASCII representation to an
474    * output.
475    *
476    * @param out (Optional) File to dump output to. Defaults to `nullptr`
477    * which will lead to selection of `stdout`.
478    */
479   void dump(FILE* out = nullptr) const {
480     ctx_.get().handle_error(
481         tiledb_attribute_dump(ctx_.get().ptr().get(), attr_.get(), out));
482   }
483 
484   /* ********************************* */
485   /*          STATIC FUNCTIONS         */
486   /* ********************************* */
487 
488   /**
489    * Factory function for creating a new attribute with datatype T.
490    *
491    * **Example:**
492    * @code{.cpp}
493    * tiledb::Context ctx;
494    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1");
495    * auto a2 = tiledb::Attribute::create<std::string>(ctx, "a2");
496    * auto a3 = tiledb::Attribute::create<std::array<float, 3>>(ctx, "a3");
497    * auto a4 = tiledb::Attribute::create<std::vector<double>>(ctx, "a4");
498    * auto a5 = tiledb::Attribute::create<char[8]>(ctx, "a5");
499    * @endcode
500    *
501    * @tparam T Datatype of the attribute. Can either be arithmetic type,
502    *         C-style array, std::string, std::vector, or any trivially
503    *         copyable classes (defined by std::is_trivially_copyable).
504    * @param ctx The TileDB context.
505    * @param name The attribute name.
506    * @return A new Attribute object.
507    */
508   template <typename T>
create(const Context & ctx,const std::string & name)509   static Attribute create(const Context& ctx, const std::string& name) {
510     using DataT = typename impl::TypeHandler<T>;
511     Attribute a(ctx, name, DataT::tiledb_type);
512     a.set_cell_val_num(DataT::tiledb_num);
513     return a;
514   }
515 
516   /** Factory function taking the type as a tiledb_datatype_t variable. */
create(const Context & ctx,const std::string & name,tiledb_datatype_t type)517   static Attribute create(
518       const Context& ctx, const std::string& name, tiledb_datatype_t type) {
519     Attribute a(ctx, name, type);
520     return a;
521   }
522 
523   /**
524    * Factory function for creating a new attribute with datatype T and
525    * a FilterList.
526    *
527    * **Example:**
528    * @code{.cpp}
529    * tiledb::Context ctx;
530    * tiledb::FilterList filter_list(ctx);
531    * filter_list.add_filter({ctx, TILEDB_FILTER_BYTESHUFFLE})
532    *     .add_filter({ctx, TILEDB_FILTER_BZIP2});
533    * auto a1 = tiledb::Attribute::create<int>(ctx, "a1", filter_list);
534    * @endcode
535    *
536    * @tparam T Datatype of the attribute. Can either be arithmetic type,
537    *         C-style array, `std::string`, `std::vector`, or any trivially
538    *         copyable classes (defined by `std::is_trivially_copyable`).
539    * @param ctx The TileDB context.
540    * @param name The attribute name.
541    * @param filter_list FilterList to use for attribute
542    * @return A new Attribute object.
543    */
544   template <typename T>
create(const Context & ctx,const std::string & name,const FilterList & filter_list)545   static Attribute create(
546       const Context& ctx,
547       const std::string& name,
548       const FilterList& filter_list) {
549     auto a = create<T>(ctx, name);
550     a.set_filter_list(filter_list);
551     return a;
552   }
553 
554  private:
555   /* ********************************* */
556   /*         PRIVATE ATTRIBUTES        */
557   /* ********************************* */
558 
559   /** The TileDB context. */
560   std::reference_wrapper<const Context> ctx_;
561 
562   /** An auxiliary deleter. */
563   impl::Deleter deleter_;
564 
565   /** The pointer to the C TileDB attribute object. */
566   std::shared_ptr<tiledb_attribute_t> attr_;
567 
568   /* ********************************* */
569   /*         PRIVATE FUNCTIONS         */
570   /* ********************************* */
571 
init_from_type(const std::string & name,tiledb_datatype_t type)572   void init_from_type(const std::string& name, tiledb_datatype_t type) {
573     tiledb_attribute_t* attr;
574     auto& ctx = ctx_.get();
575     ctx.handle_error(
576         tiledb_attribute_alloc(ctx.ptr().get(), name.c_str(), type, &attr));
577     attr_ = std::shared_ptr<tiledb_attribute_t>(attr, deleter_);
578   }
579 };
580 
581 /* ********************************* */
582 /*               MISC                */
583 /* ********************************* */
584 
585 /** Gets a string representation of an attribute for an output stream. */
586 inline std::ostream& operator<<(std::ostream& os, const Attribute& a) {
587   os << "Attr<" << a.name() << ',' << tiledb::impl::to_str(a.type()) << ','
588      << (a.cell_val_num() == TILEDB_VAR_NUM ? "VAR" :
589                                               std::to_string(a.cell_val_num()))
590      << '>';
591   return os;
592 }
593 
594 }  // namespace tiledb
595 
596 #endif  // TILEDB_CPP_API_ATTRIBUTE_H
597