use crate::write_utils::{maybe_newline, maybe_pad, newline_or_space, Indent};
use std::fmt::{Display, Formatter};
use std::rc::Rc;
use antlr_rust::token::Token;
use antlr_rust::tree::ParseTree;
use derive_more::{From, TryUnwrap};
use ordermap::OrderMap;
use crate::antlr::trinotypeparser::{
BaseContextAttrs, ParameterizedContextAttrs, RowContextAttrs, TrinotypeContextAll,
TypeParameterContextAttrs,
};
use crate::err::{TranslationError, TranslationErrors};
use crate::sql::expression::identifier::SimpleIdentifier;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SQLBaseType {
TinyInt,
SmallInt,
Integer,
BigInt,
Real,
Double,
Char,
VarChar,
Json,
Boolean,
VarBinary,
Date,
Time,
TimeStamp,
TimeStampTz,
IntervalDayToSecond,
IntervalYearToMonth,
TimeTz,
IpAddress,
Unknown,
}
impl SQLBaseType {
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indent: Indent) -> std::fmt::Result {
write!(
f,
"{}",
match self {
SQLBaseType::TinyInt => "TINYINT",
SQLBaseType::SmallInt => "SMALLINT",
SQLBaseType::Integer => "INTEGER",
SQLBaseType::BigInt => "BIGINT",
SQLBaseType::Real => "REAL",
SQLBaseType::Double => "DOUBLE",
SQLBaseType::Char => "CHAR",
SQLBaseType::VarChar => "VARCHAR",
SQLBaseType::Json => "JSON",
SQLBaseType::Boolean => "BOOLEAN",
SQLBaseType::VarBinary => "VARBINARY",
SQLBaseType::Date => "DATE",
SQLBaseType::Time => "TIME",
SQLBaseType::TimeStamp => "TIMESTAMP",
SQLBaseType::TimeStampTz => "TIMESTAMP WITH TIME ZONE",
SQLBaseType::IntervalDayToSecond => "INTERVAL DAY TO SECOND",
SQLBaseType::IntervalYearToMonth => "INTERVAL YEAR TO MONTH",
SQLBaseType::TimeTz => "TIME WITH TIME ZONE",
SQLBaseType::Unknown => "UNKNOWN",
SQLBaseType::IpAddress => "IPADDRESS",
}
)
}
}
impl Display for SQLBaseType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, From, TryUnwrap, PartialEq, Eq)]
pub enum SQLType {
SQLAnonRowType(SQLAnonRowType),
SQLArrayType(SQLArrayType),
SQLBaseType(SQLBaseType),
SQLCharType(SQLCharType),
SQLDecimalType(SQLDecimalType),
SQLMapType(SQLMapType),
SQLRowType(SQLRowType),
SQLTimeType(SQLTimeType),
SQLTimestampType(SQLTimestampType),
SQLTimestampTzType(SQLTimestampTzType),
SQLVarBinaryType(SQLVarBinaryType),
SQLVarcharType(SQLVarcharType),
}
impl SQLType {
pub fn from_database_reflection(
ctx: Rc<TrinotypeContextAll<'static>>,
) -> Result<SQLType, TranslationErrors> {
match ctx.as_ref() {
TrinotypeContextAll::BaseContext(ctx) => {
let ident_str = TranslationErrors::expect(ctx, ctx.identifier())?
.get_text()
.to_uppercase();
let base_type = match ident_str.as_str() {
"TINYINT" => SQLBaseType::TinyInt,
"SMALLINT" => SQLBaseType::SmallInt,
"INTEGER" => SQLBaseType::Integer,
"BIGINT" => SQLBaseType::BigInt,
"REAL" => SQLBaseType::Real,
"DOUBLE" => SQLBaseType::Double,
"CHAR" => SQLBaseType::Char,
"VARCHAR" => SQLBaseType::VarChar,
"JSON" => SQLBaseType::Json,
"BOOLEAN" => SQLBaseType::Boolean,
"VARBINARY" => SQLBaseType::VarBinary,
"DATE" => SQLBaseType::Date,
"TIME" | "TIME WITHOUT TIME ZONE" => SQLBaseType::Time,
"TIME WITH TIME ZONE" | "TIMETZ" => SQLBaseType::TimeTz,
"TIMESTAMP" | "TIMESTAMP WITHOUT TIME ZONE" => SQLBaseType::TimeStamp,
"TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => SQLBaseType::TimeStampTz,
"INTERVAL DAY TO SECOND" => SQLBaseType::IntervalDayToSecond,
"INTERVAL YEAR TO MONTH" => SQLBaseType::IntervalYearToMonth,
"UNKNOWN" => SQLBaseType::Unknown,
_ => return TranslationError::msg(ctx, "Unknown base type").single_result(),
};
Ok(base_type.into())
}
TrinotypeContextAll::ParameterizedContext(ctx) => {
let ident_str = TranslationErrors::expect(ctx, ctx.identifier())?
.get_text()
.to_uppercase();
let base_type: SQLType = match ident_str.as_str() {
"DECIMAL" => {
let precision = TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse precision").single()
})?;
let scale = TranslationErrors::expect(ctx, ctx.typeParameter(1))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse scale").single()
})?;
SQLDecimalType::new(precision, scale).into()
}
"CHAR" => {
let length = TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse length").single()
})?;
SQLCharType::new(length).into()
}
"VARCHAR" => {
let length = TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse length").single()
})?;
SQLVarcharType::new(length).into()
}
"VARBINARY" => {
let length = TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse length").single()
})?;
SQLVarBinaryType::new(length).into()
}
"ARRAY" => {
let element_type = Self::from_database_reflection(
TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.trinotype()
.ok_or_else(|| {
TranslationError::msg(ctx, "Expected a subtype for array()")
.single()
})?,
)?;
SQLArrayType::new(element_type).into()
}
"MAP" => {
let key_type = Self::from_database_reflection(
TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.trinotype()
.ok_or_else(|| {
TranslationError::msg(ctx, "Expected a subtype for map()")
.single()
})?,
)?;
let value_type = Self::from_database_reflection(
TranslationErrors::expect(ctx, ctx.typeParameter(1))?
.trinotype()
.ok_or_else(|| {
TranslationError::msg(ctx, "Expected a subtype for map()")
.single()
})?,
)?;
SQLMapType::new(key_type, value_type).into()
}
"ROW" => {
let elements = ctx
.typeParameter_all()
.iter()
.map(|tp| {
tp.trinotype()
.ok_or_else(|| {
TranslationError::msg(ctx, "Expected a subtype for row()")
.single()
})
.and_then(|tp| Self::from_database_reflection(tp))
})
.collect::<Result<Vec<_>, TranslationErrors>>()?;
SQLAnonRowType::new(elements).into()
}
"TIMESTAMP" => {
let precision = TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse precision").single()
})?;
SQLTimestampType::new(precision).into()
}
"TIMESTAMPTZ" => {
let precision = TranslationErrors::expect(ctx, ctx.typeParameter(0))?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse precision").single()
})?;
SQLTimestampTzType::new(precision).into()
}
_ => {
return TranslationError::msg(ctx, "Unknown parameterized type")
.single_result();
}
};
Ok(base_type.into())
}
TrinotypeContextAll::TimestampContext(_) => Ok(SQLBaseType::TimeStamp.into()),
TrinotypeContextAll::TimestampPrecisionContext(ctx) => {
let precision = TranslationErrors::expect(ctx, ctx.precision.clone())?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse precision").single()
})?;
Ok(SQLTimestampType::new(precision).into())
}
TrinotypeContextAll::TimestamptzContext(_) => Ok(SQLBaseType::TimeStampTz.into()),
TrinotypeContextAll::TimestamptzPrecisionContext(ctx) => {
let precision = TranslationErrors::expect(ctx, ctx.precision.clone())?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse precision").single()
})?;
Ok(SQLTimestampTzType::new(precision).into())
}
TrinotypeContextAll::TimeContext(_) => Ok(SQLBaseType::Time.into()),
TrinotypeContextAll::TimePrecisionContext(ctx) => {
let precision = TranslationErrors::expect(ctx, ctx.precision.clone())?
.get_text()
.parse::<u64>()
.map_err(|_| {
TranslationError::msg(ctx, "Failed to parse precision").single()
})?;
Ok(SQLTimeType::new(precision).into())
}
TrinotypeContextAll::TimetzContext(_) => Ok(SQLBaseType::TimeTz.into()),
TrinotypeContextAll::IntervalYearToMonthContext(_) => {
Ok(SQLBaseType::IntervalYearToMonth.into())
}
TrinotypeContextAll::IntervalDayToSecondContext(_) => {
Ok(SQLBaseType::IntervalDayToSecond.into())
}
TrinotypeContextAll::RowContext(ctx) => {
let identifiers = ctx.identifier_all().clone();
let types = ctx.trinotype_all().clone();
let mut bindings = OrderMap::new();
for (identifier, ty) in identifiers.iter().zip(types.iter()) {
let key = SimpleIdentifier::new(&identifier.get_text());
let value = Self::from_database_reflection(ty.clone())?;
bindings.insert(key, value);
}
Ok(SQLRowType::new(bindings).into())
}
TrinotypeContextAll::Error(ctx) => {
TranslationError::msg(ctx, "trino type parse error").single_result()
}
}
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> std::fmt::Result {
match self {
SQLType::SQLAnonRowType(t) => t.fmt_indented(f, indentation),
SQLType::SQLArrayType(t) => t.fmt_indented(f, indentation),
SQLType::SQLBaseType(t) => t.fmt_indented(f, indentation),
SQLType::SQLCharType(t) => t.fmt_indented(f, indentation),
SQLType::SQLDecimalType(t) => t.fmt_indented(f, indentation),
SQLType::SQLMapType(t) => t.fmt_indented(f, indentation),
SQLType::SQLRowType(t) => t.fmt_indented(f, indentation),
SQLType::SQLTimeType(t) => t.fmt_indented(f, indentation),
SQLType::SQLTimestampType(t) => t.fmt_indented(f, indentation),
SQLType::SQLTimestampTzType(t) => t.fmt_indented(f, indentation),
SQLType::SQLVarBinaryType(t) => t.fmt_indented(f, indentation),
SQLType::SQLVarcharType(t) => t.fmt_indented(f, indentation),
}
}
pub fn subfields(&self) -> usize {
match self {
SQLType::SQLBaseType(_) => 0,
SQLType::SQLCharType(_) => 0,
SQLType::SQLDecimalType(_) => 0,
SQLType::SQLTimeType(_) => 0,
SQLType::SQLTimestampType(_) => 0,
SQLType::SQLTimestampTzType(_) => 0,
SQLType::SQLVarBinaryType(_) => 0,
SQLType::SQLVarcharType(_) => 0,
SQLType::SQLArrayType(t) => t.subfields(),
SQLType::SQLMapType(t) => t.subfields(),
SQLType::SQLRowType(t) => t.subfields(),
SQLType::SQLAnonRowType(t) => t.subfields(),
}
}
}
impl Display for SQLType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLAnonRowType {
pub elements: Vec<SQLType>,
}
impl SQLAnonRowType {
pub fn new(elements: Vec<SQLType>) -> Self {
Self { elements }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> std::fmt::Result {
write!(f, "ROW(")?;
maybe_newline(f, indentation)?;
for (i, element) in self.elements.iter().enumerate() {
maybe_pad(f, indentation.nested())?;
element.fmt_indented(f, indentation.nested())?;
if i < self.elements.len() - 1 {
write!(f, ",")?;
newline_or_space(f, indentation)?;
}
}
maybe_newline(f, indentation)?;
maybe_pad(f, indentation)?;
write!(f, ")")
}
fn subfields(&self) -> usize {
self.elements
.iter()
.map(|element| element.subfields() + 1)
.sum()
}
}
impl Display for SQLAnonRowType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLArrayType {
pub element_type: Box<SQLType>,
}
impl SQLArrayType {
pub fn new(element_type: SQLType) -> Self {
Self {
element_type: Box::new(element_type),
}
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> std::fmt::Result {
write!(f, "ARRAY(")?;
self.element_type.fmt_indented(f, indentation.nested())?;
write!(f, ")")
}
fn subfields(&self) -> usize {
1 + self.element_type.subfields()
}
}
impl Display for SQLArrayType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLCharType {
pub length: u64,
}
impl SQLCharType {
pub fn new(length: u64) -> Self {
Self { length }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "CHAR({})", self.length)
}
}
impl Display for SQLCharType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLDecimalType {
pub precision: u64,
pub scale: u64,
}
impl SQLDecimalType {
pub fn new(precision: u64, scale: u64) -> Self {
Self { precision, scale }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "DECIMAL({}, {})", self.precision, self.scale)
}
}
impl Display for SQLDecimalType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLMapType {
pub key_type: Box<SQLType>,
pub value_type: Box<SQLType>,
}
impl SQLMapType {
pub fn new(key_type: SQLType, value_type: SQLType) -> Self {
Self {
key_type: Box::new(key_type),
value_type: Box::new(value_type),
}
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "MAP({}, {})", self.key_type, self.value_type)
}
fn subfields(&self) -> usize {
2 + self.key_type.subfields() + self.value_type.subfields()
}
}
impl Display for SQLMapType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SQLRowType {
pub bindings: OrderMap<SimpleIdentifier, SQLType>,
}
impl SQLRowType {
pub fn new(bindings: OrderMap<SimpleIdentifier, SQLType>) -> Self {
Self { bindings }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> std::fmt::Result {
let indentation = match indentation {
Indent::Pretty { .. } if self.subfields() > 3 => indentation,
_ => Indent::Compact,
};
write!(f, "ROW(")?;
maybe_newline(f, indentation)?;
for (i, (k, v)) in self.bindings.iter().enumerate() {
maybe_pad(f, indentation.nested())?;
k.fmt_indented(f, indentation.nested())?;
write!(f, " ")?;
v.fmt_indented(f, indentation.nested())?;
if i < self.bindings.len() - 1 {
write!(f, ",")?;
newline_or_space(f, indentation)?;
}
}
maybe_newline(f, indentation)?;
maybe_pad(f, indentation)?;
write!(f, ")")
}
pub fn with_str(&self, identifier: &str, ty: SQLType) -> Self {
self.with(SimpleIdentifier::new(identifier), ty)
}
pub fn with(&self, identifier: SimpleIdentifier, ty: SQLType) -> Self {
let mut bindings = self.bindings.clone();
bindings.insert(identifier, ty);
Self { bindings }
}
fn subfields(&self) -> usize {
self.bindings.values().map(|ty| ty.subfields() + 1).sum()
}
}
impl Display for SQLRowType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLTimeType {
pub precision: u64,
}
impl SQLTimeType {
pub fn new(precision: u64) -> Self {
Self { precision }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "TIME({})", self.precision)
}
}
impl Display for SQLTimeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLTimestampType {
pub precision: u64,
}
impl SQLTimestampType {
pub fn new(precision: u64) -> Self {
Self { precision }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "TIMESTAMP({})", self.precision)
}
}
impl Display for SQLTimestampType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLTimestampTzType {
pub precision: u64,
}
impl SQLTimestampTzType {
pub fn new(precision: u64) -> Self {
Self { precision }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "TIMESTAMP({}) WITH TIME ZONE", self.precision)
}
}
impl Display for SQLTimestampTzType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLVarBinaryType {
pub length: u64,
}
impl SQLVarBinaryType {
pub fn new(length: u64) -> Self {
Self { length }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "VARBINARY({})", self.length)
}
}
impl Display for SQLVarBinaryType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQLVarcharType {
pub length: u64,
}
impl SQLVarcharType {
pub fn new(length: u64) -> Self {
Self { length }
}
pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> std::fmt::Result {
write!(f, "VARCHAR({})", self.length)
}
}
impl Display for SQLVarcharType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_indented(f, Indent::default())
}
}
#[cfg(test)]
mod test {
use crate::types::decimal_type::Decimal;
use crate::types::struct_type::Struct;
use crate::types::{Type, DOUBLE, STRING};
use rstest::rstest;
use crate::parser::make_trinotype_parser_from_input;
use super::*;
#[rstest]
#[case("TINYINT")]
#[case("array(row(time_off array(row(description varchar, interval varchar))))")]
#[case("array(row(time_off array(row(description varchar, interval varchar))))")]
#[case("array(row(time_off array(row(time varchar, timestamp varchar))))")]
#[case("array(row(time_off array(row(description varchar, schnitzel timestamp(5)))))")]
#[case(
r#"array(
row(
account_expiration_time timestamp(6),
account_lockout_time timestamp(6),
account_type varchar,
attribute_cloud_availability_zone varchar,
attribute_cloud_environment varchar,
attribute_cloud_project varchar,
attribute_cloud_vpc varchar,
attribute_creation_time timestamp(6),
attribute_labels map(varchar, varchar),
attribute_last_update_time timestamp(6),
attribute_permissions array(
row(
description varchar,
name varchar, type varchar
)
),
attribute_roles array(
row(
description varchar,
name varchar,
type varchar
)
),
company_name varchar,
department array(varchar),
email_addresses array(varchar),
employee_id varchar,
first_name varchar,
first_seen_time timestamp(6),
group_identifiers array(varchar),
groupid varchar,
hire_date timestamp(6),
last_bad_password_attempt_time timestamp(6),
last_login_time timestamp(6),
last_name varchar,
last_password_change_time timestamp(6),
middle_name varchar,
office_address_city varchar,
office_address_country_or_region varchar,
office_address_desk_name varchar,
office_address_floor_name varchar,
office_address_name varchar,
office_address_region_coordinates varchar,
office_address_region_latitude double,
office_address_region_longitude double,
office_address_state varchar,
password_expiration_time timestamp(6),
personal_address_city varchar,
personal_address_country_or_region varchar,
personal_address_desk_name varchar,
personal_address_floor_name varchar,
personal_address_name varchar,
personal_address_region_coordinates varchar,
personal_address_region_latitude double,
personal_address_region_longitude double,
personal_address_state varchar,
phone_numbers array(varchar),
product_object_id varchar,
role_description varchar,
role_name varchar,
termination_date timestamp(6),
time_off array(row(
description varchar,
interval varchar
)),
title varchar,
user_authentication_status varchar,
user_display_name varchar,
user_role varchar,
userid varchar,
windows_sid varchar
))"#
)]
pub fn trino_type_parse_ensure_no_errors(#[case] trino_type: String) {
let ctx = make_trinotype_parser_from_input(trino_type)
.trinotype()
.unwrap();
Type::from_sql(SQLType::from_database_reflection(ctx).unwrap()).unwrap();
}
#[test]
pub fn test_nested_row() -> anyhow::Result<()> {
let problem_type = r#"row(
device varchar,
geographicalContext row(
city varchar,
country varchar,
geolocation row(
lat double, long double
),
postalCode varchar,
state varchar
),
id varchar,
ipAddress varchar,
userAgent row(
browser varchar,
os varchar,
rawUserAgent varchar
),
zone varchar,
currency decimal(38, 2)
)
"#;
let ctx = make_trinotype_parser_from_input(problem_type.to_owned())
.trinotype()
.unwrap();
assert_eq!(
Type::from_sql(SQLType::from_database_reflection(ctx).unwrap()).unwrap(),
Struct::default()
.with_str("device", STRING)
.with_str(
"geographicalContext",
Struct::default()
.with_str("city", STRING)
.with_str("country", STRING)
.with_str(
"geolocation",
Struct::default()
.with_str("lat", DOUBLE)
.with_str("long", DOUBLE)
.into()
)
.with_str("postalCode", STRING)
.with_str("state", STRING)
.into()
)
.with_str("id", STRING)
.with_str("ipAddress", STRING)
.with_str(
"userAgent",
Struct::default()
.with_str("browser", STRING)
.with_str("os", STRING)
.with_str("rawUserAgent", STRING)
.into()
)
.with_str("zone", STRING)
.with_str("currency", Decimal::new(38, 2)?.into())
.into()
);
Ok(())
}
}