Java UDF 처리기의 예

이 항목에는 Java로 작성된 UDF 처리기 코드의 간단한 예가 나와 있습니다.

Java를 사용하여 UDF 처리기를 만드는 자세한 방법은 Java UDF 처리기 생성하기 섹션을 참조하십시오.

이 항목의 내용:

간단한 인라인 Java UDF 만들기 및 호출

다음 문은 인라인 Java UDF를 만들고 호출합니다. 이 코드는 이에 전달된 VARCHAR 를 반환합니다.

이 함수는 입력 값이 NULL인 경우에도 함수가 호출됨을 나타내기 위해 선택적 CALLED ON NULL INPUT 절로 선언됩니다. (이 함수는 이 절을 포함하거나 포함하지 않고 NULL을 반환하지만, 사용자는 빈 문자열을 반환하는 등 다른 방식으로 NULL을 처리하도록 코드를 수정할 수 있습니다.)

UDF를 만듭니다.

create or replace function echo_varchar(x varchar)
returns varchar
language java
called on null input
handler='TestFunc.echoVarchar'
target_path='@~/testfunc.jar'
as
'class TestFunc {
  public static String echoVarchar(String x) {
    return x;
  }
}';
Copy

UDF를 호출합니다.

SELECT echo_varchar('Hello');
+-----------------------+
| ECHO_VARCHAR('HELLO') |
|-----------------------|
| Hello                 |
+-----------------------+
Copy

인라인 Java UDF에 NULL 전달

이는 위에서 정의한 echo_varchar() UDF를 사용합니다. SQL NULL 값은 암시적으로 Java null 로 변환되고 해당 Java null 이 반환되어 암시적으로 SQL NULL 로 다시 변환됩니다.

UDF를 호출합니다.

SELECT echo_varchar(NULL);
+--------------------+
| ECHO_VARCHAR(NULL) |
|--------------------|
| NULL               |
+--------------------+
Copy

배열 값 전달하기

Java 메서드는 다음 두 가지 방법 중 하나로 SQL 배열을 수신할 수 있습니다.

  • Java의 배열 기능을 사용합니다.

  • Java의 varargs (가변 인자 수) 기능을 사용합니다.

두 경우 모두 SQL 코드는 ARRAY 를 전달해야 합니다.

참고

SQL 유형에 유효한 매핑과 함께 Java 유형을 사용해야 합니다. 자세한 내용은 SQL-Java 데이터 타입 매핑 섹션을 참조하십시오.

ARRAY를 통해 전달

Java 매개 변수를 배열로 선언하십시오. 예를 들어, 다음 메서드의 세 번째 매개 변수는 문자열 배열입니다.

static int myMethod(int fixedArgument1, int fixedArgument2, String[] stringArray)
Copy

다음은 완전한 예입니다.

테이블을 만들고 로딩합니다.

CREATE TABLE string_array_table(id INTEGER, a ARRAY);
INSERT INTO string_array_table (id, a) SELECT
        1, ARRAY_CONSTRUCT('Hello');
INSERT INTO string_array_table (id, a) SELECT
        2, ARRAY_CONSTRUCT('Hello', 'Jay');
INSERT INTO string_array_table (id, a) SELECT
        3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');
Copy

UDF를 만듭니다.

create or replace function concat_varchar_2(a ARRAY)
returns varchar
language java
handler='TestFunc_2.concatVarchar2'
target_path='@~/TestFunc_2.jar'
as
$$
    class TestFunc_2 {
        public static String concatVarchar2(String[] strings) {
            return String.join(" ", strings);
        }
    }
$$;
Copy

UDF를 호출합니다.

SELECT concat_varchar_2(a)
    FROM string_array_table
    ORDER BY id;
+---------------------+
| CONCAT_VARCHAR_2(A) |
|---------------------|
| Hello               |
| Hello Jay           |
| Hello Jay Smith     |
+---------------------+
Copy

Varargs를 통해 전달

varargs를 사용하는 것은 배열을 사용하는 것과 매우 유사합니다.

