チュートリアル:Java で正規表現 (regex) を使用して文字列を検索する

適用対象: SQL Server 2019 (15.x) 以降のバージョン

このチュートリアルでは、SQL Server の言語拡張を使用し、SQL Server から 2 つの列 (ID とテキスト) と、正規表現の入力パラメーターを受け取る Java クラスを作成する方法について説明します。 このクラスでは、SQL Server に 2 つの列 (ID とテキスト) を返します。

Java クラスに送信されるテキスト列内の指定のテキストが、指定した正規表現を満たしているかどうかをコードが確認し、元の ID と共にそのテキストが返されます。

このサンプル コードでは、テキストに単語 Java または java が含まれているかどうかを確認する正規表現を使用しています。

前提条件

このチュートリアルでは、javac を使用したコマンドライン コンパイルで十分です。

サンプル データの作成

まず、新しいデータベースを作成し、testdata テーブルに ID 列と text 列を設定します。

CREATE DATABASE javatest;
GO

USE javatest;
GO

CREATE TABLE testdata (
    [id] INT NOT NULL,
    [text] NVARCHAR(100) NOT NULL
);
GO

-- Insert data into test table
INSERT INTO testdata ([id], [text])
VALUES (1, 'This sentence contains java');

INSERT INTO testdata ([id], [text])
VALUES (2, 'This sentence does not');

INSERT INTO testdata ([id], [text])
VALUES (3, 'I love Java!');
GO

メイン クラスの作成

この手順では、RegexSample.java というクラス ファイルを作成し、次の Java コードをそのファイルにコピーします。

このメイン クラスが SDK をインポートします。つまり、手順 1 でダウンロードした jar ファイルをこのクラスで検出できるようにする必要があります。

package pkg;

import com.microsoft.sqlserver.javalangextension.PrimitiveDataset;
import com.microsoft.sqlserver.javalangextension.AbstractSqlServerExtensionExecutor;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.regex.*;

public class RegexSample extends AbstractSqlServerExtensionExecutor {
    private Pattern expr;

    public RegexSample() {
        // Setup the expected extension version, and class to use for input and output dataset
        executorExtensionVersion = SQLSERVER_JAVA_LANG_EXTENSION_V1;
        executorInputDatasetClassName = PrimitiveDataset.class.getName();
        executorOutputDatasetClassName = PrimitiveDataset.class.getName();
    }

    public PrimitiveDataset execute(PrimitiveDataset input, LinkedHashMap<String, Object> params) {
        // Validate the input parameters and input column schema
        validateInput(input, params);

        int[] inIds = input.getIntColumn(0);
        String[] inValues = input.getStringColumn(1);
        int rowCount = inValues.length;

        String regexExpr = (String)params.get("regexExpr");
        expr = Pattern.compile(regexExpr);

        System.out.println("regex expression: " + regexExpr);

        // Lists to store the output data
        LinkedList<Integer> outIds = new LinkedList<Integer>();
        LinkedList<String> outValues = new LinkedList<String>();

        // Evaluate each row
        for(int i = 0; i < rowCount; i++) {
            if (check(inValues[i])) {
                outIds.add(inIds[i]);
                outValues.add(inValues[i]);
            }
        }

        int outputRowCount = outValues.size();

        int[] idOutputCol = new int[outputRowCount];
        String[] valueOutputCol = new String[outputRowCount];

        // Convert the list of output columns to arrays
        outValues.toArray(valueOutputCol);

        ListIterator<Integer> it = outIds.listIterator(0);
        int rowId = 0;

        System.out.println("Output data:");
        while (it.hasNext()) {
            idOutputCol[rowId] = it.next().intValue();

            System.out.println("ID: " + idOutputCol[rowId] + " Value: " + valueOutputCol[rowId]);
            rowId++;
        }

        // Construct the output dataset
        PrimitiveDataset output = new PrimitiveDataset();

        output.addColumnMetadata(0, "ID", java.sql.Types.INTEGER, 0, 0);
        output.addColumnMetadata(1, "Text", java.sql.Types.NVARCHAR, 0, 0);

        output.addIntColumn(0, idOutputCol, null);
        output.addStringColumn(1, valueOutputCol);

        return output;
    }

