diff --git a/e2e_test/batch/basic/table_with_default_columns.slt.part b/e2e_test/batch/basic/table_with_default_columns.slt.part index 816d03c367fa..d3d681f56aed 100644 --- a/e2e_test/batch/basic/table_with_default_columns.slt.part +++ b/e2e_test/batch/basic/table_with_default_columns.slt.part @@ -43,8 +43,25 @@ select * from t2; 1 2 2 2 +statement ok +create table tt (v1 int, v2 timestamptz default now()); + +statement ok +insert into tt values (1); + +query II +select v1, v2 >= date '2021-01-01' from tt; +---- +1 t + +statement ok +update tt set v2=default; + statement ok drop table t1; statement ok drop table t2; + +statement ok +drop table tt; diff --git a/proto/expr.proto b/proto/expr.proto index 2ec4ad70c784..371455e4b2a3 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -9,6 +9,7 @@ option java_package = "com.risingwave.proto"; option optimize_for = SPEED; message ExprNode { + message NowRexNode {} // TODO: move this into `FunctionCall`. enum Type { // `InputRef`, `Constant`, and `UserDefinedFunction` are indicated by the viriant of `rex_node`. @@ -209,6 +210,7 @@ message ExprNode { data.Datum constant = 5; FunctionCall func_call = 6; UserDefinedFunction udf = 7; + NowRexNode now = 8; } } diff --git a/src/expr/src/expr/build.rs b/src/expr/src/expr/build.rs index 9c5c3a57564c..4846ec09b177 100644 --- a/src/expr/src/expr/build.rs +++ b/src/expr/src/expr/build.rs @@ -47,6 +47,7 @@ pub fn build_from_prost(prost: &ExprNode) -> Result { RexNode::Constant(_) => return LiteralExpression::try_from_boxed(prost), RexNode::Udf(_) => return UdfExpression::try_from_boxed(prost), RexNode::FuncCall(func_call) => func_call, + RexNode::Now(_) => unreachable!("now should not be built at backend"), }; let func_type = prost.function_type(); diff --git a/src/frontend/planner_test/tests/testdata/input/insert.yaml b/src/frontend/planner_test/tests/testdata/input/insert.yaml index 1aabb60ad0ce..daece1698d4f 100644 --- a/src/frontend/planner_test/tests/testdata/input/insert.yaml +++ b/src/frontend/planner_test/tests/testdata/input/insert.yaml @@ -257,3 +257,10 @@ expected_outputs: - batch_plan - logical_plan +- name: insert to a table with default now + sql: | + create table t (a int, b timestamptz default now()); + insert into t values (1); + expected_outputs: + - batch_plan + - logical_plan diff --git a/src/frontend/planner_test/tests/testdata/output/explain.yaml b/src/frontend/planner_test/tests/testdata/output/explain.yaml index 49f2ce9d9131..7045bdfe1868 100644 --- a/src/frontend/planner_test/tests/testdata/output/explain.yaml +++ b/src/frontend/planner_test/tests/testdata/output/explain.yaml @@ -63,7 +63,7 @@ "stages": { "0": { "root": { - "plan_node_id": 10029, + "plan_node_id": 10030, "plan_node_type": "BatchValues", "schema": [ { diff --git a/src/frontend/planner_test/tests/testdata/output/insert.yaml b/src/frontend/planner_test/tests/testdata/output/insert.yaml index 1776d9edc761..96b84b65591e 100644 --- a/src/frontend/planner_test/tests/testdata/output/insert.yaml +++ b/src/frontend/planner_test/tests/testdata/output/insert.yaml @@ -334,3 +334,14 @@ BatchExchange { order: [], dist: Single } └─BatchInsert { table: t, mapping: [0:0], default: [1<-(2:Int32 + 3:Int32)] } └─BatchValues { rows: [[1:Int32]] } +- name: insert to a table with default now + sql: | + create table t (a int, b timestamptz default now()); + insert into t values (1); + logical_plan: | + LogicalInsert { table: t, mapping: [0:0], default: [1<-Now] } + └─LogicalValues { rows: [[1:Int32]], schema: Schema { fields: [*VALUES*_0.column_0:Int32] } } + batch_plan: | + BatchExchange { order: [], dist: Single } + └─BatchInsert { table: t, mapping: [0:0], default: [1<-'2021-04-01 00:00:00+00:00':Timestamptz] } + └─BatchValues { rows: [[1:Int32]] } diff --git a/src/frontend/src/expr/literal.rs b/src/frontend/src/expr/literal.rs index e5445b578ec1..bf4c95b2114d 100644 --- a/src/frontend/src/expr/literal.rs +++ b/src/frontend/src/expr/literal.rs @@ -120,7 +120,7 @@ impl Expr for Literal { } /// Convert a literal value (datum) into protobuf. -fn literal_to_value_encoding(d: &Datum) -> RexNode { +pub fn literal_to_value_encoding(d: &Datum) -> RexNode { let body = serialize_datum(d.as_ref()); RexNode::Constant(PbDatum { body }) } diff --git a/src/frontend/src/expr/mod.rs b/src/frontend/src/expr/mod.rs index e0a0c917596f..837daa6a9a11 100644 --- a/src/frontend/src/expr/mod.rs +++ b/src/frontend/src/expr/mod.rs @@ -958,6 +958,7 @@ impl ExprImpl { ret_type, )?)) } + RexNode::Now(_) => Self::Now(Box::new(Now {})), }) } } diff --git a/src/frontend/src/expr/now.rs b/src/frontend/src/expr/now.rs index 9b581120ebd8..ee174883e9d7 100644 --- a/src/frontend/src/expr/now.rs +++ b/src/frontend/src/expr/now.rs @@ -14,7 +14,8 @@ use risingwave_common::types::DataType; use risingwave_common::util::epoch::Epoch; -use risingwave_pb::expr::{expr_node, ExprNode}; +use risingwave_pb::expr::expr_node::{self, NowRexNode}; +use risingwave_pb::expr::ExprNode; use super::{Expr, ExprImpl, ExprRewriter, FunctionCall, Literal}; @@ -34,9 +35,11 @@ impl Expr for Now { } fn to_expr_proto(&self) -> ExprNode { - unreachable!( - "`Now` should be translated to `Literal` in batch mode or `NowNode` in stream mode" - ) + ExprNode { + function_type: expr_node::Type::Unspecified.into(), + return_type: Some(self.return_type().into()), + rex_node: Some(expr_node::RexNode::Now(NowRexNode {})), + } } } diff --git a/src/frontend/src/optimizer/mod.rs b/src/frontend/src/optimizer/mod.rs index f6480f3e5efa..b6fd150aaf7b 100644 --- a/src/frontend/src/optimizer/mod.rs +++ b/src/frontend/src/optimizer/mod.rs @@ -174,6 +174,10 @@ impl PlanRoot { .into()); } + let ctx = plan.ctx(); + // Inline session timezone mainly for rewriting now() + plan = inline_session_timezone_in_exprs(ctx, plan)?; + // Convert to physical plan node plan = plan.to_batch_with_order_required(&self.required_order)?; diff --git a/src/frontend/src/optimizer/plan_node/logical_insert.rs b/src/frontend/src/optimizer/plan_node/logical_insert.rs index e33f9bb6a254..89566348910f 100644 --- a/src/frontend/src/optimizer/plan_node/logical_insert.rs +++ b/src/frontend/src/optimizer/plan_node/logical_insert.rs @@ -23,7 +23,7 @@ use super::{ PlanTreeNodeUnary, PredicatePushdown, ToBatch, ToStream, }; use crate::catalog::TableId; -use crate::expr::ExprImpl; +use crate::expr::{ExprImpl, ExprRewriter}; use crate::optimizer::plan_node::{ ColumnPruningContext, PredicatePushdownContext, RewriteStreamContext, ToStreamContext, }; @@ -116,7 +116,22 @@ impl ColPrunable for LogicalInsert { } } -impl ExprRewritable for LogicalInsert {} +impl ExprRewritable for LogicalInsert { + fn has_rewritable_expr(&self) -> bool { + true + } + + fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef { + let mut new = self.clone(); + new.core.default_columns = new + .core + .default_columns + .into_iter() + .map(|(c, e)| (c, r.rewrite_expr(e))) + .collect(); + new.into() + } +} impl PredicatePushdown for LogicalInsert { fn predicate_pushdown( diff --git a/src/meta/src/manager/catalog/utils.rs b/src/meta/src/manager/catalog/utils.rs index 122fbbe81b97..1a07805b875d 100644 --- a/src/meta/src/manager/catalog/utils.rs +++ b/src/meta/src/manager/catalog/utils.rs @@ -365,6 +365,7 @@ impl ReplaceTableExprRewriter { RexNode::Constant(_) => {} RexNode::Udf(udf) => self.rewrite_udf(udf), RexNode::FuncCall(function_call) => self.rewrite_function_call(function_call), + RexNode::Now(_) => {} } } diff --git a/src/prost/build.rs b/src/prost/build.rs index 6fa4f4ce6420..fe09fd5a0c5a 100644 --- a/src/prost/build.rs +++ b/src/prost/build.rs @@ -61,6 +61,7 @@ fn main() -> Result<(), Box> { .type_attribute("expr.ExprNode", "#[derive(Eq, Hash)]") .type_attribute("data.DataType", "#[derive(Eq, Hash)]") .type_attribute("expr.ExprNode.rex_node", "#[derive(Eq, Hash)]") + .type_attribute("expr.ExprNode.NowRexNode", "#[derive(Eq, Hash)]") .type_attribute("expr.InputRef", "#[derive(Eq, Hash)]") .type_attribute("data.Datum", "#[derive(Eq, Hash)]") .type_attribute("expr.FunctionCall", "#[derive(Eq, Hash)]")