Java 코드에서 Java의 varargs 선언 스타일을 사용하십시오.

static int myMethod(int fixedArgument1, int fixedArgument2, String ... stringArray)
Copy

다음은 완전한 예입니다. 이 예와 이전 예(배열의 경우) 간의 유일한 중요한 차이점은 메서드에 대한 매개 변수 선언입니다.

테이블을 만들고 로딩합니다.

CREATE TABLE string_array_table(id INTEGER, a ARRAY);
INSERT INTO string_array_table (id, a) SELECT
        1, ARRAY_CONSTRUCT('Hello');
INSERT INTO string_array_table (id, a) SELECT
        2, ARRAY_CONSTRUCT('Hello', 'Jay');
INSERT INTO string_array_table (id, a) SELECT
        3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');
Copy

UDF를 만듭니다.

create or replace function concat_varchar(a ARRAY)
returns varchar
language java
handler='TestFunc.concatVarchar'
target_path='@~/TestFunc.jar'
as
$$
    class TestFunc {
        public static String concatVarchar(String ... stringArray) {
            return String.join(" ", stringArray);
        }
    }
$$;
Copy

UDF를 호출합니다.

SELECT concat_varchar(a)
    FROM string_array_table
    ORDER BY id;
+-------------------+
| CONCAT_VARCHAR(A) |
|-------------------|
| Hello             |
| Hello Jay         |
| Hello Jay Smith   |
+-------------------+
Copy

인라인 UDF에서 명시적으로 NULL 반환하기

다음 코드는 명시적으로 NULL 값을 반환하는 방법을 보여줍니다. Java 값 null 은 SQL NULL 로 변환됩니다.

UDF를 만듭니다.

create or replace function return_a_null()
returns varchar
null
language java
handler='TemporaryTestLibrary.returnNull'
target_path='@~/TemporaryTestLibrary.jar'
as
$$
class TemporaryTestLibrary {
  public static String returnNull() {
    return null;
  }
}
$$;
Copy

UDF를 호출합니다.

SELECT return_a_null();
+-----------------+
| RETURN_A_NULL() |
|-----------------|
| NULL            |
+-----------------+
Copy

인라인 Java UDF에 OBJECT 전달

다음 예에서는 SQL OBJECT 데이터 타입과 해당 Java 데이터 타입(Map<String, String>)을 사용하고 OBJECT 에서 값을 추출합니다. 이 예는 또한 여러 매개 변수를 Java UDF에 전달할 수 있음을 보여줍니다.

다음과 같이 OBJECT 형식의 열이 포함된 테이블을 만들고 로딩하십시오.

CREATE TABLE objectives (o OBJECT);
INSERT INTO objectives SELECT PARSE_JSON('{"outer_key" : {"inner_key" : "inner_value"} }');
Copy

UDF를 만듭니다.

create or replace function extract_from_object(x OBJECT, key VARCHAR)
returns variant
language java
handler='VariantLibrary.extract'
target_path='@~/VariantLibrary.jar'
as
$$
import java.util.Map;
class VariantLibrary {
  public static String extract(Map<String, String> m, String key) {
    return m.get(key);
  }
}
$$;
Copy

UDF를 호출합니다.

SELECT extract_from_object(o, 'outer_key'), 
       extract_from_object(o, 'outer_key')['inner_key'] FROM objectives;
+-------------------------------------+--------------------------------------------------+
| EXTRACT_FROM_OBJECT(O, 'OUTER_KEY') | EXTRACT_FROM_OBJECT(O, 'OUTER_KEY')['INNER_KEY'] |
|-------------------------------------+--------------------------------------------------|
| {                                   | "inner_value"                                    |
|   "inner_key": "inner_value"        |                                                  |
| }                                   |                                                  |
+-------------------------------------+--------------------------------------------------+
Copy

인라인 Java UDF에 GEOGRAPHY 값 전달

다음 예에서는 SQL GEOGRAPHY 데이터 타입을 사용합니다.

