mod data_type;
mod ddl;
mod error;
mod expr;
mod function;
mod literal;
mod operator;
mod param;
mod query;
pub use self::{
data_type::translate_data_type,
ddl::translate_column_def,
error::{
CreateIndexOption, CreateTableOption, DeleteOption, InsertOption, JoinConstraintReason,
QueryOption, SelectOption, TransactionOption, TranslateError, UpdateOption,
},
expr::{translate_expr, translate_order_by_expr},
param::{IntoParamLiteral, ParamLiteral},
query::{alias_or_name, translate_query, translate_select_item},
};
use {
crate::{
ast::{Assignment, Expr, ForeignKey, ReferentialAction, Statement, Variable},
result::Result,
},
ddl::{translate_alter_table_operation, translate_operate_function_arg},
sqlparser::ast::{
Assignment as SqlAssignment, AssignmentTarget as SqlAssignmentTarget,
CommentDef as SqlCommentDef, CreateFunctionBody as SqlCreateFunctionBody,
CreateIndex as SqlCreateIndex, CreateTable as SqlCreateTable, Delete as SqlDelete,
FromTable as SqlFromTable, Ident as SqlIdent, Insert as SqlInsert,
ObjectName as SqlObjectName, ObjectType as SqlObjectType,
ReferentialAction as SqlReferentialAction, Statement as SqlStatement,
TableConstraint as SqlTableConstraint, TableFactor, TableWithJoins,
},
std::num::NonZeroUsize,
};
pub(crate) const NO_PARAMS: &[ParamLiteral] = &[];
pub fn translate(sql_statement: &SqlStatement) -> Result<Statement> {
translate_with_params(sql_statement, NO_PARAMS)
}
pub fn translate_with_params(
sql_statement: &SqlStatement,
params: &[ParamLiteral],
) -> Result<Statement> {
match sql_statement {
SqlStatement::Query(query) => translate_query(query, params).map(Statement::Query),
SqlStatement::Insert(SqlInsert {
table_name,
columns,
source,
returning,
on,
table_alias,
partitioned,
overwrite,
table,
..
}) => {
let violation = if returning.is_some() {
Some(InsertOption::Returning)
} else if on.is_some() {
Some(InsertOption::OnConflict)
} else if table_alias.is_some() {
Some(InsertOption::TableAlias)
} else if partitioned.is_some() {
Some(InsertOption::Partition)
} else if *overwrite {
Some(InsertOption::Overwrite)
} else if *table {
Some(InsertOption::TableKeyword)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedInsertOption(reason).into());
}
let table_name = translate_object_name(table_name)?;
let columns = translate_idents(columns);
let source = match source.as_deref() {
Some(query) => translate_query(query, params)?,
None => {
return Err(TranslateError::DefaultValuesOnInsertNotSupported(
table_name.clone(),
)
.into());
}
};
Ok(Statement::Insert {
table_name,
columns,
source,
})
}
SqlStatement::Update {
table,
assignments,
selection,
from,
returning,
..
} => {
let violation = if from.is_some() {
Some(UpdateOption::From)
} else if returning.is_some() {
Some(UpdateOption::Returning)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedUpdateOption(reason).into());
}
Ok(Statement::Update {
table_name: translate_table_with_join(table)?,
assignments: assignments
.iter()
.map(|assignment| translate_assignment(assignment, params))
.collect::<Result<_>>()?,
selection: selection
.as_ref()
.map(|expr| translate_expr(expr, params))
.transpose()?,
})
}
SqlStatement::Delete(SqlDelete {
from,
using,
selection,
returning,
order_by,
limit,
..
}) => {
let violation = if using.is_some() {
Some(DeleteOption::Using)
} else if returning.is_some() {
Some(DeleteOption::Returning)
} else if !order_by.is_empty() {
Some(DeleteOption::OrderBy)
} else if limit.is_some() {
Some(DeleteOption::Limit)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedDeleteOption(reason).into());
}
let from = match from {
SqlFromTable::WithFromKeyword(from) => from,
SqlFromTable::WithoutKeyword(_) => {
return Err(TranslateError::UnreachableOmittingFromInDelete.into());
}
};
let table_name = from
.iter()
.map(translate_table_with_join)
.next()
.ok_or(TranslateError::UnreachableEmptyTable)??;
Ok(Statement::Delete {
table_name,
selection: selection
.as_ref()
.map(|expr| translate_expr(expr, params))
.transpose()?,
})
}
SqlStatement::CreateTable(SqlCreateTable {
if_not_exists,
name,
columns,
query,
engine,
constraints,
comment,
temporary,
like,
clone,
..
}) => {
let violation = if *temporary {
Some(CreateTableOption::Temporary)
} else if like.is_some() {
Some(CreateTableOption::Like)
} else if clone.is_some() {
Some(CreateTableOption::CloneTable)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedCreateTableOption(reason).into());
}
let columns = columns
.iter()
.map(|column_def| translate_column_def(column_def, params))
.collect::<Result<Vec<_>>>()?;
let columns = (!columns.is_empty()).then_some(columns);
let name = translate_object_name(name)?;
let foreign_keys = constraints
.iter()
.map(translate_foreign_key)
.collect::<Result<Vec<_>>>()?;
Ok(Statement::CreateTable {
if_not_exists: *if_not_exists,
name,
columns,
source: match query {
Some(v) => Some(translate_query(v, params).map(Box::new)?),
None => None,
},
engine: engine
.as_ref()
.map(|table_engine| table_engine.name.clone()),
foreign_keys,
comment: comment.as_ref().map(|comment| match comment {
SqlCommentDef::WithEq(comment)
| SqlCommentDef::WithoutEq(comment)
| SqlCommentDef::AfterColumnDefsWithoutEq(comment) => comment.to_owned(),
}),
})
}
SqlStatement::AlterTable {
name, operations, ..
} => {
if operations.len() > 1 {
return Err(TranslateError::UnsupportedMultipleAlterTableOperations.into());
}
let operation = operations
.iter()
.next()
.ok_or(TranslateError::UnreachableEmptyAlterTableOperation)?;
Ok(Statement::AlterTable {
name: translate_object_name(name)?,
operation: translate_alter_table_operation(operation, params)?,
})
}
SqlStatement::Drop {
object_type: SqlObjectType::Table,
if_exists,
names,
cascade,
..
} => Ok(Statement::DropTable {
if_exists: *if_exists,
names: names
.iter()
.map(translate_object_name)
.collect::<Result<Vec<_>>>()?,
cascade: *cascade,
}),
SqlStatement::DropFunction {
if_exists,
func_desc,
..
} => Ok(Statement::DropFunction {
if_exists: *if_exists,
names: func_desc
.iter()
.map(|v| translate_object_name(&v.name))
.collect::<Result<Vec<_>>>()?,
}),
SqlStatement::CreateIndex(SqlCreateIndex {
name,
table_name,
columns,
using,
unique,
concurrently,
if_not_exists,
include,
nulls_distinct,
with,
predicate,
}) => {
let violation = if *unique {
Some(CreateIndexOption::Unique)
} else if *concurrently {
Some(CreateIndexOption::Concurrently)
} else if *if_not_exists {
Some(CreateIndexOption::IfNotExists)
} else if using.is_some() {
Some(CreateIndexOption::Using)
} else if !include.is_empty() {
Some(CreateIndexOption::Include)
} else if nulls_distinct.is_some() {
Some(CreateIndexOption::NullsDistinct)
} else if !with.is_empty() {
Some(CreateIndexOption::With)
} else if predicate.is_some() {
Some(CreateIndexOption::Where)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedCreateIndexOption(reason).into());
}
if columns.len() > 1 {
return Err(TranslateError::CompositeIndexNotSupported.into());
}
let Some(name) = name else {
return Err(TranslateError::UnsupportedUnnamedIndex.into());
};
let name = translate_object_name(name)?;
if name.to_uppercase() == "PRIMARY" {
return Err(TranslateError::ReservedIndexName(name).into());
}
Ok(Statement::CreateIndex {
name,
table_name: translate_object_name(table_name)?,
column: translate_order_by_expr(&columns[0], params)?,
})
}
SqlStatement::Drop {
object_type: SqlObjectType::Index,
names,
..
} => {
if names.len() > 1 {
return Err(TranslateError::TooManyParamsInDropIndex.into());
}
let object_name = &names[0].0;
if object_name.len() != 2 {
return Err(TranslateError::InvalidParamsInDropIndex.into());
}
let table_name = object_name[0].value.clone();
let name = object_name[1].value.clone();
if name.to_uppercase() == "PRIMARY" {
return Err(TranslateError::CannotDropPrimary.into());
}
Ok(Statement::DropIndex { name, table_name })
}
SqlStatement::StartTransaction {
modes,
modifier,
begin: _,
} => {
let violation = if !modes.is_empty() {
Some(TransactionOption::Mode)
} else if modifier.is_some() {
Some(TransactionOption::Modifier)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedTransactionOption(reason).into());
}
Ok(Statement::StartTransaction)
}
SqlStatement::Commit { chain } => {
if *chain {
return Err(
TranslateError::UnsupportedTransactionOption(TransactionOption::Chain).into(),
);
}
Ok(Statement::Commit)
}
SqlStatement::Rollback { chain, savepoint } => {
let violation = if *chain {
Some(TransactionOption::Chain)
} else if savepoint.is_some() {
Some(TransactionOption::Savepoint)
} else {
None
};
if let Some(reason) = violation {
return Err(TranslateError::UnsupportedTransactionOption(reason).into());
}
Ok(Statement::Rollback)
}
SqlStatement::ShowTables {
filter: None,
db_name: None,
..
} => Ok(Statement::ShowVariable(Variable::Tables)),
SqlStatement::ShowFunctions { filter: None } => {
Ok(Statement::ShowVariable(Variable::Functions))
}
SqlStatement::ShowVariable { variable } => match (variable.len(), variable.first()) {
(1, Some(keyword)) => match keyword.value.to_uppercase().as_str() {
"VERSION" => Ok(Statement::ShowVariable(Variable::Version)),
v => Err(TranslateError::UnsupportedShowVariableKeyword(v.to_owned()).into()),
},
(3, Some(keyword)) => match keyword.value.to_uppercase().as_str() {
"INDEXES" => match variable.get(2) {
Some(tablename) => Ok(Statement::ShowIndexes(tablename.value.clone())),
_ => Err(TranslateError::UnsupportedShowVariableStatement(
sql_statement.to_string(),
)
.into()),
},
_ => Err(TranslateError::UnsupportedShowVariableStatement(
sql_statement.to_string(),
)
.into()),
},
_ => Err(
TranslateError::UnsupportedShowVariableStatement(sql_statement.to_string()).into(),
),
},
SqlStatement::ShowColumns { table_name, .. } => Ok(Statement::ShowColumns {
table_name: translate_object_name(table_name)?,
}),
SqlStatement::CreateFunction {
or_replace,
name,
args,
function_body: Some(SqlCreateFunctionBody::Return(return_)),
..
} => {
let args = args
.as_ref()
.map(|args| {
args.iter()
.map(|arg| translate_operate_function_arg(arg, params))
.collect::<Result<Vec<_>>>()
})
.transpose()?;
Ok(Statement::CreateFunction {
or_replace: *or_replace,
name: translate_object_name(name)?,
args: args.unwrap_or_default(),
return_: translate_expr(return_, params)?,
})
}
SqlStatement::CreateFunction { .. } => {
Err(TranslateError::UnsupportedEmptyFunctionBody.into())
}
_ => Err(TranslateError::UnsupportedStatement(sql_statement.to_string()).into()),
}
}
pub(crate) fn bind_placeholder(params: &[ParamLiteral], placeholder: &str) -> Result<Expr> {
let invalid_placeholder = || TranslateError::InvalidPlaceholder {
placeholder: placeholder.to_owned(),
};
let index = placeholder
.strip_prefix('$')
.ok_or_else(invalid_placeholder)?
.parse::<NonZeroUsize>()
.map_err(|_| invalid_placeholder())?;
let literal =
params
.get(index.get() - 1)
.cloned()
.ok_or(TranslateError::ParameterIndexOutOfRange {
index: index.get(),
len: params.len(),
})?;
Ok(literal.into_expr())
}
pub fn translate_assignment(
sql_assignment: &SqlAssignment,
params: &[ParamLiteral],
) -> Result<Assignment> {
let SqlAssignment { target, value } = sql_assignment;
let id = match target {
SqlAssignmentTarget::Tuple(_) => {
return Err(TranslateError::TupleAssignmentOnUpdateNotSupported(
sql_assignment.to_string(),
)
.into());
}
SqlAssignmentTarget::ColumnName(SqlObjectName(id)) => id,
};
if id.len() > 1 {
return Err(
TranslateError::CompoundIdentOnUpdateNotSupported(sql_assignment.to_string()).into(),
);
}
Ok(Assignment {
id: id
.first()
.ok_or(TranslateError::UnreachableEmptyIdent)?
.value
.clone(),
value: translate_expr(value, params)?,
})
}
fn translate_table_with_join(table: &TableWithJoins) -> Result<String> {
if !table.joins.is_empty() {
return Err(TranslateError::JoinOnUpdateNotSupported.into());
}
match &table.relation {
TableFactor::Table { name, .. } => translate_object_name(name),
t => Err(TranslateError::UnsupportedTableFactor(t.to_string()).into()),
}
}
fn translate_object_name(sql_object_name: &SqlObjectName) -> Result<String> {
let sql_object_name = &sql_object_name.0;
if sql_object_name.len() > 1 {
let compound_object_name = translate_idents(sql_object_name).join(".");
return Err(TranslateError::CompoundObjectNotSupported(compound_object_name).into());
}
sql_object_name
.first()
.map(|v| v.value.clone())
.ok_or_else(|| TranslateError::UnreachableEmptyObject.into())
}
pub fn translate_idents(idents: &[SqlIdent]) -> Vec<String> {
idents.iter().map(|v| v.value.clone()).collect()
}
pub fn translate_referential_action(
action: &Option<SqlReferentialAction>,
) -> Result<ReferentialAction> {
use SqlReferentialAction::*;
let action = action.unwrap_or(NoAction);
match action {
NoAction | Restrict => Ok(ReferentialAction::NoAction),
_ => Err(TranslateError::UnsupportedConstraint(action.to_string()).into()),
}
}
pub fn translate_foreign_key(table_constraint: &SqlTableConstraint) -> Result<ForeignKey> {
match table_constraint {
SqlTableConstraint::ForeignKey {
name,
columns,
foreign_table,
referred_columns,
on_delete,
on_update,
..
} => {
let referencing_column_name = columns.first().map(|i| i.value.clone()).ok_or(
TranslateError::UnreachableForeignKeyColumns(
columns.iter().map(ToString::to_string).collect::<String>(),
),
)?;
let referenced_column_name = referred_columns
.first()
.ok_or(TranslateError::UnreachableForeignKeyColumns(
columns.iter().map(ToString::to_string).collect::<String>(),
))?
.value
.clone();
let referenced_table_name = translate_object_name(foreign_table)?;
let name = match name {
Some(name) => name.value.clone(),
None => {
format!(
"FK_{referencing_column_name}-{referenced_table_name}_{referenced_column_name}"
)
}
};
Ok(ForeignKey {
name,
referencing_column_name,
referenced_table_name,
referenced_column_name,
on_delete: translate_referential_action(on_delete)?,
on_update: translate_referential_action(on_update)?,
})
}
_ => Err(TranslateError::UnsupportedConstraint(table_constraint.to_string()).into()),
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::parse_sql::parse,
sqlparser::ast::TransactionModifier as SqlTransactionModifier,
};
fn assert_translate_error(sql: &str, error: TranslateError) {
let actual = parse(sql).and_then(|parsed| translate(&parsed[0]));
let expected = Err::<Statement, _>(error.into());
assert_eq!(actual, expected);
}
fn assert_invalid_placeholder(params: &[ParamLiteral], placeholder: &str) {
let err = bind_placeholder(params, placeholder).unwrap_err();
assert_eq!(
err,
TranslateError::InvalidPlaceholder {
placeholder: placeholder.to_owned(),
}
.into()
);
}
#[test]
fn statement() {
assert_translate_error(
"INSERT INTO Foo DEFAULT VALUES",
TranslateError::DefaultValuesOnInsertNotSupported("Foo".to_owned()),
);
}
#[test]
fn test_tuple_assignment_on_update_not_supported() {
assert_translate_error(
"UPDATE Foo SET (a, b) = (1, 2)",
TranslateError::TupleAssignmentOnUpdateNotSupported("(a, b) = (1, 2)".to_owned()),
);
}
#[test]
fn insert_options_not_supported() {
let cases = [
(
"INSERT INTO Foo VALUES (1) RETURNING *",
TranslateError::UnsupportedInsertOption(InsertOption::Returning),
),
(
"INSERT INTO Foo VALUES (1) ON CONFLICT DO NOTHING",
TranslateError::UnsupportedInsertOption(InsertOption::OnConflict),
),
(
"INSERT INTO Foo AS f VALUES (1)",
TranslateError::UnsupportedInsertOption(InsertOption::TableAlias),
),
(
"INSERT INTO Foo PARTITION (bar = 1) VALUES (1)",
TranslateError::UnsupportedInsertOption(InsertOption::Partition),
),
(
"INSERT OVERWRITE TABLE Foo VALUES (1)",
TranslateError::UnsupportedInsertOption(InsertOption::Overwrite),
),
(
"INSERT TABLE Foo VALUES (1)",
TranslateError::UnsupportedInsertOption(InsertOption::TableKeyword),
),
];
for (sql, err) in cases {
assert_translate_error(sql, err);
}
}
#[test]
fn update_options_not_supported() {
let cases = [
(
"UPDATE Foo SET id = 1 FROM Bar",
TranslateError::UnsupportedUpdateOption(UpdateOption::From),
),
(
"UPDATE Foo SET id = 1 WHERE id = 1 RETURNING *",
TranslateError::UnsupportedUpdateOption(UpdateOption::Returning),
),
];
for (sql, err) in cases {
assert_translate_error(sql, err);
}
}
#[test]
fn delete_options_not_supported() {
let cases = [
(
"DELETE FROM Foo USING Bar",
TranslateError::UnsupportedDeleteOption(DeleteOption::Using),
),
(
"DELETE FROM Foo WHERE id = 1 RETURNING *",
TranslateError::UnsupportedDeleteOption(DeleteOption::Returning),
),
(
"DELETE FROM Foo WHERE id = 1 ORDER BY id",
TranslateError::UnsupportedDeleteOption(DeleteOption::OrderBy),
),
(
"DELETE FROM Foo WHERE id = 1 LIMIT 1",
TranslateError::UnsupportedDeleteOption(DeleteOption::Limit),
),
];
for (sql, err) in cases {
assert_translate_error(sql, err);
}
}
#[test]
fn create_table_options_not_supported() {
let cases = [
(
"CREATE TEMPORARY TABLE Foo (id INTEGER)",
TranslateError::UnsupportedCreateTableOption(CreateTableOption::Temporary),
),
(
"CREATE TABLE Foo LIKE Bar",
TranslateError::UnsupportedCreateTableOption(CreateTableOption::Like),
),
(
"CREATE TABLE Foo CLONE Bar",
TranslateError::UnsupportedCreateTableOption(CreateTableOption::CloneTable),
),
];
for (sql, err) in cases {
assert_translate_error(sql, err);
}
}
#[test]
fn create_index_options_not_supported() {
let cases = [
(
"CREATE UNIQUE INDEX idx ON Foo (id)",
CreateIndexOption::Unique,
),
(
"CREATE INDEX CONCURRENTLY idx ON Foo (id)",
CreateIndexOption::Concurrently,
),
(
"CREATE INDEX IF NOT EXISTS idx ON Foo (id)",
CreateIndexOption::IfNotExists,
),
(
"CREATE INDEX idx ON Foo USING btree (id)",
CreateIndexOption::Using,
),
(
"CREATE INDEX idx ON Foo (id) INCLUDE (name)",
CreateIndexOption::Include,
),
(
"CREATE INDEX idx ON Foo (id) NULLS NOT DISTINCT",
CreateIndexOption::NullsDistinct,
),
(
"CREATE INDEX idx ON Foo (id) WITH (fillfactor = 70)",
CreateIndexOption::With,
),
(
"CREATE INDEX idx ON Foo (id) WHERE id > 0",
CreateIndexOption::Where,
),
];
for (sql, option) in cases {
assert_translate_error(sql, TranslateError::UnsupportedCreateIndexOption(option));
}
}
#[test]
fn transaction_options_not_supported() {
let cases = [
(
"START TRANSACTION ISOLATION LEVEL SERIALIZABLE",
TranslateError::UnsupportedTransactionOption(TransactionOption::Mode),
),
(
"START TRANSACTION READ ONLY",
TranslateError::UnsupportedTransactionOption(TransactionOption::Mode),
),
(
"COMMIT AND CHAIN",
TranslateError::UnsupportedTransactionOption(TransactionOption::Chain),
),
(
"ROLLBACK AND CHAIN",
TranslateError::UnsupportedTransactionOption(TransactionOption::Chain),
),
(
"ROLLBACK TO SAVEPOINT sp1",
TranslateError::UnsupportedTransactionOption(TransactionOption::Savepoint),
),
(
"ROLLBACK TO sp1",
TranslateError::UnsupportedTransactionOption(TransactionOption::Savepoint),
),
];
for (sql, err) in cases {
assert_translate_error(sql, err);
}
}
#[test]
fn transaction_modifier_not_supported() {
let statement = SqlStatement::StartTransaction {
modes: Vec::new(),
begin: true,
modifier: Some(SqlTransactionModifier::Deferred),
};
assert_eq!(
translate(&statement),
Err(TranslateError::UnsupportedTransactionOption(TransactionOption::Modifier).into())
);
}
#[test]
fn plain_transaction_statements() {
let cases = [
("BEGIN", Statement::StartTransaction),
("START TRANSACTION", Statement::StartTransaction),
("COMMIT", Statement::Commit),
("COMMIT AND NO CHAIN", Statement::Commit),
("ROLLBACK", Statement::Rollback),
("ROLLBACK AND NO CHAIN", Statement::Rollback),
];
for (sql, expected) in cases {
let parsed = parse(sql).expect("parse");
assert_eq!(translate(&parsed[0]), Ok(expected));
}
}
#[test]
fn reject_non_dollar_placeholder() {
assert_invalid_placeholder(&[], "?1");
}
#[test]
fn reject_zero_index_placeholder() {
let params = [1_i64.into_param_literal()];
assert_invalid_placeholder(¶ms, "$0");
}
#[test]
fn reject_non_numeric_index_placeholder() {
let params = [1_i64.into_param_literal()];
assert_invalid_placeholder(¶ms, "$foo");
}
}