1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *    http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package org.apache.spark.sql.catalyst.analysis
19
20import org.scalatest.BeforeAndAfter
21
22import org.apache.spark.sql.AnalysisException
23import org.apache.spark.sql.catalyst.expressions.{Literal, Rand}
24import org.apache.spark.sql.catalyst.expressions.aggregate.Count
25import org.apache.spark.sql.catalyst.plans.PlanTest
26import org.apache.spark.sql.types.{LongType, NullType}
27
28/**
29 * Unit tests for [[ResolveInlineTables]]. Note that there are also test cases defined in
30 * end-to-end tests (in sql/core module) for verifying the correct error messages are shown
31 * in negative cases.
32 */
33class ResolveInlineTablesSuite extends PlanTest with BeforeAndAfter {
34
35  private def lit(v: Any): Literal = Literal(v)
36
37  test("validate inputs are foldable") {
38    ResolveInlineTables.validateInputEvaluable(
39      UnresolvedInlineTable(Seq("c1", "c2"), Seq(Seq(lit(1)))))
40
41    // nondeterministic (rand) should not work
42    intercept[AnalysisException] {
43      ResolveInlineTables.validateInputEvaluable(
44        UnresolvedInlineTable(Seq("c1"), Seq(Seq(Rand(1)))))
45    }
46
47    // aggregate should not work
48    intercept[AnalysisException] {
49      ResolveInlineTables.validateInputEvaluable(
50        UnresolvedInlineTable(Seq("c1"), Seq(Seq(Count(lit(1))))))
51    }
52
53    // unresolved attribute should not work
54    intercept[AnalysisException] {
55      ResolveInlineTables.validateInputEvaluable(
56        UnresolvedInlineTable(Seq("c1"), Seq(Seq(UnresolvedAttribute("A")))))
57    }
58  }
59
60  test("validate input dimensions") {
61    ResolveInlineTables.validateInputDimension(
62      UnresolvedInlineTable(Seq("c1"), Seq(Seq(lit(1)), Seq(lit(2)))))
63
64    // num alias != data dimension
65    intercept[AnalysisException] {
66      ResolveInlineTables.validateInputDimension(
67        UnresolvedInlineTable(Seq("c1", "c2"), Seq(Seq(lit(1)), Seq(lit(2)))))
68    }
69
70    // num alias == data dimension, but data themselves are inconsistent
71    intercept[AnalysisException] {
72      ResolveInlineTables.validateInputDimension(
73        UnresolvedInlineTable(Seq("c1"), Seq(Seq(lit(1)), Seq(lit(21), lit(22)))))
74    }
75  }
76
77  test("do not fire the rule if not all expressions are resolved") {
78    val table = UnresolvedInlineTable(Seq("c1", "c2"), Seq(Seq(UnresolvedAttribute("A"))))
79    assert(ResolveInlineTables(table) == table)
80  }
81
82  test("convert") {
83    val table = UnresolvedInlineTable(Seq("c1"), Seq(Seq(lit(1)), Seq(lit(2L))))
84    val converted = ResolveInlineTables.convert(table)
85
86    assert(converted.output.map(_.dataType) == Seq(LongType))
87    assert(converted.data.size == 2)
88    assert(converted.data(0).getLong(0) == 1L)
89    assert(converted.data(1).getLong(0) == 2L)
90  }
91
92  test("nullability inference in convert") {
93    val table1 = UnresolvedInlineTable(Seq("c1"), Seq(Seq(lit(1)), Seq(lit(2L))))
94    val converted1 = ResolveInlineTables.convert(table1)
95    assert(!converted1.schema.fields(0).nullable)
96
97    val table2 = UnresolvedInlineTable(Seq("c1"), Seq(Seq(lit(1)), Seq(Literal(null, NullType))))
98    val converted2 = ResolveInlineTables.convert(table2)
99    assert(converted2.schema.fields(0).nullable)
100  }
101}
102