UDF를 만듭니다.

create or replace function geography_equals(x geography, y geography)
returns boolean
language java
packages=('com.snowflake:snowpark:1.2.0')
handler='TestGeography.compute'
as
$$
import com.snowflake.snowpark_java.types.Geography;

class TestGeography {
  public static boolean compute(Geography geo1, Geography geo2) {
    return geo1.equals(geo2);
  }
}
$$;
Copy

PACKAGES 절을 사용하여 Snowpark 패키지 와 같은 Snowflake 시스템 패키지를 지정할 수 있습니다. 이때 Snowpark JAR 파일도 IMPORTS 절의 값으로 포함할 필요는 없습니다. PACKAGES 에 대한 자세한 내용은 CREATE FUNCTION 옵션 매개 변수 를 참조하십시오.

데이터를 생성하고 해당 데이터로 UDF를 호출합니다.

create table geocache_table (id INTEGER, g1 GEOGRAPHY, g2 GEOGRAPHY);

insert into geocache_table (id, g1, g2) select
    1, TO_GEOGRAPHY('POINT(-122.35 37.55)'), TO_GEOGRAPHY('POINT(-122.35 37.55)');
insert into geocache_table (id, g1, g2) select
    2, TO_GEOGRAPHY('POINT(-122.35 37.55)'), TO_GEOGRAPHY('POINT(90.0 45.0)');

select id, g1, g2, geography_equals(g1, g2) as "EQUAL?"
    from geocache_table
    order by id;
Copy

출력은 다음과 유사합니다.

+----+--------------------------------------------------------+---------------------------------------------------------+--------+
| ID | G1                                                     | G2                                                      | EQUAL? |
+----+--------------------------------------------------------|---------------------------------------------------------+--------+
| 1  | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | { "coordinates": [ -122.35,  37.55 ], "type": "Point" } | TRUE   |
| 2  | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | { "coordinates": [   90.0,   45.0  ], "type": "Point" } | FALSE  |
+----+--------------------------------------------------------+---------------------------------------------------------+--------+
Copy

인라인 Java UDF에 VARIANT 값 전달

SQL VARIANT 형식의 값을 Java UDF에 전달하면 Snowflake가 Snowpark 패키지 와 함께 제공되는 베리언트 형식으로 값을 변환할 수 있습니다. Variant 는 Snowpark 패키지 버전 1.4.0 이상에서 지원됩니다.

Snowpark Variant 형식은 Variant 와 다른 형식 간에 값을 변환하는 방법을 제공합니다.

Snowpark Variant 형식을 사용하려면 PACKAGES 절을 사용하여 UDF를 만들 때 Snowpark 패키지를 지정하십시오. 이때 Snowpark JAR 파일도 IMPORTS 절의 값으로 포함할 필요는 없습니다. PACKAGES 에 대한 자세한 내용은 CREATE FUNCTION 옵션 매개 변수 를 참조하십시오.

다음 예제의 코드는 VARIANT 형식으로 저장된 JSON 데이터를 받은 다음, Snowpark 라이브러리의 Variant 형식을 사용하여 JSON에서 price 값을 검색합니다. 수신된 JSON은 예에서 사용된 샘플 데이터 에 표시된 JSON과 유사한 구조를 갖습니다.

create or replace function retrieve_price(v variant)
returns integer
language java
packages=('com.snowflake:snowpark:1.4.0')
handler='VariantTest.retrievePrice'
as
$$
import java.util.Map;
import com.snowflake.snowpark_java.types.Variant;

public class VariantTest {
  public static Integer retrievePrice(Variant v) throws Exception {
    Map<String, Variant> saleMap = v.asMap();
    int price = saleMap.get("vehicle").asMap().get("price").asInt();
    return price;
  }
}
$$;
Copy

Java UDF로 파일 읽기

처리기 코드로 파일의 내용을 읽을 수 있습니다. 예를 들어 처리기로 비정형 데이터를 처리하기 위해 파일을 읽을 수 있습니다. 비정형 데이터 처리에 대한 자세한 내용과 예제 코드는 UDF 및 프로시저 처리기로 비정형 데이터 처리하기 섹션을 참조하십시오.

