SQL 파서를 구현하는 방법

작성자: 생체 인터넷 검색 팀 - Deng Jie

1. 배경

기술의 지속적인 발전으로 빅 데이터 분야에서 점점 더 많은 기술적 프레임워크가 등장했습니다. 빅 데이터의 학습 비용과 어려움을 줄이기 위해 점점 더 많은 빅 데이터 기술과 응용 프로그램에서 데이터 쿼리를 위한 SQL을 지원하기 시작합니다. 학습 비용이 낮은 언어로서 데이터 쿼리에 대한 SQL 지원은 사용자가 빅 데이터를 사용하는 임계값을 낮추어 더 많은 사용자가 빅 데이터를 사용할 수 있도록 합니다.

이 글에서는 주로 SQL parser를 구현하여 업무에 적용하는 방법을 소개함과 동시에 SQL parser의 실제적인 과정을 구체적인 사례로 소개한다.

2. SQL 구문 분석기가 필요한 이유는 무엇입니까?

프로젝트 시스템 아키텍처를 설계할 때 일반적으로 몇 가지 기술 연구를 수행합니다. SQL 구문 분석기가 필요한 이유를 고려할 것입니다. 선택한 SQL 구문 분석기가 현재 기술 요구 사항을 충족할 수 있는지 판단하는 방법은 무엇입니까?

2.1 전통적인 SQL 쿼리

기존 SQL 쿼리는 전체 데이터베이스 프로토콜에 의존합니다. 예를 들어, 데이터는 MySQL, Oracle과 같은 관계형 데이터베이스에 저장되며 표준 SQL 구문이 있습니다. 다음 그림과 같이 다양한 SQL 문을 통해 비즈니스 요구 사항을 달성할 수 있습니다.

그림

그러나 대용량 데이터를 다룰 때 관계형 데이터베이스는 실제 비즈니스 요구 사항을 충족하기 어렵기 때문에 실제 비즈니스 요구 사항을 해결하려면 빅 데이터 생태계의 기술적 구성 요소를 사용해야 합니다.

2.2 실제 적용 시나리오

빅 데이터 생태계의 기술 구성 요소를 사용할 때 Hive, Spark, Flink 등과 같은 일부 기술 구성 요소에는 자체 SQL이 있고 Kafka, HBase와 같은 일부 기술 구성 요소 자체에는 SQL이 포함되지 않습니다. 아래에서 다음 그림과 같이 SQL이 없는 시나리오와 SQL 구문 분석기를 사용하여 시나리오를 비교할 수 있습니다.

그림

위 그림에서 왼쪽 그림에서 SQL 없이 기술 구성 요소를 사용할 때 쿼리를 구현할 때 Kafka 및 HBase와 같은 기술 구성 요소와 데이터를 상호 작용하기 위해 다른 비즈니스 논리 인터페이스를 작성해야 함을 알 수 있습니다. 이러한 구성 요소의 증가에 따라 쿼리 기능의 복잡성이 증가하면 각 인터페이스 집합의 복잡성도 증가하여 후속 확장 및 유지 관리에 매우 불편합니다. 그림의 오른쪽에서 SQL 파서를 도입한 후 비즈니스 로직을 완성하고 다양한 기술 구성 요소에 적응하기 위해 인터페이스 세트만 필요합니다.

3. SQL 파서란 무엇입니까?

실제 비즈니스 시나리오에 적용할 SQL 파서를 선택하기 전에 먼저 SQL 파서의 핵심 지식 포인트를 이해합시다.

3.1 SQL 파서는 무엇을 포함합니까?

SQL 구문 분석기를 사용할 때 SQL 구문 분석 단계는 다음과 같은 Java/Python 프로그램 구문 분석 단계와 매우 유사합니다.

  • C/C++에서는 어휘 분석 및 구문 분석을 위해 LEX 및 YACC를 사용할 수 있습니다.
  • Java에서는 JavaCC 또는 ANTLR을 사용할 수 있습니다.

파서를 사용하는 과정에서 파서는 일반적으로 어휘 파싱, 구문 파싱 및 의미 파싱의 세 부분으로 구성됩니다.

3.1.1 어휘 분석이란 무엇입니까?

