테이블 함수

테이블 함수 (Table functions)

테이블 함수는 테이블을 반환해요. SQL 쿼리 안에서 커스텀 로직을 동적으로 호출할 수 있게 해줘요. 쿼리의 FROM 절에서 호출하며, 호출 방식은 스칼라 함수 호출과 비슷해요.

출처: 문서

본문

Trino는 커스텀 테이블 함수 추가를 지원해요. 커넥터가 전용 인터페이스를 구현해 선언해요.

테이블 함수 선언 (Table function declaration)

테이블 함수를 선언하려면 ConnectorTableFunction을 구현해야 해요. AbstractConnectorTableFunction을 서브클래싱하는 게 편리한 방법이에요. 커넥터의 getTableFunctions() 메서드는 구현체들의 집합을 반환해야 해요.

생성자 (The constructor)

public class MyFunction
        extends AbstractConnectorTableFunction
{
    public MyFunction()
    {
        super(
                "system",
                "my_function",
                List.of(
                        ScalarArgumentSpecification.builder()
                                .name("COLUMN_COUNT")
                                .type(INTEGER)
                                .defaultValue(2)
                                .build(),
                        ScalarArgumentSpecification.builder()
                                .name("ROW_COUNT")
                                .type(INTEGER)
                                .build()),
                GENERIC_TABLE);
    }
}

생성자는 다음 인자를 받아요.

  • 스키마 이름 (schema name)

    스키마 이름은 함수를 구성하는 데 도움이 되고 함수 해석에 사용돼요. 테이블 함수가 호출되면 카탈로그 이름, 스키마 이름, 함수 이름으로 올바른 구현이 식별돼요.

    함수는 스키마 이름을 사용할 수도 있고(예: 지시된 스키마의 데이터를 사용), 무시할 수도 있어요.

  • 함수 이름 (function name)

  • 예상 인자 목록 (list of expected arguments)

    세 가지 유형의 인자가 지원돼요: 스칼라 인자, 디스크립터 인자, 테이블 인자. 자세한 내용은 인자 유형을 참조하세요. 스칼라 및 디스크립터 인자에는 기본값을 지정할 수 있어요. 기본값이 지정된 인자는 테이블 함수 호출 시 생략할 수 있어요.

  • 반환 행 타입 (returned row type)

    테이블 함수가 만드는 행 타입을 설명해요.

    테이블 함수가 테이블 인자를 받으면, 패스스루 메커니즘(pass-through mechanism)으로 입력 테이블의 컬럼을 출력으로 추가로 전달할 수 있어요. 반환 행 타입은 패스스루 컬럼과 달리 함수가 만드는 컬럼만 설명해야 해요.

    예제에서 반환 행 타입은 GENERIC_TABLE이며, 이는 행 타입이 정적으로 알려지지 않고 전달된 인자에 따라 동적으로 결정된다는 뜻이에요.

    반환 행 타입이 정적으로 알려진 경우에는 다음으로 선언할 수 있어요.

new DescribedTable(descriptor)

테이블 함수가 컬럼을 만들지 않고 패스스루 컬럼만 출력한다면, 반환 행 타입으로 ONLY_PASS_THROUGH를 사용해요.

참고

테이블 함수는 최소한 하나의 컬럼을 반환해야 해요. 함수가 만드는 실제 컬럼이거나 패스스루 컬럼이어야 해요.

인자 유형 (Argument types)

테이블 함수는 세 가지 유형의 인자를 받아요: 스칼라 인자, 디스크립터 인자, 테이블 인자.

스칼라 인자 (Scalar arguments)

지원되는 어떤 데이터 타입이든 될 수 있어요. 기본값을 지정할 수 있어요.

ScalarArgumentSpecification.builder()
        .name("COLUMN_COUNT")
        .type(INTEGER)
        .defaultValue(2)
        .build()
ScalarArgumentSpecification.builder()
        .name("ROW_COUNT")
        .type(INTEGER)
        .build()
디스크립터 인자 (Descriptor arguments)

디스크립터는 이름과 선택적 데이터 타입을 가진 필드로 구성돼요. 이는 필요한 결과 행 타입을 함수에 전달하거나, 예를 들어 함수가 어떤 입력 컬럼을 사용해야 하는지 알려주는 편리한 방법이에요. 디스크립터 인자에는 기본값을 지정할 수 있어요. 디스크립터 인자는 null일 수 있어요.

DescriptorArgumentSpecification.builder()
        .name("SCHEMA")
        .defaultValue(null)
        .build()
테이블 인자 (Table arguments)

테이블 함수는 원하는 만큼 많은 입력 관계를 받을 수 있어요. 여러 데이터 소스를 동시에 처리할 수 있게 해줘요.

테이블 인자를 선언할 때 입력 테이블이 어떻게 처리되는지 결정하는 특성을 지정해야 해요. 또한 테이블 인자에는 기본값을 지정할 수 없다는 점에 유의하세요.

TableArgumentSpecification.builder()
        .name("INPUT")
        .rowSemantics()
        .pruneWhenEmpty()
        .passThroughColumns()
        .build()

집합 또는 행 의미론 (Set or row semantics)

집합 의미론(set semantics)은 테이블 인자의 기본값이에요. 집합 의미론의 테이블 인자는 파티션별로 처리돼요. 함수 호출 중 사용자는 인자에 대한 파티셔닝과 정렬을 지정할 수 있어요. 파티셔닝을 지정하지 않으면 인자는 단일 파티션으로 처리돼요.

행 의미론(row semantics)의 테이블 인자는 행별로 처리돼요. 파티셔닝이나 정렬은 적용되지 않아요.