파일은 처리기에 사용할 수 있는 Snowflake 스테이지에 있어야 합니다.

스테이징된 파일의 내용을 읽기 위해 처리기는 다음을 수행할 수 있습니다.

  • 파일 경로가 IMPORTS 절에 정적으로 지정된 파일을 읽습니다. 런타임에 코드는 UDF의 홈 디렉터리에서 파일을 읽습니다.

    이는 초기화 중에 파일에 정적으로 액세스하려 할 때 유용할 수 있습니다.

  • SnowflakeFile 클래스 또는 InputStream 클래스의 메서드를 호출하여 동적으로 지정된 파일을 읽습니다.

    호출자가 지정한 파일에 액세스해야 하는 경우 이렇게 할 수 있습니다. 자세한 내용은 이 항목의 다음 섹션을 참조하십시오.

    SnowflakeFile 은 다음 표에 설명된 것처럼 InputStream 과 함께 사용할 수 없는 기능을 제공합니다.

    클래스

    입력

    참고

    SnowflakeFile

    URL 형식:

    • 함수의 호출자가 소유자이기도 하지 않은 경우 파일 삽입 공격의 위험을 줄이기 위해 범위가 지정된 URL입니다.

    • UDF 소유자가 액세스할 수 있는 파일의 파일 URL 또는 문자열 경로입니다.

    파일은 명명된 내부 스테이지 또는 외부 스테이지에 있어야 합니다.

    파일 크기와 같은 추가 파일 특성에 쉽게 액세스할 수 있습니다.

    InputStream

    URL 형식:

    • 함수의 호출자가 소유자이기도 하지 않은 경우 파일 삽입 공격의 위험을 줄이기 위해 범위가 지정된 URL입니다.

    파일은 내부 또는 외부 스테이지에 있어야 합니다.

전제 조건

다음을 수행하여 코드에 스테이지의 파일을 사용할 수 있도록 해야 Java 처리기 코드가 해당 파일을 읽을 수 있습니다.

  1. 처리기가 사용할 수 있는 스테이지를 만듭니다.

    외부 스테이지 또는 내부 스테이지를 사용할 수 있습니다. 내부 스테이지를 사용하는 경우 사용자 또는 명명된 스테이지여야 합니다. Snowflake는 현재 UDF 종속 항목에 테이블 스테이지를 사용할 수 있도록 지원하지 않습니다. 스테이지 생성에 대한 자세한 내용은 CREATE STAGE 섹션을 참조하십시오. 내부 스테이지 유형 선택에 대한 자세한 내용은 로컬 파일을 위한 내부 스테이지 선택하기 섹션을 참조하십시오.

    스테이지에서 읽는 SQL 작업을 수행하는 역할에 스테이지에 대한 적절한 권한을 할당해야 한다는 점에 유의하십시오. 자세한 내용은 Granting Privileges for User-Defined Functions 섹션을 참조하십시오.

  2. 코드로 읽을 파일을 스테이지에 복사합니다.

    PUT 명령을 사용하여 로컬 드라이브에서 스테이지로 파일을 복사할 수 있습니다. 명령 참조는 PUT 섹션을 참조하십시오. PUT으로 파일을 스테이징하는 자세한 방법은 로컬 파일 시스템에서 데이터 파일 스테이징하기 섹션을 참조하십시오.

IMPORTS에서 정적으로 지정된 파일 읽기

처리기는 CREATE FUNCTION 명령의 IMPORTS 절에 스테이지 경로가 지정된 파일을 읽을 수 있습니다.

IMPORTS 절에 파일을 지정하면 Snowflake는 해당 파일을 스테이지에서 UDF의 홈 디렉터리 (가져오기 디렉터리 라고도 함)로 복사합니다. 이 디렉터리는 UDF가 실제로 파일을 읽는 디렉터리입니다.