    private void validateInput(PrimitiveDataset input, LinkedHashMap<String, Object> params) {
        // Check for the regex expression input parameter
        if (params.get("regexExpr") == null) {
            throw new IllegalArgumentException("Input parameter 'regexExpr' is not found");
        }

        // The expected input schema should be at least 2 columns, (INTEGER, STRING)
        if (input.getColumnCount() < 2) {
            throw new IllegalArgumentException("Unexpected input schema, schema should be an (INTEGER, NVARCHAR or VARCHAR)");
        }

        // Check that the input column types are expected
        if (input.getColumnType(0) != java.sql.Types.INTEGER &&
                (input.getColumnType(1) != java.sql.Types.VARCHAR && input.getColumnType(1) == java.sql.Types.NVARCHAR )) {
            throw new IllegalArgumentException("Unexpected input schema, schema should be an (INTEGER, NVARCHAR or VARCHAR)");
        }
    }

    private boolean check(String text) {
        Matcher m = expr.matcher(text);

        return m.find();
    }
}

.jar ファイルのコンパイルおよび作成

ご自分のクラスと依存関係を .jar ファイルにパッケージ化します。 (Eclipse や IntelliJ などの) ほとんどの Java IDE は、プロジェクトのビルドまたはコンパイル時の .jar ファイルの生成をサポートしています。 .jar ファイルに regex.jar という名前を付けます。

Java IDE を使用していない場合は、.jar ファイルを手動で作成できます。 詳細については、「クラス ファイルから Java jar ファイルを作成する」を参照してください。

Note

このチュートリアルではパッケージを使用します。 クラスの先頭にある package pkg; 行により、コンパイルされたコードが pkg というサブフォルダーに保存されます。 IDE を使用している場合、コンパイルしたコードはこのフォルダーに自動的に保存されます。 javac を使用してクラスを手動でコンパイルする場合は、コンパイルされたコードを pkg フォルダーに配置する必要があります。

外部言語の作成

データベースには、外部言語を作成する必要があります。 外部言語とは、データベースで Java などの外部言語を使用する場合、使用する各データベースに作成する必要があるデータベース スコープのオブジェクトです。

Windows での外部言語の作成

Windows を使用している場合は、次の手順に従って Java の外部言語を作成します。

  1. 拡張機能を含む .zip ファイルを作成します。

    Windows での SQL Server セットアップの一部として、Java 拡張機能 .zip ファイルが次の場所: [SQL Server install path]\MSSQL\Binn\java-lang-extension.zipにインストールされます。 この zip ファイルには javaextension.dll が含まれています。

  2. .zip ファイルから外部言語 Java ファイルを作成する方法は次のとおりです。

    CREATE EXTERNAL LANGUAGE Java
    FROM
    (CONTENT = N'[SQL Server install path]\MSSQL\Binn\java-lang-extension.zip', FILE_NAME = 'javaextension.dll',
    ENVIRONMENT_VARIABLES = N'{"JRE_HOME":"<path to JRE>"}' );
    GO
    

Linux での外部言語の作成

セットアップの一環として、拡張子 .tar.gz ファイルがパス: /opt/mssql-extensibility/lib/java-lang-extension.tar.gz に保存されます。

Linux で次の T-SQL ステートメントを実行し、外部言語の Java を作成します。

CREATE EXTERNAL LANGUAGE Java
FROM (CONTENT = N'/opt/mssql-extensibility/lib/java-lang-extension.tar.gz', file_name = 'javaextension.so',
ENVIRONMENT_VARIABLES = N'{"JRE_HOME":"<path to JRE>"}' );
GO

外部言語を実行するアクセス許可

Java コードを実行するユーザーには、その特定の言語を外部スクリプトから実行する許可を付与する必要があります。

詳しくは、「CREATE EXTERNAL LANGUAGE」をご覧ください。

外部ライブラリの作成

お使いの .jar ファイル用の外部ライブラリを作成するには、CREATE EXTERNAL LIBRARY を使用します。 SQL Server は .jar ファイルにアクセスできるため、classpath に特別なアクセス許可を設定する必要はありません。

このサンプルでは、2 つの外部ライブラリを作成します。 1 つは SDK 用で、もう 1 つは RegEx Java コード用です。

  1. SDK jar ファイル mssql-java-lang-extension.jar は、SQL Server 2019 (15.x) 以降のバージョンの一部として、Windows と Linux の両方にインストールされます。

    • Windows 上のデフォルのインストール パス: <instance installation home directory>\MSSQL\Binn\mssql-java-lang-extension.jar

    • Linux 上のデフォルのインストール パス: /opt/mssql/lib/mssql-java-lang-extension.jar

    このコードもオープン ソースであり、SQL Server の言語拡張機能 GitHub リポジトリに関するページにあります。 詳細については、「Microsoft Extensibility SDK for Java for SQL Server」を参照してください。

  2. SDK 用の外部ライブラリを作成します。

    CREATE EXTERNAL LIBRARY sdk
    FROM (CONTENT = '<OS specific path from above>/mssql-java-lang-extension.jar')
    WITH (LANGUAGE = 'Java');
    GO
    
  3. RegEx コード用の外部ライブラリを作成します。

    CREATE EXTERNAL LIBRARY regex
    FROM (CONTENT = '<path>/regex.jar')
    WITH (LANGUAGE = 'Java');
    GO
    

