001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver.compactions;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertThrows;
022
023import java.text.SimpleDateFormat;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.hbase.ExtendedCell;
026import org.apache.hadoop.hbase.PrivateCellUtil;
027import org.apache.hadoop.hbase.testclassification.RegionServerTests;
028import org.apache.hadoop.hbase.testclassification.SmallTests;
029import org.apache.hadoop.hbase.util.Bytes;
030import org.junit.jupiter.api.AfterEach;
031import org.junit.jupiter.api.BeforeEach;
032import org.junit.jupiter.api.Tag;
033import org.junit.jupiter.api.Test;
034
035@Tag(RegionServerTests.TAG)
036@Tag(SmallTests.TAG)
037public class TestRowKeyDateTieringValueProvider {
038
039  private RowKeyDateTieringValueProvider provider;
040  private Configuration conf;
041
042  @BeforeEach
043  public void setUp() {
044    conf = new Configuration();
045    provider = new RowKeyDateTieringValueProvider();
046  }
047
048  @AfterEach
049  public void tearDown() {
050    provider = null;
051    conf = null;
052  }
053
054  @Test
055  public void testInitWithValidConfig() throws Exception {
056    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "(\\d{4}-\\d{2}-\\d{2})");
057    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyy-MM-dd");
058    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "1");
059    provider.init(conf);
060    assertEquals("(\\d{4}-\\d{2}-\\d{2})", provider.getRowKeyPattern().pattern());
061    assertEquals("yyyy-MM-dd", provider.getDateFormat().toPattern());
062    assertEquals(Integer.valueOf(1), provider.getRowKeyRegexExtractGroup());
063  }
064
065  @Test
066  public void testInitWithMissingRegexPattern() {
067    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyy-MM-dd");
068    assertThrows(IllegalArgumentException.class, () -> provider.init(conf));
069  }
070
071  @Test
072  public void testInitWithMissingDateFormat() {
073    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "(\\d{4}-\\d{2}-\\d{2})");
074    assertThrows(IllegalArgumentException.class, () -> provider.init(conf));
075  }
076
077  @Test
078  public void testInitWithInvalidDateFormat() {
079    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "(\\d{4}-\\d{2}-\\d{2})");
080    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "invalid-format");
081    assertThrows(IllegalArgumentException.class, () -> provider.init(conf));
082  }
083
084  @Test
085  public void testInitWithInvalidExtractGroup() {
086    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "(\\d{4}-\\d{2}-\\d{2})");
087    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyy-MM-dd");
088    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "-1");
089    assertThrows(IllegalArgumentException.class, () -> provider.init(conf));
090  }
091
092  @Test
093  public void testInitWithExtractGroupExceedingPatternGroups() {
094    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "(\\d{4}-\\d{2}-\\d{2})");
095    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyy-MM-dd");
096    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "2"); // Only 1 group in
097    // pattern
098    assertThrows(IllegalArgumentException.class, () -> provider.init(conf));
099  }
100
101  @Test
102  public void testGetTieringValue() throws Exception {
103    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "_(\\d{4}-\\d{2}-\\d{2})_");
104    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyy-MM-dd");
105    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "1");
106    provider.init(conf);
107
108    String dateStr = "2023-10-15";
109    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
110    long expectedTimestamp = sdf.parse(dateStr).getTime();
111
112    String rowKeyStr = "order_" + dateStr + "_details";
113    byte[] rowKey = Bytes.toBytes(rowKeyStr);
114    ExtendedCell cell = PrivateCellUtil.createFirstOnRow(rowKey);
115    long actualTimestamp = provider.getTieringValue(cell);
116
117    assertEquals(expectedTimestamp, actualTimestamp);
118  }
119
120  @Test
121  public void testGetTieringValueWithNonMatchingRowKey() throws Exception {
122    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "_(\\d{4}-\\d{2}-\\d{2})_");
123    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyy-MM-dd");
124    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "1");
125    provider.init(conf);
126
127    String rowKeyStr = "order_details_no_date";
128    byte[] rowKey = Bytes.toBytes(rowKeyStr);
129    ExtendedCell cell = PrivateCellUtil.createFirstOnRow(rowKey);
130    long actualTimestamp = provider.getTieringValue(cell);
131
132    assertEquals(Long.MAX_VALUE, actualTimestamp);
133  }
134
135  @Test
136  public void testGetTieringValueWithInvalidDateInRowKey() throws Exception {
137    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "_(\\d{14})_");
138    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyyMMddHHmmss");
139    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "1");
140    provider.init(conf);
141
142    // Invalid Month (14)
143    String rowKeyStr = "order_20151412124556_date";
144    byte[] rowKey = Bytes.toBytes(rowKeyStr);
145    ExtendedCell cell = PrivateCellUtil.createFirstOnRow(rowKey);
146    long actualTimestamp = provider.getTieringValue(cell);
147
148    assertEquals(Long.MAX_VALUE, actualTimestamp);
149  }
150
151  @Test
152  public void testGetTieringValueWithNonUTF8RowKey() throws Exception {
153    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_PATTERN, "_(\\d{8})_");
154    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_FORMAT, "yyyyMMdd");
155    conf.set(RowKeyDateTieringValueProvider.TIERING_KEY_DATE_GROUP, "1");
156    provider.init(conf);
157
158    // Row key with non-UTF-8 bytes (invalid UTF-8 sequence)
159    byte[] rowKey =
160      new byte[] { 0x6F, 0x72, 0x64, 0x65, 0x72, 0x5F, (byte) 0xFF, (byte) 0xFE, 0x5F };
161    ExtendedCell cell = PrivateCellUtil.createFirstOnRow(rowKey);
162    long timestamp = provider.getTieringValue(cell);
163
164    assertEquals(Long.MAX_VALUE, timestamp);
165  }
166
167  @Test
168  public void testGetTieringValueWithoutInitialization() {
169    String rowKeyStr = "order_9999999999999999_date";
170    byte[] rowKey = Bytes.toBytes(rowKeyStr);
171    ExtendedCell cell = PrivateCellUtil.createFirstOnRow(rowKey);
172    assertThrows(IllegalStateException.class, () -> provider.getTieringValue(cell));
173  }
174}