가져온 파일은 단일 디렉터리에 복사되고, 해당 디렉터리 내에서 고유한 이름을 가져야 하기 때문에 IMPORTS 절의 각 파일은 고유한 이름을 가져야 합니다. 해당 파일이 다른 스테이지에서 시작하거나 스테이지 내의 다른 하위 디렉터리에서 시작하더라도 마찬가지입니다.

다음 예에서는 파일을 읽는 Java UDF를 만들고 호출합니다.

아래 Java 소스 코드는 readFile 이라는 Java 메서드를 만듭니다. 이 UDF는 이 메서드를 사용합니다.

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

class TestReadRelativeFile {
    public static String readFile(String fileName) throws IOException {
        StringBuilder contentBuilder = new StringBuilder();
        String importDirectory = System.getProperty("com.snowflake.import_directory");
        String fPath = importDirectory + fileName;
        Stream<String> stream = Files.lines(Paths.get(fPath), StandardCharsets.UTF_8);
        stream.forEach(s -> contentBuilder.append(s).append("\n"));
        return contentBuilder.toString();
    }
}
Copy

다음 SQL 코드는 UDF를 만듭니다. 이 코드는 Java 소스 코드가 컴파일되어, UDF가 가져오는 TestReadRelativeFile.jar 이라는 JAR 파일에 저장되었다고 가정합니다. 두 번째 및 세 번째 가져온 파일인 my_config_file_1.txtmy_config_file_2.txt 는 UDF가 읽을 수 있는 구성 파일입니다.

