#![allow(non_camel_case_types, non_snake_case)]
use std::ffi::{CStr, CString, NulError};
use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr::{null, null_mut};
use super::methods;
use adbc_core::{
Partitions, constants,
error::{AdbcStatusCode, Error, Status},
};
pub type FFI_AdbcDriverInitFunc =
unsafe extern "C" fn(c_int, *mut c_void, *mut FFI_AdbcError) -> AdbcStatusCode;
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcError {
pub message: *mut c_char,
pub vendor_code: i32,
pub sqlstate: [c_char; 5],
pub release: Option<unsafe extern "C" fn(*mut Self)>,
pub private_data: *mut c_void,
pub private_driver: *const FFI_AdbcDriver,
}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcErrorV100 {
pub message: *mut c_char,
pub vendor_code: i32,
pub sqlstate: [c_char; 5],
pub release: Option<unsafe extern "C" fn(*mut Self)>,
}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcErrorDetail {
pub key: *const c_char,
pub value: *const u8,
pub value_length: usize,
}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcDatabase {
pub private_data: *mut c_void,
pub private_driver: *const FFI_AdbcDriver,
}
unsafe impl Send for FFI_AdbcDatabase {}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcConnection {
pub private_data: *mut c_void,
pub private_driver: *const FFI_AdbcDriver,
}
unsafe impl Send for FFI_AdbcConnection {}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcStatement {
pub private_data: *mut c_void,
pub private_driver: *const FFI_AdbcDriver,
}
unsafe impl Send for FFI_AdbcStatement {}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcPartitions {
pub num_partitions: usize,
pub partitions: *mut *mut u8,
pub partition_lengths: *mut usize,
pub private_data: *mut c_void,
pub release: Option<unsafe extern "C" fn(*mut Self)>,
}
#[repr(C)]
#[derive(Debug)]
pub struct FFI_AdbcDriver {
pub private_data: *mut c_void,
pub private_manager: *const c_void,
pub release: Option<
unsafe extern "C" fn(driver: *mut Self, error: *mut FFI_AdbcError) -> AdbcStatusCode,
>,
pub DatabaseInit: Option<methods::FuncDatabaseInit>,
pub DatabaseNew: Option<methods::FuncDatabaseNew>,
pub DatabaseSetOption: Option<methods::FuncDatabaseSetOption>,
pub DatabaseRelease: Option<methods::FuncDatabaseRelease>,
pub ConnectionCommit: Option<methods::FuncConnectionCommit>,
pub ConnectionGetInfo: Option<methods::FuncConnectionGetInfo>,
pub ConnectionGetObjects: Option<methods::FuncConnectionGetObjects>,
pub ConnectionGetTableSchema: Option<methods::FuncConnectionGetTableSchema>,
pub ConnectionGetTableTypes: Option<methods::FuncConnectionGetTableTypes>,
pub ConnectionInit: Option<methods::FuncConnectionInit>,
pub ConnectionNew: Option<methods::FuncConnectionNew>,
pub ConnectionSetOption: Option<methods::FuncConnectionSetOption>,
pub ConnectionReadPartition: Option<methods::FuncConnectionReadPartition>,
pub ConnectionRelease: Option<methods::FuncConnectionRelease>,
pub ConnectionRollback: Option<methods::FuncConnectionRollback>,
pub StatementBind: Option<methods::FuncStatementBind>,
pub StatementBindStream: Option<methods::FuncStatementBindStream>,
pub StatementExecuteQuery: Option<methods::FuncStatementExecuteQuery>,
pub StatementExecutePartitions: Option<methods::FuncStatementExecutePartitions>,
pub StatementGetParameterSchema: Option<methods::FuncStatementGetParameterSchema>,
pub StatementNew: Option<methods::FuncStatementNew>,
pub StatementPrepare: Option<methods::FuncStatementPrepare>,
pub StatementRelease: Option<methods::FuncStatementRelease>,
pub StatementSetOption: Option<methods::FuncStatementSetOption>,
pub StatementSetSqlQuery: Option<methods::FuncStatementSetSqlQuery>,
pub StatementSetSubstraitPlan: Option<methods::FuncStatementSetSubstraitPlan>,
pub ErrorGetDetailCount: Option<methods::FuncErrorGetDetailCount>,
pub ErrorGetDetail: Option<methods::FuncErrorGetDetail>,
pub ErrorFromArrayStream: Option<methods::FuncErrorFromArrayStream>,
pub DatabaseGetOption: Option<methods::FuncDatabaseGetOption>,
pub DatabaseGetOptionBytes: Option<methods::FuncDatabaseGetOptionBytes>,
pub DatabaseGetOptionDouble: Option<methods::FuncDatabaseGetOptionDouble>,
pub DatabaseGetOptionInt: Option<methods::FuncDatabaseGetOptionInt>,
pub DatabaseSetOptionBytes: Option<methods::FuncDatabaseSetOptionBytes>,
pub DatabaseSetOptionDouble: Option<methods::FuncDatabaseSetOptionDouble>,
pub DatabaseSetOptionInt: Option<methods::FuncDatabaseSetOptionInt>,
pub ConnectionCancel: Option<methods::FuncConnectionCancel>,
pub ConnectionGetOption: Option<methods::FuncConnectionGetOption>,
pub ConnectionGetOptionBytes: Option<methods::FuncConnectionGetOptionBytes>,
pub ConnectionGetOptionDouble: Option<methods::FuncConnectionGetOptionDouble>,
pub ConnectionGetOptionInt: Option<methods::FuncConnectionGetOptionInt>,
pub ConnectionGetStatistics: Option<methods::FuncConnectionGetStatistics>,
pub ConnectionGetStatisticNames: Option<methods::FuncConnectionGetStatisticNames>,
pub ConnectionSetOptionBytes: Option<methods::FuncConnectionSetOptionBytes>,
pub ConnectionSetOptionDouble: Option<methods::FuncConnectionSetOptionDouble>,
pub ConnectionSetOptionInt: Option<methods::FuncConnectionSetOptionInt>,
pub StatementCancel: Option<methods::FuncStatementCancel>,
pub StatementExecuteSchema: Option<methods::FuncStatementExecuteSchema>,
pub StatementGetOption: Option<methods::FuncStatementGetOption>,
pub StatementGetOptionBytes: Option<methods::FuncStatementGetOptionBytes>,
pub StatementGetOptionDouble: Option<methods::FuncStatementGetOptionDouble>,
pub StatementGetOptionInt: Option<methods::FuncStatementGetOptionInt>,
pub StatementSetOptionBytes: Option<methods::FuncStatementSetOptionBytes>,
pub StatementSetOptionDouble: Option<methods::FuncStatementSetOptionDouble>,
pub StatementSetOptionInt: Option<methods::FuncStatementSetOptionInt>,
}
unsafe impl Send for FFI_AdbcDriver {}
unsafe impl Sync for FFI_AdbcDriver {}
#[macro_export]
macro_rules! driver_method {
($driver:expr, $method:ident) => {
$driver.$method.unwrap_or($crate::methods::$method)
};
}
impl From<FFI_AdbcPartitions> for Partitions {
fn from(value: FFI_AdbcPartitions) -> Self {
let mut partitions = Vec::with_capacity(value.num_partitions);
for p in 0..value.num_partitions {
let partition = unsafe {
let ptr = *value.partitions.add(p);
let len = *value.partition_lengths.add(p);
std::slice::from_raw_parts(ptr, len)
};
partitions.push(partition.to_vec());
}
partitions
}
}
fn vec_into_raw_parts<T>(data: Vec<T>) -> (*mut T, usize, usize) {
let mut md = ManuallyDrop::new(data);
(md.as_mut_ptr(), md.len(), md.capacity())
}
struct PartitionsPrivateData {
partitions_capacity: usize,
partition_lengths_capacity: usize,
partition_capacities: Vec<usize>,
}
impl From<Partitions> for FFI_AdbcPartitions {
fn from(value: Partitions) -> Self {
let num_partitions = value.len();
let mut partition_lengths = Vec::with_capacity(num_partitions);
let mut partition_capacities = Vec::with_capacity(num_partitions);
let mut partition_ptrs = Vec::with_capacity(num_partitions);
for partition in value.into_iter() {
let (partition_ptr, partition_len, partition_cap) = vec_into_raw_parts(partition);
partition_lengths.push(partition_len);
partition_capacities.push(partition_cap);
partition_ptrs.push(partition_ptr);
}
let (partition_lengths_ptr, _, partition_lengths_cap) =
vec_into_raw_parts(partition_lengths);
let (partitions_ptr, _, partitions_cap) = vec_into_raw_parts(partition_ptrs);
let private_data = Box::new(PartitionsPrivateData {
partitions_capacity: partitions_cap,
partition_lengths_capacity: partition_lengths_cap,
partition_capacities,
});
FFI_AdbcPartitions {
num_partitions,
partition_lengths: partition_lengths_ptr,
partitions: partitions_ptr,
private_data: Box::into_raw(private_data) as *mut c_void,
release: Some(release_ffi_partitions),
}
}
}
unsafe extern "C" fn release_ffi_partitions(partitions: *mut FFI_AdbcPartitions) {
match partitions.as_mut() {
None => (),
Some(partitions) => {
let private_data = Box::from_raw(partitions.private_data as *mut PartitionsPrivateData);
for p in 0..partitions.num_partitions {
let partition_ptr = *partitions.partitions.add(p);
let partition_len = *partitions.partition_lengths.add(p);
let partition_cap = private_data.partition_capacities[p];
let partition = Vec::from_raw_parts(partition_ptr, partition_len, partition_cap);
drop(partition);
}
let partition_lengths = Vec::from_raw_parts(
partitions.partition_lengths,
partitions.num_partitions,
private_data.partition_lengths_capacity,
);
drop(partition_lengths);
let partitions_vec = Vec::from_raw_parts(
partitions.partitions,
partitions.num_partitions,
private_data.partitions_capacity,
);
drop(partitions_vec);
drop(private_data);
}
}
}
impl Default for FFI_AdbcDriver {
fn default() -> Self {
Self {
private_data: null_mut(),
private_manager: null_mut(),
release: None,
DatabaseInit: None,
DatabaseNew: None,
DatabaseSetOption: None,
DatabaseRelease: None,
ConnectionCommit: None,
ConnectionGetInfo: None,
ConnectionGetObjects: None,
ConnectionGetTableSchema: None,
ConnectionGetTableTypes: None,
ConnectionInit: None,
ConnectionNew: None,
ConnectionSetOption: None,
ConnectionReadPartition: None,
ConnectionRelease: None,
ConnectionRollback: None,
StatementBind: None,
StatementBindStream: None,
StatementExecuteQuery: None,
StatementExecutePartitions: None,
StatementGetParameterSchema: None,
StatementNew: None,
StatementPrepare: None,
StatementRelease: None,
StatementSetOption: None,
StatementSetSqlQuery: None,
StatementSetSubstraitPlan: None,
ErrorGetDetailCount: None,
ErrorGetDetail: None,
ErrorFromArrayStream: None,
DatabaseGetOption: None,
DatabaseGetOptionBytes: None,
DatabaseGetOptionDouble: None,
DatabaseGetOptionInt: None,
DatabaseSetOptionBytes: None,
DatabaseSetOptionDouble: None,
DatabaseSetOptionInt: None,
ConnectionCancel: None,
ConnectionGetOption: None,
ConnectionGetOptionBytes: None,
ConnectionGetOptionDouble: None,
ConnectionGetOptionInt: None,
ConnectionGetStatistics: None,
ConnectionGetStatisticNames: None,
ConnectionSetOptionBytes: None,
ConnectionSetOptionDouble: None,
ConnectionSetOptionInt: None,
StatementCancel: None,
StatementExecuteSchema: None,
StatementGetOption: None,
StatementGetOptionBytes: None,
StatementGetOptionDouble: None,
StatementGetOptionInt: None,
StatementSetOptionBytes: None,
StatementSetOptionDouble: None,
StatementSetOptionInt: None,
}
}
}
impl Default for FFI_AdbcError {
fn default() -> Self {
Self {
message: null_mut(),
vendor_code: constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA,
sqlstate: [0; 5],
release: None,
private_data: null_mut(),
private_driver: null(),
}
}
}
impl Default for FFI_AdbcDatabase {
fn default() -> Self {
Self {
private_data: null_mut(),
private_driver: null_mut(),
}
}
}
impl Default for FFI_AdbcConnection {
fn default() -> Self {
Self {
private_data: null_mut(),
private_driver: null_mut(),
}
}
}
impl Default for FFI_AdbcErrorDetail {
fn default() -> Self {
Self {
key: null(),
value: null(),
value_length: 0,
}
}
}
impl Default for FFI_AdbcStatement {
fn default() -> Self {
Self {
private_data: null_mut(),
private_driver: null(),
}
}
}
impl Default for FFI_AdbcPartitions {
fn default() -> Self {
Self {
num_partitions: 0,
partitions: null_mut(),
partition_lengths: null_mut(),
private_data: null_mut(),
release: None,
}
}
}
impl FFI_AdbcError {
pub fn with_driver(driver: &FFI_AdbcDriver) -> Self {
FFI_AdbcError {
private_driver: driver,
..Default::default()
}
}
}
impl TryFrom<&FFI_AdbcError> for Error {
type Error = Error;
fn try_from(value: &FFI_AdbcError) -> Result<Self, Self::Error> {
let message = match value.message.is_null() {
true => "<empty>".to_string(),
false => {
let message = unsafe { CStr::from_ptr(value.message) };
let message = message.to_owned();
message.into_string()?
}
};
let mut error = Error {
message,
status: Status::Unknown,
vendor_code: value.vendor_code,
sqlstate: value.sqlstate,
details: None,
};
if value.vendor_code == constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA {
if let Some(driver) = unsafe { value.private_driver.as_ref() } {
let get_detail_count = driver_method!(driver, ErrorGetDetailCount);
let get_detail = driver_method!(driver, ErrorGetDetail);
let num_details = unsafe { get_detail_count(value) };
let details = (0..num_details)
.map(|i| unsafe { get_detail(value, i) })
.filter(|d| !d.key.is_null() && !d.value.is_null())
.map(|d| unsafe {
let key = CStr::from_ptr(d.key).to_string_lossy().to_string();
let value = std::slice::from_raw_parts(d.value, d.value_length);
(key, value.to_vec())
})
.collect();
error.details = Some(details);
}
}
Ok(error)
}
}
impl TryFrom<FFI_AdbcError> for Error {
type Error = Error;
fn try_from(value: FFI_AdbcError) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
pub(crate) struct ErrorPrivateData {
pub(crate) keys: Vec<CString>,
pub(crate) values: Vec<Vec<u8>>,
}
impl From<NulError> for FFI_AdbcError {
fn from(value: NulError) -> Self {
let message = CString::new(format!(
"Interior null byte was found at position {}",
value.nul_position()
))
.unwrap();
FFI_AdbcError {
message: message.into_raw(),
vendor_code: constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA,
sqlstate: [0; 5],
release: Some(release_ffi_error),
private_data: null_mut(),
private_driver: null(),
}
}
}
impl TryFrom<Error> for FFI_AdbcError {
type Error = NulError;
fn try_from(mut value: Error) -> Result<Self, Self::Error> {
let message = CString::new(value.message)?;
let private_data = match value.details.take() {
None => null_mut(),
Some(details) => {
let keys = details
.iter()
.map(|(key, _)| CString::new(key.as_str()))
.collect::<Result<Vec<_>, _>>()?;
let values: Vec<Vec<u8>> = details.into_iter().map(|(_, value)| value).collect();
let private_data = Box::new(ErrorPrivateData { keys, values });
let private_data = Box::into_raw(private_data);
private_data as *mut c_void
}
};
Ok(FFI_AdbcError {
message: message.into_raw(),
release: Some(release_ffi_error),
vendor_code: value.vendor_code,
sqlstate: value.sqlstate,
private_data,
private_driver: null(),
})
}
}
impl From<NulError> for FFI_AdbcErrorV100 {
fn from(value: NulError) -> Self {
let message = CString::new(format!(
"Interior null byte was found at position {}",
value.nul_position()
))
.unwrap();
FFI_AdbcErrorV100 {
message: message.into_raw(),
vendor_code: 0,
sqlstate: [0; 5],
release: Some(release_ffi_error_v100),
}
}
}
impl TryFrom<Error> for FFI_AdbcErrorV100 {
type Error = NulError;
fn try_from(value: Error) -> Result<Self, Self::Error> {
let message = CString::new(value.message)?;
Ok(FFI_AdbcErrorV100 {
message: message.into_raw(),
release: Some(release_ffi_error_v100),
vendor_code: value.vendor_code,
sqlstate: value.sqlstate,
})
}
}
unsafe extern "C" fn release_ffi_error(error: *mut FFI_AdbcError) {
match error.as_mut() {
None => (),
Some(error) => {
if !error.message.is_null() {
drop(CString::from_raw(error.message));
error.message = null_mut();
}
if !error.private_data.is_null() {
let private_data = Box::from_raw(error.private_data as *mut ErrorPrivateData);
drop(private_data);
error.private_data = null_mut();
}
error.release = None;
}
}
}
unsafe extern "C" fn release_ffi_error_v100(error: *mut FFI_AdbcErrorV100) {
match error.as_mut() {
None => (),
Some(error) => {
if !error.message.is_null() {
drop(CString::from_raw(error.message));
error.message = null_mut();
}
error.release = None;
}
}
}
#[doc(hidden)]
pub unsafe fn export_error(err_out: *mut FFI_AdbcError, error: Error) {
if err_out.is_null() {
return;
}
let is_v110 = (*err_out).vendor_code == constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA;
if let Some(release) = (*err_out).release {
release(err_out);
}
if is_v110 {
let mut ffi_error = FFI_AdbcError::try_from(error).unwrap_or_else(Into::into);
ffi_error.vendor_code = constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA;
ffi_error.private_driver = (*err_out).private_driver;
std::ptr::write_unaligned(err_out, ffi_error);
} else {
let ffi_error = FFI_AdbcErrorV100::try_from(error).unwrap_or_else(Into::into);
let out = &mut *(err_out as *mut FFI_AdbcErrorV100);
std::ptr::write_unaligned(out, ffi_error);
}
}
impl Drop for FFI_AdbcError {
fn drop(&mut self) {
if let Some(release) = self.release {
unsafe { release(self) };
}
}
}
impl Drop for FFI_AdbcDriver {
fn drop(&mut self) {
if let Some(release) = self.release {
unsafe { release(self, null_mut()) };
}
}
}
impl Drop for FFI_AdbcPartitions {
fn drop(&mut self) {
if let Some(release) = self.release {
unsafe { release(self) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_roundtrip() {
let error_expected: Error = Error {
message: "Hello world!".into(),
status: Status::Unknown,
vendor_code: constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA,
sqlstate: [1, 2, 3, 4, 5],
details: None, };
let error_ffi: FFI_AdbcError = error_expected.clone().try_into().unwrap();
let error_actual: Error = error_ffi.try_into().unwrap();
assert_eq!(error_expected, error_actual);
}
#[test]
fn test_partitions_roundtrip() {
let partitions_expected: Partitions = vec![b"A".into(), b"BB".into(), b"CCC".into()];
let partitions_ffi: FFI_AdbcPartitions = partitions_expected.clone().into();
let partitions_actual: Partitions = partitions_ffi.into();
assert_eq!(partitions_expected, partitions_actual);
}
#[test]
fn test_release_ffi_error_nulls_fields() {
let error = Error {
message: "test error".into(),
status: Status::Unknown,
vendor_code: 0,
sqlstate: [0; 5],
details: Some(vec![("key".to_string(), b"value".to_vec())]),
};
let mut ffi_error: FFI_AdbcError = error.try_into().unwrap();
assert!(!ffi_error.message.is_null());
assert!(!ffi_error.private_data.is_null());
unsafe { release_ffi_error(&mut ffi_error) };
assert!(ffi_error.message.is_null());
assert!(ffi_error.private_data.is_null());
assert!(ffi_error.release.is_none());
}
#[test]
fn test_release_ffi_error_idempotent() {
let error = Error {
message: "test error".into(),
status: Status::Unknown,
vendor_code: 0,
sqlstate: [0; 5],
details: None,
};
let mut ffi_error: FFI_AdbcError = error.try_into().unwrap();
unsafe {
release_ffi_error(&mut ffi_error);
release_ffi_error(&mut ffi_error);
}
}
#[test]
fn test_release_ffi_error_null_message() {
let mut ffi_error = FFI_AdbcError {
message: null_mut(),
vendor_code: 0,
sqlstate: [0; 5],
release: Some(release_ffi_error),
private_data: null_mut(),
private_driver: null(),
};
unsafe { release_ffi_error(&mut ffi_error) };
assert!(ffi_error.message.is_null());
assert!(ffi_error.release.is_none());
}
#[test]
fn test_set_error_out_preserves_caller_private_fields() {
let mut canary_data = 0x1234;
let canary_ptr = (&raw mut canary_data) as *mut c_void;
let canary_driver: FFI_AdbcDriver = Default::default();
let mut out = FFI_AdbcError {
message: null_mut(),
vendor_code: 0,
sqlstate: [0; 5],
release: None,
private_data: canary_ptr,
private_driver: &raw const canary_driver,
};
let error = Error {
message: "boom".into(),
status: Status::Internal,
vendor_code: 0,
sqlstate: [0; 5],
details: Some(vec![("key".to_string(), b"value".to_vec())]),
};
unsafe { export_error(&mut out, error) };
assert_eq!(out.private_data, canary_ptr);
assert_eq!(out.private_driver, &raw const canary_driver);
assert!(!out.message.is_null());
assert!(out.release.is_some());
unsafe { (out.release.unwrap())(&mut out) };
assert!(out.message.is_null());
assert!(out.release.is_none());
assert_eq!(out.private_data, canary_ptr);
assert_eq!(out.private_driver, &raw const canary_driver);
}
#[test]
fn test_set_error_out_extended_layout_overwrites() {
let canary_driver: FFI_AdbcDriver = Default::default();
let mut out = FFI_AdbcError {
message: null_mut(),
vendor_code: constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA,
sqlstate: [0; 5],
release: None,
private_data: null_mut(),
private_driver: &raw const canary_driver,
};
let error = Error {
message: "boom".into(),
status: Status::Internal,
vendor_code: 0,
sqlstate: [0; 5],
details: Some(vec![("key".to_string(), b"value".to_vec())]),
};
unsafe { export_error(&mut out, error) };
assert!(!out.message.is_null());
assert!(!out.private_data.is_null()); assert_eq!(out.private_driver, &raw const canary_driver); assert_eq!(
out.vendor_code,
constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA
);
assert!(out.release.is_some());
unsafe { (out.release.unwrap())(&mut out) };
assert!(out.message.is_null());
assert!(out.private_data.is_null());
}
#[test]
fn test_set_error_out_releases_a_previous_error_on_reuse() {
let mut out = FFI_AdbcError {
message: null_mut(),
vendor_code: 0,
sqlstate: [0; 5],
release: None,
private_data: null_mut(),
private_driver: null(),
};
let error = |msg: &str| Error {
message: msg.into(),
status: Status::Internal,
vendor_code: 0,
sqlstate: [0; 5],
details: None,
};
unsafe { export_error(&mut out, error("first")) };
let first_message = out.message;
assert!(!first_message.is_null());
unsafe { export_error(&mut out, error("second")) };
assert!(!out.message.is_null());
let message = unsafe { CStr::from_ptr(out.message) };
assert_eq!(message.to_str().unwrap(), "second");
unsafe { (out.release.unwrap())(&mut out) };
assert!(out.message.is_null());
}
}