$ Apache Calcite学习笔记

$ Parser代码生成

一般数据库里都是用DSL文件描述语法,然后通过代码生成的方式生成运行时代码文件。Java中常见的生成Parser的工具有antlr等,Calcite使用了JavaCC,除此之外为了方便扩展语法,还使用了fmpp模板工具。

fmpp基于FreeMarker的预处理工具,为什么不直接使用FreeMarker呢?大概是因为FreeMarker的变量是以代码的形式存在,而fmpp可以从文件中加载变量,更方便使用吧。

Calcite的相关语法描述文件在core模块的src/main/codegen目录中,相关的文件有:

src/main/codegen/
├── config.fmpp
├── default_config.fmpp
├── includes
│   └── parserImpls.ftl
└── templates
    └── Parser.jj

其中templates/Parser.jj是JavaCC语法的模板文件,config.fmpp是fmpp的配置文件,default_config.fmpp是默认的配置文件,inlcudes目录下是为了扩展语法而添加的模板文件,会被config.fmpp引用。

fmpp替换之后的模板文件输出在build/fmpp/fmppMain/javacc/Parser.jj,最终JavaCC生成的运行时的代码在build/javacc/javaCCMain目录下。这两个步骤是由core模块的build.gradle.kts (opens new window)FmppTask (opens new window)JavaCCTask (opens new window)完成的。

$ 扩展Parser

如果想要用Calcite解析一种自定义SQL语法,就需要扩展core模块的语法文件,参考server模块。calcite是用gradle组织的,在maven项目中如何使用呢?参考FmppTask (opens new window)JavaCCTask (opens new window),和Fmpp的Ant Task (opens new window)文档,如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <target>
                    <taskdef name="fmpp" classname="fmpp.tools.AntTask" />
                    <fmpp
                            configuration="src/main/codegen/config.fmpp"
                            sourceRoot="src/main/codegen/templates"
                            outputRoot="${project.build.directory}/generated-sources/fmpp"
                    >
                        <data>
                            tdd(${project.basedir}/src/main/codegen/config.fmpp),default:tdd(${project.basedir}/src/main/codegen/default_config.fmpp)</data>
                    </fmpp>
                </target>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <dependency>
            <groupId>net.sourceforge.fmpp</groupId>
            <artifactId>fmpp</artifactId>
            <version>0.9.16</version>
        </dependency>
    </dependencies>
</plugin>

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>javacc-maven-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
        <execution>
            <id>javacc</id>
            <goals>
                <goal>javacc</goal>
            </goals>
            <configuration>
                <sourceDirectory>${project.build.directory}/generated-sources/fmpp</sourceDirectory>
                <includes>
                    <include>**/Parser.jj</include>
                </includes>
                <lookAhead>1</lookAhead>
                <isStatic>false</isStatic>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>net.java.dev.javacc</groupId>
            <artifactId>javacc</artifactId>
            <version>4.0</version>
        </dependency>
    </dependencies>
</plugin>

其中<data>标签那一段比较难理解,tdd()是TDD functions (opens new window),用来把文件confg.fmpp解析成为FreeMarker用的数据模型,如果tdd()前带上default:标签,则数据模型的key会统一带上标签,这就是Parser.jj里面default (opens new window)的来源。

$ 参考

Extending the parserPer (opens new window)

The Ant Task (opens new window)

List of all options (opens new window)

更新时间: 6/7/2025, 4:07:15 PM