# Developing Record UDFs

This page describes how to create and apply a single-record User-Defined Function (UDF). Record UDFs augment both read and write behavior.

## Typical uses for record-based UDFs

-   Record UDFs can implement an atomic operation that does not currently exist in the client. UDFs do not perform at the same speed and scale as native operations, so it is important to consider using the rich [List](https://aerospike.com/docs/develop/data-types/collections/list) and [Map](https://aerospike.com/docs/develop/data-types/collections/map) API methods instead. Before using a UDF, consider combining multiple native operations with the `operate()` command, too.
    
-   You can use record UDFs for single-record, multi-operation transactions where data from one bin determines the value of another bin.
    
-   Use background UDFs for large scale maintenance and data normalization, with a record UDF applied to all the records returned by a query.
    

## Guidelines for using record UDFs

-   The first argument of the function, such as `rec`, refers to the database record. Do not name the variable `record` as this is one of the types in the [Aerospike Lua API](https://aerospike.com/docs/database/advanced/udf/api/types/record).
-   Each subsequent argument is specific to the UDF, and must be one of the types supported by the database: numeric (integer or double), string, list or map.
-   A record UDF should have one or more parameters (the record and optionally more). If a UDF has N parameters, and only (N-k) arguments passed in, the last k will automatically be assigned a value of `nil`.
-   A record UDF may return one of the types supported by the database: numeric (integer or double), string, bytes, list or map.
-   A background UDF, where a single UDF is applied by [primary index query](https://aerospike.com/docs/develop/learn/queries/primary-index#background-queries) or a [secondary index query](https://aerospike.com/docs/develop/learn/queries/secondary-index), modifies records without returning a result (write only).
-   A record UDF command inside a transaction will lock the record (blocking others from writing to it with an [`AS_ERR_MRT_BLOCKED`](https://aerospike.com/docs/database/reference/error-codes) error code 120) regardless of whether it performs read or write operations.

## Record TTL and UDF

When creating or updating a record, the record’s new TTL (time to live) value adheres to the following hierarchy:

-   If the UDF explicitly calls [`record.set_ttl()`](https://aerospike.com/docs/database/advanced/udf/api/types/record), that value is used. See [Record TTL and UDF](https://aerospike.com/docs/database/advanced/udf/modules/record/develop/#record-ttl-and-udf).
-   If there is a TTL specified in the policy field of the client UDF call, that value is used. See [Expiration (Time to Live)](https://aerospike.com/docs/database/learn/policies/#expiration-time-to-live).
-   If the client policy passes a TTL of 0, the namespace’s [`default-ttl`](https://aerospike.com/docs/database/reference/config#namespace__default-ttl) will be used.

## Example: Record create or update

In this simple example, [Annotated Record UDF Example](https://aerospike.com/docs/database/advanced/udf/modules/record/examples/annotated), we show how the UDF can create or update an Aerospike record.

## Example: String slice operator

Aerospike has two atomic operations for the [String](https://aerospike.com/docs/develop/data-types/scalar#string) data type - append and prepend. The following record UDF adds an atomic string slice operator.

```lua
function slice(rec, bin, a, b)

  local s = rec[bin]

  if type(s) == 'string' then

    return s:sub(a, b)

  end

  return nil

end
```

In aql

Terminal window

```bash
aql> register module 'util.lua'

OK, 1 module added.

aql> insert into test.foo (PK, x) values ('1', "Alright I like the beat except the snare, kick and keys")

OK, 1 record affected.

aql> execute util.slice('x', 9, 23) on test.foo where PK='1'

+-------------------+

| slice             |

+-------------------+

| "I like the beat" |

+-------------------+

1 row in set (0.001 secs)
```

## Example: Using Protobuf

In this example, a UDF makes use of an external Lua module, which in turns calls a C shared object. The [Protobuf Module Example](https://aerospike.com/docs/database/advanced/udf/modules/record/examples/protobuf) gives more details about the code involved in this example.

```plaintext
aql> select * from test.foo where PK='1'

Error: (2) AEROSPIKE_ERR_RECORD_NOT_FOUND

aql> execute pbuf.vartix(123, 'JDoe', 'j.doe@gmail.com') on test.foo where PK='1'

+--------+

| vartix |

+--------+

|        |

+--------+

1 row in set (0.001 secs)

aql> select * from test.foo where PK='1'

+----------------------------------------------------------------------------------------------+

| person                                                                                       |

+----------------------------------------------------------------------------------------------+

| 12 03 57 69 6C 08 7B 1A 16 77 69 6C 2E 6A 61 6D 69 65 73 6F 6E 40 67 6D 61 69 6C 2E 63 6F 6D |

+----------------------------------------------------------------------------------------------+

1 row in set (0.000 secs)

aql> execute pbuf.velkor() on test.foo where PK='1'

+--------+

| velkor |

+--------+

| 123    |

+--------+

1 row in set (0.000 secs)
```

## Example: Using a background UDF to reset TTLs

The [Background UDF Example](https://aerospike.com/docs/database/advanced/udf/modules/record/examples/background) resets the TTL of records accidentally set to never expire.