アクセス許可を設定する

Note

前の手順で外部ライブラリを使用している場合は、この手順を省略してください。 外部ライブラリは、ご自分の .jar ファイルから作成することをお勧めします。

外部ライブラリを使用したくない場合は、必要な権限を設定する必要があります。 スクリプトの実行は、プロセス ID がお使いのコードにアクセスできる場合にのみ成功します。 アクセス許可の設定の詳細については、インストール ガイドを参照してください。

Linux の場合

クラスパスに対する読み取り/実行権限を mssql_satellite ユーザーに付与します。

Windows の場合

コンパイルされたご自分の Java コードを含むフォルダー上で SQLRUserGroup と、All application packages SID に対して "読み取りと実行" アクセス許可を付与します。

アクセス許可は、ルートの親から最後のサブ フォルダーまで、ツリー全体で必要です。

  1. フォルダーを右クリックし (C:\myJavaCode など)、[プロパティ]>[セキュリティ] の順に選択します。
  2. 編集を選択します。
  3. [追加] を選択します。
  4. [ユーザー、コンピューター、サービス アカウントまたはグループの選択] で次を実行します。
    1. [オブジェクト タイプ] を選択し、[組み込みセキュリティ原則] と [グループ] が選択されていることを確認します。
    2. [場所] を選択して、リストの先頭にあるローカル コンピューター名を選択します。
  5. SQLRUserGroup」と入力し、名前を確認し、[OK] をクリックしてグループを追加します。
  6. ALL APPLICATION PACKAGES」と入力し、名前を確認し、[OK] をクリックして追加します。 名前が解決されない場合は、[場所] の手順を再実行します。 SID は、お使いのコンピューターに対してローカルです。

両方のセキュリティ ID に、フォルダーと pkg サブフォルダーに対する読み取り権限と実行権限があることを確認してください。

Java クラスの呼び出し

SQL Server から Java コードを呼び出す、sp_execute_external_script を呼び出すストアド プロシージャを作成します。 script パラメータで、どの package.class を呼び出すかを定義します。 次のコードでは、クラスは pkg というパッケージと RegexSample.java というクラス ファイルに属しています。

Note

コードでは、どのメソッドを呼び出すかが定義されていません。 デフォルトでは、execute メソッドが呼び出されます。 つまり、SQL Server からクラスを呼び出したい場合には、SDK インターフェイスに従い、メソッドをお使いの Java クラスに実装して実行する必要があります。

このストアド プロシージャは、入力クエリ (入力データセット) と正規表現を受け取り、指定された正規表現を満たす行を返します。 正規表現 [Jj]ava を使用して、テキストに Java または java という単語が含まれているかどうかを確認します。

CREATE OR ALTER PROCEDURE [dbo].[java_regex]
    @expr NVARCHAR(200), @query NVARCHAR(400)
AS
BEGIN
    --Call the Java program by giving the package.className in @script
    --The method invoked in the Java code is always the "execute" method
    EXEC sp_execute_external_script @language = N'Java',
        @script = N'pkg.RegexSample',
        @input_data_1 = @query,
        @params = N'@regexExpr nvarchar(200)',
        @regexExpr = @expr
    WITH result sets((
        ID INT,
        TEXT NVARCHAR(100)
    ));
END
GO

--Now execute the above stored procedure and provide the regular expression and an input query
EXECUTE [dbo].[java_regex] N'[Jj]ava',
    N'SELECT id, text FROM testdata'
GO

結果

呼び出しを実行すると、2 つの行の結果セットが返されるはずです。

Screenshot of Results from Java sample.

エラーが表示された場合

  • クラスをコンパイルするとき、pkg サブフォルダーには、3 つのクラスすべてのコンパイルされたコードが含まれている必要があります。

  • 外部ライブラリを使用していない場合は、root から pkg サブ フォルダーまでのフォルダーのアクセス許可を確認して、外部プロセスを実行しているセキュリティ ID にコードの読み取りと実行のアクセス許可があることを確認します。