어휘 분석을 이해하는 방법? 우리는 이런 식으로 어휘 구문 분석을 이해할 수 있습니다. 어휘 구문 분석 작업을 시작할 때 왼쪽에서 오른쪽으로 문자를 하나씩 읽고 구문 분석 프로그램에 로드한 다음 바이트 스트림을 스캔한 다음 단어 형성 규칙에 따라 식별합니다. .문자는 하나의 항목으로 절단됩니다.단어 분할의 규칙은 공백이 발생하면 분할하고 세미콜론을 만나면 어휘 분석을 종료하는 것입니다. 예를 들어 간단한 SQL은 다음과 같습니다.

SQL 예제

SELECT name FROM tab;

어휘 분석 후 결과는 다음과 같습니다.

3.1.2 파싱이란?

문법 파싱을 이해하는 방법? 문법 분석은 이런 식으로 이해할 수 있습니다. 문법 분석 작업을 시작할 때 문법 분석 작업은 어휘 분석 결과에 따라 입력 순서를 다른 문법 구문으로 결합하고 구성된 문법 문장을 해당 문법 규칙과 비교합니다. 적응이 성공하면 해당 추상 구문 트리가 생성되고, 그렇지 않으면 구문 오류 예외가 발생합니다. 예를 들어, 다음 SQL 문:

SQL 예제

SELECT name FROM tab WHERE id=1001;

합의된 규칙은 다음과 같습니다.

위의 표에서 빨간색 내용은 일반적으로 대문자 키워드 또는 기호 등인 터미널 기호를 나타내고 소문자 내용은 필드 및 테이블 이름과 같은 명명 규칙에 일반적으로 사용되는 비단말 기호입니다. 특정 AST 데이터 구조는 다음 그림에 나와 있습니다.

그림

3.1.3 시맨틱 파싱이란?

시맨틱 파싱을 이해하는 방법? 시맨틱 파싱은 이렇게 이해될 수 있는데, 시맨틱 파싱의 임무는 필드, 필드 유형, 함수, 테이블 등을 파싱하여 얻은 추상 구문 트리를 효과적으로 검증하는 것입니다. 예를 들어 다음 문장:

SQL 예제

SELECT name FROM tab WHERE id=1001;

위의 SQL 문에 대해 의미 분석 작업은 다음 검사를 수행합니다.

  • 테이블 이름이 SQL 문에 존재하는지 여부.
  • 필드 이름이 테이블 탭에 있는지 여부.
  • WHERE 조건의 id 필드 유형을 1001과 비교할 수 있는지 여부.

위의 검사가 완료된 후 의미론적 구문 분석은 옵티마이저가 사용할 해당 표현식을 생성합니다.

4. SQL 파서를 선택하는 방법은 무엇입니까?

파서의 핵심 지식을 이해한 후 실제 비즈니스에 적용할 적합한 SQL 파서를 선택하는 방법은 무엇입니까? 다음으로 두 가지 주류 SQL 파서를 비교하겠습니다. 그들은 각각 ANTLR과 방해석입니다.

4.1 ANTLR

ANTLR은 구조화된 텍스트 또는 바이너리 파일을 읽고, 처리하고, 실행하고, 변환하는 데 사용할 수 있는 강력한 파서 생성기입니다. 빅데이터의 일부 SQL 프레임워크에서 널리 사용되고 있는데, 예를 들어 Hive의 렉시컬 파일은 ANTLR3로 작성되었고, Presto의 렉시컬 파일도 ANTLR4로 구현되었으며, SparkSQLambda의 렉시컬 파일도 의 렉시컬 파일로 다시 작성되었습니다. Presto. 또한 HBase의 SQL이 있으며 Phoenix 도구도 SQL 구문 분석을 위해 ANTLR 도구를 사용합니다.