CREATE FUNCTION file_reader(file_name VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVA
IMPORTS = ('@my_stage/my_package/TestReadRelativeFile.jar',
           '@my_stage/my_path/my_config_file_1.txt',
           '@my_stage/my_path/my_config_file_2.txt')
HANDLER = 'my_package.TestReadRelativeFile.readFile';
Copy

이 코드는 UDF를 호출합니다.

SELECT file_reader('my_config_file_1.txt') ...;
...
SELECT file_reader('my_config_file_2.txt') ...;
Copy

압축 또는 비압축 형식의 파일 액세스 여부 선택

스테이지의 파일은 압축 또는 비압축 형식으로 저장할 수 있습니다. 사용자는 파일을 스테이지에 복사하기 전에 압축하거나, PUT 명령에 파일 압축을 지시할 수 있습니다.

Snowflake가 GZIP 형식으로 압축된 파일을 스테이지에서 UDF 홈 디렉터리로 복사할 때 Snowflake는 복사본을 있는 그대로 쓰거나, 파일을 쓰기 전에 내용의 압축을 풀 수 있습니다.

스테이지의 파일이 압축되어 있고 UDF 홈 디렉터리의 복사본도 압축하려는 경우, IMPORTS 절에 파일 이름을 지정할 때 원래 파일 이름(예: 《MyData.txt.gz》)을 IMPORTS 절에 사용하기만 하면 됩니다. 예:

... imports = ('@MyStage/MyData.txt.gz', ...)
Copy

스테이지에 있는 파일이 GZIP으로 압축되어 있지만, UDF 홈 디렉터리에 있는 복사본의 압축을 풀려는 경우, IMPORTS 절에 파일 이름을 지정할 때 《.gz》 확장자를 생략하십시오. 예를 들어, 스테이지에 《MyData.txt.gz》가 포함되어 있지만, UDF가 압축되지 않은 형식으로 파일을 읽도록 하려면 IMPORTS 절에 《MyData.txt》를 지정하십시오. 《MyData.txt》라는 압축되지 않은 파일이 없는 경우, Snowflake는 《MyData.txt.gz》를 검색하고, 압축을 푼 복사본을 UDF 홈 디렉터리의 《MyData.txt》에 자동으로 씁니다. 그러면 UDF는 압축되지 않은 《MyData.txt》 파일을 열고 읽을 수 있습니다.

스마트 압축 풀기는 UDF 홈 디렉터리의 복사본에만 적용됩니다. 스테이지의 원본 파일은 변경되지 않습니다.

압축 파일 처리에 대한 다음 모범 사례를 따르십시오.

  • 적절한 파일 명명 규칙을 따르십시오. 파일이 GZIP 압축 형식인 경우, 파일 이름 끝에 《.gz》 확장자를 포함하십시오. 파일이 GZIP 압축 형식이 아닌 경우, 파일 이름을 《.gz》 확장자로 끝내지 마십시오.

  • 이름 차이가 《.gz》 확장자일 뿐인 파일을 생성하지 마십시오. 예를 들어, 동일한 스테이지와 디렉터리에서 《MyData.txt》와 《MyData.txt.gz》를 만들지 말고, 동일한 CREATEFUNCTION 명령에서 《MyData.txt》와 《MyData.txt.gz》를 둘 다 가져오려고 하지 마십시오.

  • 파일을 두 번 압축하지 마십시오. 예를 들어, 파일을 수동으로 압축한 다음, AUTO_COMPRESS=FALSE를 사용하지 않고 해당 파일을 PUT 하면 파일이 두 번째로 압축됩니다. 스마트 압축 풀기는 한 번만 압축을 풀기 때문에 데이터(또는 JAR) 파일은 UDF 홈 디렉터리에 저장될 때 여전히 압축됩니다.

  • 앞으로 Snowflake는 스마트 압축 풀기를 GZIP 이외의 압축 알고리즘으로 확장할 수 있습니다. 향후 호환성 문제를 방지하려면 이러한 모범 사례를 모든 형식의 압축 사용 파일에 적용하십시오.

참고

JAR 파일은 스테이지에서 압축 또는 비압축 형식으로 저장할 수도 있습니다. Snowflake는 압축된 모든 JAR 파일을 Java UDF에서 사용할 수 있도록 자동으로 압축을 풉니다.

SnowflakeFile 을 사용하여 동적으로 지정된 파일 읽기

SnowflakeFile 클래스의 메서드를 사용하여 Java 처리기 코드로 스테이지에서 파일을 읽을 수 있습니다. SnowflakeFile 클래스는 Snowflake의 Java UDF 처리기가 사용할 수 있는 클래스 경로에 포함됩니다.

참고

파일 삽입 공격에 대한 코드의 탄력성을 높이려면 파일의 위치를 UDF에 전달할 때, 특히 함수 호출자가 소유자이기도 하지 않을 때는 항상 범위가 지정된 URL을 사용하십시오. 기본 제공 함수 BUILD_SCOPED_FILE_URL 을 사용하여 SQL에서 범위가 지정된 URL을 생성할 수 있습니다. BUILD_SCOPED_FILE_URL 의 기능에 대한 자세한 내용은 반정형 데이터 로딩 소개 섹션을 참조하십시오.

UDF 코드를 로컬에서 개발하려면 SnowflakeFile 이 포함된 Snowpark JAR을 코드의 클래스 경로에 추가하십시오. snowpark.jar 에 대한 정보는 Snowpark Java를 위한 개발 환경 설정하기 섹션을 참조하십시오. Snowpark 클라이언트 애플리케이션은 이 클래스를 사용할 수 없습니다.

SnowflakeFile 을 사용할 때는 CREATE FUNCTION 문을 포함한 SQL에서처럼 UDF를 생성할 때 스테이징된 파일이나 SnowflakeFile 이 포함된 JAR을 IMPORTS 절로도 지정할 필요가 없습니다.

다음 예제의 코드에서는 SnowflakeFile 을 사용하여 지정된 스테이지 위치에서 파일을 읽습니다. getInputStream 메서드의 InputStream 을 사용하여 파일의 내용을 String 변수로 읽습니다.

CREATE OR REPLACE FUNCTION sum_total_sales(file string)
RETURNS INTEGER
LANGUAGE JAVA
HANDLER = 'SalesSum.sumTotalSales'
TARGET_PATH = '@jar_stage/sales_functions2.jar'
AS
$$
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.snowflake.snowpark_java.types.SnowflakeFile;

public class SalesSum {

  public static int sumTotalSales(String filePath) throws IOException {
    int total = -1;

    // Use a SnowflakeFile instance to read sales data from a stage.
    SnowflakeFile file = SnowflakeFile.newInstance(filePath);
    InputStream stream = file.getInputStream();
    String contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);

    // Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.

    return total;
  }
}
$$;
Copy

