This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new be4282c95 feat(rust): Move setters and getters of options to the
adbc_ffi crate (#3927)
be4282c95 is described below
commit be4282c952e422e9cdc2d94d1049409d0b3986b6
Author: Felipe Oliveira Carvalho <[email protected]>
AuthorDate: Wed Jan 28 00:13:31 2026 -0300
feat(rust): Move setters and getters of options to the adbc_ffi crate
(#3927)
These are very close to the functionality provided by the `adbc_ffi`
create and would be helpful in a custom implementation of the core
traits.
---
rust/driver_manager/src/lib.rs | 229 +-----------------------------------
rust/ffi/src/driver_exporter.rs | 2 +-
rust/ffi/src/lib.rs | 2 +-
rust/ffi/src/options.rs | 254 ++++++++++++++++++++++++++++++++++++++++
rust/ffi/src/utils.rs | 30 -----
5 files changed, 262 insertions(+), 255 deletions(-)
diff --git a/rust/driver_manager/src/lib.rs b/rust/driver_manager/src/lib.rs
index 8ecf38829..36389b47d 100644
--- a/rust/driver_manager/src/lib.rs
+++ b/rust/driver_manager/src/lib.rs
@@ -104,7 +104,7 @@ pub mod error;
use std::collections::HashSet;
use std::env;
-use std::ffi::{CStr, CString, OsStr};
+use std::ffi::{CString, OsStr};
use std::fs;
use std::ops::DerefMut;
use std::os::raw::{c_char, c_void};
@@ -113,14 +113,17 @@ use std::pin::Pin;
use std::ptr::{null, null_mut};
use std::sync::{Arc, Mutex};
+use adbc_ffi::options::{
+ check_status, get_option_bytes, get_option_string, set_option_connection,
set_option_database,
+ set_option_statement,
+};
use arrow_array::ffi::{to_ffi, FFI_ArrowSchema};
use arrow_array::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream};
use arrow_array::{Array, RecordBatch, RecordBatchReader, StructArray};
use toml::de::DeTable;
use adbc_core::{
- constants,
- error::{AdbcStatusCode, Error, Result, Status},
+ error::{Error, Result, Status},
options::{self, AdbcVersion, InfoCode, OptionDatabase, OptionValue},
Connection, Database, Driver, LoadFlags, Optionable, PartitionedResult,
Statement,
LOAD_FLAG_ALLOW_RELATIVE_PATHS, LOAD_FLAG_SEARCH_ENV,
LOAD_FLAG_SEARCH_SYSTEM,
@@ -130,22 +133,10 @@ use adbc_ffi::driver_method;
use crate::error::libloading_error_to_adbc_error;
-const ERR_ONLY_STRING_OPT: &str = "Only string option value are supported with
ADBC 1.0.0";
const ERR_CANCEL_UNSUPPORTED: &str =
"Canceling connection or statement is not supported with ADBC 1.0.0";
const ERR_STATISTICS_UNSUPPORTED: &str = "Statistics are not supported with
ADBC 1.0.0";
-fn check_status(status: AdbcStatusCode, error: adbc_ffi::FFI_AdbcError) ->
Result<()> {
- match status {
- constants::ADBC_STATUS_OK => Ok(()),
- _ => {
- let mut error: Error = error.try_into()?;
- error.status = status.try_into()?;
- Err(error)
- }
- }
-}
-
#[derive(Debug)]
struct ManagedDriverInner {
driver: adbc_ffi::FFI_AdbcDriver,
@@ -802,122 +793,6 @@ fn get_default_entrypoint(driver_path: impl AsRef<OsStr>)
-> String {
entrypoint
}
-fn set_option_database(
- driver: &adbc_ffi::FFI_AdbcDriver,
- database: &mut adbc_ffi::FFI_AdbcDatabase,
- version: AdbcVersion,
- key: impl AsRef<str>,
- value: OptionValue,
-) -> Result<()> {
- let key = CString::new(key.as_ref())?;
- let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
- #[allow(unknown_lints)]
- #[warn(non_exhaustive_omitted_patterns)]
- let status = match (version, value) {
- (_, OptionValue::String(value)) => {
- let value = CString::new(value)?;
- let method = driver_method!(driver, DatabaseSetOption);
- unsafe { method(database, key.as_ptr(), value.as_ptr(), &mut
error) }
- }
- (AdbcVersion::V110, OptionValue::Bytes(value)) => {
- let method = driver_method!(driver, DatabaseSetOptionBytes);
- unsafe {
- method(
- database,
- key.as_ptr(),
- value.as_ptr(),
- value.len(),
- &mut error,
- )
- }
- }
- (AdbcVersion::V110, OptionValue::Int(value)) => {
- let method = driver_method!(driver, DatabaseSetOptionInt);
- unsafe { method(database, key.as_ptr(), value, &mut error) }
- }
- (AdbcVersion::V110, OptionValue::Double(value)) => {
- let method = driver_method!(driver, DatabaseSetOptionDouble);
- unsafe { method(database, key.as_ptr(), value, &mut error) }
- }
- (AdbcVersion::V100, _) => Err(Error::with_message_and_status(
- ERR_ONLY_STRING_OPT,
- Status::NotImplemented,
- ))?,
- (_, _) => unreachable!(),
- };
- check_status(status, error)
-}
-
-// Utility function to implement `*GetOption` and `*GetOptionBytes`. Basically,
-// it allocates a fixed-sized buffer to store the option's value, call the
driver's
-// `*GetOption`/`*GetOptionBytes` method that will fill this buffer and finally
-// we return the option's value as a `Vec`. Note that if the fixed-size buffer
-// is too small, we retry the same operation with a bigger buffer (the size of
-// which is obtained via the out parameter `length` of
`*GetOption`/`*GetOptionBytes`).
-fn get_option_buffer<F, T>(
- key: impl AsRef<str>,
- mut populate: F,
- driver: &adbc_ffi::FFI_AdbcDriver,
-) -> Result<Vec<T>>
-where
- F: FnMut(*const c_char, *mut T, *mut usize, *mut adbc_ffi::FFI_AdbcError)
-> AdbcStatusCode,
- T: Default + Clone,
-{
- const DEFAULT_LENGTH: usize = 128;
- let key = CString::new(key.as_ref())?;
- let mut run = |length| {
- let mut value = vec![T::default(); length];
- let mut length: usize = core::mem::size_of::<T>() * value.len();
- let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
- (
- populate(key.as_ptr(), value.as_mut_ptr(), &mut length, &mut
error),
- length,
- value,
- error,
- )
- };
-
- let (status, length, value, error) = run(DEFAULT_LENGTH);
- check_status(status, error)?;
-
- if length <= DEFAULT_LENGTH {
- Ok(value[..length].to_vec())
- } else {
- let (status, _, value, error) = run(length);
- check_status(status, error)?;
- Ok(value)
- }
-}
-
-fn get_option_bytes<F>(
- key: impl AsRef<str>,
- populate: F,
- driver: &adbc_ffi::FFI_AdbcDriver,
-) -> Result<Vec<u8>>
-where
- F: FnMut(*const c_char, *mut u8, *mut usize, *mut adbc_ffi::FFI_AdbcError)
-> AdbcStatusCode,
-{
- get_option_buffer(key, populate, driver)
-}
-
-fn get_option_string<F>(
- key: impl AsRef<str>,
- populate: F,
- driver: &adbc_ffi::FFI_AdbcDriver,
-) -> Result<String>
-where
- F: FnMut(
- *const c_char,
- *mut c_char,
- *mut usize,
- *mut adbc_ffi::FFI_AdbcError,
- ) -> AdbcStatusCode,
-{
- let value = get_option_buffer(key, populate, driver)?;
- let value = unsafe { CStr::from_ptr(value.as_ptr()) };
- Ok(value.to_string_lossy().to_string())
-}
-
struct ManagedDatabaseInner {
database: Mutex<adbc_ffi::FFI_AdbcDatabase>,
driver: Pin<Arc<ManagedDriverInner>>,
@@ -1130,52 +1005,6 @@ impl Database for ManagedDatabase {
}
}
-fn set_option_connection(
- driver: &adbc_ffi::FFI_AdbcDriver,
- connection: &mut adbc_ffi::FFI_AdbcConnection,
- version: AdbcVersion,
- key: impl AsRef<str>,
- value: OptionValue,
-) -> Result<()> {
- let key = CString::new(key.as_ref())?;
- let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
- #[allow(unknown_lints)]
- #[warn(non_exhaustive_omitted_patterns)]
- let status = match (version, value) {
- (_, OptionValue::String(value)) => {
- let value = CString::new(value)?;
- let method = driver_method!(driver, ConnectionSetOption);
- unsafe { method(connection, key.as_ptr(), value.as_ptr(), &mut
error) }
- }
- (AdbcVersion::V110, OptionValue::Bytes(value)) => {
- let method = driver_method!(driver, ConnectionSetOptionBytes);
- unsafe {
- method(
- connection,
- key.as_ptr(),
- value.as_ptr(),
- value.len(),
- &mut error,
- )
- }
- }
- (AdbcVersion::V110, OptionValue::Int(value)) => {
- let method = driver_method!(driver, ConnectionSetOptionInt);
- unsafe { method(connection, key.as_ptr(), value, &mut error) }
- }
- (AdbcVersion::V110, OptionValue::Double(value)) => {
- let method = driver_method!(driver, ConnectionSetOptionDouble);
- unsafe { method(connection, key.as_ptr(), value, &mut error) }
- }
- (AdbcVersion::V100, _) => Err(Error::with_message_and_status(
- ERR_ONLY_STRING_OPT,
- Status::NotImplemented,
- ))?,
- (_, _) => unreachable!(),
- };
- check_status(status, error)
-}
-
struct ManagedConnectionInner {
connection: Mutex<adbc_ffi::FFI_AdbcConnection>,
database: Arc<ManagedDatabaseInner>,
@@ -1546,52 +1375,6 @@ impl Connection for ManagedConnection {
}
}
-fn set_option_statement(
- driver: &adbc_ffi::FFI_AdbcDriver,
- statement: &mut adbc_ffi::FFI_AdbcStatement,
- version: AdbcVersion,
- key: impl AsRef<str>,
- value: OptionValue,
-) -> Result<()> {
- let key = CString::new(key.as_ref())?;
- let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
- #[allow(unknown_lints)]
- #[warn(non_exhaustive_omitted_patterns)]
- let status = match (version, value) {
- (_, OptionValue::String(value)) => {
- let value = CString::new(value)?;
- let method = driver_method!(driver, StatementSetOption);
- unsafe { method(statement, key.as_ptr(), value.as_ptr(), &mut
error) }
- }
- (AdbcVersion::V110, OptionValue::Bytes(value)) => {
- let method = driver_method!(driver, StatementSetOptionBytes);
- unsafe {
- method(
- statement,
- key.as_ptr(),
- value.as_ptr(),
- value.len(),
- &mut error,
- )
- }
- }
- (AdbcVersion::V110, OptionValue::Int(value)) => {
- let method = driver_method!(driver, StatementSetOptionInt);
- unsafe { method(statement, key.as_ptr(), value, &mut error) }
- }
- (AdbcVersion::V110, OptionValue::Double(value)) => {
- let method = driver_method!(driver, StatementSetOptionDouble);
- unsafe { method(statement, key.as_ptr(), value, &mut error) }
- }
- (AdbcVersion::V100, _) => Err(Error::with_message_and_status(
- ERR_ONLY_STRING_OPT,
- Status::NotImplemented,
- ))?,
- (_, _) => unreachable!(),
- };
- check_status(status, error)
-}
-
struct ManagedStatementInner {
statement: Mutex<adbc_ffi::FFI_AdbcStatement>,
connection: Arc<ManagedConnectionInner>,
diff --git a/rust/ffi/src/driver_exporter.rs b/rust/ffi/src/driver_exporter.rs
index e24d9b473..e4573ac55 100644
--- a/rust/ffi/src/driver_exporter.rs
+++ b/rust/ffi/src/driver_exporter.rs
@@ -26,7 +26,7 @@ use arrow_array::StructArray;
use arrow_schema::DataType;
use super::{
- types::ErrorPrivateData, utils::get_opt_name, FFI_AdbcConnection,
FFI_AdbcDatabase,
+ options::get_opt_name, types::ErrorPrivateData, FFI_AdbcConnection,
FFI_AdbcDatabase,
FFI_AdbcDriver, FFI_AdbcError, FFI_AdbcErrorDetail, FFI_AdbcPartitions,
FFI_AdbcStatement,
};
use adbc_core::constants::ADBC_STATUS_OK;
diff --git a/rust/ffi/src/lib.rs b/rust/ffi/src/lib.rs
index 68ae30375..d4a29410b 100644
--- a/rust/ffi/src/lib.rs
+++ b/rust/ffi/src/lib.rs
@@ -39,7 +39,7 @@
//! [export_driver] macro.
pub mod driver_exporter;
-mod utils;
+pub mod options;
#[doc(hidden)]
pub use driver_exporter::FFIDriver;
pub mod methods;
diff --git a/rust/ffi/src/options.rs b/rust/ffi/src/options.rs
new file mode 100644
index 000000000..5a9717eb6
--- /dev/null
+++ b/rust/ffi/src/options.rs
@@ -0,0 +1,254 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+
+use adbc_core::constants;
+use adbc_core::error::{AdbcStatusCode, Error, Result, Status};
+use adbc_core::options::{AdbcVersion, OptionValue};
+
+use crate::{driver_method, FFI_AdbcConnection, FFI_AdbcStatement};
+use crate::{FFI_AdbcDatabase, FFI_AdbcDriver, FFI_AdbcError};
+
+const ERR_ONLY_STRING_OPT: &str = "Only string option value are supported with
ADBC 1.0.0";
+
+pub fn check_status(status: AdbcStatusCode, error: FFI_AdbcError) ->
Result<()> {
+ match status {
+ constants::ADBC_STATUS_OK => Ok(()),
+ _ => {
+ let mut error: Error = error.try_into()?;
+ error.status = status.try_into()?;
+ Err(error)
+ }
+ }
+}
+
+#[allow(unknown_lints)]
+#[warn(non_exhaustive_omitted_patterns)]
+pub(crate) fn get_opt_name(value: &OptionValue) -> &str {
+ match value {
+ OptionValue::String(_) => "String",
+ OptionValue::Bytes(_) => "Bytes",
+ OptionValue::Int(_) => "Int",
+ OptionValue::Double(_) => "Double",
+ _ => unreachable!(),
+ }
+}
+
+pub fn set_option_database(
+ driver: &FFI_AdbcDriver,
+ database: &mut FFI_AdbcDatabase,
+ version: AdbcVersion,
+ key: impl AsRef<str>,
+ value: OptionValue,
+) -> Result<()> {
+ let key = CString::new(key.as_ref())?;
+ let mut error = FFI_AdbcError::with_driver(driver);
+ #[allow(unknown_lints)]
+ #[warn(non_exhaustive_omitted_patterns)]
+ let status = match (version, value) {
+ (_, OptionValue::String(value)) => {
+ let value = CString::new(value)?;
+ let method = driver_method!(driver, DatabaseSetOption);
+ unsafe { method(database, key.as_ptr(), value.as_ptr(), &mut
error) }
+ }
+ (AdbcVersion::V110, OptionValue::Bytes(value)) => {
+ let method = driver_method!(driver, DatabaseSetOptionBytes);
+ unsafe {
+ method(
+ database,
+ key.as_ptr(),
+ value.as_ptr(),
+ value.len(),
+ &mut error,
+ )
+ }
+ }
+ (AdbcVersion::V110, OptionValue::Int(value)) => {
+ let method = driver_method!(driver, DatabaseSetOptionInt);
+ unsafe { method(database, key.as_ptr(), value, &mut error) }
+ }
+ (AdbcVersion::V110, OptionValue::Double(value)) => {
+ let method = driver_method!(driver, DatabaseSetOptionDouble);
+ unsafe { method(database, key.as_ptr(), value, &mut error) }
+ }
+ (AdbcVersion::V100, _) => Err(Error::with_message_and_status(
+ ERR_ONLY_STRING_OPT,
+ Status::NotImplemented,
+ ))?,
+ (_, _) => unreachable!(),
+ };
+ check_status(status, error)
+}
+
+// Utility function to implement `*GetOption` and `*GetOptionBytes`. Basically,
+// it allocates a fixed-sized buffer to store the option's value, call the
driver's
+// `*GetOption`/`*GetOptionBytes` method that will fill this buffer and finally
+// we return the option's value as a `Vec`. Note that if the fixed-size buffer
+// is too small, we retry the same operation with a bigger buffer (the size of
+// which is obtained via the out parameter `length` of
`*GetOption`/`*GetOptionBytes`).
+pub fn get_option_buffer<F, T>(
+ key: impl AsRef<str>,
+ mut populate: F,
+ driver: &FFI_AdbcDriver,
+) -> Result<Vec<T>>
+where
+ F: FnMut(*const c_char, *mut T, *mut usize, *mut FFI_AdbcError) ->
AdbcStatusCode,
+ T: Default + Clone,
+{
+ const DEFAULT_LENGTH: usize = 128;
+ let key = CString::new(key.as_ref())?;
+ let mut run = |length| {
+ let mut value = vec![T::default(); length];
+ let mut length: usize = core::mem::size_of::<T>() * value.len();
+ let mut error = FFI_AdbcError::with_driver(driver);
+ (
+ populate(key.as_ptr(), value.as_mut_ptr(), &mut length, &mut
error),
+ length,
+ value,
+ error,
+ )
+ };
+
+ let (status, length, value, error) = run(DEFAULT_LENGTH);
+ check_status(status, error)?;
+
+ if length <= DEFAULT_LENGTH {
+ Ok(value[..length].to_vec())
+ } else {
+ let (status, _, value, error) = run(length);
+ check_status(status, error)?;
+ Ok(value)
+ }
+}
+
+pub fn get_option_bytes<F>(
+ key: impl AsRef<str>,
+ populate: F,
+ driver: &FFI_AdbcDriver,
+) -> Result<Vec<u8>>
+where
+ F: FnMut(*const c_char, *mut u8, *mut usize, *mut FFI_AdbcError) ->
AdbcStatusCode,
+{
+ get_option_buffer(key, populate, driver)
+}
+
+pub fn get_option_string<F>(
+ key: impl AsRef<str>,
+ populate: F,
+ driver: &FFI_AdbcDriver,
+) -> Result<String>
+where
+ F: FnMut(*const c_char, *mut c_char, *mut usize, *mut FFI_AdbcError) ->
AdbcStatusCode,
+{
+ let value = get_option_buffer(key, populate, driver)?;
+ let value = unsafe { CStr::from_ptr(value.as_ptr()) };
+ Ok(value.to_string_lossy().to_string())
+}
+
+pub fn set_option_connection(
+ driver: &FFI_AdbcDriver,
+ connection: &mut FFI_AdbcConnection,
+ version: AdbcVersion,
+ key: impl AsRef<str>,
+ value: OptionValue,
+) -> Result<()> {
+ let key = CString::new(key.as_ref())?;
+ let mut error = FFI_AdbcError::with_driver(driver);
+ #[allow(unknown_lints)]
+ #[warn(non_exhaustive_omitted_patterns)]
+ let status = match (version, value) {
+ (_, OptionValue::String(value)) => {
+ let value = CString::new(value)?;
+ let method = driver_method!(driver, ConnectionSetOption);
+ unsafe { method(connection, key.as_ptr(), value.as_ptr(), &mut
error) }
+ }
+ (AdbcVersion::V110, OptionValue::Bytes(value)) => {
+ let method = driver_method!(driver, ConnectionSetOptionBytes);
+ unsafe {
+ method(
+ connection,
+ key.as_ptr(),
+ value.as_ptr(),
+ value.len(),
+ &mut error,
+ )
+ }
+ }
+ (AdbcVersion::V110, OptionValue::Int(value)) => {
+ let method = driver_method!(driver, ConnectionSetOptionInt);
+ unsafe { method(connection, key.as_ptr(), value, &mut error) }
+ }
+ (AdbcVersion::V110, OptionValue::Double(value)) => {
+ let method = driver_method!(driver, ConnectionSetOptionDouble);
+ unsafe { method(connection, key.as_ptr(), value, &mut error) }
+ }
+ (AdbcVersion::V100, _) => Err(Error::with_message_and_status(
+ ERR_ONLY_STRING_OPT,
+ Status::NotImplemented,
+ ))?,
+ (_, _) => unreachable!(),
+ };
+ check_status(status, error)
+}
+
+pub fn set_option_statement(
+ driver: &FFI_AdbcDriver,
+ statement: &mut FFI_AdbcStatement,
+ version: AdbcVersion,
+ key: impl AsRef<str>,
+ value: OptionValue,
+) -> Result<()> {
+ let key = CString::new(key.as_ref())?;
+ let mut error = FFI_AdbcError::with_driver(driver);
+ #[allow(unknown_lints)]
+ #[warn(non_exhaustive_omitted_patterns)]
+ let status = match (version, value) {
+ (_, OptionValue::String(value)) => {
+ let value = CString::new(value)?;
+ let method = driver_method!(driver, StatementSetOption);
+ unsafe { method(statement, key.as_ptr(), value.as_ptr(), &mut
error) }
+ }
+ (AdbcVersion::V110, OptionValue::Bytes(value)) => {
+ let method = driver_method!(driver, StatementSetOptionBytes);
+ unsafe {
+ method(
+ statement,
+ key.as_ptr(),
+ value.as_ptr(),
+ value.len(),
+ &mut error,
+ )
+ }
+ }
+ (AdbcVersion::V110, OptionValue::Int(value)) => {
+ let method = driver_method!(driver, StatementSetOptionInt);
+ unsafe { method(statement, key.as_ptr(), value, &mut error) }
+ }
+ (AdbcVersion::V110, OptionValue::Double(value)) => {
+ let method = driver_method!(driver, StatementSetOptionDouble);
+ unsafe { method(statement, key.as_ptr(), value, &mut error) }
+ }
+ (AdbcVersion::V100, _) => Err(Error::with_message_and_status(
+ ERR_ONLY_STRING_OPT,
+ Status::NotImplemented,
+ ))?,
+ (_, _) => unreachable!(),
+ };
+ check_status(status, error)
+}
diff --git a/rust/ffi/src/utils.rs b/rust/ffi/src/utils.rs
deleted file mode 100644
index e23f6ce4b..000000000
--- a/rust/ffi/src/utils.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-use adbc_core::options::OptionValue;
-
-#[allow(unknown_lints)]
-#[warn(non_exhaustive_omitted_patterns)]
-pub(crate) fn get_opt_name(value: &OptionValue) -> &str {
- match value {
- OptionValue::String(_) => "String",
- OptionValue::Bytes(_) => "Bytes",
- OptionValue::Int(_) => "Int",
- OptionValue::Double(_) => "Double",
- _ => unreachable!(),
- }
-}