비었을 때 가지치기 또는 유지 (Prune or keep when empty)

비었을 때 가지치기(prune when empty) 속성은 주어진 테이블 인자가 비어 있으면 함수가 빈 결과를 반환한다는 뜻이에요. 이 속성은 테이블 함수를 포함하는 쿼리를 최적화하는 데 사용돼요. 비었을 때 유지(keep when empty) 속성은 테이블 인자가 비어 있어도 함수를 실행해야 한다는 뜻이에요. 사용자는 함수를 호출할 때 이 속성을 오버라이드할 수 있어요. 비었을 때 유지 속성을 쓰면 테이블 인자가 비어 있지 않을 때 성능에 악영향을 줄 수 있어요.

패스스루 컬럼 (Pass-through columns)

테이블 인자에 패스스루 컬럼이 있으면 모든 컬럼이 출력으로 전달돼요. 이 속성이 없는 테이블 인자는 파티셔닝 컬럼만 출력으로 전달돼요.

analyze() 메서드 (The analyze() method)

Trino 엔진에 필요한 모든 정보를 제공하려면 클래스가 analyze() 메서드를 구현해야 해요. 이 메서드는 쿼리 처리의 분석 단계에서 엔진이 호출해요. analyze() 메서드는 인자에 대한 커스텀 검사를 수행하는 자리이기도 해요.

@Override
public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransactionHandle transaction, Map<String, Argument> arguments)
{
    long columnCount = (long) ((ScalarArgument) arguments.get("COLUMN_COUNT")).getValue();
    long rowCount = (long) ((ScalarArgument) arguments.get("ROW_COUNT")).getValue();

    // custom validation of arguments
    if (columnCount < 1 || columnCount > 3) {
         throw new TrinoException(INVALID_FUNCTION_ARGUMENT, "column_count must be in range [1, 3]");
    }

    if (rowCount < 1) {
        throw new TrinoException(INVALID_FUNCTION_ARGUMENT, "row_count must be positive");
    }

    // determine the returned row type
    List<Descriptor.Field> fields = List.of("col_a", "col_b", "col_c").subList(0, (int) columnCount).stream()
            .map(name -> new Descriptor.Field(name, Optional.of(BIGINT)))
            .collect(toList());

    Descriptor returnedType = new Descriptor(fields);

    return TableFunctionAnalysis.builder()
            .returnedType(returnedType)
            .handle(new MyHandle(columnCount, rowCount))
            .build();
}

analyze() 메서드는 엔진이 테이블 함수 호출을 분석·계획·실행하는 데 필요한 모든 정보를 담은 TableFunctionAnalysis 객체를 반환해요.

  • 선택적 Descriptor로 지정된 반환 행 타입. 테이블 함수가 GENERIC_TABLE 반환 타입으로 선언된 경우에만 전달해야 해요.
  • 테이블 인자의 필요한 컬럼. 테이블 인자 이름을 컬럼 인덱스 목록에 매핑한 맵으로 지정돼요.
  • 분석 중 수집된, 계획·실행 시 유용한 모든 정보. ConnectorTableFunctionHandle 형태로 전달돼요. ConnectorTableFunctionHandle은 이후 쿼리 처리 단계에 걸쳐 엔진에 불투명한 방식으로 정보를 전달하는 마커 인터페이스예요.

테이블 함수 실행 (Table function execution)

테이블 함수에는 두 가지 실행 경로가 있어요.

  • 커넥터로 푸시다운 (Pushdown to the connector)

    테이블 함수를 제공하는 커넥터가 applyTableFunction() 메서드를 구현해요. 이 메서드는 쿼리 처리의 최적화 단계에서 호출돼요. 테이블 함수 결과를 나타내는 ConnectorTableHandleColumnHandle 목록을 반환해요. 그러면 테이블 함수 호출이 TableScanNode로 대체돼요.

    이 실행 경로는 결과를 ConnectorTableHandle로 표현하기 쉬운 테이블 함수(예: 쿼리 패스스루)에 편리해요. 스칼라와 디스크립터 인자만 지원해요.

  • 연산자로 실행 (Execution by operator)

    Trino에는 테이블 함수 전용 연산자가 있어요. 원하는 만큼 많은 테이블 인자와 스칼라·디스크립터 인자를 가진 테이블 함수를 처리할 수 있어요. 이 실행 경로를 쓰려면 프로세서 구현을 제공해요.

    테이블 함수에 테이블 인자가 하나 이상 있으면 TableFunctionDataProcessor를 구현해야 해요. 이 프로세서는 입력 데이터의 페이지를 처리해요.

    테이블 함수가 소스 연산자(테이블 인자가 없는 경우)라면 TableFunctionSplitProcessor를 구현해야 해요. 이 프로세서는 스플릿을 처리해요. 함수를 제공하는 커넥터는 함수에 대한 ConnectorSplitSource를 제공해야 해요. 스플릿을 사용하면 각 스플릿이 하위 작업을 나타내도록 작업을 나눌 수 있어요.

접근 제어 (Access control)

테이블 함수의 접근 제어는 시스템과 커넥터 수준 모두에서 제공할 수 있어요. 이는 catalog.schema.function 문법의 카탈로그 이름, 스키마 이름, 함수 이름으로 구성된 정규화된 테이블 함수 이름을 기반으로 해요.

더 알아보기 (Learn more)

테이블 함수를 SQL에서 어떻게 호출하는지 쓰임새가 궁금하다면 테이블 함수 사용 문서를 참고해 보세요.