파일 삽입 공격 가능성을 줄이기 위해 범위가 지정된 URL에서 파일 위치를 전달하는 UDF를 호출합니다.

SELECT sum_total_sales(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

참고

UDF 소유자는 위치가 범위 지정된 URL이 아닌 모든 파일에 액세스할 수 있어야 합니다. 처리기 코드가 새 requireScopedUrl 매개 변수에 대한 boolean 값으로 SnowflakeFile.newInstance 메서드를 호출하도록 하여 이들 스테이징된 파일을 읽을 수 있습니다.

다음 예에서는 범위가 지정된 URL이 필요하지 않음을 지정하면서 SnowflakeFile.newInstance 를 사용합니다.

String filename = "@my_stage/filename.txt";
String sfFile = SnowflakeFile.newInstance(filename, false);
Copy

InputStream 을 사용하여 동적으로 지정된 파일 읽기

처리기 함수의 인자를 InputStream 변수로 만들어 파일 내용을 java.io.InputStream 으로 직접 읽을 수 있습니다. 이것은 함수의 호출자가 파일 경로를 인자로 전달하고 싶을 때 유용할 수 있습니다.

참고

파일 삽입 공격에 대한 코드의 탄력성을 높이려면 파일의 위치를 UDF에 전달할 때, 특히 함수 호출자가 소유자이기도 하지 않을 때는 항상 범위가 지정된 URL을 사용하십시오. 기본 제공 함수 BUILD_SCOPED_FILE_URL 을 사용하여 SQL에서 범위가 지정된 URL을 생성할 수 있습니다. BUILD_SCOPED_FILE_URL 의 기능에 대한 자세한 내용은 반정형 데이터 로딩 소개 섹션을 참조하십시오.

다음 예제의 코드에는 InputStream 을 받아 int 를 반환하는 처리기 함수 sumTotalSales 가 있습니다. 런타임에 Snowflake는 file 변수의 경로에 있는 파일의 내용을 stream 인자 변수에 자동으로 할당합니다.

CREATE OR REPLACE FUNCTION sum_total_sales(file string)
RETURNS INTEGER
LANGUAGE JAVA
HANDLER = 'SalesSum.sumTotalSales'
TARGET_PATH = '@jar_stage/sales_functions2.jar'
AS
$$
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class SalesSum {

  public static int sumTotalSales(InputStream stream) throws IOException {
    int total = -1;
    String contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);

    // Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.

    return total;
  }
}
$$;
Copy

파일 삽입 공격 가능성을 줄이기 위해 범위가 지정된 URL에서 파일 위치를 전달하는 UDF를 호출합니다.

