Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions extensions/functions_list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,48 @@ scalar_functions:
value: func<any1 -> boolean?>
nullability: DECLARED_OUTPUT
return: boolean?

- name: "element_at"
description: >-
Returns the element at the specified index from the list.

Index is 1-based (i.e., the first element is at index 1). Zero is not
a valid index. Handling of negative indices depends on the
negative_index_semantics argument.

If the input list is null, the result is null. If the index is null,
the result is null. If the element at the specified index is null,
the result is null.
impls:
- args:
- name: input
value: list<any1>
- name: index
value: i64
- name: negative_index_semantics
description: >-
How to interpret negative index values.

* WRAP_AROUND: -1 refers to the last element, -2 to second-to-last, etc.
Negative indices whose absolute value exceeds the list length are still
out of bounds (e.g., -6 on a 5-element list is out of bounds, not wrapped).

* OUT_OF_BOUNDS: All negative indices are treated as out of bounds.
options: [WRAP_AROUND, OUT_OF_BOUNDS]
options:
on_out_of_bounds:
description: >-
Behavior when the index is out of bounds (index is 0, positive index > list length,
or for WRAP_AROUND negative index whose absolute value > list length,
or for OUT_OF_BOUNDS any negative index).

* RETURN_NULL: Return null for out-of-bounds indices.

* ERROR: Raise an error for out-of-bounds indices.

* ERROR_ON_ZERO_ONLY: Raise an error if the index is 0, but return null for
Comment thread
benbellick marked this conversation as resolved.
Outdated
other out-of-bounds indices. This matches Trino's behavior which treats index 0
as a programmer error (wrong indexing convention) rather than a simple out-of-bounds.
values: [RETURN_NULL, ERROR, ERROR_ON_ZERO_ONLY]
nullability: DECLARED_OUTPUT
Comment thread
benbellick marked this conversation as resolved.
return: any1?
52 changes: 52 additions & 0 deletions tests/cases/list/element_at.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
### SUBSTRAIT_SCALAR_TEST: v1.0
### SUBSTRAIT_INCLUDE: '/extensions/functions_list.yaml'

# basic: Basic positive index (1-based)
element_at([10, 20, 30]::list<i32>, 1::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 10::i32?
element_at([10, 20, 30]::list<i32>, 2::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 20::i32?
element_at([10, 20, 30]::list<i32>, 3::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 30::i32?
element_at(['a', 'b', 'c']::list<string>, 2::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 'b'::string?

# wrap_around: Negative indices count from end with WRAP_AROUND
element_at([10, 20, 30]::list<i32>, -1::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 30::i32?
element_at([10, 20, 30]::list<i32>, -2::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 20::i32?
element_at([10, 20, 30]::list<i32>, -3::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = 10::i32?

# out_of_bounds_negative: Negative indices are out of bounds with OUT_OF_BOUNDS
element_at([10, 20, 30]::list<i32>, -1::i64, OUT_OF_BOUNDS::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?
element_at([10, 20, 30]::list<i32>, -3::i64, OUT_OF_BOUNDS::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# out_of_bounds_positive: Positive index exceeds list length
element_at([10, 20, 30]::list<i32>, 5::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?
element_at([10, 20, 30]::list<i32>, 100::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# out_of_bounds_wrap_around: Negative index exceeds list length with WRAP_AROUND
element_at([10, 20, 30]::list<i32>, -5::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# zero_out_of_bounds: Zero index is out of bounds
element_at([10, 20, 30]::list<i32>, 0::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?
element_at([10, 20, 30]::list<i32>, 0::i64, OUT_OF_BOUNDS::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# null_list: Null list returns null
element_at(null::list?<i32>, 1::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# null_index: Null index returns null
element_at([10, 20, 30]::list<i32>, null::i64?, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# null_element: Retrieving a null element returns null
element_at([1, null, 3]::list<i32?>, 2::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# empty_list: Out of bounds on empty list
element_at([]::list<i32>, 1::i64, WRAP_AROUND::enum) [on_out_of_bounds:RETURN_NULL] = null::i32?

# error_on_out_of_bounds: ERROR option raises error for out-of-bounds indices
element_at([10, 20, 30]::list<i32>, 0::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR] = <!ERROR>
element_at([10, 20, 30]::list<i32>, 5::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR] = <!ERROR>
element_at([10, 20, 30]::list<i32>, -1::i64, OUT_OF_BOUNDS::enum) [on_out_of_bounds:ERROR] = <!ERROR>
element_at([]::list<i32>, 1::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR] = <!ERROR>

# error_on_zero_only: ERROR_ON_ZERO_ONLY errors on index 0, returns null otherwise (Trino behavior)
element_at([10, 20, 30]::list<i32>, 0::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR_ON_ZERO_ONLY] = <!ERROR>
element_at([10, 20, 30]::list<i32>, 5::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR_ON_ZERO_ONLY] = null::i32?
element_at([10, 20, 30]::list<i32>, -5::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR_ON_ZERO_ONLY] = null::i32?
element_at([10, 20, 30]::list<i32>, 2::i64, WRAP_AROUND::enum) [on_out_of_bounds:ERROR_ON_ZERO_ONLY] = 20::i32?
Loading