diff --git a/mdbx.h b/mdbx.h
index d01bd4f3..867ec5e5 100644
--- a/mdbx.h
+++ b/mdbx.h
@@ -1631,6 +1631,167 @@ LIBMDBX_API int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr);
LIBMDBX_API int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result,
uint64_t increment);
+/*----------------------------------------------------------------------------*/
+/* attribute support functions for Nexenta */
+typedef uint64_t mdbx_attr_t;
+
+ /** @brief Store by cursor with attribute.
+ *
+ * This function stores key/data pairs into the database.
+ * The cursor is positioned at the new item, or on failure usually near it.
+ * @note Internally based on #MDBX_RESERVE feature, therefore doesn't support #MDBX_DUPSORT.
+ * @note Earlier documentation incorrectly said errors would leave the
+ * state of the cursor unchanged.
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ * @param[in] key The key operated on.
+ * @param[in] data The data operated on.
+ * @param[in] attr The attribute.
+ * @param[in] flags Options for this operation. This parameter
+ * must be set to 0 or one of the values described here.
+ *
+ * - #MDBX_CURRENT - replace the item at the current cursor position.
+ * The \b key parameter must still be provided, and must match it.
+ * This is intended to be used when the
+ * new data is the same size as the old. Otherwise it will simply
+ * perform a delete of the old record followed by an insert.
+ *
- #MDBX_NOOVERWRITE - enter the new key/data pair only if the key
+ * does not already appear in the database. The function will return
+ * #MDBX_KEYEXIST if the key already appears in the database.
+ *
- #MDBX_RESERVE - reserve space for data of the given size, but
+ * don't copy the given data. Instead, return a pointer to the
+ * reserved space, which the caller can fill in later. This saves
+ * an extra memcpy if the data is being generated later.
+ *
- #MDBX_APPEND - append the given key/data pair to the end of the
+ * database. No key comparisons are performed. This option allows
+ * fast bulk loading when keys are already known to be in the
+ * correct order. Loading unsorted keys with this flag will cause
+ * data corruption.
+ *
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ *
+ * - #MDBX_MAP_FULL - the database is full, see #mdb_env_set_mapsize().
+ *
- #MDBX_TXN_FULL - the transaction has too many dirty pages.
+ *
- EACCES - an attempt was made to write in a read-only transaction.
+ *
- EINVAL - an invalid parameter was specified.
+ *
+ */
+int mdbx_cursor_put_attr(MDBX_cursor *cursor, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t attr, unsigned flags);
+
+ /** @brief Store items and attributes into a database.
+ *
+ * This function stores key/data pairs in the database. The default behavior
+ * is to enter the new key/data pair, replacing any previously existing key
+ * if duplicates are disallowed.
+ * @note Internally based on #MDBX_RESERVE feature, therefore doesn't support #MDBX_DUPSORT.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] key The key to store in the database
+ * @param[in] attr The attribute to store in the database
+ * @param[in,out] data The data to store
+ * @param[in] flags Special options for this operation. This parameter
+ * must be set to 0 or by bitwise OR'ing together one or more of the
+ * values described here.
+ *
+ * - #MDBX_NOOVERWRITE - enter the new key/data pair only if the key
+ * does not already appear in the database. The function will return
+ * #MDBX_KEYEXIST if the key already appears in the database. The \b data
+ * parameter will be set to point to the existing item.
+ *
- #MDBX_RESERVE - reserve space for data of the given size, but
+ * don't copy the given data. Instead, return a pointer to the
+ * reserved space, which the caller can fill in later - before
+ * the next update operation or the transaction ends. This saves
+ * an extra memcpy if the data is being generated later.
+ * LMDB does nothing else with this memory, the caller is expected
+ * to modify all of the space requested.
+ *
- #MDBX_APPEND - append the given key/data pair to the end of the
+ * database. This option allows fast bulk loading when keys are
+ * already known to be in the correct order. Loading unsorted keys
+ * with this flag will cause a #MDBX_KEYEXIST error.
+ *
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ *
+ * - #MDBX_MAP_FULL - the database is full, see #mdb_env_set_mapsize().
+ *
- #MDBX_TXN_FULL - the transaction has too many dirty pages.
+ *
- EACCES - an attempt was made to write in a read-only transaction.
+ *
- EINVAL - an invalid parameter was specified.
+ *
+ */
+int mdbx_put_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t attr, unsigned flags);
+
+ /** @brief Set items attribute from a database.
+ *
+ * This function stores key/data pairs attribute to the database.
+ * @note Internally based on #MDBX_RESERVE feature, therefore doesn't support #MDBX_DUPSORT.
+ *
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] key The key to search for in the database
+ * @param[in] data The data to be stored or NULL to save previous value.
+ * @param[in] attr The attribute to be stored
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ *
+ * - #MDBX_NOTFOUND - the key-value pair was not in the database.
+ *
- EINVAL - an invalid parameter was specified.
+ *
+ */
+int mdbx_set_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t attr);
+
+ /** @brief Get items attribute from a database cursor.
+ *
+ * This function retrieves key/data pairs attribute from the database.
+ * The attribute of the specified key-value pair is returned in
+ * uint64_t to which \b attrptr refers.
+ * If the database supports duplicate keys (#MDBX_DUPSORT) then both
+ * key and data parameters are required, otherwise data could be NULL.
+ *
+ * @note Values returned from the database are valid only until a
+ * subsequent update operation, or the end of the transaction.
+ * @param[in] mc A database cursor pointing at the node
+ * @param[in] key The key to search for in the database
+ * @param[in,out] data The data for #MDBX_DUPSORT databases
+ * @param[out] attrptr The pointer to the result
+ * @param[in] op A cursor operation #MDBX_cursor_op
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ *
+ * - #MDBX_NOTFOUND - the key-value pair was not in the database.
+ *
- EINVAL - an invalid parameter was specified.
+ *
+ */
+int mdbx_cursor_get_attr(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t *attrptr, MDBX_cursor_op op);
+
+ /** @brief Get items attribute from a database.
+ *
+ * This function retrieves key/data pairs attribute from the database.
+ * The attribute of the specified key-value pair is returned in
+ * uint64_t to which \b attrptr refers.
+ * If the database supports duplicate keys (#MDBX_DUPSORT) then both
+ * key and data parameters are required, otherwise data is ignored.
+ *
+ * @note Values returned from the database are valid only until a
+ * subsequent update operation, or the end of the transaction.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] key The key to search for in the database
+ * @param[in] data The data for #MDBX_DUPSORT databases
+ * @param[out] attrptr The pointer to the result
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ *
+ * - #MDBX_NOTFOUND - the key-value pair was not in the database.
+ *
- EINVAL - an invalid parameter was specified.
+ *
+ */
+int mdbx_get_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t *attrptr);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/mdbx.c b/src/mdbx.c
index ae1a71ff..57ebe42d 100644
--- a/src/mdbx.c
+++ b/src/mdbx.c
@@ -11097,3 +11097,141 @@ int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result,
return MDBX_SUCCESS;
}
+
+/*----------------------------------------------------------------------------*/
+/* attribute support functions for Nexenta */
+
+static __inline int
+mdbx_attr_peek(MDBX_val *data, mdbx_attr_t *attrptr)
+{
+ if (unlikely(data->iov_len < sizeof(mdbx_attr_t)))
+ return MDBX_INCOMPATIBLE;
+
+ if (likely(attrptr != NULL))
+ *attrptr = *(mdbx_attr_t*) data->iov_base;
+ data->iov_len -= sizeof(mdbx_attr_t);
+ data->iov_base = likely(data->iov_len > 0)
+ ? ((mdbx_attr_t*) data->iov_base) + 1 : NULL;
+
+ return MDBX_SUCCESS;
+}
+
+static __inline int
+mdbx_attr_poke(MDBX_val *reserved, MDBX_val *data, mdbx_attr_t attr, unsigned flags)
+{
+ mdbx_attr_t *space = reserved->iov_base;
+ if (flags & MDBX_RESERVE) {
+ if (likely(data != NULL)) {
+ data->iov_base = data->iov_len ? space + 1 : NULL;
+ }
+ } else {
+ *space = attr;
+ if (likely(data != NULL)) {
+ memcpy(space + 1, data->iov_base, data->iov_len );
+ }
+ }
+
+ return MDBX_SUCCESS;
+}
+
+int
+mdbx_cursor_get_attr(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t *attrptr, MDBX_cursor_op op)
+{
+ int rc = mdbx_cursor_get(mc, key, data, op);
+ if (unlikely(rc != MDBX_SUCCESS))
+ return rc;
+
+ return mdbx_attr_peek(data, attrptr);
+}
+
+int
+mdbx_get_attr(MDBX_txn *txn, MDBX_dbi dbi,
+ MDBX_val *key, MDBX_val *data, uint64_t *attrptr)
+{
+ int rc = mdbx_get(txn, dbi, key, data);
+ if (unlikely(rc != MDBX_SUCCESS))
+ return rc;
+
+ return mdbx_attr_peek(data, attrptr);
+}
+
+int
+mdbx_put_attr(MDBX_txn *txn, MDBX_dbi dbi,
+ MDBX_val *key, MDBX_val *data, mdbx_attr_t attr, unsigned flags)
+{
+ MDBX_val reserve = {
+ .iov_base = NULL,
+ .iov_len = (data ? data->iov_len : 0) + sizeof(mdbx_attr_t)
+ };
+
+ int rc = mdbx_put(txn, dbi, key, &reserve, flags | MDBX_RESERVE);
+ if (unlikely(rc != MDBX_SUCCESS))
+ return rc;
+
+ return mdbx_attr_poke(&reserve, data, attr, flags);
+}
+
+int mdbx_cursor_put_attr(MDBX_cursor *cursor, MDBX_val *key, MDBX_val *data,
+ mdbx_attr_t attr, unsigned flags)
+{
+ MDBX_val reserve = {
+ .iov_base = NULL,
+ .iov_len = (data ? data->iov_len : 0) + sizeof(mdbx_attr_t)
+ };
+
+ int rc = mdbx_cursor_put(cursor, key, &reserve, flags | MDBX_RESERVE);
+ if (unlikely(rc != MDBX_SUCCESS))
+ return rc;
+
+ return mdbx_attr_poke(&reserve, data, attr, flags);
+}
+
+int mdbx_set_attr(MDBX_txn *txn, MDBX_dbi dbi,
+ MDBX_val *key, MDBX_val *data, mdbx_attr_t attr)
+{
+ MDBX_cursor mc;
+ MDBX_xcursor mx;
+ MDBX_val old_data;
+ mdbx_attr_t old_attr;
+ int rc;
+
+ if (unlikely(!key || !txn))
+ return EINVAL;
+
+ if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
+ return MDBX_VERSION_MISMATCH;
+
+ if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID)))
+ return EINVAL;
+
+ if (unlikely(txn->mt_flags & (MDBX_TXN_RDONLY|MDBX_TXN_BLOCKED)))
+ return (txn->mt_flags & MDBX_TXN_RDONLY) ? EACCES : MDBX_BAD_TXN;
+
+ mdbx_cursor_init(&mc, txn, dbi, &mx);
+ rc = mdbx_cursor_set(&mc, key, &old_data, MDBX_SET, NULL);
+ if (unlikely(rc != MDBX_SUCCESS)) {
+ if (rc == MDBX_NOTFOUND && data) {
+ mc.mc_next = txn->mt_cursors[dbi];
+ txn->mt_cursors[dbi] = &mc;
+ rc = mdbx_cursor_put_attr(&mc, key, data, attr, 0);
+ txn->mt_cursors[dbi] = mc.mc_next;
+ }
+ return rc;
+ }
+
+ rc = mdbx_attr_peek(&old_data, &old_attr);
+ if (unlikely(rc != MDBX_SUCCESS))
+ return rc;
+
+ if (old_attr == attr && (!data ||
+ (data->iov_len == old_data.iov_len
+ && memcpy(data->iov_base, old_data.iov_base, old_data.iov_len) == 0)))
+ return MDBX_SUCCESS;
+
+ mc.mc_next = txn->mt_cursors[dbi];
+ txn->mt_cursors[dbi] = &mc;
+ rc = mdbx_cursor_put_attr(&mc, key, data ? data : &old_data, attr, MDBX_CURRENT);
+ txn->mt_cursors[dbi] = mc.mc_next;
+ return rc;
+}