Help us understand the problem. What is going on with this article?

HiveRunner を試す

モチベーション

Hive のクエリのテストしたい。

mysql だったら、docker-compose とか比較的簡単にテストできるけど、hiveのクエリのテストも同じようにしたいけど、どうしたらいいですかね。(docker で ゴニョゴニョするのはちょっと辛そう..)

ってわけで良さそうなプラグインを適当に選んで試してみました。

選んだのは、公式でいくつか紹介されているツールの中で git のスターが一番多かった HiveRunner です。(バージョンは 4.1.0)

準備

HiveRunner は基本的に Junit のテストとして、クエリのテストをします。

外部の依存関係は必要なく、JVM上にHiveServerを立てて、Junit がそのHiveServerhive sql を実行するイメージみたいです。

今回は maven でプロジェクトを作っていきます。
以下、今回実行する際の pom です。 特に理由はありませんが、BEELINE のエミュレータを指定しています。

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <configuration>
                <forkMode>always</forkMode>
                <systemProperties>
                    <!-- Defaults to HIVE_CLI, other options include BEELINE and HIVE_CLI_PRE_V200 -->
                    <commandShellEmulator>BEELINE</commandShellEmulator>
                </systemProperties>
            </configuration>
        </plugin>
    </plugins>
</build>

<dependencies>
    <dependency>
        <groupId>com.klarna</groupId>
        <artifactId>hiverunner</artifactId>
        <version>4.1.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

とりあえず実行してみる

@RunWith(StandaloneHiveRunner.class)
public class TestHiveRunner {
    @HiveSQL(files = {})
    private HiveShell shell;

    /*
     * DB と テーブルを作成
     */
    @Before
    public void setupSourceDatabase() {
        shell.execute(
                "CREATE DATABASE source_db; " +
                "CREATE TABLE source_db.test_table (year STRING, value INT);");

        shell.execute(
                "CREATE DATABASE my_schema; " +
                "CREATE EXTERNAL TABLE my_schema.result " +
                "(year STRING, value INT) " +
                "STORED AS PARQUET " +
                "TBLPROPERTIES (\"parquet.compress\"=\"snappy\")");
    }

    @Test
    public void testMaxValueByYear() {
        /*
         * 集計対象のテーブルにテストデータを格納
         */
        shell.insertInto("source_db", "test_table")
                .withColumns("year", "value")
                .addRow("2014", 3)
                .addRow("2014", 4)
                .addRow("2015", 2)
                .addRow("2015", 5)
                .commit();

        /*
         * 集計クエリを実行 (INSERT クエリ)
         */
        shell.executeStatement("INSERT INTO my_schema.result " +
                "SELECT " +
                "   year, " +
                "   MAX(value) " +
                "FROM " +
                "   source_db.test_table " +
                "GROUP BY year");

        /*
         * 集計結果が INSERT されているテーブルから結果を取得
         */
        List<Object[]> result = shell.executeStatement("SELECT * FROM my_schema.result");

        assertEquals(2, result.size());
        assertArrayEquals(new Object[]{"2014",4}, result.get(0));
        assertArrayEquals(new Object[]{"2015",5}, result.get(1));
    }
}

上記ほとんど Example に載っているコードをちょっと書き換えたものです。

とても簡単にテストすることができるように見えます。

切り出して置いたクエリの読み込みや、テストデータのインサートが多くなってきて別途tsv等に切り出した場合など、それらを読み込むこともできます。

それぞれちょっとだけ見ていく

@HiveSQL

// デフォルトで src/test/resources ディレクトリを参照している。
@HiveSQL(files = {"create_test_table.sql", "create_max.sql"})
private HiveShell shell;

files = {} にSQLファイルを指定することで、インスタンスが作られた後に自動で実行してくれる。

@HiveSQL(files = {...}, autoStart = false) とすることで、任意のセットアップを行なった後、起動させることができる。(start()メソッドをコールする)

ちなみに、任意のセットアップで設定出来る項目は以下のようなものがあります。(オーバーロードされているメソッド多々あり.)

// HiveConf を設定
void setProperty(String key, String value);
void setHiveConfValue(String key, String value);

// テストデータを HDFS にコピー
void addResource(String targetFile, File sourceFile);

// HiveShell起動時に実行されるスクリプトの登録
// @HiveSetupScript でも同様だが、以下はスクリプトの実行順序が保証される。
void addSetupScript(String script);
void addSetupScripts(Charset charset, File... scripts);

// stream を開いて、テストデータを HDFS に書き込んでくれるらしい
OutputStream getResourceOutputStream(String targetFile);

execute

// 直書き
shell.execute("CREATE DATABASE source_db; " +
        "CREATE TABLE source_db.test_table (year STRING, value INT);");


// 切り出されたSQLの読み込みも可
shell.execute(Paths.get("src/test/resources/calculate_max.sql"));

返り値なしの、スクリプト(クエリ)実行。

; で区切ることで、複数のクエリを実行できる。

複数のクエリを実行できるから、返り値がないってことですかね。

executeQuery & executeStatement

// 下記、executeQuery でも同様に実行できる。
shell.executeStatement("INSERT INTO my_schema.result " +
        "SELECT " +
        "   year, " +
        "   MAX(value) " +
        "FROM " +
        "   source_db.test_table " +
        "GROUP BY year");

// executeQuery に限り、切り出されたSQLの読み込みも可
shell.execute(Paths.get("src/test/resources/calculate_max.sql"));

execute と異なり、複数のクエリを一回で実行することはできないし、文末に ; が含まれるとエラーになる。

その代わり、List<String> でクエリの実行結果が返ってくる。

insertInto

shell.insertInto("source_db", "test_table")
        .withColumns("year", "value")
        .addRow("2014", 3)
        .addRow("2014", 4)
        .addRow("2015", 2)
        .addRow("2015", 5)
        .commit();

// tsv 等からデータをインサートすることもできる。
shell.insertInto("source_db", "test_table")
        .withColumns("year", "value")
        .addRowsFromTsv(new File("src/test/resources/insert_data_of_test_table.tsv"))
        .commit();

このメソッドは、commit() しないと実行されない。

テストデータの準備はめんどくさいですが、tsvでインサートしてくれるのはとても便利ですね。

まとめ

クエリのテストやちょっとクエリ試したいときなど、(ちょっと重い感じがしますが)使えそうですね。

一方で、カラム数が多いテーブルだったり、テストデータの量が多い場合のテストだったり、めっちゃ複雑なクエリのテスト等では、メモリや実行速度的に辛そうですが、どうなるのでしょうか (READMEに対処法など書いてありますが..)

とはいえ、pomhiverunner を入れるだけで、hivesql のテストができるのは魅力的ですね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away