content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.cassandra.cql3.functions.masking;
  
  import java.util.Arrays;
  import java.util.Collection;
  
  import org.junit.Before;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.junit.runners.Parameterized;
  
  import org.apache.cassandra.cql3.CQL3Type;
  
  import static java.lang.String.format;
  import static org.apache.cassandra.cql3.CQL3Type.Native.ASCII;
  import static org.apache.cassandra.cql3.CQL3Type.Native.BIGINT;
  import static org.apache.cassandra.cql3.CQL3Type.Native.BLOB;
  import static org.apache.cassandra.cql3.CQL3Type.Native.INT;
  import static org.apache.cassandra.cql3.CQL3Type.Native.TEXT;
  import static org.apache.cassandra.cql3.CQL3Type.Native.VARINT;
  import static org.assertj.core.api.Assertions.assertThatThrownBy;
  
  /**
   * {@link ColumnMaskTester} for masks using functions that might return values with a type different to the type of the
   * masked column. Those queries should fail.
   */
  @RunWith(Parameterized.class)
  public class ColumnMaskWithTypeAlteringFunctionTest extends ColumnMaskTester
  {
      /** The column mask as expressed in CQL statements right after the {@code MASKED WITH} keywords. */
      @Parameterized.Parameter
      public String mask;
  
      /** The type of the masked column. */
      @Parameterized.Parameter(1)
      public CQL3Type.Native type;
  
      /** The type returned by the tested masking function. */
      @Parameterized.Parameter(2)
      public CQL3Type returnedType;
  
      private boolean shouldSucceed;
      private String errorMessage;
  
      @Parameterized.Parameters(name = "mask={0} type={1}")
      public static Collection<Object[]> options()
      {
          return Arrays.asList(new Object[][]{
          { "mask_default()", INT, INT },
          { "mask_default()", TEXT, TEXT },
          { "mask_null()", INT, INT },
          { "mask_null()", TEXT, TEXT },
          { "mask_hash()", INT, BLOB },
          { "mask_hash('SHA-512')", INT, BLOB },
          { "mask_inner(1,2)", TEXT, TEXT },
          { "mask_outer(1,2)", TEXT, TEXT },
          { "mask_replace(0)", INT, INT },
          { "mask_replace(0)", BIGINT, BIGINT },
          { "mask_replace(0)", VARINT, VARINT },
          { "mask_replace('redacted')", ASCII, ASCII },
          { "mask_replace('redacted')", TEXT, TEXT } });
      }
  
      @Before
      public void setupExpectedResults()
      {
          shouldSucceed = returnedType == type;
          errorMessage = shouldSucceed ? null : format("Masking function %s return type is %s.", mask, returnedType);
      }
  
      @Test
      public void testTypeAlteringFunctionOnCreateTable()
      {
          testOnCreateTable("CREATE TABLE %s.%s (k int PRIMARY KEY, v %s MASKED WITH %s)");
          testOnCreateTable("CREATE TABLE %s.%s (k %s MASKED WITH %s PRIMARY KEY, v int)");
          testOnCreateTable("CREATE TABLE %s.%s (k1 int, k2 %s MASKED WITH %s, PRIMARY KEY((k1, k2)))");
          testOnCreateTable("CREATE TABLE %s.%s (k int, c %s MASKED WITH %s, PRIMARY KEY(k, c)) WITH CLUSTERING ORDER BY (c ASC)");
          testOnCreateTable("CREATE TABLE %s.%s (k int, c %s MASKED WITH %s, PRIMARY KEY(k, c)) WITH CLUSTERING ORDER BY (c DESC)");
          testOnCreateTable("CREATE TABLE %s.%s (k int, c int, s %s STATIC MASKED WITH %s, PRIMARY KEY(k, c))");
      }
  
      private void testOnCreateTable(String query)
      {
          String formattedQuery = format(query, KEYSPACE, createTableName(), type, mask);
          if (shouldSucceed)
              createTable(formattedQuery);
          else
              assertThatThrownBy(() -> execute(formattedQuery)).hasMessageContaining(errorMessage);
      }
  
      @Test
      public void testTypeAlteringFunctionOnMaskColumn()
      {
          testOnAlterColumn("CREATE TABLE %s.%s (k int PRIMARY KEY, v %s)",
                            "ALTER TABLE %s.%s ALTER v MASKED WITH %s");
          testOnAlterColumn("CREATE TABLE %s.%s (k %s PRIMARY KEY, v int)",
                            "ALTER TABLE %s.%s ALTER k MASKED WITH %s");
          testOnAlterColumn("CREATE TABLE %s.%s (k1 int, k2 %s, PRIMARY KEY((k1, k2)))",
                            "ALTER TABLE %s.%s ALTER k2 MASKED WITH %s");
          testOnAlterColumn("CREATE TABLE %s.%s (k int, c %s, PRIMARY KEY(k, c)) WITH CLUSTERING ORDER BY (c ASC)",
                            "ALTER TABLE %s.%s ALTER c MASKED WITH %s");
          testOnAlterColumn("CREATE TABLE %s.%s (k int, c %s, PRIMARY KEY(k, c)) WITH CLUSTERING ORDER BY (c DESC)",
                            "ALTER TABLE %s.%s ALTER c MASKED WITH %s");
          testOnAlterColumn("CREATE TABLE %s.%s (k int, c int, s %s STATIC, PRIMARY KEY(k, c))",
                            "ALTER TABLE %s.%s ALTER s MASKED WITH %s");
      }
  
      private void testOnAlterColumn(String createQuery, String alterQuery)
      {
          String table = createTableName();
          createTable(format(createQuery, KEYSPACE, table, type));
  
          String formattedQuery = format(alterQuery, KEYSPACE, table, mask);
          if (shouldSucceed)
              alterTable(formattedQuery);
          else
              assertThatThrownBy(() -> execute(formattedQuery)).hasMessageContaining(errorMessage);
      }
  
      @Test
      public void testTypeAlteringFunctionOnAddColumn()
      {
          String table = createTable(format("CREATE TABLE %%s (k int PRIMARY KEY, v %s)", type));
          String query = format("ALTER TABLE %s.%s ADD n %s MASKED WITH %s", KEYSPACE, table, type, mask);
          if (shouldSucceed)
              alterTable(query);
          else
              assertThatThrownBy(() -> execute(query)).hasMessageContaining(errorMessage);
      }
  }
  
  