SELECT sum_total_sales(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

간단한 스테이징된 Java UDF 생성 및 호출하기

다음 문은 간단한 Java UDF를 만듭니다. 일반적으로 이 샘플은 파일 정리 에 설명된 파일 및 디렉터리 구조를 따릅니다.

Java 처리기 코드 생성 및 컴파일하기

  1. 프로젝트의 루트 디렉터리(이 경우 my_udf)에서 소스 .java 파일을 보관할 src 하위 디렉터리, 그리고 생성된 .class 파일을 보관할 classes 하위 디렉터리를 생성합니다.

    다음과 유사한 디렉터리 계층 구조가 있어야 합니다.

    my_udf/
    |-- classes/
    |-- src/
    
    Copy
  2. src 디렉터리에서, 클래스가 mypackage 패키지에 있는 .java 파일을 보유할 mypackage 라는 디렉터리를 생성합니다.

  3. mypackage 디렉터리에서, 소스 코드가 포함된 MyUDFHandler.java 파일을 생성합니다.

    package mypackage;
    
    public class MyUDFHandler {
    
      public static int decrementValue(int i)
      {
          return i - 1;
      }
    
      public static void main(String[] argv)
      {
          System.out.println("This main() function won't be called.");
      }
    }
    
    Copy
  4. 프로젝트 루트 디렉터리(이 경우 my_udf)에서 javac 명령을 사용하여 소스 코드를 컴파일합니다.

    다음 예의 javac 명령은 MyUDFHandler.java 를 컴파일하여 classes 디렉터리에 MyUDFHandler.class 파일을 생성합니다.

    javac -d classes src/mypackage/MyUDFHandler.java
    
    Copy

    이 예에는 다음 인수가 포함됩니다.

    • -d classes – 생성된 클래스 파일이 기록되어야 하는 디렉터리.

    • src/mypackage/MyUDFHandler.java – 형식의 .java 파일 경로: source_directory/package_directory/Java_file_name.

컴파일된 코드를 JAR 파일로 패키징하기

  1. 선택적으로, 프로젝트 루트 디렉터리에서, 다음 속성을 포함하는 my_udf.manifest 라는 매니페스트 파일을 생성합니다.

    Manifest-Version: 1.0
    Main-Class: mypackage.MyUDFHandler
    
    Copy
  2. 프로젝트 루트 디렉터리에서 jar 명령을 실행하여, .class 파일과 매니페스트가 포함된 JAR 파일을 생성합니다.

    다음 예의 jar 명령은 mypackage 패키지 폴더에 생성된 MyUDFHandler.class 파일을 my_udf.jar 이라는 .jar 파일에 넣습니다. -C ./classes 플래그는 .class 파일의 위치를 지정합니다.

    jar cmf my_udf.manifest my_udf.jar -C ./classes mypackage/MyUDFHandler.class
    
    Copy

    이 예에는 다음 인수가 포함됩니다.

    • cmf – 명령 인수: c 는 JAR 파일을 생성하고, m 은 지정된 .manifest 파일을 사용하고, f 는 지정된 이름을 JAR 파일에 부여합니다.

    • my_udf.manifest – 매니페스트 파일.

    • my_udf.jar – 생성할 JAR 파일의 이름.

    • -C ./classes – 생성된 .class 파일을 포함하는 디렉터리.

    • mypackage/MyUDFHandler.class – JAR에 포함할 .class 파일의 패키지 및 이름.

컴파일된 처리기가 있는 JAR 파일을 스테이지에 업로드하기

  1. Snowflake에서 jar_stage 라는 스테이지를 생성하여, UDF 핸들러가 포함된 JAR 파일을 저장합니다.

    스테이지 생성에 대한 자세한 내용은 CREATE STAGE 섹션을 참조하십시오.

  2. PUT 명령을 사용하여 JAR 파일을 로컬 파일 시스템에서 스테이지로 복사하십시오.

put
    file:///Users/Me/my_udf/my_udf.jar
    @jar_stage
    auto_compress = false
    overwrite = true
    ;
Copy

PUT 명령을 스크립트 파일에 저장한 다음, SnowSQL 을 통해 해당 파일을 실행할 수 있습니다.

snowsql 명령은 다음과 유사합니다.

snowsql -a <account_identifier> -w <warehouse> -d <database> -s <schema> -u <user> -f put_command.sql
Copy

이 예에서는 사용자의 비밀번호가 SNOWSQL_PWD 환경 변수에 지정되어 있다고 가정합니다.

컴파일된 코드를 처리기로 사용하여 UDF 만들기

UDF를 만듭니다.

create function decrement_value(i numeric(9, 0))
  returns numeric
  language java
  imports = ('@jar_stage/my_udf.jar')
  handler = 'mypackage.MyUDFHandler.decrementValue'
  ;
Copy

UDF를 호출합니다.

select decrement_value(-15);
+----------------------+
| DECREMENT_VALUE(-15) |
|----------------------|
|                  -16 |
+----------------------+
Copy