Skip to content

Commit

Permalink
Support user level SQL custom function (gluesql#1095)
Browse files Browse the repository at this point in the history
Support user level sql function

* Show function statement
* Drop function statement
* Create function statement

e.g.
CREATE FUNCTION nothing();
CREATE FUNCTION hello() RETURN 'hello!';
CREATE FUNCTION add(x int) RETURN x+1;
CREATE FUNCTION div(a int, b int) RETURN a/b;
CREATE FUNCTION incr(a int, b int default 1) RETURN (SELECT bar from foo);
SELECT nothing();
SELECT incr(1);
SHOW FUNCTIONS;
  • Loading branch information
pythonbrad committed Apr 12, 2023
1 parent e67e1e2 commit 7dd2a91
Show file tree
Hide file tree
Showing 39 changed files with 843 additions and 29 deletions.
5 changes: 5 additions & 0 deletions cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ impl Command {
".help" => Ok(Self::Help),
".quit" => Ok(Self::Quit),
".tables" => Ok(Self::Execute("SHOW TABLES".to_owned())),
".functions" => Ok(Self::Execute("SHOW FUNCTIONS".to_owned())),
".columns" => match params.get(1) {
Some(table_name) => {
Ok(Self::Execute(format!("SHOW COLUMNS FROM {}", table_name)))
Expand Down Expand Up @@ -174,6 +175,10 @@ mod tests {
parse(".tables"),
Ok(Command::Execute("SHOW TABLES".to_owned())),
);
assert_eq!(
parse(".functions"),
Ok(Command::Execute("SHOW FUNCTIONS".to_owned())),
);
assert_eq!(
parse(".columns Foo"),
Ok(Command::Execute("SHOW COLUMNS FROM Foo".to_owned())),
Expand Down
41 changes: 40 additions & 1 deletion cli/src/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ impl<'a, W: Write> Print<W> {
match payload {
Payload::Create => self.write("Table created")?,
Payload::DropTable => self.write("Table dropped")?,
Payload::DropFunction => self.write("Function dropped")?,
Payload::AlterTable => self.write("Table altered")?,
Payload::CreateIndex => self.write("Index created")?,
Payload::DropIndex => self.write("Index dropped")?,
Expand All @@ -123,6 +124,14 @@ impl<'a, W: Write> Print<W> {
let table = self.build_table(table);
self.write(table)?;
}
Payload::ShowVariable(PayloadVariable::Functions(names)) => {
let mut table = self.get_table(["functions"]);
for name in names {
table.add_record([name]);
}
let table = self.build_table(table);
self.write(table)?;
}
Payload::ShowColumns(columns) => {
let mut table = self.get_table(vec!["Field", "Type"]);
for (field, field_type) in columns {
Expand Down Expand Up @@ -252,10 +261,11 @@ impl<'a, W: Write> Print<W> {

pub fn help(&mut self) -> IOResult<()> {
const HEADER: [&str; 2] = ["command", "description"];
const CONTENT: [[&str; 2]; 11] = [
const CONTENT: [[&str; 2]; 12] = [
[".help", "show help"],
[".quit", "quit program"],
[".tables", "show table names"],
[".functions", "show function names"],
[".columns TABLE", "show columns from TABLE"],
[".version", "show version"],
[".execute PATH", "execute SQL from PATH"],
Expand Down Expand Up @@ -337,6 +347,7 @@ mod tests {
| .help | show help |
| .quit | quit program |
| .tables | show table names |
| .functions | show function names |
| .columns TABLE | show columns from TABLE |
| .version | show version |
| .execute PATH | execute SQL from PATH |
Expand Down Expand Up @@ -382,6 +393,7 @@ mod tests {
test!(Payload::AlterTable, "Table altered");
test!(Payload::CreateIndex, "Index created");
test!(Payload::DropIndex, "Index dropped");
test!(Payload::DropFunction, "Function dropped");
test!(Payload::Commit, "Commit completed");
test!(Payload::Rollback, "Rollback completed");
test!(Payload::StartTransaction, "Transaction started");
Expand All @@ -398,6 +410,11 @@ mod tests {
Payload::ShowVariable(PayloadVariable::Tables(Vec::new())),
"
| tables |"
);
test!(
Payload::ShowVariable(PayloadVariable::Functions(Vec::new())),
"
| functions |"
);
test!(
Payload::ShowVariable(PayloadVariable::Tables(
Expand All @@ -419,6 +436,28 @@ mod tests {
| ExtendFromWithin |
| IntoRawParts |
| Reserve |
| Splice |"
);
test!(
Payload::ShowVariable(PayloadVariable::Functions(
[
"Allocator",
"ExtendFromWithin",
"IntoRawParts",
"Reserve",
"Splice",
]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)),
"
| functions |
|------------------|
| Allocator |
| ExtendFromWithin |
| IntoRawParts |
| Reserve |
| Splice |"
);
test!(
Expand Down
50 changes: 49 additions & 1 deletion core/src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ pub struct ColumnUniqueOption {
pub is_primary: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OperateFunctionArg {
pub name: String,
pub data_type: DataType,
/// `DEFAULT <restricted-expr>`
pub default: Option<Expr>,
}

impl ToSql for AlterTableOperation {
fn to_sql(&self) -> String {
match self {
Expand Down Expand Up @@ -102,9 +110,26 @@ impl ToSql for ColumnUniqueOption {
}
}

impl ToSql for OperateFunctionArg {
fn to_sql(&self) -> String {
let OperateFunctionArg {
name,
data_type,
default,
} = self;
let default = default
.as_ref()
.map(|expr| format!(" DEFAULT {}", expr.to_sql()))
.unwrap_or_else(|| "".to_owned());
format!(r#""{name}" {data_type}{default}"#)
}
}

#[cfg(test)]
mod tests {
use crate::ast::{AstLiteral, ColumnDef, ColumnUniqueOption, DataType, Expr, ToSql};
use crate::ast::{
AstLiteral, ColumnDef, ColumnUniqueOption, DataType, Expr, OperateFunctionArg, ToSql,
};

#[test]
fn to_sql_column_def() {
Expand Down Expand Up @@ -168,4 +193,27 @@ mod tests {
.to_sql()
);
}

#[test]
fn to_sql_operate_function_arg() {
assert_eq!(
r#""name" TEXT"#,
OperateFunctionArg {
name: "name".to_owned(),
data_type: DataType::Text,
default: None,
}
.to_sql()
);

assert_eq!(
r#""accepted" BOOLEAN DEFAULT FALSE"#,
OperateFunctionArg {
name: "accepted".to_owned(),
data_type: DataType::Boolean,
default: Some(Expr::Literal(AstLiteral::Boolean(false))),
}
.to_sql()
);
}
}
44 changes: 43 additions & 1 deletion core/src/ast/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ pub enum Function {
separator: Expr,
exprs: Vec<Expr>,
},
Custom {
name: String,
exprs: Vec<Expr>,
},
IfNull {
expr: Expr,
then: Expr,
Expand Down Expand Up @@ -196,6 +200,14 @@ impl ToSql for Function {
.join(", ");
format!("CONCAT({items})")
}
Function::Custom { name, exprs } => {
let exprs = exprs
.iter()
.map(ToSql::to_sql)
.collect::<Vec<_>>()
.join(", ");
format!("{name}({exprs})")
}
Function::ConcatWs { separator, exprs } => {
let exprs = exprs
.iter()
Expand Down Expand Up @@ -509,7 +521,37 @@ mod tests {
);

assert_eq!(
r#"CONCAT("Tic", "tac", "toe")"#,
r#"CUSTOM_FUNC("Tic", 1, "num", 'abc')"#,
&Expr::Function(Box::new(Function::Custom {
name: "CUSTOM_FUNC".to_owned(),
exprs: vec![
Expr::Identifier("Tic".to_owned()),
Expr::Literal(AstLiteral::Number(BigDecimal::from_str("1").unwrap())),
Expr::Identifier("num".to_owned()),
Expr::Literal(AstLiteral::QuotedString("abc".to_owned()))
]
}))
.to_sql()
);
assert_eq!(
r#"CUSTOM_FUNC("num")"#,
&Expr::Function(Box::new(Function::Custom {
name: "CUSTOM_FUNC".to_owned(),
exprs: vec![Expr::Identifier("num".to_owned())]
}))
.to_sql()
);
assert_eq!(
"CUSTOM_FUNC()",
&Expr::Function(Box::new(Function::Custom {
name: "CUSTOM_FUNC".to_owned(),
exprs: vec![]
}))
.to_sql()
);

assert_eq!(
"CONCAT(\"Tic\", \"tac\", \"toe\")",
&Expr::Function(Box::new(Function::Concat(vec![
Expr::Identifier("Tic".to_owned()),
Expr::Identifier("tac".to_owned()),
Expand Down
Loading

0 comments on commit 7dd2a91

Please sign in to comment.