ANTLR을 사용하여 SQL을 구현하면 실행 또는 구현 프로세스가 대략 이렇습니다. 어휘 파일 구현(. 추상 구문 트리 탐색, 의미 트리 생성, 통계 정보 액세스, 옵티마이저가 논리적 실행 계획을 생성한 다음 생성 실행할 물리적 실행 계획.

그림

공식 웹사이트 예시:

ANTLR 표현식

assign : ID '=' expr ';' ;

파서의 코드는 다음과 같습니다.

ANTLR 파서 코드

void assign() {
  match(ID);
  match('=');
  expr();
  match(';');
}

4.1.1 파서

Parser는 언어를 인식하는 데 사용되는 프로그램으로, 어휘 분석기와 구문 분석기의 두 부분으로 구성됩니다. 어휘 분석 단계에서 해결되는 주요 문제는 키워드와 INT(유형 키워드) 및 ID(변수 식별자)와 같은 다양한 식별자입니다. 구문 분석은 주로 어휘 분석 결과를 기반으로 구문 분석 번호를 구성하며 그 과정은 대략 다음과 같습니다.

그림

따라서 어휘 분석 및 구문 분석이 제대로 작동하려면 ANTLR4를 사용할 때 문법(Grammar)을 정의해야 합니다.

문자 스트림(CharStream)을 구문 분석 트리로 변환할 수 있으며 문자 스트림은 어휘 분석 후 토큰 스트림이 됩니다. 토큰 스트림은 최종적으로 리프 노드(TerminalNode)와 리프가 아닌 노드(RuleNode)를 포함하는 구문 분석 트리로 조합됩니다. 특정 구문 분석 트리는 다음 그림에 나와 있습니다.

그림

4.1.2 문법

ANTLR은 공식적으로 수정하고 직접 재사용할 수 있는 많은 일반적으로 사용되는 언어에 대한 문법 파일을 제공합니다: https://github.com/antlr/grammars-v4

구문을 사용할 때 다음 사항에 주의하십시오.

  • 구문 이름과 파일 이름은 일치해야 합니다.
  • 파서 규칙은 소문자로 시작합니다.
  • Lexer 규칙은 대문자로 시작합니다.
  • 문자열을 인용하려면 '문자열' 작은따옴표를 사용하십시오.
  • 시작 기호를 지정할 필요가 없습니다.
  • 규칙은 세미콜론으로 끝납니다.
  • ...

4.1.3 ANTLR4는 간단한 계산 기능을 구현합니다.

다음은 ANTLR4의 사용을 설명하기 위한 간단한 예이며 달성해야 하는 기능적 효과는 다음과 같습니다.

ANTLR 예제

1+2 => 1+2=3
1+2*4 => 1+2*4=9
1+2*4-5 => 1+2*4-5=4
1+2*4-5+20/5 => 1+2*4-5+20/5=8
(1+2)*4 => (1+2)*4=12

ANTLR을 통한 처리 흐름은 다음 그림에 나와 있습니다.

그림

전체적으로 재귀 하강을 원칙으로 합니다. 즉, 직접 호출하거나 루프에서 다른 표현식을 호출할 수 있는 표현식(예: expr)을 정의하지만 결국에는 더 이상 호출할 수 없는 핵심 표현식이 분명히 있을 것입니다.

1단계: 어휘 규칙 파일 정의(CommonLexerRules.g4)

CommonLexerRules.g4

// 定义词法规则
lexer grammar CommonLexerRules;
 
//////// 定义词法
// 匹配ID
ID     : [a-zA-Z]+ ;
// 匹配INT
INT    : [0-9]+    ;
// 匹配换行符
NEWLINE: '\n'('\r'?);
// 跳过空格、跳格、换行符
WS     : [ \t\n\r]+ -> skip;
 
//////// 运算符
DIV:'/';
MUL:'*';
ADD:'+';
SUB:'-';
EQU:'=';

2단계: 문법 규칙 파일(LibExpr.g4) 정의

LibExpr.g4

// 定于语法规则
grammar LibExpr;
 
// 导入词法规则
import CommonLexerRules;
 
// 词法根
prog:stat+ EOF?;
 
// 定义声明
stat:expr (NEWLINE)?         # printExpr
    | ID '=' expr (NEWLINE)? # assign
    | NEWLINE                # blank
    ;
 
// 定义表达式
expr:expr op=('*'|'/') expr # MulDiv
    |expr op=('+'|'-') expr # AddSub
    |'(' expr ')'           # Parens
    |ID                     # Id
    |INT                    # Int
    ;

3단계: 생성된 파일 컴파일

Maven 프로젝트인 경우 pom 파일에 다음 종속성을 추가합니다.

ANTLR은 JAR에 의존

<dependencies>
    <dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4</artifactId>
        <version>4.9.3</version>
    </dependency>
    <dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4-runtime</artifactId>
        <version>4.9.3</version>
    </dependency>
</dependencies>

그런 다음 Maven 컴파일 명령을 실행합니다.

메이븐 컴파일 명령어

mvn generate-sources

4단계: 간단한 샘플 코드 작성

예산에 대한 샘플 텍스트:

샘플 텍스트

1+2
1+2*4
1+2*4-5
1+2*4-5+20/5
(1+2)*4

더하기, 빼기, 곱하기 및 나누기 논리 클래스:

논리 구현 클래스

package com.vivo.learn.sql;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 重写访问器规则,实现数据计算功能
 * 目标:
 *     1+2 => 1+2=3
 *     1+2*4 => 1+2*4=9
 *     1+2*4-5 => 1+2*4-5=4
 *     1+2*4-5+20/5 => 1+2*4-5+20/5=8
 *     (1+2)*4 => (1+2)*4=12
 */
public class LibExprVisitorImpl extends LibExprBaseVisitor<Integer> {
    // 定义数据
    Map<String,Integer> data = new HashMap<String,Integer>();
 
    // expr (NEWLINE)?         # printExpr
    @Override
    public Integer visitPrintExpr(LibExprParser.PrintExprContext ctx) {
        System.out.println(ctx.expr().getText()+"="+visit(ctx.expr()));
        return visit(ctx.expr());
    }
 
    // ID '=' expr (NEWLINE)? # assign
    @Override
    public Integer visitAssign(LibExprParser.AssignContext ctx) {
        // 获取id
        String id = ctx.ID().getText();
        // // 获取value
        int value = Integer.valueOf(visit(ctx.expr()));
 
        // 缓存ID数据
        data.put(id,value);
 
        // 打印日志
        System.out.println(id+"="+value);
 
        return value;
    }
 
    // NEWLINE                # blank
    @Override
    public Integer visitBlank(LibExprParser.BlankContext ctx) {
        return 0;
    }
 
    // expr op=('*'|'/') expr # MulDiv
    @Override
    public Integer visitMulDiv(LibExprParser.MulDivContext ctx) {
        // 左侧数字
        int left = Integer.valueOf(visit(ctx.expr(0)));
        // 右侧数字
        int right = Integer.valueOf(visit(ctx.expr(1)));
        // 操作符号
        int opType = ctx.op.getType();
 
        // 调试
        // System.out.println("visitMulDiv>>>>> left:"+left+",opType:"+opType+",right:"+right);
 
        // 判断是否为乘法
        if(LibExprParser.MUL==opType){
            return left*right;
        }
 
        // 判断是否为除法
        return left/right;
 
    }
 
    // expr op=('+'|'-') expr # AddSub
    @Override
    public Integer visitAddSub(LibExprParser.AddSubContext ctx) {
        // 获取值和符号
 
        // 左侧数字
        int left = Integer.valueOf(visit(ctx.expr(0)));
        // 右侧数字
        int right = Integer.valueOf(visit(ctx.expr(1)));
        // 操作符号
        int opType = ctx.op.getType();
 
        // 调试
        // System.out.println("visitAddSub>>>>> left:"+left+",opType:"+opType+",right:"+right);
 
        // 判断是否为加法
        if(LibExprParser.ADD==opType){
            return left+right;
        }
 
        // 判断是否为减法
        return left-right;
 
    }
 
    // '(' expr ')'           # Parens
    @Override
    public Integer visitParens(LibExprParser.ParensContext ctx) {
        // 递归下调
        return visit(ctx.expr());
    }
 
    // ID                     # Id
    @Override
    public Integer visitId(LibExprParser.IdContext ctx) {
        // 获取id
        String id = ctx.ID().getText();
        // 判断ID是否被定义
        if(data.containsKey(id)){
            // System.out.println("visitId>>>>> id:"+id+",value:"+data.get(id));
            return data.get(id);
        }
        return 0;
    }
 
    // INT                    # Int
    @Override
    public Integer visitInt(LibExprParser.IntContext ctx) {
        // System.out.println("visitInt>>>>> int:"+ctx.INT().getText());
        return Integer.valueOf(ctx.INT().getText());
    }
 
}

Main 함수는 결과 클래스를 출력합니다.

package com.vivo.learn.sql;
 
import org.antlr.v4.runtime.tree.ParseTree;
 
import java.io.FileNotFoundException;
import java.io.IOException;
import org.antlr.v4.runtime.*;
 
/**
 * 打印语法树
 */
public class TestLibExprPrint {
 
    // 打印语法树 input -> lexer -> tokens -> parser -> tree -> print
    public static void main(String args[]){
        printTree("E:\\smartloli\\hadoop\\sql-parser-example\\src\\main\\resources\\testCase.txt");
    }
 
 
    /**
     * 打印语法树 input -> lexer -> token -> parser -> tree
     * @param fileName
     */
    private static void printTree(String fileName){
        // 定义输入流
        ANTLRInputStream input = null;
 
        // 判断文件名是否为空,若不为空,则读取文件内容,若为空,则读取输入流
        if(fileName!=null){
            try{
                input = new ANTLRFileStream(fileName);
            }catch(FileNotFoundException fnfe){
                System.out.println("文件不存在,请检查后重试!");
            }catch(IOException ioe){
                System.out.println("文件读取异常,请检查后重试!");
            }
        }else{
            try{
                input = new ANTLRInputStream(System.in);
            }catch(FileNotFoundException fnfe){
                System.out.println("文件不存在,请检查后重试!");
 
            }catch(IOException ioe){
                System.out.println("文件读取异常,请检查后重试!");
            }
        }
 
        // 定义词法规则分析器
        LibExprLexer lexer = new LibExprLexer(input);
 
        // 生成通用字符流
        CommonTokenStream tokens = new CommonTokenStream(lexer);
 
        // 语法解析
        LibExprParser parser = new LibExprParser(tokens);
 
        // 生成语法树
        ParseTree tree = parser.prog();
 
        // 打印语法树
        // System.out.println(tree.toStringTree(parser));
 
        // 生命访问器
        LibExprVisitorImpl visitor = new LibExprVisitorImpl();
        visitor.visit(tree);
 
    }
 
}

코드를 실행하면 최종 출력이 다음 그림과 같이 표시됩니다.

그림

4.2 방해석

위의 ANTLR 콘텐츠는 어휘 분석 및 구문 분석의 간단한 프로세스를 보여주지만 ANTLR은 SQL 쿼리를 구현해야 하므로 자체 어휘 및 문법 관련 파일을 정의한 다음 ANTLR 플러그인을 사용하여 파일을 컴파일해야 합니다. 그런 다음 코드를 생성합니다(Thrift의 유사 사용으로 인터페이스를 먼저 정의한 다음 해당 언어 파일로 컴파일하고 마지막으로 생성된 클래스 또는 인터페이스를 상속하거나 구현합니다).

4.2.1 원칙 및 이점

Apache Calcite의 출현은 이러한 복잡한 프로젝트를 크게 단순화합니다. Calcite를 사용하면 사용자가 시스템에 SQL 셸을 쉽게 배치하고 효율적인 쿼리 성능 최적화를 제공할 수 있습니다.

  • 쿼리 언어;
  • 쿼리 최적화;
  • 쿼리 실행;
  • 데이터 관리;
  • 데이터 저장고;

위의 5가지 기능은 일반적으로 데이터베이스 시스템에 포함된 공통 기능입니다. Calcite는 설계할 때 세 가지 녹색 부분에만 집중하기로 결정하고 다음과 같은 데이터 관리 및 데이터 저장을 다양한 외부 저장소 또는 컴퓨팅 엔진에 맡겼습니다.

데이터 관리 및 데이터 저장, 특히 데이터 저장은 매우 복잡하고 데이터 자체의 특성으로 인해 구현의 다양성으로 이어질 것입니다. 방해석은 이 두 부분의 디자인을 포기하고 상위 계층의 보다 일반적인 모듈에 중점을 두어 충분히 가벼울 수 있고 시스템의 복잡성을 제어할 수 있으며 개발자의 에너지가 과하지 않을 것입니다.

동시에 방해석은 초기 수레바퀴에 반복적으로 가지 않았고, 재사용할 수 있는 것은 재사용을 위해 직접 사용합니다. 개발자들이 받아들일 수 있는 이유이기도 하다. 예를 들어 다음 두 가지 예를 고려하십시오.

  • 예 1: SQL 파서로서 Calcite는 주요 SQL 파싱을 위한 휠을 재발명하지 않고 오픈 소스 JavaCC를 직접 사용하여 SQL 문을 Java 코드로 변환한 다음 이를 AST(추상 구문 트리)로 변환합니다. 다음 단계에서 사용하기 위해;
  • 예제 2: Calcite는 나중에 언급되는 유연한 메타데이터 기능을 지원하기 위해 Java 코드의 런타임 컴파일을 지원해야 합니다. 기본 JavaC는 너무 무겁고 더 가벼운 컴파일러가 필요하며 Calcite도 휠 빌드를 선택하지 않고 오픈 소스 Janino 솔루션을 사용했습니다.

그림

위의 그림은 Calcite에서 공식적으로 제공한 아키텍처 다이어그램입니다.이 그림에서 얻을 수 있는 정보는 한편으로 위에서 언급한 것을 확인하는 것입니다. 다른 한편으로, 더 중요한 것은 Calcite가 충분히 모듈화되고 플러그 가능하도록 설계되었다는 것입니다.

  • [JDBC 클라이언트]: 이 모듈은 JDBC 클라이언트를 사용하는 애플리케이션을 지원하는 데 사용됩니다.
  • [SQL 구문 분석기 및 유효성 검사기]: 이 모듈은 SQL 구문 분석 및 유효성 검사에 사용됩니다.
  • [Expressions Builder]: 자체 SQL 구문 분석 및 확인을 지원하는 프레임워크 도킹
  • [연산자 표현식]: 이 모듈은 관계식을 처리하는 데 사용됩니다.
  • [메타데이터 공급자]: 이 모듈은 외부 사용자 지정 메타데이터를 지원하는 데 사용됩니다.
  • [플러그 가능 규칙]: 이 모듈은 최적화 규칙을 정의하는 데 사용됩니다.
  • [Query Optimizer] : 쿼리 최적화에 중점을 둔 핵심 모듈입니다.

기능 모듈의 분할은 충분히 합리적이고 독립적이어서 완전한 통합이 아닌 일부만 선택하여 사용할 수 있습니다.기본적으로 각 모듈은 사용자 정의를 지원하므로 사용자가 시스템을 더 사용자 정의할 수 있습니다.

그림

위의 빅데이터에서 많이 사용되는 컴포넌트들은 모두 Calcite와 통합되어 있으며, Hive는 자체 SQL 파싱을 하고 Calcite의 쿼리 최적화 기능만 사용하고 있음을 알 수 있다. Flink와 마찬가지로 Calcite는 구문 분석에서 최적화까지 직접 사용됩니다.

위에서 설명한 Calcite 통합 방법은 모두 Calcite의 모듈을 라이브러리로 사용합니다. 너무 무거우면 더 간단한 어댑터 기능을 선택할 수 있습니다. 외부 시스템과의 데이터 상호 작용은 Spark와 같은 프레임워크의 사용자 지정 소스 또는 싱크를 통해 이루어집니다.

위의 그림은 일반적인 어댑터 사용법으로, 예를 들어 Kafka 어댑터를 통해 애플리케이션 계층에서 SQL을 직접 전달할 수 있으며, 하위 계층은 데이터 상호작용을 위해 자동으로 Java와 Kafka로 변환됩니다(후자에 케이스 연산이 있습니다. 부분).

4.2.2 Calcite는 KSQL 쿼리 Kafka를 구현합니다.

Topic in Kafka의 데이터를 쿼리하려면 EFAK(원래 Kafka Eagle 오픈 소스 프로젝트)의 SQL 구현을 참조하십시오.

1. 일반 SQL 쿼리

SQL 쿼리

select * from video_search_query where partition in (0) limit 10

스크린샷 미리보기:

그림

2. UDF 쿼리

SQL 쿼리

select JSON(msg,'query') as query,JSON(msg,'pv') as pv from video_search_query where `partition` in (0) limit 10

스크린샷 미리보기:

그림

4.3 ANTLR4와 방해석 SQL 구문 분석의 비교

4.3.1 ANTLR4는 SQL 구문 분석

ANTLR4 SQL 구문 분석의 주요 프로세스에는 어휘 및 문법 파일 정의, SQL 구문 분석 논리 클래스 작성, 기본 서비스에서 SQL 논리 클래스 호출이 포함됩니다.

1. 어휘 및 문법 파일 정의

공식 웹 사이트에서 제공하는 오픈 소스 주소를 참조할 수 있습니다 .

2. SQL 구문 분석 로직 클래스 작성

여기에 SQL 테이블 이름을 파싱하는 클래스를 작성하는데 구체적인 구현 코드는 다음과 같다.

테이블 이름 구문 분석

public class TableListener extends antlr4.sql.MySqlParserBaseListener {

    private String tableName = null;

    public void enterQueryCreateTable(antlr4.sql.MySqlParser.QueryCreateTableContext ctx) {
        List<MySqlParser.TableNameContext> tableSourceContexts = ctx.getRuleContexts(antlr4.sql.MySqlParser.TableNameContext.class);
        for (antlr4.sql.MySqlParser.TableNameContext tableSource : tableSourceContexts) {
            // 获取表名
            tableName = tableSource.getText();
        }
    }

    public String getTableName() {
        return tableName;
    }
}

3. 메인 서비스는 SQL 로직 클래스를 호출합니다.

SQL 구문 분석을 구현하는 논리 클래스를 호출합니다.구체적인 코드는 다음과 같습니다.

주요 서비스

public class AntlrClient {

    public static void main(String[] args) {
        // antlr4 格式化SQL
        antlr4.sql.MySqlLexer lexer = new antlr4.sql.MySqlLexer(CharStreams.fromString("create table table2 select tid from table1;"));
        antlr4.sql.MySqlParser parser = new antlr4.sql.MySqlParser(new CommonTokenStream(lexer));
        // 定义TableListener
        TableListener listener = new TableListener();
        ParseTreeWalker.DEFAULT.walk(listener, parser.sqlStatements());
        // 获取表名
        String tableName= listener.getTableName();
        // 输出表名
        System.out.println(tableName);
    }
}

4.3.2 방해석 파싱 SQL

ANTLR에 비해 Calcite의 SQL 구문 분석 프로세스는 비교적 간단하며 개발 시 어휘 및 문법 파일의 정의 및 작성에 주의를 기울일 필요가 없고 특정 비즈니스 로직의 구현에만 주의를 기울일 필요가 있습니다. 예를 들어, SQL COUNT 연산을 구현하기 위한 방해석 구현 단계는 다음과 같습니다.

1.pom 종속성

방해석은 JAR에 의존

<dependencies>
  <!-- 这里对Calcite适配依赖进行封装,引入下列包即可 -->
  <dependency>
    <groupId>org.smartloli</groupId>
    <artifactId>jsql-client</artifactId>
    <version>1.0.0</version>
  </dependency>
</dependencies>

2. 코드 구현

방해석 샘플 코드

package com.vivo.learn.sql.calcite;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.smartloli.util.JSqlUtils;

public class JSqlClient {
    public static void main(String[] args) {
        JSONObject tabSchema = new JSONObject();
        tabSchema.put("id","integer");
        tabSchema.put("name","varchar");

        JSONArray datasets = JSON.parseArray("[{\"id\":1,\"name\":\"aaa\",\"age\":20},{\"id\":2,\"name\":\"bbb\",\"age\":21},{\"id\":3,\"name\":\"ccc\",\"age\":22}]");

        String tabName = "userinfo";
        String sql = "select count(*) as cnt from \"userinfo\"";
        try{
           String result = JSqlUtils.query(tabSchema,tabName,datasets,sql);
            System.out.println("result: "+result);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

3. 스크린샷 미리보기

그림

4.3.3 비교 결과

포괄적인 비교, 두 기술의 학습 비용, 사용 복잡성 및 유연성을 비교하고 실제 비즈니스 요구를 처리하기 위해 SQL 파서로 Calcite를 선택할 수 있습니다.

V. 요약

또한, 독립형 모드의 경우 실행 계획을 실행 코드로 쉽게 변환할 수 있지만 분산형 분야에서는 컴퓨팅 엔진의 다양성으로 인해 특정 컴퓨팅 엔진에 가까운 설명이 필요하고, 즉, 물리적 계획입니다. 즉, 논리적 계획은 추상적인 계층 설명일 뿐이며 물리적 계획은 특정 컴퓨팅 엔진과 직접 연결됩니다.

그림

위의 시나리오를 충족하기 위해 일반적으로 SQL 파서를 도입할 수 있습니다.

  • 관계형 데이터베이스(예: MySQL, Oracle)를 위한 맞춤형 SQL을 대화형 쿼리로 제공합니다.
  • 개발자에게 JDBC, ODBC 및 다양한 데이터베이스와 같은 표준 인터페이스를 제공합니다.
  • 프로그래밍 언어에 능숙하지 않지만 데이터를 사용해야 하는 데이터 분석가 및 기타 사람들을 위해;
  • 빅 데이터 기술 구성 요소는 SQL과 함께 제공되지 않습니다.

참조:

  1. https://github.com/smartloli/EFAK
  2. https://github.com/antlr/antlr4
  3. https://github.com/antlr/grammars-v4
  4. https://github.com/apache/calcite
{{o.name}}
{{m.name}}

추천

출처my.oschina.net/vivotech/blog/5585953