This is an automated email from the ASF dual-hosted git repository.
yangzy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-gluten.git
The following commit(s) were added to refs/heads/main by this push:
new cd21b2ba6a [VL] Use Velox config in plan converter (#10012)
cd21b2ba6a is described below
commit cd21b2ba6a7572d2ad6f0d66e432d5d35bd7e21e
Author: Yang Zhang <[email protected]>
AuthorDate: Fri Jun 20 17:50:54 2025 +0800
[VL] Use Velox config in plan converter (#10012)
---
cpp/velox/compute/VeloxPlanConverter.cc | 10 ++--------
cpp/velox/compute/VeloxPlanConverter.h | 6 +-----
cpp/velox/compute/VeloxRuntime.cc | 5 ++---
cpp/velox/substrait/SubstraitToVeloxPlan.cc | 10 +++-------
cpp/velox/substrait/SubstraitToVeloxPlan.h | 10 ++++++----
.../substrait/SubstraitToVeloxPlanValidator.cc | 22 +++++++++++-----------
.../substrait/SubstraitToVeloxPlanValidator.h | 12 ++++++++----
cpp/velox/tests/FunctionTest.cc | 4 +++-
.../tests/Substrait2VeloxPlanConversionTest.cc | 8 ++++----
.../Substrait2VeloxValuesNodeConversionTest.cc | 4 ++--
cpp/velox/tests/VeloxSubstraitRoundTripTest.cc | 10 ++++++----
11 files changed, 48 insertions(+), 53 deletions(-)
diff --git a/cpp/velox/compute/VeloxPlanConverter.cc
b/cpp/velox/compute/VeloxPlanConverter.cc
index 8f97aa5fb9..a3ae60cd0b 100644
--- a/cpp/velox/compute/VeloxPlanConverter.cc
+++ b/cpp/velox/compute/VeloxPlanConverter.cc
@@ -31,11 +31,11 @@ using namespace facebook;
VeloxPlanConverter::VeloxPlanConverter(
const std::vector<std::shared_ptr<ResultIterator>>& inputIters,
velox::memory::MemoryPool* veloxPool,
- const std::unordered_map<std::string, std::string>& confMap,
+ const facebook::velox::config::ConfigBase* veloxCfg,
const std::optional<std::string> writeFilesTempPath,
bool validationMode)
: validationMode_(validationMode),
- substraitVeloxPlanConverter_(veloxPool, confMap, writeFilesTempPath,
validationMode) {
+ substraitVeloxPlanConverter_(veloxPool, veloxCfg, writeFilesTempPath,
validationMode) {
substraitVeloxPlanConverter_.setInputIters(std::move(inputIters));
}
@@ -127,10 +127,4 @@ std::shared_ptr<const facebook::velox::core::PlanNode>
VeloxPlanConverter::toVel
return substraitVeloxPlanConverter_.toVeloxPlan(substraitPlan);
}
-std::string VeloxPlanConverter::nextPlanNodeId() {
- auto id = fmt::format("{}", planNodeId_);
- planNodeId_++;
- return id;
-}
-
} // namespace gluten
diff --git a/cpp/velox/compute/VeloxPlanConverter.h
b/cpp/velox/compute/VeloxPlanConverter.h
index ededf7f903..2528a46914 100644
--- a/cpp/velox/compute/VeloxPlanConverter.h
+++ b/cpp/velox/compute/VeloxPlanConverter.h
@@ -32,7 +32,7 @@ class VeloxPlanConverter {
explicit VeloxPlanConverter(
const std::vector<std::shared_ptr<ResultIterator>>& inputIters,
facebook::velox::memory::MemoryPool* veloxPool,
- const std::unordered_map<std::string, std::string>& confMap,
+ const facebook::velox::config::ConfigBase* veloxCfg,
const std::optional<std::string> writeFilesTempPath = std::nullopt,
bool validationMode = false);
@@ -45,10 +45,6 @@ class VeloxPlanConverter {
}
private:
- std::string nextPlanNodeId();
-
- int planNodeId_ = 0;
-
bool validationMode_;
SubstraitToVeloxPlanConverter substraitVeloxPlanConverter_;
diff --git a/cpp/velox/compute/VeloxRuntime.cc
b/cpp/velox/compute/VeloxRuntime.cc
index 1c32dcb923..a45151168f 100644
--- a/cpp/velox/compute/VeloxRuntime.cc
+++ b/cpp/velox/compute/VeloxRuntime.cc
@@ -26,7 +26,6 @@
#include "compute/VeloxPlanConverter.h"
#include "config/VeloxConfig.h"
#include "operators/serializer/VeloxRowToColumnarConverter.h"
-#include "operators/writer/VeloxColumnarBatchWriter.h"
#include "shuffle/VeloxShuffleReader.h"
#include "shuffle/VeloxShuffleWriter.h"
#include "utils/ConfigExtractor.h"
@@ -139,7 +138,7 @@ void VeloxRuntime::getInfoAndIds(
std::string VeloxRuntime::planString(bool details, const
std::unordered_map<std::string, std::string>& sessionConf) {
std::vector<std::shared_ptr<ResultIterator>> inputs;
auto veloxMemoryPool = gluten::defaultLeafVeloxMemoryPool();
- VeloxPlanConverter veloxPlanConverter(inputs, veloxMemoryPool.get(),
sessionConf, std::nullopt, true);
+ VeloxPlanConverter veloxPlanConverter(inputs, veloxMemoryPool.get(),
veloxCfg_.get(), std::nullopt, true);
auto veloxPlan = veloxPlanConverter.toVeloxPlan(substraitPlan_, localFiles_);
return veloxPlan->toString(details, true);
}
@@ -157,7 +156,7 @@ std::shared_ptr<ResultIterator>
VeloxRuntime::createResultIterator(
LOG_IF(INFO, debugModeEnabled_) << "VeloxRuntime session config:" <<
printConfig(confMap_);
VeloxPlanConverter veloxPlanConverter(
- inputs, memoryManager()->getLeafMemoryPool().get(), sessionConf,
*localWriteFilesTempPath());
+ inputs, memoryManager()->getLeafMemoryPool().get(), veloxCfg_.get(),
*localWriteFilesTempPath());
veloxPlan_ = veloxPlanConverter.toVeloxPlan(substraitPlan_,
std::move(localFiles_));
LOG_IF(INFO, debugModeEnabled_ && taskInfo_.has_value())
<< "############### Velox plan for task " << taskInfo_.value() << "
###############" << std::endl
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.cc
b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
index d4ec14d085..0939bd2934 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
@@ -24,7 +24,6 @@
#include "operators/plannodes/RowVectorStream.h"
#include "velox/connectors/hive/HiveDataSink.h"
#include "velox/exec/TableWriter.h"
-#include "velox/type/Filter.h"
#include "velox/type/Type.h"
#include "utils/ConfigExtractor.h"
@@ -32,7 +31,6 @@
#include "config.pb.h"
#include "config/GlutenConfig.h"
#include "config/VeloxConfig.h"
-#include "operators/plannodes/RowVectorStream.h"
#include "operators/writer/VeloxParquetDataSource.h"
namespace gluten {
@@ -986,7 +984,7 @@ core::PlanNodePtr
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
windowColumnNames.push_back(windowFunction.column_name());
windowNodeFunctions.push_back(
- {std::move(windowCall), std::move(createWindowFrame(lowerBound,
upperBound, type, inputType)), ignoreNulls});
+ {std::move(windowCall), createWindowFrame(lowerBound, upperBound,
type, inputType), ignoreNulls});
}
// Construct partitionKeys
@@ -1262,7 +1260,7 @@ core::PlanNodePtr
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
auto streamIdx = getStreamIndex(readRel);
if (streamIdx >= 0) {
// Only used in benchmark enable query trace, replace ValueStreamNode to
ValuesNode to support serialization.
- if (LIKELY(confMap_[kQueryTraceEnabled] != "true")) {
+ if (LIKELY(!veloxCfg_->get<bool>(kQueryTraceEnabled, false))) {
return constructValueStreamNode(readRel, streamIdx);
} else {
return constructValuesNode(readRel, streamIdx);
@@ -1281,9 +1279,7 @@ core::PlanNodePtr
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
std::vector<TypePtr> veloxTypeList;
std::vector<ColumnType> columnTypes;
// Convert field names into lower case when not case-sensitive.
- std::unique_ptr<facebook::velox::config::ConfigBase> veloxCfg =
-
std::make_unique<facebook::velox::config::ConfigBase>(std::unordered_map<std::string,
std::string>(confMap_));
- bool asLowerCase = !veloxCfg->get<bool>(kCaseSensitive, false);
+ bool asLowerCase = !veloxCfg_->get<bool>(kCaseSensitive, false);
if (readRel.has_base_schema()) {
const auto& baseSchema = readRel.base_schema();
colNameList.reserve(baseSchema.names().size());
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.h
b/cpp/velox/substrait/SubstraitToVeloxPlan.h
index 9636f6615f..3d90b6ebe3 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.h
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.h
@@ -62,12 +62,14 @@ struct SplitInfo {
/// This class is used to convert the Substrait plan into Velox plan.
class SubstraitToVeloxPlanConverter {
public:
- SubstraitToVeloxPlanConverter(
+ explicit SubstraitToVeloxPlanConverter(
memory::MemoryPool* pool,
- const std::unordered_map<std::string, std::string>& confMap = {},
+ const facebook::velox::config::ConfigBase* veloxCfg,
const std::optional<std::string> writeFilesTempPath = std::nullopt,
bool validationMode = false)
- : pool_(pool), confMap_(confMap),
writeFilesTempPath_(writeFilesTempPath), validationMode_(validationMode) {}
+ : pool_(pool), veloxCfg_(veloxCfg),
writeFilesTempPath_(writeFilesTempPath), validationMode_(validationMode) {
+ VELOX_USER_CHECK_NOT_NULL(veloxCfg_);
+ }
/// Used to convert Substrait WriteRel into Velox PlanNode.
core::PlanNodePtr toVeloxPlan(const ::substrait::WriteRel& writeRel);
@@ -278,7 +280,7 @@ class SubstraitToVeloxPlanConverter {
memory::MemoryPool* pool_;
/// A map of custom configs.
- std::unordered_map<std::string, std::string> confMap_;
+ const facebook::velox::config::ConfigBase* veloxCfg_;
/// The temporary path used to write files.
std::optional<std::string> writeFilesTempPath_;
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
index 8288e44465..c5aa800b10 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
@@ -211,7 +211,7 @@ bool SubstraitToVeloxPlanValidator::validateScalarFunction(
}
const auto& function =
- SubstraitParser::findFunctionSpec(planConverter_.getFunctionMap(),
scalarFunction.function_reference());
+ SubstraitParser::findFunctionSpec(planConverter_->getFunctionMap(),
scalarFunction.function_reference());
const auto& name = SubstraitParser::getNameBeforeDelimiter(function);
std::vector<std::string> types =
SubstraitParser::getSubFunctionTypes(function);
@@ -505,7 +505,7 @@ bool SubstraitToVeloxPlanValidator::validate(const
::substrait::TopNRel& topNRel
return false;
}
- auto [sortingKeys, sortingOrders] =
planConverter_.processSortField(topNRel.sorts(), rowType);
+ auto [sortingKeys, sortingOrders] =
planConverter_->processSortField(topNRel.sorts(), rowType);
folly::F14FastSet<std::string> sortingKeyNames;
for (const auto& sortingKey : sortingKeys) {
auto result = sortingKeyNames.insert(sortingKey->name());
@@ -672,7 +672,7 @@ bool SubstraitToVeloxPlanValidator::validate(const
::substrait::WindowRel& windo
funcSpecs.reserve(windowRel.measures().size());
for (const auto& smea : windowRel.measures()) {
const auto& windowFunction = smea.measure();
-
funcSpecs.emplace_back(planConverter_.findFuncSpec(windowFunction.function_reference()));
+
funcSpecs.emplace_back(planConverter_->findFuncSpec(windowFunction.function_reference()));
SubstraitParser::parseType(windowFunction.output_type());
for (const auto& arg : windowFunction.arguments()) {
auto typeCase = arg.value().rex_type_case();
@@ -1073,7 +1073,7 @@ bool SubstraitToVeloxPlanValidator::validate(const
::substrait::JoinRel& joinRel
if (joinRel.has_expression()) {
std::vector<const ::substrait::Expression::FieldReference*> leftExprs,
rightExprs;
- planConverter_.extractJoinKeys(joinRel.expression(), leftExprs,
rightExprs);
+ planConverter_->extractJoinKeys(joinRel.expression(), leftExprs,
rightExprs);
}
if (joinRel.has_post_join_filter()) {
@@ -1148,8 +1148,8 @@ bool
SubstraitToVeloxPlanValidator::validateAggRelFunctionType(const ::substrait
for (const auto& smea : aggRel.measures()) {
const auto& aggFunction = smea.measure();
- const auto& funcStep =
planConverter_.toAggregationFunctionStep(aggFunction);
- auto funcSpec =
planConverter_.findFuncSpec(aggFunction.function_reference());
+ const auto& funcStep =
planConverter_->toAggregationFunctionStep(aggFunction);
+ auto funcSpec =
planConverter_->findFuncSpec(aggFunction.function_reference());
std::vector<TypePtr> types;
bool isDecimal = false;
types = SubstraitParser::sigToTypes(funcSpec);
@@ -1160,7 +1160,7 @@ bool
SubstraitToVeloxPlanValidator::validateAggRelFunctionType(const ::substrait
}
auto baseFuncName =
SubstraitParser::mapToVeloxFunction(SubstraitParser::getNameBeforeDelimiter(funcSpec),
isDecimal);
- auto funcName = planConverter_.toAggregationFunctionName(baseFuncName,
funcStep);
+ auto funcName = planConverter_->toAggregationFunctionName(baseFuncName,
funcStep);
auto signaturesOpt = exec::getAggregateFunctionSignatures(funcName);
if (!signaturesOpt) {
LOG_VALIDATION_MSG("can not find function signature for " + funcName + "
in AggregateRel.");
@@ -1244,7 +1244,7 @@ bool SubstraitToVeloxPlanValidator::validate(const
::substrait::AggregateRel& ag
}
const auto& aggFunction = smea.measure();
- const auto& functionSpec =
planConverter_.findFuncSpec(aggFunction.function_reference());
+ const auto& functionSpec =
planConverter_->findFuncSpec(aggFunction.function_reference());
funcSpecs.emplace_back(functionSpec);
SubstraitParser::parseType(aggFunction.output_type());
// Validate the size of arguments.
@@ -1336,7 +1336,7 @@ bool SubstraitToVeloxPlanValidator::validate(const
::substrait::AggregateRel& ag
}
bool SubstraitToVeloxPlanValidator::validate(const ::substrait::ReadRel&
readRel) {
- planConverter_.toVeloxPlan(readRel);
+ planConverter_->toVeloxPlan(readRel);
// Validate filter in ReadRel.
if (readRel.has_filter()) {
@@ -1427,8 +1427,8 @@ bool SubstraitToVeloxPlanValidator::validate(const
::substrait::RelRoot& relRoot
bool SubstraitToVeloxPlanValidator::validate(const ::substrait::Plan& plan) {
try {
// Create plan converter and expression converter to help the validation.
- planConverter_.constructFunctionMap(plan);
- exprConverter_ = planConverter_.getExprConverter();
+ planConverter_->constructFunctionMap(plan);
+ exprConverter_ = planConverter_->getExprConverter();
for (const auto& rel : plan.relations()) {
if (rel.has_root()) {
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
index 28d82f9cce..370b9501df 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
@@ -28,10 +28,12 @@ namespace gluten {
/// a Substrait plan is supported in Velox.
class SubstraitToVeloxPlanValidator {
public:
- SubstraitToVeloxPlanValidator(memory::MemoryPool* pool) :
planConverter_(pool, {}, std::nullopt, true) {
- const std::unordered_map<std::string, std::string> configs{
+ SubstraitToVeloxPlanValidator(memory::MemoryPool* pool) {
+ std::unordered_map<std::string, std::string> configs{
{velox::core::QueryConfig::kSparkPartitionId, "0"},
{velox::core::QueryConfig::kSessionTimezone, "GMT"}};
- queryCtx_ = velox::core::QueryCtx::create(nullptr,
velox::core::QueryConfig(configs));
+ veloxCfg_ =
std::make_shared<facebook::velox::config::ConfigBase>(std::move(configs));
+ planConverter_ = std::make_unique<SubstraitToVeloxPlanConverter>(pool,
veloxCfg_.get(), std::nullopt, true);
+ queryCtx_ = velox::core::QueryCtx::create(nullptr,
velox::core::QueryConfig(veloxCfg_->rawConfigs()));
// An execution context used for function validation.
execCtx_ = std::make_unique<velox::core::ExecCtx>(pool, queryCtx_.get());
}
@@ -100,8 +102,10 @@ class SubstraitToVeloxPlanValidator {
/// An execution context used for function validation.
std::unique_ptr<core::ExecCtx> execCtx_;
+ std::shared_ptr<facebook::velox::config::ConfigBase> veloxCfg_{nullptr};
+
/// A converter used to convert Substrait plan into Velox's plan node.
- SubstraitToVeloxPlanConverter planConverter_;
+ std::unique_ptr<SubstraitToVeloxPlanConverter> planConverter_{nullptr};
/// An expression converter used to convert Substrait representations into
/// Velox expressions.
diff --git a/cpp/velox/tests/FunctionTest.cc b/cpp/velox/tests/FunctionTest.cc
index 4e4bbccdfd..fc7892003f 100644
--- a/cpp/velox/tests/FunctionTest.cc
+++ b/cpp/velox/tests/FunctionTest.cc
@@ -41,8 +41,10 @@ class FunctionTest : public ::testing::Test, public
test::VectorTestBase {
memory::MemoryManager::testingSetInstance(memory::MemoryManager::Options{});
}
+ std::shared_ptr<facebook::velox::config::ConfigBase> veloxCfg_ =
+
std::make_shared<facebook::velox::config::ConfigBase>(std::unordered_map<std::string,
std::string>());
std::shared_ptr<gluten::SubstraitToVeloxPlanConverter> planConverter_ =
- std::make_shared<gluten::SubstraitToVeloxPlanConverter>(pool());
+ std::make_shared<gluten::SubstraitToVeloxPlanConverter>(pool(),
veloxCfg_.get());
};
TEST_F(FunctionTest, makeNames) {
diff --git a/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
b/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
index 42c1cb79b8..6659db33f1 100644
--- a/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
+++ b/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
@@ -69,10 +69,10 @@ class Substrait2VeloxPlanConversionTest : public
exec::test::HiveConnectorTestBa
}
std::shared_ptr<exec::test::TempDirectoryPath>
tmpDir_{exec::test::TempDirectoryPath::create()};
- std::shared_ptr<VeloxPlanConverter> planConverter_ =
std::make_shared<VeloxPlanConverter>(
- std::vector<std::shared_ptr<ResultIterator>>(),
- pool(),
- std::unordered_map<std::string, std::string>());
+ std::shared_ptr<facebook::velox::config::ConfigBase> veloxCfg_ =
+
std::make_shared<facebook::velox::config::ConfigBase>(std::unordered_map<std::string,
std::string>());
+ std::shared_ptr<VeloxPlanConverter> planConverter_ =
+
std::make_shared<VeloxPlanConverter>(std::vector<std::shared_ptr<ResultIterator>>(),
pool(), veloxCfg_.get());
};
// This test will firstly generate mock TPC-H lineitem ORC file. Then, Velox's
diff --git a/cpp/velox/tests/Substrait2VeloxValuesNodeConversionTest.cc
b/cpp/velox/tests/Substrait2VeloxValuesNodeConversionTest.cc
index b5a131790a..ef8dd8cd03 100644
--- a/cpp/velox/tests/Substrait2VeloxValuesNodeConversionTest.cc
+++ b/cpp/velox/tests/Substrait2VeloxValuesNodeConversionTest.cc
@@ -41,9 +41,9 @@ TEST_F(Substrait2VeloxValuesNodeConversionTest, valuesNode) {
::substrait::Plan substraitPlan;
JsonToProtoConverter::readFromFile(planPath, substraitPlan);
- std::unordered_map<std::string, std::string> sessionConf = {};
+ auto veloxCfg =
std::make_shared<facebook::velox::config::ConfigBase>(std::unordered_map<std::string,
std::string>());
std::shared_ptr<SubstraitToVeloxPlanConverter> planConverter_ =
- std::make_shared<SubstraitToVeloxPlanConverter>(pool_.get(),
sessionConf, std::nullopt, true);
+ std::make_shared<SubstraitToVeloxPlanConverter>(pool_.get(),
veloxCfg.get(), std::nullopt, true);
auto veloxPlan = planConverter_->toVeloxPlan(substraitPlan);
RowVectorPtr expectedData = makeRowVector(
diff --git a/cpp/velox/tests/VeloxSubstraitRoundTripTest.cc
b/cpp/velox/tests/VeloxSubstraitRoundTripTest.cc
index 1a9b80af7d..60cafd1c6f 100644
--- a/cpp/velox/tests/VeloxSubstraitRoundTripTest.cc
+++ b/cpp/velox/tests/VeloxSubstraitRoundTripTest.cc
@@ -68,9 +68,10 @@ class VeloxSubstraitRoundTripTest : public OperatorTestBase {
// Convert Velox Plan to Substrait Plan.
google::protobuf::Arena arena;
auto substraitPlan = veloxConvertor_->toSubstrait(arena, plan);
- std::unordered_map<std::string, std::string> sessionConf = {};
+ auto veloxCfg =
+
std::make_shared<facebook::velox::config::ConfigBase>(std::unordered_map<std::string,
std::string>());
std::shared_ptr<SubstraitToVeloxPlanConverter> substraitConverter_ =
- std::make_shared<SubstraitToVeloxPlanConverter>(pool_.get(),
sessionConf, std::nullopt, true);
+ std::make_shared<SubstraitToVeloxPlanConverter>(pool_.get(),
veloxCfg.get(), std::nullopt, true);
// Convert Substrait Plan to the same Velox Plan.
auto samePlan = substraitConverter_->toVeloxPlan(substraitPlan);
@@ -88,9 +89,10 @@ class VeloxSubstraitRoundTripTest : public OperatorTestBase {
// Convert Velox Plan to Substrait Plan.
google::protobuf::Arena arena;
auto substraitPlan = veloxConvertor_->toSubstrait(arena, plan);
- std::unordered_map<std::string, std::string> sessionConf = {};
+ auto veloxCfg =
+
std::make_shared<facebook::velox::config::ConfigBase>(std::unordered_map<std::string,
std::string>());
std::shared_ptr<SubstraitToVeloxPlanConverter> substraitConverter_ =
- std::make_shared<SubstraitToVeloxPlanConverter>(pool_.get(),
sessionConf, std::nullopt, true);
+ std::make_shared<SubstraitToVeloxPlanConverter>(pool_.get(),
veloxCfg.get(), std::nullopt, true);
// Convert Substrait Plan to the same Velox Plan.
auto samePlan = substraitConverter_->toVeloxPlan(substraitPlan);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]