├── .github └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── demo ├── .gitignore ├── README.md ├── js │ ├── .gcloudignore │ ├── index.js │ ├── package-lock.json │ └── package.json └── src │ └── main │ └── java │ └── com │ └── github │ └── vertical_blank │ └── sqlformatter │ └── SqlFormatterDemo.java ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── vertical_blank │ └── sqlformatter │ ├── SqlFormatter.java │ ├── core │ ├── AbstractFormatter.java │ ├── DialectConfig.java │ ├── FormatConfig.java │ ├── Indentation.java │ ├── InlineBlock.java │ ├── Params.java │ ├── Token.java │ ├── TokenTypes.java │ ├── Tokenizer.java │ └── util │ │ ├── JSLikeList.java │ │ ├── RegexUtil.java │ │ └── Util.java │ └── languages │ ├── Db2Formatter.java │ ├── Dialect.java │ ├── DialectConfigurator.java │ ├── MariaDbFormatter.java │ ├── MySqlFormatter.java │ ├── N1qlFormatter.java │ ├── PlSqlFormatter.java │ ├── PostgreSqlFormatter.java │ ├── RedshiftFormatter.java │ ├── SparkSqlFormatter.java │ ├── StandardSqlFormatter.java │ ├── StringLiteral.java │ └── TSqlFormatter.java └── test ├── java └── com │ └── github │ └── vertical_blank │ └── sqlformatter │ ├── Benchmark.java │ └── SqlFormatterTest.java └── kotlin └── com └── github └── vertical_blank └── sqlformatter ├── BehavesLikeMariaDbFormatter.kt ├── BehavesLikeSqlFormatter.kt ├── Db2FormatterTest.kt ├── DialectTest.kt ├── MariaDbFormatterTest.kt ├── ModifiedFormatterTest.kt ├── MySqlFormatterTest.kt ├── N1qlFormatterTest.kt ├── PlSqlFormatterTest.kt ├── PostgreSqlFormatterTest.kt ├── RedshiftFormatterTest.kt ├── SparkSqlFormatterTest.kt ├── StandardSqlFormatterTest.kt ├── TSqlFormatterTest.kt ├── TestUtil.kt └── features ├── AlterTable.kt ├── AlterTableModify.kt ├── Between.kt ├── Case.kt ├── CreateTable.kt ├── Join.kt ├── Operators.kt ├── Schema.kt └── Strings.kt /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | push: 13 | branches: [ "master" ] 14 | pull_request: 15 | branches: [ "master" ] 16 | workflow_dispatch: 17 | 18 | jobs: 19 | build: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up JDK 17 26 | uses: actions/setup-java@v3 27 | with: 28 | java-version: '17' 29 | distribution: 'temurin' 30 | cache: maven 31 | - name: Build with Maven 32 | run: mvn -B package --file pom.xml 33 | 34 | - name: Codecov 35 | uses: codecov/codecov-action@v1.0.15 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | out 7 | bin 8 | target 9 | 10 | *.iml 11 | 12 | .idea 13 | .settings 14 | .classpath 15 | .project 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Yohei Yamana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sql-formatter 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/com.github.vertical-blank/sql-formatter.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.github.vertical-blank%22%20AND%20a:%22sql-formatter%22) 4 | ![Java CI with Maven](https://github.com/vertical-blank/sql-formatter/workflows/Java%20CI%20with%20Maven/badge.svg) 5 | [![codecov](https://codecov.io/gh/vertical-blank/sql-formatter/branch/master/graph/badge.svg)](https://codecov.io/gh/vertical-blank/sql-formatter) 6 | 7 | Java port of great SQL formatter . 8 | 9 | Written with only Java Standard Library, without dependencies. 10 | 11 | [Demo](http://www.vertical-blank.com/sql-formatter/) 12 | 13 | Demo is running on Google Cloud Function, with native-compiled shared library by GraalVM. 14 | 15 | This does not support: 16 | 17 | - Stored procedures. 18 | - Changing of the delimiter type to something else than ;. 19 | 20 | ## Usage 21 | 22 | ### Maven 23 | 24 | ```xml 25 | 26 | com.github.vertical-blank 27 | sql-formatter 28 | 2.0.5 29 | 30 | ``` 31 | 32 | ### Gradle 33 | 34 | ```gradle 35 | implementation 'com.github.vertical-blank:sql-formatter:2.0.5' 36 | ``` 37 | 38 | ## Examples 39 | 40 | You can easily use `com.github.vertical_blank.sqlformatter.SqlFormatter` : 41 | 42 | ```java 43 | SqlFormatter.format("SELECT * FROM table1") 44 | ``` 45 | 46 | This will output: 47 | 48 | ```sql 49 | SELECT 50 | * 51 | FROM 52 | table1 53 | ``` 54 | 55 | You can also pass `FormatConfig` object built by builder: 56 | 57 | ```js 58 | SqlFormatter.format('SELECT * FROM tbl', 59 | FormatConfig.builder() 60 | .indent(" ") // Defaults to two spaces 61 | .uppercase(true) // Defaults to false (not safe to use when SQL dialect has case-sensitive identifiers) 62 | .linesBetweenQueries(2) // Defaults to 1 63 | .maxColumnLength(100) // Defaults to 50 64 | .params(Arrays.asList("a", "b", "c")) // Map or List. See Placeholders replacement. 65 | .build() 66 | ); 67 | ``` 68 | 69 | ### Dialect 70 | 71 | You can pass dialect `com.github.vertical_blank.sqlformatter.languages.Dialect` or `String` to `SqlFormatter.of` : 72 | 73 | ```java 74 | SqlFormatter 75 | .of(Dialect.N1ql) // Recommended 76 | //.of("n1ql") // String can be passed 77 | .format("SELECT *"); 78 | ``` 79 | 80 | SQL formatter supports the following dialects: 81 | 82 | - **sql** - [Standard SQL][] 83 | - **mariadb** - [MariaDB][] 84 | - **mysql** - [MySQL][] 85 | - **postgresql** - [PostgreSQL][] 86 | - **db2** - [IBM DB2][] 87 | - **plsql** - [Oracle PL/SQL][] 88 | - **n1ql** - [Couchbase N1QL][] 89 | - **redshift** - [Amazon Redshift][] 90 | - **spark** - [Spark][] 91 | - **tsql** - [SQL Server Transact-SQL][tsql] 92 | 93 | ### Extend formatters 94 | 95 | Formatters can be extended as below : 96 | 97 | ```java 98 | SqlFormatter 99 | .of(Dialect.MySql) 100 | .extend(cfg -> cfg.plusOperators("=>")) 101 | .format("SELECT * FROM table WHERE A => 4") 102 | ``` 103 | 104 | Then it results in: 105 | 106 | ```sql 107 | SELECT 108 | * 109 | FROM 110 | table 111 | WHERE 112 | A => 4 113 | ``` 114 | 115 | ### Placeholders replacement 116 | 117 | You can pass `List` or `Map` to `format` : 118 | 119 | ```java 120 | // Named placeholders 121 | Map namedParams = new HashMap<>(); 122 | namedParams.put("foo", "'bar'"); 123 | SqlFormatter.of(Dialect.TSql).format("SELECT * FROM tbl WHERE foo = @foo", namedParams); 124 | 125 | // Indexed placeholders 126 | SqlFormatter.format("SELECT * FROM tbl WHERE foo = ?", Arrays.asList("'bar'")); 127 | ``` 128 | 129 | Both result in: 130 | 131 | ```sql 132 | SELECT 133 | * 134 | FROM 135 | tbl 136 | WHERE 137 | foo = 'bar' 138 | ``` 139 | 140 | ## Build 141 | 142 | Building this library requires JDK 11 because of [ktfmt](https://github.com/facebookincubator/ktfmt). 143 | 144 | 145 | [standard sql]: https://en.wikipedia.org/wiki/SQL:2011 146 | [couchbase n1ql]: http://www.couchbase.com/n1ql 147 | [ibm db2]: https://www.ibm.com/analytics/us/en/technology/db2/ 148 | [oracle pl/sql]: http://www.oracle.com/technetwork/database/features/plsql/index.html 149 | [amazon redshift]: https://docs.aws.amazon.com/redshift/latest/dg/cm_chap_SQLCommandRef.html 150 | [spark]: https://spark.apache.org/docs/latest/api/sql/index.html 151 | [postgresql]: https://www.postgresql.org/ 152 | [mariadb]: https://mariadb.com/ 153 | [mysql]: https://www.mysql.com/ 154 | [tsql]: https://docs.microsoft.com/en-us/sql/sql-server/ -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.h 3 | *.so 4 | *.jar 5 | *.class 6 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Demo 3 | 4 | ## How to build 5 | 6 | ### Download jar 7 | 8 | ```bash 9 | wget https://repo1.maven.org/maven2/com/github/vertical-blank/sql-formatter/2.0.0/sql-formatter-2.0.0.jar 10 | ``` 11 | 12 | ### compile java 13 | 14 | ```bash 15 | path/to/graalvm/bin/javac src/main/java/com/github/vertical_blank/sqlformatter/SqlFormatterDemo.java -cp ./sql-formatter-2.0.0.jar 16 | ``` 17 | 18 | ### generate shared object 19 | 20 | ```bash 21 | path/to/graalvm/bin/native-image --shared -H:Name=sqlformatterdemo -cp src/main/java/:sql-formatter-2.0.0.jar 22 | ``` 23 | 24 | ### setup javascript 25 | 26 | Note: Python 2.7 is requied by node-gyp. 27 | 28 | ```bash 29 | cp sqlformatterdemo.so js/ 30 | cd js/ 31 | npm i 32 | ``` 33 | 34 | ## deploy to google cloud function 35 | 36 | ```bash 37 | gcloud functions deploy format_sql --runtime nodejs12 --entry-point handler --trigger-http 38 | ``` 39 | -------------------------------------------------------------------------------- /demo/js/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | -------------------------------------------------------------------------------- /demo/js/index.js: -------------------------------------------------------------------------------- 1 | const ref = require('ref-napi'); 2 | const ffi = require('ffi-napi'); 3 | 4 | const version = '2.0.0' 5 | 6 | const libJava = ffi.Library(__dirname + '/sqlformatterdemo', { 7 | graal_create_isolate: [ 8 | ref.types.int, [ 9 | ref.refType(ref.types.void), 10 | ref.refType(ref.types.void), 11 | ref.refType(ref.refType(ref.types.void)) 12 | ]], 13 | graal_tear_down_isolate: [ 14 | ref.types.int, [ 15 | ref.refType(ref.types.void)] 16 | ], 17 | hello: [ 18 | ref.types.CString, 19 | [ref.refType(ref.types.void)]], 20 | format_sql: [ 21 | ref.types.CString, [ 22 | ref.refType(ref.types.void), 23 | ref.refType(ref.types.CString) 24 | ]], 25 | }) 26 | 27 | /** 28 | * HTTP Cloud Function. 29 | * 30 | * @param {Object} req Cloud Function request context. 31 | * @param {Object} res Cloud Function response context. 32 | */ 33 | exports.handler = (req, res) => { 34 | const p_graal_isolatethread_t = ref.alloc(ref.refType(ref.types.void)) 35 | 36 | const rc = libJava.graal_create_isolate(ref.NULL, ref.NULL, p_graal_isolatethread_t) 37 | if (rc !== 0) { 38 | res.header('Access-Control-Allow-Origin', "*"); 39 | res.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept"); 40 | res.send('error on isolate creation or attach') 41 | return; 42 | } 43 | if (req.method !== 'POST') { 44 | const hello = libJava.hello(ref.deref(p_graal_isolatethread_t)) 45 | res.header('Access-Control-Allow-Origin', "*"); 46 | res.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept"); 47 | res.send(`${hello} ${version}`); 48 | } else { 49 | const formatted = libJava.format_sql(ref.deref(p_graal_isolatethread_t), ref.allocCString(req.rawBody.toString())) 50 | res.header('Access-Control-Allow-Origin', "*"); 51 | res.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept"); 52 | res.send(formatted); 53 | } 54 | 55 | libJava.graal_tear_down_isolate(ref.deref(p_graal_isolatethread_t)); 56 | }; 57 | -------------------------------------------------------------------------------- /demo/js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-cloud-function-node", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bindings": { 8 | "version": "1.2.1", 9 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", 10 | "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" 11 | }, 12 | "debug": { 13 | "version": "2.6.9", 14 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 15 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 16 | "requires": { 17 | "ms": "2.0.0" 18 | } 19 | }, 20 | "ffi": { 21 | "version": "2.3.0", 22 | "resolved": "https://registry.npmjs.org/ffi/-/ffi-2.3.0.tgz", 23 | "integrity": "sha512-vkPA9Hf9CVuQ5HeMZykYvrZF2QNJ/iKGLkyDkisBnoOOFeFXZQhUPxBARPBIZMJVulvBI2R+jgofW03gyPpJcQ==", 24 | "requires": { 25 | "bindings": "~1.2.0", 26 | "debug": "2", 27 | "nan": "2", 28 | "ref": "1", 29 | "ref-struct": "1" 30 | } 31 | }, 32 | "ms": { 33 | "version": "2.0.0", 34 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 35 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 36 | }, 37 | "nan": { 38 | "version": "2.14.1", 39 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", 40 | "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" 41 | }, 42 | "ref": { 43 | "version": "1.3.5", 44 | "resolved": "https://registry.npmjs.org/ref/-/ref-1.3.5.tgz", 45 | "integrity": "sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA==", 46 | "requires": { 47 | "bindings": "1", 48 | "debug": "2", 49 | "nan": "2" 50 | } 51 | }, 52 | "ref-struct": { 53 | "version": "1.1.0", 54 | "resolved": "https://registry.npmjs.org/ref-struct/-/ref-struct-1.1.0.tgz", 55 | "integrity": "sha1-XV7mWtQc78Olxf60BYcmHkee3BM=", 56 | "requires": { 57 | "debug": "2", 58 | "ref": "1" 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /demo/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-cloud-function-node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ffi-napi": "^4.0.2", 13 | "ref-napi": "^3.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/vertical_blank/sqlformatter/SqlFormatterDemo.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter; 2 | 3 | import org.graalvm.nativeimage.IsolateThread; 4 | import org.graalvm.nativeimage.c.function.CEntryPoint; 5 | import org.graalvm.nativeimage.c.type.CTypeConversion; 6 | import org.graalvm.nativeimage.c.type.CCharPointer; 7 | 8 | public final class SqlFormatterDemo { 9 | 10 | @CEntryPoint(name = "hello") 11 | static CCharPointer hello(IsolateThread thread) { 12 | return CTypeConversion.toCString("hello from native lib").get(); 13 | } 14 | 15 | @CEntryPoint(name = "format_sql") 16 | static CCharPointer formatSql(IsolateThread thread, CCharPointer sql) { 17 | return CTypeConversion.toCString(SqlFormatter.format(CTypeConversion.toJavaString(sql))).get(); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.github.vertical-blank 4 | sql-formatter 5 | 2.0.5 6 | jar 7 | sql-formatter 8 | Simple SQL formatter without dependencies. 9 | https://github.com/vertical-blank/sql-formatter 10 | 11 | 12 | The MIT License 13 | https://opensource.org/licenses/MIT 14 | 15 | 16 | 17 | 18 | vertical-blank 19 | Yohei Yamana 20 | fixeight@gmail.com 21 | 22 | 23 | 24 | scm:git@github.com:vertical-blank/sql-formatter.git 25 | scm:git@github.com:vertical-blank/sql-formatter.git 26 | scm:git@github.com:vertical-blank/sql-formatter.git 27 | 28 | 29 | 30 | UTF-8 31 | UTF-8 32 | 1.5.31 33 | 2.0.17 34 | 35 | 36 | 37 | 38 | 39 | org.junit 40 | junit-bom 41 | 5.8.1 42 | import 43 | pom 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.junit.jupiter 52 | junit-jupiter 53 | 5.8.1 54 | test 55 | 56 | 57 | 58 | org.openjdk.jmh 59 | jmh-generator-annprocess 60 | 1.33 61 | test 62 | 63 | 64 | 65 | org.jetbrains.kotlin 66 | kotlin-stdlib-jdk8 67 | ${kotlin.version} 68 | test 69 | 70 | 71 | org.spekframework.spek2 72 | spek-dsl-jvm 73 | ${spek_version} 74 | test 75 | 76 | 77 | org.spekframework.spek2 78 | spek-runner-junit5 79 | ${spek_version} 80 | test 81 | 82 | 83 | org.jetbrains.kotlin 84 | kotlin-reflect 85 | ${kotlin.version} 86 | test 87 | 88 | 89 | 90 | org.jetbrains.kotlin 91 | kotlin-test-junit 92 | ${kotlin.version} 93 | test 94 | 95 | 96 | 97 | org.amshove.kluent 98 | kluent 99 | 1.68 100 | test 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | org.apache.maven.wagon 109 | wagon-ssh 110 | 1.0 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-resources-plugin 119 | 2.5 120 | 121 | UTF-8 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-compiler-plugin 127 | 3.8.0 128 | 129 | 1.8 130 | 1.8 131 | 132 | 133 | 134 | maven-source-plugin 135 | 2.1.2 136 | 137 | 138 | source-jar 139 | package 140 | 141 | jar 142 | 143 | 144 | 145 | 146 | 147 | maven-javadoc-plugin 148 | 3.1.0 149 | 150 | UTF-8 151 | UTF-8 152 | en 153 | 154 | 155 | 156 | javadoc-jar 157 | package 158 | 159 | jar 160 | 161 | 162 | 163 | 164 | 165 | 166 | de.sormuras.junit 167 | junit-platform-maven-plugin 168 | 1.1.2 169 | true 170 | 171 | JAVA 172 | 173 | true 174 | 175 | ${jacoco.java.option} 176 | 177 | 178 | 179 | 180 | 181 | 182 | org.jetbrains.kotlin 183 | kotlin-maven-plugin 184 | ${kotlin.version} 185 | 186 | 187 | test-compile 188 | test-compile 189 | 190 | test-compile 191 | 192 | 193 | 194 | 195 | 196 | 197 | org.codehaus.mojo 198 | build-helper-maven-plugin 199 | 3.0.0 200 | 201 | 202 | generate-test-sources 203 | 204 | add-test-source 205 | 206 | 207 | 208 | src/test/kotlin 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | com.diffplug.spotless 217 | spotless-maven-plugin 218 | 2.28.0 219 | 220 | 221 | 222 | src/main/java/**/*.java 223 | src/test/java/**/*.java 224 | 225 | 226 | 1.15.0 227 | 228 | 229 | 230 | 231 | 232 | src/test/kotlin/**/*.kt 233 | 234 | 235 | 0.39 236 | 237 | 238 | 239 | 240 | 241 | 242 | compile 243 | 244 | check 245 | 246 | 247 | 248 | 249 | 250 | 251 | org.jacoco 252 | jacoco-maven-plugin 253 | 0.8.6 254 | 255 | 256 | 257 | prepare-agent 258 | 259 | 260 | jacoco.java.option 261 | 262 | 263 | 264 | report 265 | test 266 | 267 | report 268 | 269 | 270 | 271 | 272 | 273 | 274 | org.codehaus.mojo 275 | versions-maven-plugin 276 | 2.8.1 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | org.jacoco 286 | jacoco-maven-plugin 287 | 0.8.6 288 | 289 | 290 | 291 | 292 | report 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | release-sign-artifacts 303 | 304 | 305 | performRelease 306 | true 307 | 308 | 309 | 310 | 311 | 312 | org.apache.maven.plugins 313 | maven-gpg-plugin 314 | 1.6 315 | 316 | 317 | sign-artifacts 318 | verify 319 | 320 | sign 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | sonatype-nexus-staging 333 | https://oss.sonatype.org/service/local/staging/deploy/maven2 334 | 335 | 336 | sonatype-nexus-snapshot 337 | https://oss.sonatype.org/content/repositories/snapshots 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/SqlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import com.github.vertical_blank.sqlformatter.languages.*; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.function.Function; 10 | import java.util.function.UnaryOperator; 11 | 12 | public class SqlFormatter { 13 | /** 14 | * FormatConfig whitespaces in a query to make it easier to read. 15 | * 16 | * @param query sql 17 | * @param cfg FormatConfig 18 | * @return Formatted query 19 | */ 20 | public static String format(String query, FormatConfig cfg) { 21 | return standard().format(query, cfg); 22 | } 23 | 24 | public static String format(String query, String indent, List params) { 25 | return standard().format(query, indent, params); 26 | } 27 | 28 | public static String format(String query, List params) { 29 | return standard().format(query, params); 30 | } 31 | 32 | public static String format(String query, String indent, Map params) { 33 | return standard().format(query, indent, params); 34 | } 35 | 36 | public static String format(String query, Map params) { 37 | return standard().format(query, params); 38 | } 39 | 40 | public static String format(String query, String indent) { 41 | return standard().format(query, indent); 42 | } 43 | 44 | public static String format(String query) { 45 | return standard().format(query); 46 | } 47 | 48 | public static Formatter extend(UnaryOperator operator) { 49 | return standard().extend(operator); 50 | } 51 | 52 | public static Formatter standard() { 53 | return of(Dialect.StandardSql); 54 | } 55 | 56 | public static Formatter of(String name) { 57 | return Dialect.nameOf(name) 58 | .map(Formatter::new) 59 | .orElseThrow(() -> new RuntimeException("Unsupported SQL dialect: " + name)); 60 | } 61 | 62 | public static Formatter of(Dialect dialect) { 63 | return new Formatter(dialect); 64 | } 65 | 66 | public static class Formatter { 67 | 68 | private final Function underlying; 69 | 70 | private Formatter(Function underlying) { 71 | this.underlying = underlying; 72 | } 73 | 74 | private Formatter(Dialect dialect) { 75 | this(dialect.func); 76 | } 77 | 78 | public String format(String query, FormatConfig cfg) { 79 | return this.underlying.apply(cfg).format(query); 80 | } 81 | 82 | public String format(String query, String indent, List params) { 83 | return format(query, FormatConfig.builder().indent(indent).params(params).build()); 84 | } 85 | 86 | public String format(String query, List params) { 87 | return format(query, FormatConfig.builder().params(params).build()); 88 | } 89 | 90 | public String format(String query, String indent, Map params) { 91 | return format(query, FormatConfig.builder().indent(indent).params(params).build()); 92 | } 93 | 94 | public String format(String query, Map params) { 95 | return format(query, FormatConfig.builder().params(params).build()); 96 | } 97 | 98 | public String format(String query, String indent) { 99 | return format(query, FormatConfig.builder().indent(indent).build()); 100 | } 101 | 102 | public String format(String query) { 103 | return format(query, FormatConfig.builder().build()); 104 | } 105 | 106 | public Formatter extend(UnaryOperator operator) { 107 | return new Formatter( 108 | cfg -> 109 | new AbstractFormatter(cfg) { 110 | @Override 111 | public DialectConfig dialectConfig() { 112 | return operator.apply(Formatter.this.underlying.apply(cfg).dialectConfig()); 113 | } 114 | }); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/AbstractFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.util.JSLikeList; 4 | import com.github.vertical_blank.sqlformatter.core.util.Util; 5 | import com.github.vertical_blank.sqlformatter.languages.DialectConfigurator; 6 | import java.util.EnumSet; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | public abstract class AbstractFormatter implements DialectConfigurator { 11 | private final FormatConfig cfg; 12 | private final Indentation indentation; 13 | private final InlineBlock inlineBlock; 14 | private final Params params; 15 | protected Token previousReservedToken; 16 | private JSLikeList tokens; 17 | private int index; 18 | 19 | /** 20 | * @param cfg FormatConfig 21 | */ 22 | public AbstractFormatter(FormatConfig cfg) { 23 | this.cfg = cfg; 24 | this.indentation = new Indentation(cfg.indent); 25 | this.inlineBlock = new InlineBlock(cfg.maxColumnLength); 26 | this.params = cfg.params; 27 | this.previousReservedToken = null; 28 | this.index = 0; 29 | } 30 | 31 | public Tokenizer tokenizer() { 32 | return new Tokenizer(this.dialectConfig()); 33 | } 34 | 35 | /** 36 | * Reprocess and modify a token based on parsed context. 37 | * 38 | * @param token The token to modify 39 | * @return token 40 | */ 41 | protected Token tokenOverride(Token token) { 42 | // subclasses can override this to modify tokens during formatting 43 | return token; 44 | } 45 | 46 | /** 47 | * Formats whitespaces in a SQL string to make it easier to read. 48 | * 49 | * @param query The SQL query string 50 | * @return formatted query 51 | */ 52 | public String format(String query) { 53 | this.tokens = this.tokenizer().tokenize(query); 54 | String formattedQuery = this.getFormattedQueryFromTokens(); 55 | 56 | return formattedQuery.trim(); 57 | } 58 | 59 | private String getFormattedQueryFromTokens() { 60 | String formattedQuery = ""; 61 | 62 | int _index = -1; 63 | for (Token token : this.tokens) { 64 | this.index = ++_index; 65 | 66 | token = this.tokenOverride(token); 67 | 68 | if (token.type == TokenTypes.LINE_COMMENT) { 69 | formattedQuery = this.formatLineComment(token, formattedQuery); 70 | } else if (token.type == TokenTypes.BLOCK_COMMENT) { 71 | formattedQuery = this.formatBlockComment(token, formattedQuery); 72 | } else if (token.type == TokenTypes.RESERVED_TOP_LEVEL) { 73 | formattedQuery = this.formatToplevelReservedWord(token, formattedQuery); 74 | this.previousReservedToken = token; 75 | } else if (token.type == TokenTypes.RESERVED_TOP_LEVEL_NO_INDENT) { 76 | formattedQuery = this.formatTopLevelReservedWordNoIndent(token, formattedQuery); 77 | this.previousReservedToken = token; 78 | } else if (token.type == TokenTypes.RESERVED_NEWLINE) { 79 | formattedQuery = this.formatNewlineReservedWord(token, formattedQuery); 80 | this.previousReservedToken = token; 81 | } else if (token.type == TokenTypes.RESERVED) { 82 | formattedQuery = this.formatWithSpaces(token, formattedQuery); 83 | this.previousReservedToken = token; 84 | } else if (token.type == TokenTypes.OPEN_PAREN) { 85 | formattedQuery = this.formatOpeningParentheses(token, formattedQuery); 86 | } else if (token.type == TokenTypes.CLOSE_PAREN) { 87 | formattedQuery = this.formatClosingParentheses(token, formattedQuery); 88 | } else if (token.type == TokenTypes.PLACEHOLDER) { 89 | formattedQuery = this.formatPlaceholder(token, formattedQuery); 90 | } else if (token.value.equals(",")) { 91 | formattedQuery = this.formatComma(token, formattedQuery); 92 | } else if (token.value.equals(":")) { 93 | formattedQuery = this.formatWithSpaceAfter(token, formattedQuery); 94 | } else if (token.value.equals(".")) { 95 | formattedQuery = this.formatWithoutSpaces(token, formattedQuery); 96 | } else if (token.value.equals(";")) { 97 | formattedQuery = this.formatQuerySeparator(token, formattedQuery); 98 | } else { 99 | formattedQuery = this.formatWithSpaces(token, formattedQuery); 100 | } 101 | } 102 | 103 | return formattedQuery; 104 | } 105 | 106 | private String formatLineComment(Token token, String query) { 107 | return this.addNewline(query + this.show(token)); 108 | } 109 | 110 | private String formatBlockComment(Token token, String query) { 111 | return this.addNewline(this.addNewline(query) + this.indentComment(token.value)); 112 | } 113 | 114 | private String indentComment(String comment) { 115 | return comment.replaceAll("\n", "\n" + this.indentation.getIndent()); 116 | } 117 | 118 | private String formatTopLevelReservedWordNoIndent(Token token, String query) { 119 | this.indentation.decreaseTopLevel(); 120 | query = this.addNewline(query) + this.equalizeWhitespace(this.show(token)); 121 | return this.addNewline(query); 122 | } 123 | 124 | private String formatToplevelReservedWord(Token token, String query) { 125 | this.indentation.decreaseTopLevel(); 126 | 127 | query = this.addNewline(query); 128 | 129 | this.indentation.increaseToplevel(); 130 | 131 | query += this.equalizeWhitespace(this.show(token)); 132 | return this.addNewline(query); 133 | } 134 | 135 | private String formatNewlineReservedWord(Token token, String query) { 136 | if (Token.isAnd(token) && Token.isBetween(this.tokenLookBehind(2))) { 137 | return this.formatWithSpaces(token, query); 138 | } 139 | return this.addNewline(query) + this.equalizeWhitespace(this.show(token)) + " "; 140 | } 141 | 142 | // Replace any sequence of whitespace characters with single space 143 | private String equalizeWhitespace(String string) { 144 | return string.replaceAll("\\s+", " "); 145 | } 146 | 147 | private static final Set preserveWhitespaceFor = 148 | EnumSet.of( 149 | TokenTypes.OPEN_PAREN, 150 | TokenTypes.LINE_COMMENT, 151 | TokenTypes.OPERATOR, 152 | TokenTypes.RESERVED_NEWLINE); 153 | 154 | // Opening parentheses increase the block indent level and start a new line 155 | private String formatOpeningParentheses(Token token, String query) { 156 | // Take out the preceding space unless there was whitespace there in the original query 157 | // or another opening parens or line comment 158 | if (token.whitespaceBefore.isEmpty() 159 | && !Optional.ofNullable(this.tokenLookBehind()) 160 | .map(t -> preserveWhitespaceFor.contains(t.type)) 161 | .orElse(false)) { 162 | query = Util.trimSpacesEnd(query); 163 | } 164 | query += this.show(token); 165 | 166 | this.inlineBlock.beginIfPossible(this.tokens, this.index); 167 | 168 | if (!this.inlineBlock.isActive()) { 169 | this.indentation.increaseBlockLevel(); 170 | if (!cfg.skipWhitespaceNearBlockParentheses) { 171 | query = this.addNewline(query); 172 | } 173 | } 174 | return query; 175 | } 176 | 177 | // Closing parentheses decrease the block indent level 178 | private String formatClosingParentheses(Token token, String query) { 179 | if (this.inlineBlock.isActive()) { 180 | this.inlineBlock.end(); 181 | return this.formatWithSpaceAfter(token, query); 182 | } else { 183 | this.indentation.decreaseBlockLevel(); 184 | if (!cfg.skipWhitespaceNearBlockParentheses) { 185 | return this.formatWithSpaces(token, this.addNewline(query)); 186 | } else { 187 | return this.formatWithoutSpaces(token, query); 188 | } 189 | } 190 | } 191 | 192 | private String formatPlaceholder(Token token, String query) { 193 | return query + this.params.get(token) + " "; 194 | } 195 | 196 | // Commas start a new line (unless within inline parentheses or SQL "LIMIT" clause) 197 | private String formatComma(Token token, String query) { 198 | query = Util.trimSpacesEnd(query) + this.show(token) + " "; 199 | 200 | if (this.inlineBlock.isActive()) { 201 | return query; 202 | } else if (Token.isLimit(this.previousReservedToken)) { 203 | return query; 204 | } else { 205 | return this.addNewline(query); 206 | } 207 | } 208 | 209 | private String formatWithSpaceAfter(Token token, String query) { 210 | return Util.trimSpacesEnd(query) + this.show(token) + " "; 211 | } 212 | 213 | private String formatWithoutSpaces(Token token, String query) { 214 | return Util.trimSpacesEnd(query) + this.show(token); 215 | } 216 | 217 | private String formatWithSpaces(Token token, String query) { 218 | return query + this.show(token) + " "; 219 | } 220 | 221 | private String formatQuerySeparator(Token token, String query) { 222 | this.indentation.resetIndentation(); 223 | return Util.trimSpacesEnd(query) 224 | + this.show(token) 225 | + Util.repeat("\n", Optional.ofNullable(this.cfg.linesBetweenQueries).orElse(1)); 226 | } 227 | 228 | // Converts token to string (uppercasing it if needed) 229 | private String show(Token token) { 230 | if (this.cfg.uppercase 231 | && (token.type == TokenTypes.RESERVED 232 | || token.type == TokenTypes.RESERVED_TOP_LEVEL 233 | || token.type == TokenTypes.RESERVED_TOP_LEVEL_NO_INDENT 234 | || token.type == TokenTypes.RESERVED_NEWLINE 235 | || token.type == TokenTypes.OPEN_PAREN 236 | || token.type == TokenTypes.CLOSE_PAREN)) { 237 | return token.value.toUpperCase(); 238 | } else { 239 | return token.value; 240 | } 241 | } 242 | 243 | private String addNewline(String query) { 244 | query = Util.trimSpacesEnd(query); 245 | if (!query.endsWith("\n")) { 246 | query += "\n"; 247 | } 248 | return query + this.indentation.getIndent(); 249 | } 250 | 251 | protected Token tokenLookBehind() { 252 | return this.tokenLookBehind(1); 253 | } 254 | 255 | protected Token tokenLookBehind(int n) { 256 | return this.tokens.get(this.index - n); 257 | } 258 | 259 | protected Token tokenLookAhead() { 260 | return this.tokenLookAhead(1); 261 | } 262 | 263 | protected Token tokenLookAhead(int n) { 264 | return this.tokens.get(this.index + n); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/FormatConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** Configurations for formatting. */ 7 | public class FormatConfig { 8 | 9 | public static final String DEFAULT_INDENT = " "; 10 | public static final int DEFAULT_COLUMN_MAX_LENGTH = 50; 11 | 12 | public final String indent; 13 | public final int maxColumnLength; 14 | public final Params params; 15 | public final boolean uppercase; 16 | public final Integer linesBetweenQueries; 17 | public final boolean skipWhitespaceNearBlockParentheses; 18 | 19 | FormatConfig( 20 | String indent, 21 | int maxColumnLength, 22 | Params params, 23 | boolean uppercase, 24 | Integer linesBetweenQueries, 25 | boolean skipWhitespaceNearBlockParentheses) { 26 | this.indent = indent; 27 | this.maxColumnLength = maxColumnLength; 28 | this.params = params == null ? Params.EMPTY : params; 29 | this.uppercase = uppercase; 30 | this.linesBetweenQueries = linesBetweenQueries; 31 | this.skipWhitespaceNearBlockParentheses = skipWhitespaceNearBlockParentheses; 32 | } 33 | 34 | /** 35 | * Returns a new empty Builder. 36 | * 37 | * @return A new empty Builder 38 | */ 39 | public static FormatConfigBuilder builder() { 40 | return new FormatConfigBuilder(); 41 | } 42 | 43 | /** FormatConfigBuilder */ 44 | public static class FormatConfigBuilder { 45 | private String indent = DEFAULT_INDENT; 46 | private int maxColumnLength = DEFAULT_COLUMN_MAX_LENGTH; 47 | private Params params; 48 | private boolean uppercase; 49 | private Integer linesBetweenQueries; 50 | private boolean skipWhitespaceNearBlockParentheses; 51 | 52 | FormatConfigBuilder() {} 53 | 54 | /** 55 | * @param indent Characters used for indentation, default is " " (2 spaces) 56 | * @return This 57 | */ 58 | public FormatConfigBuilder indent(String indent) { 59 | this.indent = indent; 60 | return this; 61 | } 62 | 63 | /** 64 | * @param maxColumnLength Maximum length to treat inline block as one line 65 | * @return This 66 | */ 67 | public FormatConfigBuilder maxColumnLength(int maxColumnLength) { 68 | this.maxColumnLength = maxColumnLength; 69 | return this; 70 | } 71 | 72 | /** 73 | * @param params Collection of params for placeholder replacement 74 | * @return This 75 | */ 76 | public FormatConfigBuilder params(Params params) { 77 | this.params = params; 78 | return this; 79 | } 80 | 81 | /** 82 | * @param params Collection of params for placeholder replacement 83 | * @return This 84 | */ 85 | public FormatConfigBuilder params(Map params) { 86 | return params(Params.of(params)); 87 | } 88 | 89 | /** 90 | * @param params Collection of params for placeholder replacement 91 | * @return This 92 | */ 93 | public FormatConfigBuilder params(List params) { 94 | return params(Params.of(params)); 95 | } 96 | 97 | /** 98 | * @param uppercase Converts keywords to uppercase 99 | * @return This 100 | */ 101 | public FormatConfigBuilder uppercase(boolean uppercase) { 102 | this.uppercase = uppercase; 103 | return this; 104 | } 105 | 106 | /** 107 | * @param linesBetweenQueries How many line breaks between queries 108 | * @return This 109 | */ 110 | public FormatConfigBuilder linesBetweenQueries(int linesBetweenQueries) { 111 | this.linesBetweenQueries = linesBetweenQueries; 112 | return this; 113 | } 114 | 115 | /** 116 | * @param skipWhitespaceNearBlockParentheses skip adding whitespace before and after block 117 | * Parentheses 118 | * @return This 119 | */ 120 | public FormatConfigBuilder skipWhitespaceNearBlockParentheses( 121 | boolean skipWhitespaceNearBlockParentheses) { 122 | this.skipWhitespaceNearBlockParentheses = skipWhitespaceNearBlockParentheses; 123 | return this; 124 | } 125 | 126 | /** 127 | * Returns an instance of FormatConfig created from the fields set on this builder. 128 | * 129 | * @return FormatConfig 130 | */ 131 | public FormatConfig build() { 132 | return new FormatConfig( 133 | this.indent, 134 | this.maxColumnLength, 135 | this.params, 136 | this.uppercase, 137 | this.linesBetweenQueries, 138 | this.skipWhitespaceNearBlockParentheses); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/Indentation.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import java.util.Stack; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.IntStream; 6 | 7 | /** 8 | * Manages indentation levels. 9 | * 10 | *

There are two types of indentation levels: 11 | * 12 | *

- BLOCK_LEVEL : increased by open-parenthesis - TOP_LEVEL : increased by RESERVED_TOPLEVEL 13 | * words 14 | */ 15 | public class Indentation { 16 | 17 | enum IndentTypes { 18 | INDENT_TYPE_TOP_LEVEL, 19 | INDENT_TYPE_BLOCK_LEVEL 20 | } 21 | 22 | private final String indent; 23 | private final Stack indentTypes; 24 | 25 | /** 26 | * @param indent Indent value, default is " " (2 spaces) 27 | */ 28 | Indentation(String indent) { 29 | this.indent = indent; 30 | this.indentTypes = new Stack<>(); 31 | } 32 | 33 | /** 34 | * Returns current indentation string. 35 | * 36 | * @return {String} 37 | */ 38 | public String getIndent() { 39 | return IntStream.range(0, this.indentTypes.size()) 40 | .mapToObj(i -> this.indent) 41 | .collect(Collectors.joining()); 42 | } 43 | 44 | /** Increases indentation by one top-level indent. */ 45 | void increaseToplevel() { 46 | this.indentTypes.push(IndentTypes.INDENT_TYPE_TOP_LEVEL); 47 | } 48 | 49 | /** Increases indentation by one block-level indent. */ 50 | void increaseBlockLevel() { 51 | this.indentTypes.push(IndentTypes.INDENT_TYPE_BLOCK_LEVEL); 52 | } 53 | 54 | /** 55 | * Decreases indentation by one top-level indent. Does nothing when the previous indent is not 56 | * top-level. 57 | */ 58 | void decreaseTopLevel() { 59 | if (!this.indentTypes.isEmpty() 60 | && this.indentTypes.peek() == IndentTypes.INDENT_TYPE_TOP_LEVEL) { 61 | this.indentTypes.pop(); 62 | } 63 | } 64 | 65 | /** 66 | * Decreases indentation by one block-level indent. If there are top-level indents within the 67 | * block-level indent, throws away these as well. 68 | */ 69 | void decreaseBlockLevel() { 70 | while (this.indentTypes.size() > 0) { 71 | IndentTypes type = this.indentTypes.pop(); 72 | if (type != IndentTypes.INDENT_TYPE_TOP_LEVEL) { 73 | break; 74 | } 75 | } 76 | } 77 | 78 | void resetIndentation() { 79 | this.indentTypes.clear(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/InlineBlock.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.util.JSLikeList; 4 | 5 | /** 6 | * Bookkeeper for inline blocks. 7 | * 8 | *

Inline blocks are parenthized expressions that are shorter than maxColumnLength. These blocks 9 | * are formatted on a single line, unlike longer parenthized expressions where open-parenthesis 10 | * causes newline and increase of indentation. 11 | */ 12 | class InlineBlock { 13 | 14 | private int level; 15 | private final int maxColumnLength; 16 | 17 | InlineBlock(int maxColumnLength) { 18 | this.maxColumnLength = maxColumnLength; 19 | this.level = 0; 20 | } 21 | 22 | /** 23 | * Begins inline block when lookahead through upcoming tokens determines that the block would be 24 | * smaller than INLINE_MAX_LENGTH. 25 | * 26 | * @param tokens Array of all tokens 27 | * @param index Current token position 28 | */ 29 | void beginIfPossible(JSLikeList tokens, int index) { 30 | if (this.level == 0 && this.isInlineBlock(tokens, index)) { 31 | this.level = 1; 32 | } else if (this.level > 0) { 33 | this.level++; 34 | } else { 35 | this.level = 0; 36 | } 37 | } 38 | 39 | /** Finishes current inline block. There might be several nested ones. */ 40 | public void end() { 41 | this.level--; 42 | } 43 | 44 | /** 45 | * True when inside an inline block 46 | * 47 | * @return {Boolean} 48 | */ 49 | boolean isActive() { 50 | return this.level > 0; 51 | } 52 | 53 | // Check if this should be an inline parentheses block 54 | // Examples are "NOW()", "COUNT(*)", "int(10)", key(`somecolumn`), DECIMAL(7,2) 55 | private boolean isInlineBlock(JSLikeList tokens, int index) { 56 | int length = 0; 57 | int level = 0; 58 | 59 | for (int i = index; i < tokens.size(); i++) { 60 | Token token = tokens.get(i); 61 | length += token.value.length(); 62 | 63 | // Overran max length 64 | if (length > maxColumnLength) { 65 | return false; 66 | } 67 | 68 | if (token.type == TokenTypes.OPEN_PAREN) { 69 | level++; 70 | } else if (token.type == TokenTypes.CLOSE_PAREN) { 71 | level--; 72 | if (level == 0) { 73 | return true; 74 | } 75 | } 76 | 77 | if (this.isForbiddenToken(token)) { 78 | return false; 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | // Reserved words that cause newlines, comments and semicolons 85 | // are not allowed inside inline parentheses block 86 | private boolean isForbiddenToken(Token token) { 87 | return token.type == TokenTypes.RESERVED_TOP_LEVEL 88 | || token.type == TokenTypes.RESERVED_NEWLINE 89 | || 90 | // originally `TokenTypes.LINE_COMMENT` but this symbol is not defined 91 | // token.type == TokenTypes.LINE_COMMENT || 92 | token.type == TokenTypes.BLOCK_COMMENT 93 | || token.value.equals(";"); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/Params.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Queue; 7 | 8 | /** Handles placeholder replacement with given params. */ 9 | public interface Params { 10 | 11 | public static final Params EMPTY = new Empty(); 12 | 13 | boolean isEmpty(); 14 | 15 | Object get(); 16 | 17 | Object getByName(String key); 18 | 19 | /** 20 | * @param params query param 21 | */ 22 | public static Params of(Map params) { 23 | return new NamedParams(params); 24 | } 25 | 26 | /** 27 | * @param params query param 28 | */ 29 | public static Params of(List params) { 30 | return new IndexedParams(params); 31 | } 32 | 33 | /** 34 | * Returns param value that matches given placeholder with param key. 35 | * 36 | * @param token token.key Placeholder key token.value Placeholder value 37 | * @return param or token.value when params are missing 38 | */ 39 | default Object get(Token token) { 40 | if (this.isEmpty()) { 41 | return token.value; 42 | } 43 | if (!(token.key == null || token.key.isEmpty())) { 44 | return this.getByName(token.key); 45 | } else { 46 | return this.get(); 47 | } 48 | } 49 | 50 | public static class NamedParams implements Params { 51 | private final Map params; 52 | 53 | NamedParams(Map params) { 54 | this.params = params; 55 | } 56 | 57 | public boolean isEmpty() { 58 | return this.params.isEmpty(); 59 | } 60 | 61 | @Override 62 | public Object get() { 63 | return null; 64 | } 65 | 66 | @Override 67 | public Object getByName(String key) { 68 | return this.params.get(key); 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return this.params.toString(); 74 | } 75 | } 76 | 77 | public static class IndexedParams implements Params { 78 | private final Queue params; 79 | 80 | IndexedParams(List params) { 81 | this.params = new ArrayDeque<>(params); 82 | } 83 | 84 | public boolean isEmpty() { 85 | return this.params.isEmpty(); 86 | } 87 | 88 | @Override 89 | public Object get() { 90 | return this.params.poll(); 91 | } 92 | 93 | @Override 94 | public Object getByName(String key) { 95 | return null; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return this.params.toString(); 101 | } 102 | } 103 | 104 | public static class Empty implements Params { 105 | Empty() {} 106 | 107 | public boolean isEmpty() { 108 | return true; 109 | } 110 | 111 | @Override 112 | public Object get() { 113 | return null; 114 | } 115 | 116 | @Override 117 | public Object getByName(String key) { 118 | return null; 119 | } 120 | 121 | @Override 122 | public String toString() { 123 | return "[]"; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/Token.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Function; 5 | import java.util.regex.Pattern; 6 | 7 | public class Token { 8 | public final TokenTypes type; 9 | public final String value; 10 | public final String regex; 11 | public final String whitespaceBefore; 12 | public final String key; 13 | 14 | public Token(TokenTypes type, String value, String regex, String whitespaceBefore, String key) { 15 | this.type = type; 16 | this.value = value; 17 | this.regex = regex; 18 | this.whitespaceBefore = whitespaceBefore; 19 | this.key = key; 20 | } 21 | 22 | public Token(TokenTypes type, String value, String regex, String whitespaceBefore) { 23 | this(type, value, regex, whitespaceBefore, null); 24 | } 25 | 26 | public Token(TokenTypes type, String value, String regex) { 27 | this(type, value, regex, null); 28 | } 29 | 30 | public Token(TokenTypes type, String value) { 31 | this(type, value, null, null); 32 | } 33 | 34 | public Token withWhitespaceBefore(String whitespaceBefore) { 35 | return new Token(this.type, this.value, this.regex, whitespaceBefore, this.key); 36 | } 37 | 38 | public Token withKey(String key) { 39 | return new Token(this.type, this.value, this.regex, this.whitespaceBefore, key); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "type: " + type + ", value: [" + value + "], regex: /" + regex + "/" + ", key: " + key; 45 | } 46 | 47 | private static final Pattern AND = 48 | Pattern.compile("^AND$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 49 | private static final Pattern BETWEEN = 50 | Pattern.compile("^BETWEEN$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 51 | private static final Pattern LIMIT = 52 | Pattern.compile("^LIMIT$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 53 | private static final Pattern SET = 54 | Pattern.compile("^SET$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 55 | private static final Pattern BY = 56 | Pattern.compile("^BY$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 57 | private static final Pattern WINDOW = 58 | Pattern.compile("^WINDOW$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 59 | private static final Pattern END = 60 | Pattern.compile("^END$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); 61 | 62 | private static Function isToken(TokenTypes type, Pattern pattern) { 63 | return token -> token.type == type && pattern.matcher(token.value).matches(); 64 | } 65 | 66 | public static boolean isAnd(Token token) { 67 | return isAnd(Optional.ofNullable(token)); 68 | } 69 | 70 | public static boolean isAnd(Optional token) { 71 | return token.map(isToken(TokenTypes.RESERVED_NEWLINE, AND)).orElse(false); 72 | } 73 | 74 | public static boolean isBetween(Token token) { 75 | return isBetween(Optional.ofNullable(token)); 76 | } 77 | 78 | public static boolean isBetween(Optional token) { 79 | return token.map(isToken(TokenTypes.RESERVED, BETWEEN)).orElse(false); 80 | } 81 | 82 | public static boolean isLimit(Token token) { 83 | return isLimit(Optional.ofNullable(token)); 84 | } 85 | 86 | public static boolean isLimit(Optional token) { 87 | return token.map(isToken(TokenTypes.RESERVED_TOP_LEVEL, LIMIT)).orElse(false); 88 | } 89 | 90 | public static boolean isSet(Token token) { 91 | return isSet(Optional.ofNullable(token)); 92 | } 93 | 94 | public static boolean isSet(Optional token) { 95 | return token.map(isToken(TokenTypes.RESERVED_TOP_LEVEL, SET)).orElse(false); 96 | } 97 | 98 | public static boolean isBy(Token token) { 99 | return isBy(Optional.ofNullable(token)); 100 | } 101 | 102 | public static boolean isBy(Optional token) { 103 | return token.map(isToken(TokenTypes.RESERVED, BY)).orElse(false); 104 | } 105 | 106 | public static boolean isWindow(Token token) { 107 | return isWindow(Optional.ofNullable(token)); 108 | } 109 | 110 | public static boolean isWindow(Optional token) { 111 | return token.map(isToken(TokenTypes.RESERVED_TOP_LEVEL, WINDOW)).orElse(false); 112 | } 113 | 114 | public static boolean isEnd(Token token) { 115 | return isEnd(Optional.ofNullable(token)); 116 | } 117 | 118 | public static boolean isEnd(Optional token) { 119 | return token.map(isToken(TokenTypes.CLOSE_PAREN, END)).orElse(false); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/TokenTypes.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | /** Constants for token types */ 4 | public enum TokenTypes { 5 | WORD, 6 | STRING, 7 | RESERVED, 8 | RESERVED_TOP_LEVEL, 9 | RESERVED_TOP_LEVEL_NO_INDENT, 10 | RESERVED_NEWLINE, 11 | OPERATOR, 12 | OPEN_PAREN, 13 | CLOSE_PAREN, 14 | LINE_COMMENT, 15 | BLOCK_COMMENT, 16 | NUMBER, 17 | PLACEHOLDER, 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/Tokenizer.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.util.JSLikeList; 4 | import com.github.vertical_blank.sqlformatter.core.util.RegexUtil; 5 | import com.github.vertical_blank.sqlformatter.core.util.Util; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | public class Tokenizer { 13 | // private final Pattern WHITESPACE_PATTERN; 14 | private final Pattern NUMBER_PATTERN; 15 | private final Pattern OPERATOR_PATTERN; 16 | 17 | private final Pattern BLOCK_COMMENT_PATTERN; 18 | private final Pattern LINE_COMMENT_PATTERN; 19 | 20 | private final Pattern RESERVED_TOP_LEVEL_PATTERN; 21 | private final Pattern RESERVED_TOP_LEVEL_NO_INDENT_PATTERN; 22 | private final Pattern RESERVED_NEWLINE_PATTERN; 23 | private final Pattern RESERVED_PLAIN_PATTERN; 24 | 25 | private final Pattern WORD_PATTERN; 26 | private final Pattern STRING_PATTERN; 27 | 28 | private final Pattern OPEN_PAREN_PATTERN; 29 | private final Pattern CLOSE_PAREN_PATTERN; 30 | 31 | private final Pattern INDEXED_PLACEHOLDER_PATTERN; 32 | private final Pattern IDENT_NAMED_PLACEHOLDER_PATTERN; 33 | private final Pattern STRING_NAMED_PLACEHOLDER_PATTERN; 34 | 35 | /** 36 | * @param cfg {String[]} cfg.reservedWords Reserved words in SQL {String[]} 37 | * cfg.reservedTopLevelWords Words that are set to new line separately {String[]} 38 | * cfg.reservedNewlineWords Words that are set to newline {String[]} cfg.stringTypes String 39 | * types to enable: "", "", ``, [], N"" {String[]} cfg.openParens Opening parentheses to 40 | * enable, like (, [ {String[]} cfg.closeParens Closing parentheses to enable, like ), ] 41 | * {String[]} cfg.indexedPlaceholderTypes Prefixes for indexed placeholders, like ? {String[]} 42 | * cfg.namedPlaceholderTypes Prefixes for named placeholders, like @ and : {String[]} 43 | * cfg.lineCommentTypes Line comments to enable, like # and -- {String[]} cfg.specialWordChars 44 | * Special chars that can be found inside of words, like @ and # 45 | */ 46 | public Tokenizer(DialectConfig cfg) { 47 | // this.WHITESPACE_PATTERN = Pattern.compile("^(\\s+)"); 48 | this.NUMBER_PATTERN = 49 | Pattern.compile( 50 | "^((-\\s*)?[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+(\\.[0-9]+)?)?|0x[0-9a-fA-F]+|0b[01]+)\\b"); 51 | this.OPERATOR_PATTERN = 52 | Pattern.compile( 53 | RegexUtil.createOperatorRegex( 54 | new JSLikeList<>(Arrays.asList("<>", "<=", ">=")).with(cfg.operators))); 55 | 56 | // this.BLOCK_COMMENT_REGEX = /^(\/\*[^]*?(?:\*\/|$))/; 57 | this.BLOCK_COMMENT_PATTERN = Pattern.compile("^(/\\*(?s).*?(?:\\*/|$))"); 58 | this.LINE_COMMENT_PATTERN = 59 | Pattern.compile(RegexUtil.createLineCommentRegex(new JSLikeList<>(cfg.lineCommentTypes))); 60 | 61 | this.RESERVED_TOP_LEVEL_PATTERN = 62 | Pattern.compile( 63 | RegexUtil.createReservedWordRegex(new JSLikeList<>(cfg.reservedTopLevelWords))); 64 | this.RESERVED_TOP_LEVEL_NO_INDENT_PATTERN = 65 | Pattern.compile( 66 | RegexUtil.createReservedWordRegex(new JSLikeList<>(cfg.reservedTopLevelWordsNoIndent))); 67 | this.RESERVED_NEWLINE_PATTERN = 68 | Pattern.compile( 69 | RegexUtil.createReservedWordRegex(new JSLikeList<>(cfg.reservedNewlineWords))); 70 | this.RESERVED_PLAIN_PATTERN = 71 | Pattern.compile(RegexUtil.createReservedWordRegex(new JSLikeList<>(cfg.reservedWords))); 72 | 73 | this.WORD_PATTERN = 74 | Pattern.compile(RegexUtil.createWordRegex(new JSLikeList<>(cfg.specialWordChars))); 75 | this.STRING_PATTERN = 76 | Pattern.compile(RegexUtil.createStringRegex(new JSLikeList<>(cfg.stringTypes))); 77 | 78 | this.OPEN_PAREN_PATTERN = 79 | Pattern.compile(RegexUtil.createParenRegex(new JSLikeList<>(cfg.openParens))); 80 | this.CLOSE_PAREN_PATTERN = 81 | Pattern.compile(RegexUtil.createParenRegex(new JSLikeList<>(cfg.closeParens))); 82 | 83 | this.INDEXED_PLACEHOLDER_PATTERN = 84 | RegexUtil.createPlaceholderRegexPattern( 85 | new JSLikeList<>(cfg.indexedPlaceholderTypes), "[0-9]*"); 86 | this.IDENT_NAMED_PLACEHOLDER_PATTERN = 87 | RegexUtil.createPlaceholderRegexPattern( 88 | new JSLikeList<>(cfg.namedPlaceholderTypes), "[a-zA-Z0-9._$]+"); 89 | this.STRING_NAMED_PLACEHOLDER_PATTERN = 90 | RegexUtil.createPlaceholderRegexPattern( 91 | new JSLikeList<>(cfg.namedPlaceholderTypes), 92 | RegexUtil.createStringPattern(new JSLikeList<>(cfg.stringTypes))); 93 | } 94 | 95 | /** 96 | * Takes a SQL string and breaks it into tokens. Each token is an object with type and value. 97 | * 98 | * @param input input The SQL string 99 | * @return {Object[]} tokens An array of tokens. 100 | */ 101 | public JSLikeList tokenize(String input) { 102 | List tokens = new ArrayList<>(); 103 | Token token = null; 104 | 105 | // Keep processing the string until it is empty 106 | while (!input.isEmpty()) { 107 | // grab any preceding whitespace 108 | String[] findBeforeWhitespace = findBeforeWhitespace(input); 109 | String whitespaceBefore = findBeforeWhitespace[0]; 110 | input = findBeforeWhitespace[1]; 111 | 112 | if (!input.isEmpty()) { 113 | // Get the next token and the token type 114 | token = this.getNextToken(input, token); 115 | // Advance the string 116 | input = input.substring(token.value.length()); 117 | 118 | tokens.add(token.withWhitespaceBefore(whitespaceBefore)); 119 | } 120 | } 121 | return new JSLikeList<>(tokens); 122 | } 123 | 124 | private String[] findBeforeWhitespace(String input) { 125 | int index = 0; 126 | char[] chars = input.toCharArray(); 127 | int beforeLength = chars.length; 128 | while (index != beforeLength && Character.isWhitespace(chars[index])) { 129 | index++; 130 | } 131 | return new String[] { 132 | new String(chars, 0, index), new String(chars, index, beforeLength - index) 133 | }; 134 | } 135 | 136 | // private String getWhitespace(String input) { 137 | // String firstMatch = getFirstMatch(input, this.WHITESPACE_PATTERN); 138 | // return firstMatch != null ? firstMatch : ""; 139 | // } 140 | 141 | private Token getNextToken(String input, Token previousToken) { 142 | return Util.firstNotnull( 143 | () -> this.getCommentToken(input), 144 | () -> this.getStringToken(input), 145 | () -> this.getOpenParenToken(input), 146 | () -> this.getCloseParenToken(input), 147 | () -> this.getPlaceholderToken(input), 148 | () -> this.getNumberToken(input), 149 | () -> this.getReservedWordToken(input, previousToken), 150 | () -> this.getWordToken(input), 151 | () -> this.getOperatorToken(input)); 152 | } 153 | 154 | private Token getCommentToken(String input) { 155 | return Util.firstNotnull( 156 | () -> this.getLineCommentToken(input), () -> this.getBlockCommentToken(input)); 157 | } 158 | 159 | private Token getLineCommentToken(String input) { 160 | return this.getTokenOnFirstMatch(input, TokenTypes.LINE_COMMENT, this.LINE_COMMENT_PATTERN); 161 | } 162 | 163 | private Token getBlockCommentToken(String input) { 164 | return this.getTokenOnFirstMatch(input, TokenTypes.BLOCK_COMMENT, this.BLOCK_COMMENT_PATTERN); 165 | } 166 | 167 | private Token getStringToken(String input) { 168 | return this.getTokenOnFirstMatch(input, TokenTypes.STRING, this.STRING_PATTERN); 169 | } 170 | 171 | private Token getOpenParenToken(String input) { 172 | return this.getTokenOnFirstMatch(input, TokenTypes.OPEN_PAREN, this.OPEN_PAREN_PATTERN); 173 | } 174 | 175 | private Token getCloseParenToken(String input) { 176 | return this.getTokenOnFirstMatch(input, TokenTypes.CLOSE_PAREN, this.CLOSE_PAREN_PATTERN); 177 | } 178 | 179 | private Token getPlaceholderToken(String input) { 180 | return Util.firstNotnull( 181 | () -> this.getIdentNamedPlaceholderToken(input), 182 | () -> this.getStringNamedPlaceholderToken(input), 183 | () -> this.getIndexedPlaceholderToken(input)); 184 | } 185 | 186 | private Token getIdentNamedPlaceholderToken(String input) { 187 | return this.getPlaceholderTokenWithKey( 188 | input, this.IDENT_NAMED_PLACEHOLDER_PATTERN, v -> v.substring(1)); 189 | } 190 | 191 | private Token getStringNamedPlaceholderToken(String input) { 192 | return this.getPlaceholderTokenWithKey( 193 | input, 194 | this.STRING_NAMED_PLACEHOLDER_PATTERN, 195 | v -> 196 | this.getEscapedPlaceholderKey( 197 | v.substring(2, v.length() - 1), v.substring(v.length() - 1))); 198 | } 199 | 200 | private Token getIndexedPlaceholderToken(String input) { 201 | return this.getPlaceholderTokenWithKey( 202 | input, this.INDEXED_PLACEHOLDER_PATTERN, v -> v.substring(1)); 203 | } 204 | 205 | private Token getPlaceholderTokenWithKey( 206 | String input, Pattern regex, java.util.function.Function parseKey) { 207 | Token token = this.getTokenOnFirstMatch(input, TokenTypes.PLACEHOLDER, regex); 208 | if (token != null) { 209 | return token.withKey(parseKey.apply(token.value)); 210 | } 211 | return token; 212 | } 213 | 214 | private String getEscapedPlaceholderKey(String key, String quoteChar) { 215 | return key.replaceAll(RegexUtil.escapeRegExp("\\") + quoteChar, quoteChar); 216 | } 217 | 218 | // Decimal, binary, or hex numbers 219 | private Token getNumberToken(String input) { 220 | return this.getTokenOnFirstMatch(input, TokenTypes.NUMBER, this.NUMBER_PATTERN); 221 | } 222 | 223 | // Punctuation and symbols 224 | private Token getOperatorToken(String input) { 225 | return this.getTokenOnFirstMatch(input, TokenTypes.OPERATOR, this.OPERATOR_PATTERN); 226 | } 227 | 228 | private Token getReservedWordToken(String input, Token previousToken) { 229 | // A reserved word cannot be preceded by a "." 230 | // this makes it so in "mytable.from", "from" is not considered a reserved word 231 | if (previousToken != null && previousToken.value != null && previousToken.value.equals(".")) { 232 | return null; 233 | } 234 | return Util.firstNotnull( 235 | () -> this.getToplevelReservedToken(input), 236 | () -> this.getNewlineReservedToken(input), 237 | () -> this.getTopLevelReservedTokenNoIndent(input), 238 | () -> this.getPlainReservedToken(input)); 239 | } 240 | 241 | private Token getToplevelReservedToken(String input) { 242 | return this.getTokenOnFirstMatch( 243 | input, TokenTypes.RESERVED_TOP_LEVEL, this.RESERVED_TOP_LEVEL_PATTERN); 244 | } 245 | 246 | private Token getNewlineReservedToken(String input) { 247 | return this.getTokenOnFirstMatch( 248 | input, TokenTypes.RESERVED_NEWLINE, this.RESERVED_NEWLINE_PATTERN); 249 | } 250 | 251 | private Token getTopLevelReservedTokenNoIndent(String input) { 252 | return this.getTokenOnFirstMatch( 253 | input, TokenTypes.RESERVED_TOP_LEVEL_NO_INDENT, this.RESERVED_TOP_LEVEL_NO_INDENT_PATTERN); 254 | } 255 | 256 | private Token getPlainReservedToken(String input) { 257 | return this.getTokenOnFirstMatch(input, TokenTypes.RESERVED, this.RESERVED_PLAIN_PATTERN); 258 | } 259 | 260 | private Token getWordToken(String input) { 261 | return this.getTokenOnFirstMatch(input, TokenTypes.WORD, this.WORD_PATTERN); 262 | } 263 | 264 | private static String getFirstMatch(String input, Pattern regex) { 265 | if (regex == null) { 266 | return null; 267 | } 268 | 269 | Matcher matcher = regex.matcher(input); 270 | if (matcher.find()) { 271 | return matcher.group(1); 272 | } else { 273 | return null; 274 | } 275 | } 276 | 277 | private Token getTokenOnFirstMatch(String input, TokenTypes type, Pattern regex) { 278 | String firstMatch = getFirstMatch(input, regex); 279 | 280 | if (firstMatch != null) { 281 | return new Token(type, firstMatch); 282 | } else { 283 | return null; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/util/JSLikeList.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.function.Function; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | public class JSLikeList implements Iterable { 13 | 14 | private List tList; 15 | 16 | public JSLikeList(List tList) { 17 | this.tList = tList == null ? Collections.emptyList() : new ArrayList<>(tList); 18 | } 19 | 20 | public List toList() { 21 | return this.tList; 22 | } 23 | 24 | public JSLikeList map(Function mapper) { 25 | return new JSLikeList<>(this.tList.stream().map(mapper).collect(Collectors.toList())); 26 | } 27 | 28 | public String join(CharSequence delimiter) { 29 | return this.tList.stream() 30 | .map(Optional::ofNullable) 31 | .map(x -> x.map(String::valueOf).orElse("")) 32 | .collect(Collectors.joining(delimiter)); 33 | } 34 | 35 | public JSLikeList with(List other) { 36 | List list = new ArrayList<>(); 37 | list.addAll(this.toList()); 38 | list.addAll(other); 39 | return new JSLikeList<>(list); 40 | } 41 | 42 | public String join() { 43 | return join(","); 44 | } 45 | 46 | public boolean isEmpty() { 47 | return this.tList == null || this.tList.isEmpty(); 48 | } 49 | 50 | public T get(int index) { 51 | if (index < 0) { 52 | return null; 53 | } 54 | if (tList.size() <= index) { 55 | return null; 56 | } 57 | return this.tList.get(index); 58 | } 59 | 60 | @Override 61 | public Iterator iterator() { 62 | return this.tList.iterator(); 63 | } 64 | 65 | public Stream stream() { 66 | return this.tList.stream(); 67 | } 68 | 69 | public int size() { 70 | return this.tList.size(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/util/RegexUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core.util; 2 | 3 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral; 4 | import java.util.regex.Pattern; 5 | import java.util.stream.Collectors; 6 | import java.util.stream.Stream; 7 | 8 | public class RegexUtil { 9 | 10 | private static final String ESCAPE_REGEX = 11 | Stream.of("^", "$", "\\", ".", "*", "+", "*", "?", "(", ")", "[", "]", "{", "}", "|") 12 | .map(spChr -> "(\\" + spChr + ")") 13 | .collect(Collectors.joining("|")); 14 | public static final Pattern ESCAPE_REGEX_PATTERN = Pattern.compile(ESCAPE_REGEX); 15 | 16 | public static String escapeRegExp(String s) { 17 | return ESCAPE_REGEX_PATTERN.matcher(s).replaceAll("\\\\$0"); 18 | } 19 | 20 | public static String createOperatorRegex(JSLikeList multiLetterOperators) { 21 | return String.format( 22 | "^(%s|.)", 23 | Util.sortByLengthDesc(multiLetterOperators).map(RegexUtil::escapeRegExp).join("|")); 24 | } 25 | 26 | public static String createLineCommentRegex(JSLikeList lineCommentTypes) { 27 | return String.format( 28 | "^((?:%s).*?)(?:\r\n|\r|\n|$)", lineCommentTypes.map(RegexUtil::escapeRegExp).join("|")); 29 | } 30 | 31 | public static String createReservedWordRegex(JSLikeList reservedWords) { 32 | if (reservedWords.isEmpty()) { 33 | return "^\b$"; 34 | } 35 | String reservedWordsPattern = 36 | Util.sortByLengthDesc(reservedWords).join("|").replaceAll(" ", "\\\\s+"); 37 | return "(?i)" + "^(" + reservedWordsPattern + ")\\b"; 38 | } 39 | 40 | public static String createWordRegex(JSLikeList specialChars) { 41 | return "^([\\p{IsAlphabetic}\\p{Mc}\\p{Me}\\p{Mn}\\p{Nd}\\p{Pc}\\p{IsJoin_Control}" 42 | + specialChars.join("") 43 | + "]+)"; 44 | } 45 | 46 | public static String createStringRegex(JSLikeList stringTypes) { 47 | return "^(" + createStringPattern(stringTypes) + ")"; 48 | } 49 | 50 | // This enables the following string patterns: 51 | // 1. backtick quoted string using `` to escape 52 | // 2. square bracket quoted string (SQL Server) using ]] to escape 53 | // 3. double quoted string using "" or \" to escape 54 | // 4. single quoted string using '' or \' to escape 55 | // 5. national character quoted string using N'' or N\' to escape 56 | public static String createStringPattern(JSLikeList stringTypes) { 57 | return stringTypes.map(StringLiteral::get).join("|"); 58 | } 59 | 60 | public static String createParenRegex(JSLikeList parens) { 61 | return "(?i)^(" + parens.map(RegexUtil::escapeParen).join("|") + ")"; 62 | } 63 | 64 | public static String escapeParen(String paren) { 65 | if (paren.length() == 1) { 66 | // A single punctuation character 67 | return RegexUtil.escapeRegExp(paren); 68 | } else { 69 | // longer word 70 | return "\\b" + paren + "\\b"; 71 | } 72 | } 73 | 74 | public static Pattern createPlaceholderRegexPattern(JSLikeList types, String pattern) { 75 | if (types.isEmpty()) { 76 | return null; 77 | } 78 | String typesRegex = types.map(RegexUtil::escapeRegExp).join("|"); 79 | 80 | return Pattern.compile(String.format("^((?:%s)(?:%s))", typesRegex, pattern)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/core/util/Util.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.core.util; 2 | 3 | import java.util.Collections; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.function.Supplier; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | public class Util { 12 | 13 | public static List nullToEmpty(List ts) { 14 | if (ts == null) { 15 | return Collections.emptyList(); 16 | } else { 17 | return ts; 18 | } 19 | } 20 | 21 | public static String trimSpacesEnd(String s) { 22 | int endIndex = s.length(); 23 | char[] chars = s.toCharArray(); 24 | while (endIndex > 0 && (chars[endIndex - 1] == ' ' || chars[endIndex - 1] == '\t')) { 25 | endIndex--; 26 | } 27 | return new String(chars, 0, endIndex); 28 | // return s.replaceAll("[ \t]+$", ""); 29 | } 30 | 31 | @SafeVarargs 32 | public static R firstNotnull(Supplier... sups) { 33 | for (Supplier sup : sups) { 34 | R ret = sup.get(); 35 | if (ret != null) { 36 | return ret; 37 | } 38 | } 39 | return null; 40 | } 41 | 42 | @SafeVarargs 43 | public static Optional firstPresent(Supplier>... sups) { 44 | for (Supplier> sup : sups) { 45 | Optional ret = sup.get(); 46 | if (ret.isPresent()) { 47 | return ret; 48 | } 49 | } 50 | return Optional.empty(); 51 | } 52 | 53 | public static String repeat(String s, int n) { 54 | return Stream.generate(() -> s).limit(n).collect(Collectors.joining()); 55 | } 56 | 57 | public static List concat(List l1, List l2) { 58 | return Stream.of(l1, l2).flatMap(List::stream).collect(Collectors.toList()); 59 | } 60 | 61 | public static JSLikeList sortByLengthDesc(JSLikeList strings) { 62 | return new JSLikeList<>( 63 | strings.stream() 64 | .sorted(Comparator.comparingInt(String::length).reversed()) 65 | .collect(Collectors.toList())); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/Dialect.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.function.Function; 9 | 10 | public enum Dialect { 11 | Db2(Db2Formatter::new), 12 | MariaDb(MariaDbFormatter::new), 13 | MySql(MySqlFormatter::new), 14 | N1ql(N1qlFormatter::new), 15 | PlSql(PlSqlFormatter::new, "pl/sql"), 16 | PostgreSql(PostgreSqlFormatter::new), 17 | Redshift(RedshiftFormatter::new), 18 | SparkSql(SparkSqlFormatter::new, "spark"), 19 | StandardSql(StandardSqlFormatter::new, "sql"), 20 | TSql(TSqlFormatter::new), 21 | ; 22 | 23 | public final Function func; 24 | public final List aliases; 25 | 26 | Dialect(Function func, String... aliases) { 27 | this.func = func; 28 | this.aliases = Arrays.asList(aliases); 29 | } 30 | 31 | private boolean matches(String name) { 32 | return this.name().equalsIgnoreCase(name) 33 | || this.aliases.stream().anyMatch(s -> s.equalsIgnoreCase(name)); 34 | } 35 | 36 | public static Optional nameOf(String name) { 37 | return Arrays.stream(values()).filter(d -> d.matches(name)).findFirst(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/DialectConfigurator.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 4 | 5 | public interface DialectConfigurator { 6 | DialectConfig dialectConfig(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/MariaDbFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class MariaDbFormatter extends AbstractFormatter { 11 | 12 | private static final List reservedWords = 13 | Arrays.asList( 14 | "ACCESSIBLE", 15 | "ADD", 16 | "ALL", 17 | "ALTER", 18 | "ANALYZE", 19 | "AND", 20 | "AS", 21 | "ASC", 22 | "ASENSITIVE", 23 | "BEFORE", 24 | "BETWEEN", 25 | "BIGINT", 26 | "BINARY", 27 | "BLOB", 28 | "BOTH", 29 | "BY", 30 | "CALL", 31 | "CASCADE", 32 | "CASE", 33 | "CHANGE", 34 | "CHAR", 35 | "CHARACTER", 36 | "CHECK", 37 | "COLLATE", 38 | "COLUMN", 39 | "CONDITION", 40 | "CONSTRAINT", 41 | "CONTINUE", 42 | "CONVERT", 43 | "CREATE", 44 | "CROSS", 45 | "CURRENT_DATE", 46 | "CURRENT_ROLE", 47 | "CURRENT_TIME", 48 | "CURRENT_TIMESTAMP", 49 | "CURRENT_USER", 50 | "CURSOR", 51 | "DATABASE", 52 | "DATABASES", 53 | "DAY_HOUR", 54 | "DAY_MICROSECOND", 55 | "DAY_MINUTE", 56 | "DAY_SECOND", 57 | "DEC", 58 | "DECIMAL", 59 | "DECLARE", 60 | "DEFAULT", 61 | "DELAYED", 62 | "DELETE", 63 | "DESC", 64 | "DESCRIBE", 65 | "DETERMINISTIC", 66 | "DISTINCT", 67 | "DISTINCTROW", 68 | "DIV", 69 | "DO_DOMAIN_IDS", 70 | "DOUBLE", 71 | "DROP", 72 | "DUAL", 73 | "EACH", 74 | "ELSE", 75 | "ELSEIF", 76 | "ENCLOSED", 77 | "ESCAPED", 78 | "EXCEPT", 79 | "EXISTS", 80 | "EXIT", 81 | "EXPLAIN", 82 | "FALSE", 83 | "FETCH", 84 | "FLOAT", 85 | "FLOAT4", 86 | "FLOAT8", 87 | "FOR", 88 | "FORCE", 89 | "FOREIGN", 90 | "FROM", 91 | "FULLTEXT", 92 | "GENERAL", 93 | "GRANT", 94 | "GROUP", 95 | "HAVING", 96 | "HIGH_PRIORITY", 97 | "HOUR_MICROSECOND", 98 | "HOUR_MINUTE", 99 | "HOUR_SECOND", 100 | "IF", 101 | "IGNORE", 102 | "IGNORE_DOMAIN_IDS", 103 | "IGNORE_SERVER_IDS", 104 | "IN", 105 | "INDEX", 106 | "INFILE", 107 | "INNER", 108 | "INOUT", 109 | "INSENSITIVE", 110 | "INSERT", 111 | "INT", 112 | "INT1", 113 | "INT2", 114 | "INT3", 115 | "INT4", 116 | "INT8", 117 | "INTEGER", 118 | "INTERSECT", 119 | "INTERVAL", 120 | "INTO", 121 | "IS", 122 | "ITERATE", 123 | "JOIN", 124 | "KEY", 125 | "KEYS", 126 | "KILL", 127 | "LEADING", 128 | "LEAVE", 129 | "LEFT", 130 | "LIKE", 131 | "LIMIT", 132 | "LINEAR", 133 | "LINES", 134 | "LOAD", 135 | "LOCALTIME", 136 | "LOCALTIMESTAMP", 137 | "LOCK", 138 | "LONG", 139 | "LONGBLOB", 140 | "LONGTEXT", 141 | "LOOP", 142 | "LOW_PRIORITY", 143 | "MASTER_HEARTBEAT_PERIOD", 144 | "MASTER_SSL_VERIFY_SERVER_CERT", 145 | "MATCH", 146 | "MAXVALUE", 147 | "MEDIUMBLOB", 148 | "MEDIUMINT", 149 | "MEDIUMTEXT", 150 | "MIDDLEINT", 151 | "MINUTE_MICROSECOND", 152 | "MINUTE_SECOND", 153 | "MOD", 154 | "MODIFIES", 155 | "NATURAL", 156 | "NOT", 157 | "NO_WRITE_TO_BINLOG", 158 | "NULL", 159 | "NUMERIC", 160 | "ON", 161 | "OPTIMIZE", 162 | "OPTION", 163 | "OPTIONALLY", 164 | "OR", 165 | "ORDER", 166 | "OUT", 167 | "OUTER", 168 | "OUTFILE", 169 | "OVER", 170 | "PAGE_CHECKSUM", 171 | "PARSE_VCOL_EXPR", 172 | "PARTITION", 173 | "POSITION", 174 | "PRECISION", 175 | "PRIMARY", 176 | "PROCEDURE", 177 | "PURGE", 178 | "RANGE", 179 | "READ", 180 | "READS", 181 | "READ_WRITE", 182 | "REAL", 183 | "RECURSIVE", 184 | "REF_SYSTEM_ID", 185 | "REFERENCES", 186 | "REGEXP", 187 | "RELEASE", 188 | "RENAME", 189 | "REPEAT", 190 | "REPLACE", 191 | "REQUIRE", 192 | "RESIGNAL", 193 | "RESTRICT", 194 | "RETURN", 195 | "RETURNING", 196 | "REVOKE", 197 | "RIGHT", 198 | "RLIKE", 199 | "ROWS", 200 | "SCHEMA", 201 | "SCHEMAS", 202 | "SECOND_MICROSECOND", 203 | "SELECT", 204 | "SENSITIVE", 205 | "SEPARATOR", 206 | "SET", 207 | "SHOW", 208 | "SIGNAL", 209 | "SLOW", 210 | "SMALLINT", 211 | "SPATIAL", 212 | "SPECIFIC", 213 | "SQL", 214 | "SQLEXCEPTION", 215 | "SQLSTATE", 216 | "SQLWARNING", 217 | "SQL_BIG_RESULT", 218 | "SQL_CALC_FOUND_ROWS", 219 | "SQL_SMALL_RESULT", 220 | "SSL", 221 | "STARTING", 222 | "STATS_AUTO_RECALC", 223 | "STATS_PERSISTENT", 224 | "STATS_SAMPLE_PAGES", 225 | "STRAIGHT_JOIN", 226 | "TABLE", 227 | "TERMINATED", 228 | "THEN", 229 | "TINYBLOB", 230 | "TINYINT", 231 | "TINYTEXT", 232 | "TO", 233 | "TRAILING", 234 | "TRIGGER", 235 | "TRUE", 236 | "UNDO", 237 | "UNION", 238 | "UNIQUE", 239 | "UNLOCK", 240 | "UNSIGNED", 241 | "UPDATE", 242 | "USAGE", 243 | "USE", 244 | "USING", 245 | "UTC_DATE", 246 | "UTC_TIME", 247 | "UTC_TIMESTAMP", 248 | "VALUES", 249 | "VARBINARY", 250 | "VARCHAR", 251 | "VARCHARACTER", 252 | "VARYING", 253 | "WHEN", 254 | "WHERE", 255 | "WHILE", 256 | "WINDOW", 257 | "WITH", 258 | "WRITE", 259 | "XOR", 260 | "YEAR_MONTH", 261 | "ZEROFILL"); 262 | 263 | private static final List reservedTopLevelWords = 264 | Arrays.asList( 265 | "ADD", 266 | "ALTER COLUMN", 267 | "ALTER TABLE", 268 | "DELETE FROM", 269 | "EXCEPT", 270 | "FROM", 271 | "GROUP BY", 272 | "HAVING", 273 | "INSERT INTO", 274 | "INSERT", 275 | "LIMIT", 276 | "ORDER BY", 277 | "SELECT", 278 | "SET", 279 | "UPDATE", 280 | "VALUES", 281 | "WHERE"); 282 | 283 | private static final List reservedTopLevelWordsNoIndent = 284 | Arrays.asList("INTERSECT", "INTERSECT ALL", "UNION", "UNION ALL"); 285 | 286 | private static final List reservedNewlineWords = 287 | Arrays.asList( 288 | "AND", 289 | "ELSE", 290 | "OR", 291 | "WHEN", 292 | // joins 293 | "JOIN", 294 | "INNER JOIN", 295 | "LEFT JOIN", 296 | "LEFT OUTER JOIN", 297 | "RIGHT JOIN", 298 | "RIGHT OUTER JOIN", 299 | "CROSS JOIN", 300 | "NATURAL JOIN", 301 | // non-standard joins 302 | "STRAIGHT_JOIN", 303 | "NATURAL LEFT JOIN", 304 | "NATURAL LEFT OUTER JOIN", 305 | "NATURAL RIGHT JOIN", 306 | "NATURAL RIGHT OUTER JOIN"); 307 | 308 | @Override 309 | public DialectConfig dialectConfig() { 310 | return DialectConfig.builder() 311 | .reservedWords(reservedWords) 312 | .reservedTopLevelWords(reservedTopLevelWords) 313 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 314 | .reservedNewlineWords(reservedNewlineWords) 315 | .stringTypes( 316 | Arrays.asList( 317 | StringLiteral.DOUBLE_QUOTE, 318 | StringLiteral.SINGLE_QUOTE, 319 | StringLiteral.BACK_QUOTE, 320 | StringLiteral.BRACKET)) 321 | .openParens(Arrays.asList("(", "CASE")) 322 | .closeParens(Arrays.asList(")", "END")) 323 | .indexedPlaceholderTypes(Collections.singletonList("?")) 324 | .namedPlaceholderTypes(Collections.emptyList()) 325 | .lineCommentTypes(Arrays.asList("--", "#")) 326 | .specialWordChars(Arrays.asList("@")) 327 | .operators(Arrays.asList(":=", "<<", ">>", "!=", "<>", "<=>", "&&", "||")) 328 | .build(); 329 | } 330 | 331 | public MariaDbFormatter(FormatConfig cfg) { 332 | super(cfg); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/MySqlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class MySqlFormatter extends AbstractFormatter { 11 | 12 | private static final List reservedWords = 13 | Arrays.asList( 14 | "ACCESSIBLE", 15 | "ADD", 16 | "ALL", 17 | "ALTER", 18 | "ANALYZE", 19 | "AND", 20 | "AS", 21 | "ASC", 22 | "ASENSITIVE", 23 | "BEFORE", 24 | "BETWEEN", 25 | "BIGINT", 26 | "BINARY", 27 | "BLOB", 28 | "BOTH", 29 | "BY", 30 | "CALL", 31 | "CASCADE", 32 | "CASE", 33 | "CHANGE", 34 | "CHAR", 35 | "CHARACTER", 36 | "CHECK", 37 | "COLLATE", 38 | "COLUMN", 39 | "CONDITION", 40 | "CONSTRAINT", 41 | "CONTINUE", 42 | "CONVERT", 43 | "CREATE", 44 | "CROSS", 45 | "CUBE", 46 | "CUME_DIST", 47 | "CURRENT_DATE", 48 | "CURRENT_TIME", 49 | "CURRENT_TIMESTAMP", 50 | "CURRENT_USER", 51 | "CURSOR", 52 | "DATABASE", 53 | "DATABASES", 54 | "DAY_HOUR", 55 | "DAY_MICROSECOND", 56 | "DAY_MINUTE", 57 | "DAY_SECOND", 58 | "DEC", 59 | "DECIMAL", 60 | "DECLARE", 61 | "DEFAULT", 62 | "DELAYED", 63 | "DELETE", 64 | "DENSE_RANK", 65 | "DESC", 66 | "DESCRIBE", 67 | "DETERMINISTIC", 68 | "DISTINCT", 69 | "DISTINCTROW", 70 | "DIV", 71 | "DOUBLE", 72 | "DROP", 73 | "DUAL", 74 | "EACH", 75 | "ELSE", 76 | "ELSEIF", 77 | "EMPTY", 78 | "ENCLOSED", 79 | "ESCAPED", 80 | "EXCEPT", 81 | "EXISTS", 82 | "EXIT", 83 | "EXPLAIN", 84 | "FALSE", 85 | "FETCH", 86 | "FIRST_VALUE", 87 | "FLOAT", 88 | "FLOAT4", 89 | "FLOAT8", 90 | "FOR", 91 | "FORCE", 92 | "FOREIGN", 93 | "FROM", 94 | "FULLTEXT", 95 | "FUNCTION", 96 | "GENERATED", 97 | "GET", 98 | "GRANT", 99 | "GROUP", 100 | "GROUPING", 101 | "GROUPS", 102 | "HAVING", 103 | "HIGH_PRIORITY", 104 | "HOUR_MICROSECOND", 105 | "HOUR_MINUTE", 106 | "HOUR_SECOND", 107 | "IF", 108 | "IGNORE", 109 | "IN", 110 | "INDEX", 111 | "INFILE", 112 | "INNER", 113 | "INOUT", 114 | "INSENSITIVE", 115 | "INSERT", 116 | "INT", 117 | "INT1", 118 | "INT2", 119 | "INT3", 120 | "INT4", 121 | "INT8", 122 | "INTEGER", 123 | "INTERVAL", 124 | "INTO", 125 | "IO_AFTER_GTIDS", 126 | "IO_BEFORE_GTIDS", 127 | "IS", 128 | "ITERATE", 129 | "JOIN", 130 | "JSON_TABLE", 131 | "KEY", 132 | "KEYS", 133 | "KILL", 134 | "LAG", 135 | "LAST_VALUE", 136 | "LATERAL", 137 | "LEAD", 138 | "LEADING", 139 | "LEAVE", 140 | "LEFT", 141 | "LIKE", 142 | "LIMIT", 143 | "LINEAR", 144 | "LINES", 145 | "LOAD", 146 | "LOCALTIME", 147 | "LOCALTIMESTAMP", 148 | "LOCK", 149 | "LONG", 150 | "LONGBLOB", 151 | "LONGTEXT", 152 | "LOOP", 153 | "LOW_PRIORITY", 154 | "MASTER_BIND", 155 | "MASTER_SSL_VERIFY_SERVER_CERT", 156 | "MATCH", 157 | "MAXVALUE", 158 | "MEDIUMBLOB", 159 | "MEDIUMINT", 160 | "MEDIUMTEXT", 161 | "MIDDLEINT", 162 | "MINUTE_MICROSECOND", 163 | "MINUTE_SECOND", 164 | "MOD", 165 | "MODIFIES", 166 | "NATURAL", 167 | "NOT", 168 | "NO_WRITE_TO_BINLOG", 169 | "NTH_VALUE", 170 | "NTILE", 171 | "NULL", 172 | "NUMERIC", 173 | "OF", 174 | "ON", 175 | "OPTIMIZE", 176 | "OPTIMIZER_COSTS", 177 | "OPTION", 178 | "OPTIONALLY", 179 | "OR", 180 | "ORDER", 181 | "OUT", 182 | "OUTER", 183 | "OUTFILE", 184 | "OVER", 185 | "PARTITION", 186 | "PERCENT_RANK", 187 | "PRECISION", 188 | "PRIMARY", 189 | "PROCEDURE", 190 | "PURGE", 191 | "RANGE", 192 | "RANK", 193 | "READ", 194 | "READS", 195 | "READ_WRITE", 196 | "REAL", 197 | "RECURSIVE", 198 | "REFERENCES", 199 | "REGEXP", 200 | "RELEASE", 201 | "RENAME", 202 | "REPEAT", 203 | "REPLACE", 204 | "REQUIRE", 205 | "RESIGNAL", 206 | "RESTRICT", 207 | "RETURN", 208 | "REVOKE", 209 | "RIGHT", 210 | "RLIKE", 211 | "ROW", 212 | "ROWS", 213 | "ROW_NUMBER", 214 | "SCHEMA", 215 | "SCHEMAS", 216 | "SECOND_MICROSECOND", 217 | "SELECT", 218 | "SENSITIVE", 219 | "SEPARATOR", 220 | "SET", 221 | "SHOW", 222 | "SIGNAL", 223 | "SMALLINT", 224 | "SPATIAL", 225 | "SPECIFIC", 226 | "SQL", 227 | "SQLEXCEPTION", 228 | "SQLSTATE", 229 | "SQLWARNING", 230 | "SQL_BIG_RESULT", 231 | "SQL_CALC_FOUND_ROWS", 232 | "SQL_SMALL_RESULT", 233 | "SSL", 234 | "STARTING", 235 | "STORED", 236 | "STRAIGHT_JOIN", 237 | "SYSTEM", 238 | "TABLE", 239 | "TERMINATED", 240 | "THEN", 241 | "TINYBLOB", 242 | "TINYINT", 243 | "TINYTEXT", 244 | "TO", 245 | "TRAILING", 246 | "TRIGGER", 247 | "TRUE", 248 | "UNDO", 249 | "UNION", 250 | "UNIQUE", 251 | "UNLOCK", 252 | "UNSIGNED", 253 | "UPDATE", 254 | "USAGE", 255 | "USE", 256 | "USING", 257 | "UTC_DATE", 258 | "UTC_TIME", 259 | "UTC_TIMESTAMP", 260 | "VALUES", 261 | "VARBINARY", 262 | "VARCHAR", 263 | "VARCHARACTER", 264 | "VARYING", 265 | "VIRTUAL", 266 | "WHEN", 267 | "WHERE", 268 | "WHILE", 269 | "WINDOW", 270 | "WITH", 271 | "WRITE", 272 | "XOR", 273 | "YEAR_MONTH", 274 | "ZEROFILL"); 275 | 276 | private static final List reservedTopLevelWords = 277 | Arrays.asList( 278 | "ADD", 279 | "ALTER COLUMN", 280 | "ALTER TABLE", 281 | "DELETE FROM", 282 | "EXCEPT", 283 | "FROM", 284 | "GROUP BY", 285 | "HAVING", 286 | "INSERT INTO", 287 | "INSERT", 288 | "LIMIT", 289 | "ORDER BY", 290 | "SELECT", 291 | "SET", 292 | "UPDATE", 293 | "VALUES", 294 | "WHERE"); 295 | 296 | private static final List reservedTopLevelWordsNoIndent = 297 | Arrays.asList("INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL"); 298 | 299 | private static final List reservedNewlineWords = 300 | Arrays.asList( 301 | "AND", 302 | "ELSE", 303 | "OR", 304 | "WHEN", 305 | // joins 306 | "JOIN", 307 | "INNER JOIN", 308 | "LEFT JOIN", 309 | "LEFT OUTER JOIN", 310 | "RIGHT JOIN", 311 | "RIGHT OUTER JOIN", 312 | "CROSS JOIN", 313 | "NATURAL JOIN", 314 | // non-standard joins 315 | "STRAIGHT_JOIN", 316 | "NATURAL LEFT JOIN", 317 | "NATURAL LEFT OUTER JOIN", 318 | "NATURAL RIGHT JOIN", 319 | "NATURAL RIGHT OUTER JOIN"); 320 | 321 | @Override 322 | public DialectConfig dialectConfig() { 323 | return DialectConfig.builder() 324 | .reservedWords(reservedWords) 325 | .reservedTopLevelWords(reservedTopLevelWords) 326 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 327 | .reservedNewlineWords(reservedNewlineWords) 328 | .stringTypes( 329 | Arrays.asList( 330 | StringLiteral.DOUBLE_QUOTE, 331 | StringLiteral.SINGLE_QUOTE, 332 | StringLiteral.BACK_QUOTE, 333 | StringLiteral.BRACKET)) 334 | .openParens(Arrays.asList("(", "CASE")) 335 | .closeParens(Arrays.asList(")", "END")) 336 | .indexedPlaceholderTypes(Collections.singletonList("?")) 337 | .namedPlaceholderTypes(Collections.emptyList()) 338 | .lineCommentTypes(Arrays.asList("--", "#")) 339 | .specialWordChars(Arrays.asList("@")) 340 | .operators(Arrays.asList(":=", "<<", ">>", "!=", "<>", "<=>", "&&", "||", "->", "->>")) 341 | .build(); 342 | } 343 | 344 | public MySqlFormatter(FormatConfig cfg) { 345 | super(cfg); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/N1qlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class N1qlFormatter extends AbstractFormatter { 11 | 12 | private static final List reservedWords = 13 | Arrays.asList( 14 | "ALL", 15 | "ALTER", 16 | "ANALYZE", 17 | "AND", 18 | "ANY", 19 | "ARRAY", 20 | "AS", 21 | "ASC", 22 | "BEGIN", 23 | "BETWEEN", 24 | "BINARY", 25 | "BOOLEAN", 26 | "BREAK", 27 | "BUCKET", 28 | "BUILD", 29 | "BY", 30 | "CALL", 31 | "CASE", 32 | "CAST", 33 | "CLUSTER", 34 | "COLLATE", 35 | "COLLECTION", 36 | "COMMIT", 37 | "CONNECT", 38 | "CONTINUE", 39 | "CORRELATE", 40 | "COVER", 41 | "CREATE", 42 | "DATABASE", 43 | "DATASET", 44 | "DATASTORE", 45 | "DECLARE", 46 | "DECREMENT", 47 | "DELETE", 48 | "DERIVED", 49 | "DESC", 50 | "DESCRIBE", 51 | "DISTINCT", 52 | "DO", 53 | "DROP", 54 | "EACH", 55 | "ELEMENT", 56 | "ELSE", 57 | "END", 58 | "EVERY", 59 | "EXCEPT", 60 | "EXCLUDE", 61 | "EXECUTE", 62 | "EXISTS", 63 | "EXPLAIN", 64 | "FALSE", 65 | "FETCH", 66 | "FIRST", 67 | "FLATTEN", 68 | "FOR", 69 | "FORCE", 70 | "FROM", 71 | "FUNCTION", 72 | "GRANT", 73 | "GROUP", 74 | "GSI", 75 | "HAVING", 76 | "IF", 77 | "IGNORE", 78 | "ILIKE", 79 | "IN", 80 | "INCLUDE", 81 | "INCREMENT", 82 | "INDEX", 83 | "INFER", 84 | "INLINE", 85 | "INNER", 86 | "INSERT", 87 | "INTERSECT", 88 | "INTO", 89 | "IS", 90 | "JOIN", 91 | "KEY", 92 | "KEYS", 93 | "KEYSPACE", 94 | "KNOWN", 95 | "LAST", 96 | "LEFT", 97 | "LET", 98 | "LETTING", 99 | "LIKE", 100 | "LIMIT", 101 | "LSM", 102 | "MAP", 103 | "MAPPING", 104 | "MATCHED", 105 | "MATERIALIZED", 106 | "MERGE", 107 | "MISSING", 108 | "NAMESPACE", 109 | "NEST", 110 | "NOT", 111 | "NULL", 112 | "NUMBER", 113 | "OBJECT", 114 | "OFFSET", 115 | "ON", 116 | "OPTION", 117 | "OR", 118 | "ORDER", 119 | "OUTER", 120 | "OVER", 121 | "PARSE", 122 | "PARTITION", 123 | "PASSWORD", 124 | "PATH", 125 | "POOL", 126 | "PREPARE", 127 | "PRIMARY", 128 | "PRIVATE", 129 | "PRIVILEGE", 130 | "PROCEDURE", 131 | "PUBLIC", 132 | "RAW", 133 | "REALM", 134 | "REDUCE", 135 | "RENAME", 136 | "RETURN", 137 | "RETURNING", 138 | "REVOKE", 139 | "RIGHT", 140 | "ROLE", 141 | "ROLLBACK", 142 | "SATISFIES", 143 | "SCHEMA", 144 | "SELECT", 145 | "SELF", 146 | "SEMI", 147 | "SET", 148 | "SHOW", 149 | "SOME", 150 | "START", 151 | "STATISTICS", 152 | "STRING", 153 | "SYSTEM", 154 | "THEN", 155 | "TO", 156 | "TRANSACTION", 157 | "TRIGGER", 158 | "TRUE", 159 | "TRUNCATE", 160 | "UNDER", 161 | "UNION", 162 | "UNIQUE", 163 | "UNKNOWN", 164 | "UNNEST", 165 | "UNSET", 166 | "UPDATE", 167 | "UPSERT", 168 | "USE", 169 | "USER", 170 | "USING", 171 | "VALIDATE", 172 | "VALUE", 173 | "VALUED", 174 | "VALUES", 175 | "VIA", 176 | "VIEW", 177 | "WHEN", 178 | "WHERE", 179 | "WHILE", 180 | "WITH", 181 | "WITHIN", 182 | "WORK", 183 | "XOR"); 184 | 185 | private static final List reservedTopLevelWords = 186 | Arrays.asList( 187 | "DELETE FROM", 188 | "EXCEPT ALL", 189 | "EXCEPT", 190 | "EXPLAIN DELETE FROM", 191 | "EXPLAIN UPDATE", 192 | "EXPLAIN UPSERT", 193 | "FROM", 194 | "GROUP BY", 195 | "HAVING", 196 | "INFER", 197 | "INSERT INTO", 198 | "LET", 199 | "LIMIT", 200 | "MERGE", 201 | "NEST", 202 | "ORDER BY", 203 | "PREPARE", 204 | "SELECT", 205 | "SET CURRENT SCHEMA", 206 | "SET SCHEMA", 207 | "SET", 208 | "UNNEST", 209 | "UPDATE", 210 | "UPSERT", 211 | "USE KEYS", 212 | "VALUES", 213 | "WHERE"); 214 | 215 | private static final List reservedTopLevelWordsNoIndent = 216 | Arrays.asList("INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL"); 217 | 218 | private static final List reservedNewlineWords = 219 | Arrays.asList( 220 | "AND", 221 | "OR", 222 | "XOR", 223 | // joins 224 | "JOIN", 225 | "INNER JOIN", 226 | "LEFT JOIN", 227 | "LEFT OUTER JOIN", 228 | "RIGHT JOIN", 229 | "RIGHT OUTER JOIN"); 230 | 231 | @Override 232 | public DialectConfig dialectConfig() { 233 | return DialectConfig.builder() 234 | .reservedWords(reservedWords) 235 | .reservedTopLevelWords(reservedTopLevelWords) 236 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 237 | .reservedNewlineWords(reservedNewlineWords) 238 | .stringTypes( 239 | Arrays.asList( 240 | StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE)) 241 | .openParens(Arrays.asList("(", "[", "{")) 242 | .closeParens(Arrays.asList(")", "]", "}")) 243 | .namedPlaceholderTypes(Collections.singletonList("$")) 244 | .lineCommentTypes(Arrays.asList("#", "--")) 245 | .operators(Arrays.asList("==", "!=")) 246 | .build(); 247 | } 248 | 249 | public N1qlFormatter(FormatConfig cfg) { 250 | super(cfg); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/PlSqlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import com.github.vertical_blank.sqlformatter.core.Token; 7 | import com.github.vertical_blank.sqlformatter.core.TokenTypes; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class PlSqlFormatter extends AbstractFormatter { 13 | 14 | private static final List reservedWords = 15 | Arrays.asList( 16 | "A", 17 | "ACCESSIBLE", 18 | "AGENT", 19 | "AGGREGATE", 20 | "ALL", 21 | "ALTER", 22 | "ANY", 23 | "ARRAY", 24 | "AS", 25 | "ASC", 26 | "AT", 27 | "ATTRIBUTE", 28 | "AUTHID", 29 | "AVG", 30 | "BETWEEN", 31 | "BFILE_BASE", 32 | "BINARY_INTEGER", 33 | "BINARY", 34 | "BLOB_BASE", 35 | "BLOCK", 36 | "BODY", 37 | "BOOLEAN", 38 | "BOTH", 39 | "BOUND", 40 | "BREADTH", 41 | "BULK", 42 | "BY", 43 | "BYTE", 44 | "C", 45 | "CALL", 46 | "CALLING", 47 | "CASCADE", 48 | "CASE", 49 | "CHAR_BASE", 50 | "CHAR", 51 | "CHARACTER", 52 | "CHARSET", 53 | "CHARSETFORM", 54 | "CHARSETID", 55 | "CHECK", 56 | "CLOB_BASE", 57 | "CLONE", 58 | "CLOSE", 59 | "CLUSTER", 60 | "CLUSTERS", 61 | "COALESCE", 62 | "COLAUTH", 63 | "COLLECT", 64 | "COLUMNS", 65 | "COMMENT", 66 | "COMMIT", 67 | "COMMITTED", 68 | "COMPILED", 69 | "COMPRESS", 70 | "CONNECT", 71 | "CONSTANT", 72 | "CONSTRUCTOR", 73 | "CONTEXT", 74 | "CONTINUE", 75 | "CONVERT", 76 | "COUNT", 77 | "CRASH", 78 | "CREATE", 79 | "CREDENTIAL", 80 | "CURRENT", 81 | "CURRVAL", 82 | "CURSOR", 83 | "CUSTOMDATUM", 84 | "DANGLING", 85 | "DATA", 86 | "DATE_BASE", 87 | "DATE", 88 | "DAY", 89 | "DECIMAL", 90 | "DEFAULT", 91 | "DEFINE", 92 | "DELETE", 93 | "DEPTH", 94 | "DESC", 95 | "DETERMINISTIC", 96 | "DIRECTORY", 97 | "DISTINCT", 98 | "DO", 99 | "DOUBLE", 100 | "DROP", 101 | "DURATION", 102 | "ELEMENT", 103 | "ELSIF", 104 | "EMPTY", 105 | "END", 106 | "ESCAPE", 107 | "EXCEPTIONS", 108 | "EXCLUSIVE", 109 | "EXECUTE", 110 | "EXISTS", 111 | "EXIT", 112 | "EXTENDS", 113 | "EXTERNAL", 114 | "EXTRACT", 115 | "FALSE", 116 | "FETCH", 117 | "FINAL", 118 | "FIRST", 119 | "FIXED", 120 | "FLOAT", 121 | "FOR", 122 | "FORALL", 123 | "FORCE", 124 | "FROM", 125 | "FUNCTION", 126 | "GENERAL", 127 | "GOTO", 128 | "GRANT", 129 | "GROUP", 130 | "HASH", 131 | "HEAP", 132 | "HIDDEN", 133 | "HOUR", 134 | "IDENTIFIED", 135 | "IF", 136 | "IMMEDIATE", 137 | "IN", 138 | "INCLUDING", 139 | "INDEX", 140 | "INDEXES", 141 | "INDICATOR", 142 | "INDICES", 143 | "INFINITE", 144 | "INSTANTIABLE", 145 | "INT", 146 | "INTEGER", 147 | "INTERFACE", 148 | "INTERVAL", 149 | "INTO", 150 | "INVALIDATE", 151 | "IS", 152 | "ISOLATION", 153 | "JAVA", 154 | "LANGUAGE", 155 | "LARGE", 156 | "LEADING", 157 | "LENGTH", 158 | "LEVEL", 159 | "LIBRARY", 160 | "LIKE", 161 | "LIKE2", 162 | "LIKE4", 163 | "LIKEC", 164 | "LIMITED", 165 | "LOCAL", 166 | "LOCK", 167 | "LONG", 168 | "MAP", 169 | "MAX", 170 | "MAXLEN", 171 | "MEMBER", 172 | "MERGE", 173 | "MIN", 174 | "MINUTE", 175 | "MLSLABEL", 176 | "MOD", 177 | "MODE", 178 | "MONTH", 179 | "MULTISET", 180 | "NAME", 181 | "NAN", 182 | "NATIONAL", 183 | "NATIVE", 184 | "NATURAL", 185 | "NATURALN", 186 | "NCHAR", 187 | "NEW", 188 | "NEXTVAL", 189 | "NOCOMPRESS", 190 | "NOCOPY", 191 | "NOT", 192 | "NOWAIT", 193 | "NULL", 194 | "NULLIF", 195 | "NUMBER_BASE", 196 | "NUMBER", 197 | "OBJECT", 198 | "OCICOLL", 199 | "OCIDATE", 200 | "OCIDATETIME", 201 | "OCIDURATION", 202 | "OCIINTERVAL", 203 | "OCILOBLOCATOR", 204 | "OCINUMBER", 205 | "OCIRAW", 206 | "OCIREF", 207 | "OCIREFCURSOR", 208 | "OCIROWID", 209 | "OCISTRING", 210 | "OCITYPE", 211 | "OF", 212 | "OLD", 213 | "ON", 214 | "ONLY", 215 | "OPAQUE", 216 | "OPEN", 217 | "OPERATOR", 218 | "OPTION", 219 | "ORACLE", 220 | "ORADATA", 221 | "ORDER", 222 | "ORGANIZATION", 223 | "ORLANY", 224 | "ORLVARY", 225 | "OTHERS", 226 | "OUT", 227 | "OVERLAPS", 228 | "OVERRIDING", 229 | "PACKAGE", 230 | "PARALLEL_ENABLE", 231 | "PARAMETER", 232 | "PARAMETERS", 233 | "PARENT", 234 | "PARTITION", 235 | "PASCAL", 236 | "PCTFREE", 237 | "PIPE", 238 | "PIPELINED", 239 | "PLS_INTEGER", 240 | "PLUGGABLE", 241 | "POSITIVE", 242 | "POSITIVEN", 243 | "PRAGMA", 244 | "PRECISION", 245 | "PRIOR", 246 | "PRIVATE", 247 | "PROCEDURE", 248 | "PUBLIC", 249 | "RAISE", 250 | "RANGE", 251 | "RAW", 252 | "READ", 253 | "REAL", 254 | "RECORD", 255 | "REF", 256 | "REFERENCE", 257 | "RELEASE", 258 | "RELIES_ON", 259 | "REM", 260 | "REMAINDER", 261 | "RENAME", 262 | "RESOURCE", 263 | "RESULT_CACHE", 264 | "RESULT", 265 | "RETURN", 266 | "RETURNING", 267 | "REVERSE", 268 | "REVOKE", 269 | "ROLLBACK", 270 | "ROW", 271 | "ROWID", 272 | "ROWNUM", 273 | "ROWTYPE", 274 | "SAMPLE", 275 | "SAVE", 276 | "SAVEPOINT", 277 | "SB1", 278 | "SB2", 279 | "SB4", 280 | "SEARCH", 281 | "SECOND", 282 | "SEGMENT", 283 | "SELF", 284 | "SEPARATE", 285 | "SEQUENCE", 286 | "SERIALIZABLE", 287 | "SHARE", 288 | "SHORT", 289 | "SIZE_T", 290 | "SIZE", 291 | "SMALLINT", 292 | "SOME", 293 | "SPACE", 294 | "SPARSE", 295 | "SQL", 296 | "SQLCODE", 297 | "SQLDATA", 298 | "SQLERRM", 299 | "SQLNAME", 300 | "SQLSTATE", 301 | "STANDARD", 302 | "START", 303 | "STATIC", 304 | "STDDEV", 305 | "STORED", 306 | "STRING", 307 | "STRUCT", 308 | "STYLE", 309 | "SUBMULTISET", 310 | "SUBPARTITION", 311 | "SUBSTITUTABLE", 312 | "SUBTYPE", 313 | "SUCCESSFUL", 314 | "SUM", 315 | "SYNONYM", 316 | "SYSDATE", 317 | "TABAUTH", 318 | "TABLE", 319 | "TDO", 320 | "THE", 321 | "THEN", 322 | "TIME", 323 | "TIMESTAMP", 324 | "TIMEZONE_ABBR", 325 | "TIMEZONE_HOUR", 326 | "TIMEZONE_MINUTE", 327 | "TIMEZONE_REGION", 328 | "TO", 329 | "TRAILING", 330 | "TRANSACTION", 331 | "TRANSACTIONAL", 332 | "TRIGGER", 333 | "TRUE", 334 | "TRUSTED", 335 | "TYPE", 336 | "UB1", 337 | "UB2", 338 | "UB4", 339 | "UID", 340 | "UNDER", 341 | "UNIQUE", 342 | "UNPLUG", 343 | "UNSIGNED", 344 | "UNTRUSTED", 345 | "USE", 346 | "USER", 347 | "USING", 348 | "VALIDATE", 349 | "VALIST", 350 | "VALUE", 351 | "VARCHAR", 352 | "VARCHAR2", 353 | "VARIABLE", 354 | "VARIANCE", 355 | "VARRAY", 356 | "VARYING", 357 | "VIEW", 358 | "VIEWS", 359 | "VOID", 360 | "WHENEVER", 361 | "WHILE", 362 | "WITH", 363 | "WORK", 364 | "WRAPPED", 365 | "WRITE", 366 | "YEAR", 367 | "ZONE"); 368 | 369 | private static final List reservedTopLevelWords = 370 | Arrays.asList( 371 | "ADD", 372 | "ALTER COLUMN", 373 | "ALTER TABLE", 374 | "BEGIN", 375 | "CONNECT BY", 376 | "DECLARE", 377 | "DELETE FROM", 378 | "DELETE", 379 | "END", 380 | "EXCEPT", 381 | "EXCEPTION", 382 | "FETCH FIRST", 383 | "FROM", 384 | "GROUP BY", 385 | "HAVING", 386 | "INSERT INTO", 387 | "INSERT", 388 | "LIMIT", 389 | "LOOP", 390 | "MODIFY", 391 | "ORDER BY", 392 | "SELECT", 393 | "SET CURRENT SCHEMA", 394 | "SET SCHEMA", 395 | "SET", 396 | "START WITH", 397 | "UPDATE", 398 | "VALUES", 399 | "WHERE"); 400 | 401 | private static final List reservedTopLevelWordsNoIndent = 402 | Arrays.asList("INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL"); 403 | 404 | private static final List reservedNewlineWords = 405 | Arrays.asList( 406 | "AND", 407 | "CROSS APPLY", 408 | "ELSE", 409 | "END", 410 | "OR", 411 | "OUTER APPLY", 412 | "WHEN", 413 | "XOR", 414 | // joins 415 | "JOIN", 416 | "INNER JOIN", 417 | "LEFT JOIN", 418 | "LEFT OUTER JOIN", 419 | "RIGHT JOIN", 420 | "RIGHT OUTER JOIN", 421 | "FULL JOIN", 422 | "FULL OUTER JOIN", 423 | "CROSS JOIN", 424 | "NATURAL JOIN"); 425 | 426 | @Override 427 | public DialectConfig dialectConfig() { 428 | return DialectConfig.builder() 429 | .reservedWords(reservedWords) 430 | .reservedTopLevelWords(reservedTopLevelWords) 431 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 432 | .reservedNewlineWords(reservedNewlineWords) 433 | .stringTypes( 434 | Arrays.asList( 435 | StringLiteral.DOUBLE_QUOTE, 436 | StringLiteral.N_SINGLE_QUOTE, 437 | StringLiteral.Q_SINGLE_QUOTE, 438 | StringLiteral.SINGLE_QUOTE, 439 | StringLiteral.BACK_QUOTE)) 440 | .openParens(Arrays.asList("(", "CASE")) 441 | .closeParens(Arrays.asList(")", "END")) 442 | .indexedPlaceholderTypes(Collections.singletonList("?")) 443 | .namedPlaceholderTypes(Collections.singletonList(":")) 444 | .lineCommentTypes(Collections.singletonList("--")) 445 | .specialWordChars(Arrays.asList("_", "$", "#", ".", "@")) 446 | .operators(Arrays.asList("||", "**", "!=", ":=")) 447 | .build(); 448 | } 449 | 450 | @Override 451 | public Token tokenOverride(Token token) { 452 | if (Token.isSet(token) && Token.isBy(super.previousReservedToken)) { 453 | return new Token(TokenTypes.RESERVED, token.value); 454 | } 455 | return token; 456 | } 457 | 458 | public PlSqlFormatter(FormatConfig cfg) { 459 | super(cfg); 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/RedshiftFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class RedshiftFormatter extends AbstractFormatter { 11 | 12 | private static final List reservedWords = 13 | Arrays.asList( 14 | "AES128", 15 | "AES256", 16 | "ALLOWOVERWRITE", 17 | "ANALYSE", 18 | "ARRAY", 19 | "AS", 20 | "ASC", 21 | "AUTHORIZATION", 22 | "BACKUP", 23 | "BINARY", 24 | "BLANKSASNULL", 25 | "BOTH", 26 | "BYTEDICT", 27 | "BZIP2", 28 | "CAST", 29 | "CHECK", 30 | "COLLATE", 31 | "COLUMN", 32 | "CONSTRAINT", 33 | "CREATE", 34 | "CREDENTIALS", 35 | "CURRENT_DATE", 36 | "CURRENT_TIME", 37 | "CURRENT_TIMESTAMP", 38 | "CURRENT_USER", 39 | "CURRENT_USER_ID", 40 | "DEFAULT", 41 | "DEFERRABLE", 42 | "DEFLATE", 43 | "DEFRAG", 44 | "DELTA", 45 | "DELTA32K", 46 | "DESC", 47 | "DISABLE", 48 | "DISTINCT", 49 | "DO", 50 | "ELSE", 51 | "EMPTYASNULL", 52 | "ENABLE", 53 | "ENCODE", 54 | "ENCRYPT", 55 | "ENCRYPTION", 56 | "END", 57 | "EXPLICIT", 58 | "FALSE", 59 | "FOR", 60 | "FOREIGN", 61 | "FREEZE", 62 | "FULL", 63 | "GLOBALDICT256", 64 | "GLOBALDICT64K", 65 | "GRANT", 66 | "GZIP", 67 | "IDENTITY", 68 | "IGNORE", 69 | "ILIKE", 70 | "INITIALLY", 71 | "INTO", 72 | "LEADING", 73 | "LOCALTIME", 74 | "LOCALTIMESTAMP", 75 | "LUN", 76 | "LUNS", 77 | "LZO", 78 | "LZOP", 79 | "MINUS", 80 | "MOSTLY13", 81 | "MOSTLY32", 82 | "MOSTLY8", 83 | "NATURAL", 84 | "NEW", 85 | "NULLS", 86 | "OFF", 87 | "OFFLINE", 88 | "OFFSET", 89 | "OLD", 90 | "ON", 91 | "ONLY", 92 | "OPEN", 93 | "ORDER", 94 | "OVERLAPS", 95 | "PARALLEL", 96 | "PARTITION", 97 | "PERCENT", 98 | "PERMISSIONS", 99 | "PLACING", 100 | "PRIMARY", 101 | "RAW", 102 | "READRATIO", 103 | "RECOVER", 104 | "REFERENCES", 105 | "REJECTLOG", 106 | "RESORT", 107 | "RESTORE", 108 | "SESSION_USER", 109 | "SIMILAR", 110 | "SYSDATE", 111 | "SYSTEM", 112 | "TABLE", 113 | "TAG", 114 | "TDES", 115 | "TEXT255", 116 | "TEXT32K", 117 | "THEN", 118 | "TIMESTAMP", 119 | "TO", 120 | "TOP", 121 | "TRAILING", 122 | "TRUE", 123 | "TRUNCATECOLUMNS", 124 | "UNIQUE", 125 | "USER", 126 | "USING", 127 | "VERBOSE", 128 | "WALLET", 129 | "WHEN", 130 | "WITH", 131 | "WITHOUT", 132 | "PREDICATE", 133 | "COLUMNS", 134 | "COMPROWS", 135 | "COMPRESSION", 136 | "COPY", 137 | "FORMAT", 138 | "DELIMITER", 139 | "FIXEDWIDTH", 140 | "AVRO", 141 | "JSON", 142 | "ENCRYPTED", 143 | "BZIP2", 144 | "GZIP", 145 | "LZOP", 146 | "PARQUET", 147 | "ORC", 148 | "ACCEPTANYDATE", 149 | "ACCEPTINVCHARS", 150 | "BLANKSASNULL", 151 | "DATEFORMAT", 152 | "EMPTYASNULL", 153 | "ENCODING", 154 | "ESCAPE", 155 | "EXPLICIT_IDS", 156 | "FILLRECORD", 157 | "IGNOREBLANKLINES", 158 | "IGNOREHEADER", 159 | "NULL AS", 160 | "REMOVEQUOTES", 161 | "ROUNDEC", 162 | "TIMEFORMAT", 163 | "TRIMBLANKS", 164 | "TRUNCATECOLUMNS", 165 | "COMPROWS", 166 | "COMPUPDATE", 167 | "MAXERROR", 168 | "NOLOAD", 169 | "STATUPDATE", 170 | "MANIFEST", 171 | "REGION", 172 | "IAM_ROLE", 173 | "MASTER_SYMMETRIC_KEY", 174 | "SSH", 175 | "ACCEPTANYDATE", 176 | "ACCEPTINVCHARS", 177 | "ACCESS_KEY_ID", 178 | "SECRET_ACCESS_KEY", 179 | "AVRO", 180 | "BLANKSASNULL", 181 | "BZIP2", 182 | "COMPROWS", 183 | "COMPUPDATE", 184 | "CREDENTIALS", 185 | "DATEFORMAT", 186 | "DELIMITER", 187 | "EMPTYASNULL", 188 | "ENCODING", 189 | "ENCRYPTED", 190 | "ESCAPE", 191 | "EXPLICIT_IDS", 192 | "FILLRECORD", 193 | "FIXEDWIDTH", 194 | "FORMAT", 195 | "IAM_ROLE", 196 | "GZIP", 197 | "IGNOREBLANKLINES", 198 | "IGNOREHEADER", 199 | "JSON", 200 | "LZOP", 201 | "MANIFEST", 202 | "MASTER_SYMMETRIC_KEY", 203 | "MAXERROR", 204 | "NOLOAD", 205 | "NULL AS", 206 | "READRATIO", 207 | "REGION", 208 | "REMOVEQUOTES", 209 | "ROUNDEC", 210 | "SSH", 211 | "STATUPDATE", 212 | "TIMEFORMAT", 213 | "SESSION_TOKEN", 214 | "TRIMBLANKS", 215 | "TRUNCATECOLUMNS", 216 | "EXTERNAL", 217 | "DATA CATALOG", 218 | "HIVE METASTORE", 219 | "CATALOG_ROLE", 220 | "VACUUM", 221 | "COPY", 222 | "UNLOAD", 223 | "EVEN", 224 | "ALL"); 225 | 226 | private static final List reservedTopLevelWords = 227 | Arrays.asList( 228 | "ADD", 229 | "AFTER", 230 | "ALTER COLUMN", 231 | "ALTER TABLE", 232 | "DELETE FROM", 233 | "EXCEPT", 234 | "FROM", 235 | "GROUP BY", 236 | "HAVING", 237 | "INSERT INTO", 238 | "INSERT", 239 | "INTERSECT", 240 | "TOP", 241 | "LIMIT", 242 | "MODIFY", 243 | "ORDER BY", 244 | "SELECT", 245 | "SET CURRENT SCHEMA", 246 | "SET SCHEMA", 247 | "SET", 248 | "UNION ALL", 249 | "UNION", 250 | "UPDATE", 251 | "VALUES", 252 | "WHERE", 253 | "VACUUM", 254 | "COPY", 255 | "UNLOAD", 256 | "ANALYZE", 257 | "ANALYSE", 258 | "DISTKEY", 259 | "SORTKEY", 260 | "COMPOUND", 261 | "INTERLEAVED", 262 | "FORMAT", 263 | "DELIMITER", 264 | "FIXEDWIDTH", 265 | "AVRO", 266 | "JSON", 267 | "ENCRYPTED", 268 | "BZIP2", 269 | "GZIP", 270 | "LZOP", 271 | "PARQUET", 272 | "ORC", 273 | "ACCEPTANYDATE", 274 | "ACCEPTINVCHARS", 275 | "BLANKSASNULL", 276 | "DATEFORMAT", 277 | "EMPTYASNULL", 278 | "ENCODING", 279 | "ESCAPE", 280 | "EXPLICIT_IDS", 281 | "FILLRECORD", 282 | "IGNOREBLANKLINES", 283 | "IGNOREHEADER", 284 | "NULL AS", 285 | "REMOVEQUOTES", 286 | "ROUNDEC", 287 | "TIMEFORMAT", 288 | "TRIMBLANKS", 289 | "TRUNCATECOLUMNS", 290 | "COMPROWS", 291 | "COMPUPDATE", 292 | "MAXERROR", 293 | "NOLOAD", 294 | "STATUPDATE", 295 | "MANIFEST", 296 | "REGION", 297 | "IAM_ROLE", 298 | "MASTER_SYMMETRIC_KEY", 299 | "SSH", 300 | "ACCEPTANYDATE", 301 | "ACCEPTINVCHARS", 302 | "ACCESS_KEY_ID", 303 | "SECRET_ACCESS_KEY", 304 | "AVRO", 305 | "BLANKSASNULL", 306 | "BZIP2", 307 | "COMPROWS", 308 | "COMPUPDATE", 309 | "CREDENTIALS", 310 | "DATEFORMAT", 311 | "DELIMITER", 312 | "EMPTYASNULL", 313 | "ENCODING", 314 | "ENCRYPTED", 315 | "ESCAPE", 316 | "EXPLICIT_IDS", 317 | "FILLRECORD", 318 | "FIXEDWIDTH", 319 | "FORMAT", 320 | "IAM_ROLE", 321 | "GZIP", 322 | "IGNOREBLANKLINES", 323 | "IGNOREHEADER", 324 | "JSON", 325 | "LZOP", 326 | "MANIFEST", 327 | "MASTER_SYMMETRIC_KEY", 328 | "MAXERROR", 329 | "NOLOAD", 330 | "NULL AS", 331 | "READRATIO", 332 | "REGION", 333 | "REMOVEQUOTES", 334 | "ROUNDEC", 335 | "SSH", 336 | "STATUPDATE", 337 | "TIMEFORMAT", 338 | "SESSION_TOKEN", 339 | "TRIMBLANKS", 340 | "TRUNCATECOLUMNS", 341 | "EXTERNAL", 342 | "DATA CATALOG", 343 | "HIVE METASTORE", 344 | "CATALOG_ROLE"); 345 | 346 | private static final List reservedTopLevelWordsNoIndent = Collections.emptyList(); 347 | 348 | private static final List reservedNewlineWords = 349 | Arrays.asList( 350 | "AND", 351 | "ELSE", 352 | "OR", 353 | "OUTER APPLY", 354 | "WHEN", 355 | "VACUUM", 356 | "COPY", 357 | "UNLOAD", 358 | "ANALYZE", 359 | "ANALYSE", 360 | "DISTKEY", 361 | "SORTKEY", 362 | "COMPOUND", 363 | "INTERLEAVED", 364 | // joins 365 | "JOIN", 366 | "INNER JOIN", 367 | "LEFT JOIN", 368 | "LEFT OUTER JOIN", 369 | "RIGHT JOIN", 370 | "RIGHT OUTER JOIN", 371 | "FULL JOIN", 372 | "FULL OUTER JOIN", 373 | "CROSS JOIN", 374 | "NATURAL JOIN"); 375 | 376 | @Override 377 | public DialectConfig dialectConfig() { 378 | return DialectConfig.builder() 379 | .reservedWords(reservedWords) 380 | .reservedTopLevelWords(reservedTopLevelWords) 381 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 382 | .reservedNewlineWords(reservedNewlineWords) 383 | .stringTypes( 384 | Arrays.asList( 385 | StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE)) 386 | .openParens(Collections.singletonList("(")) 387 | .closeParens(Collections.singletonList(")")) 388 | .indexedPlaceholderTypes(Collections.singletonList("?")) 389 | .namedPlaceholderTypes(Arrays.asList("@", "#", "$")) 390 | .lineCommentTypes(Collections.singletonList("--")) 391 | .specialWordChars(Arrays.asList("#", "@")) 392 | .operators(Arrays.asList("|/", "||/", "<<", ">>", "!=", "||")) 393 | .build(); 394 | } 395 | 396 | public RedshiftFormatter(FormatConfig cfg) { 397 | super(cfg); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/SparkSqlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import com.github.vertical_blank.sqlformatter.core.Token; 7 | import com.github.vertical_blank.sqlformatter.core.TokenTypes; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class SparkSqlFormatter extends AbstractFormatter { 13 | 14 | private static final List reservedWords = 15 | Arrays.asList( 16 | "ALL", 17 | "ALTER", 18 | "ANALYSE", 19 | "ANALYZE", 20 | "ARRAY_ZIP", 21 | "ARRAY", 22 | "AS", 23 | "ASC", 24 | "AVG", 25 | "BETWEEN", 26 | "CASCADE", 27 | "CASE", 28 | "CAST", 29 | "COALESCE", 30 | "COLLECT_LIST", 31 | "COLLECT_SET", 32 | "COLUMN", 33 | "COLUMNS", 34 | "COMMENT", 35 | "CONSTRAINT", 36 | "CONTAINS", 37 | "CONVERT", 38 | "COUNT", 39 | "CUME_DIST", 40 | "CURRENT ROW", 41 | "CURRENT_DATE", 42 | "CURRENT_TIMESTAMP", 43 | "DATABASE", 44 | "DATABASES", 45 | "DATE_ADD", 46 | "DATE_SUB", 47 | "DATE_TRUNC", 48 | "DAY_HOUR", 49 | "DAY_MINUTE", 50 | "DAY_SECOND", 51 | "DAY", 52 | "DAYS", 53 | "DECODE", 54 | "DEFAULT", 55 | "DELETE", 56 | "DENSE_RANK", 57 | "DESC", 58 | "DESCRIBE", 59 | "DISTINCT", 60 | "DISTINCTROW", 61 | "DIV", 62 | "DROP", 63 | "ELSE", 64 | "ENCODE", 65 | "END", 66 | "EXISTS", 67 | "EXPLAIN", 68 | "EXPLODE_OUTER", 69 | "EXPLODE", 70 | "FILTER", 71 | "FIRST_VALUE", 72 | "FIRST", 73 | "FIXED", 74 | "FLATTEN", 75 | "FOLLOWING", 76 | "FROM_UNIXTIME", 77 | "FULL", 78 | "GREATEST", 79 | "GROUP_CONCAT", 80 | "HOUR_MINUTE", 81 | "HOUR_SECOND", 82 | "HOUR", 83 | "HOURS", 84 | "IF", 85 | "IFNULL", 86 | "IN", 87 | "INSERT", 88 | "INTERVAL", 89 | "INTO", 90 | "IS", 91 | "LAG", 92 | "LAST_VALUE", 93 | "LAST", 94 | "LEAD", 95 | "LEADING", 96 | "LEAST", 97 | "LEVEL", 98 | "LIKE", 99 | "MAX", 100 | "MERGE", 101 | "MIN", 102 | "MINUTE_SECOND", 103 | "MINUTE", 104 | "MONTH", 105 | "NATURAL", 106 | "NOT", 107 | "NOW()", 108 | "NTILE", 109 | "NULL", 110 | "NULLIF", 111 | "OFFSET", 112 | "ON DELETE", 113 | "ON UPDATE", 114 | "ON", 115 | "ONLY", 116 | "OPTIMIZE", 117 | "OVER", 118 | "PERCENT_RANK", 119 | "PRECEDING", 120 | "RANGE", 121 | "RANK", 122 | "REGEXP", 123 | "RENAME", 124 | "RLIKE", 125 | "ROW", 126 | "ROWS", 127 | "SECOND", 128 | "SEPARATOR", 129 | "SEQUENCE", 130 | "SIZE", 131 | "STRING", 132 | "STRUCT", 133 | "SUM", 134 | "TABLE", 135 | "TABLES", 136 | "TEMPORARY", 137 | "THEN", 138 | "TO_DATE", 139 | "TO_JSON", 140 | "TO", 141 | "TRAILING", 142 | "TRANSFORM", 143 | "TRUE", 144 | "TRUNCATE", 145 | "TYPE", 146 | "TYPES", 147 | "UNBOUNDED", 148 | "UNIQUE", 149 | "UNIX_TIMESTAMP", 150 | "UNLOCK", 151 | "UNSIGNED", 152 | "USING", 153 | "VARIABLES", 154 | "VIEW", 155 | "WHEN", 156 | "WITH", 157 | "YEAR_MONTH"); 158 | 159 | private static final List reservedTopLevelWords = 160 | Arrays.asList( 161 | "ADD", 162 | "AFTER", 163 | "ALTER COLUMN", 164 | "ALTER DATABASE", 165 | "ALTER SCHEMA", 166 | "ALTER TABLE", 167 | "CLUSTER BY", 168 | "CLUSTERED BY", 169 | "DELETE FROM", 170 | "DISTRIBUTE BY", 171 | "FROM", 172 | "GROUP BY", 173 | "HAVING", 174 | "INSERT INTO", 175 | "INSERT", 176 | "LIMIT", 177 | "OPTIONS", 178 | "ORDER BY", 179 | "PARTITION BY", 180 | "PARTITIONED BY", 181 | "RANGE", 182 | "ROWS", 183 | "SELECT", 184 | "SET CURRENT SCHEMA", 185 | "SET SCHEMA", 186 | "SET", 187 | "TBLPROPERTIES", 188 | "UPDATE", 189 | "USING", 190 | "VALUES", 191 | "WHERE", 192 | "WINDOW"); 193 | 194 | private static final List reservedTopLevelWordsNoIndent = 195 | Arrays.asList("EXCEPT ALL", "EXCEPT", "INTERSECT ALL", "INTERSECT", "UNION ALL", "UNION"); 196 | 197 | private static final List reservedNewlineWords = 198 | Arrays.asList( 199 | "AND", 200 | "CREATE OR", 201 | "CREATE", 202 | "ELSE", 203 | "LATERAL VIEW", 204 | "OR", 205 | "OUTER APPLY", 206 | "WHEN", 207 | "XOR", 208 | // joins 209 | "JOIN", 210 | "INNER JOIN", 211 | "LEFT JOIN", 212 | "LEFT OUTER JOIN", 213 | "RIGHT JOIN", 214 | "RIGHT OUTER JOIN", 215 | "FULL JOIN", 216 | "FULL OUTER JOIN", 217 | "CROSS JOIN", 218 | "NATURAL JOIN", 219 | // non-standard-joins 220 | "ANTI JOIN", 221 | "SEMI JOIN", 222 | "LEFT ANTI JOIN", 223 | "LEFT SEMI JOIN", 224 | "RIGHT OUTER JOIN", 225 | "RIGHT SEMI JOIN", 226 | "NATURAL ANTI JOIN", 227 | "NATURAL FULL OUTER JOIN", 228 | "NATURAL INNER JOIN", 229 | "NATURAL LEFT ANTI JOIN", 230 | "NATURAL LEFT OUTER JOIN", 231 | "NATURAL LEFT SEMI JOIN", 232 | "NATURAL OUTER JOIN", 233 | "NATURAL RIGHT OUTER JOIN", 234 | "NATURAL RIGHT SEMI JOIN", 235 | "NATURAL SEMI JOIN"); 236 | 237 | @Override 238 | public DialectConfig dialectConfig() { 239 | return DialectConfig.builder() 240 | .reservedWords(reservedWords) 241 | .reservedTopLevelWords(reservedTopLevelWords) 242 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 243 | .reservedNewlineWords(reservedNewlineWords) 244 | .stringTypes( 245 | Arrays.asList( 246 | StringLiteral.DOUBLE_QUOTE, 247 | StringLiteral.SINGLE_QUOTE, 248 | StringLiteral.BACK_QUOTE, 249 | StringLiteral.BRACE)) 250 | .openParens(Arrays.asList("(", "CASE")) 251 | .closeParens(Arrays.asList(")", "END")) 252 | .indexedPlaceholderTypes(Collections.singletonList("?")) 253 | .namedPlaceholderTypes(Collections.singletonList("$")) 254 | .lineCommentTypes(Collections.singletonList("--")) 255 | .operators(Arrays.asList("!=", "<=>", "&&", "||", "==", "->")) 256 | .build(); 257 | } 258 | 259 | @Override 260 | public Token tokenOverride(Token token) { 261 | // Fix cases where names are ambiguously keywords or functions 262 | if (Token.isWindow(token)) { 263 | Token aheadToken = this.tokenLookAhead(); 264 | if (aheadToken != null && aheadToken.type == TokenTypes.OPEN_PAREN) { 265 | // This is a function call, treat it as a reserved word 266 | return new Token(TokenTypes.RESERVED, token.value); 267 | } 268 | } 269 | 270 | // Fix cases where names are ambiguously keywords or properties 271 | if (Token.isEnd(token)) { 272 | Token backToken = this.tokenLookBehind(); 273 | if (backToken != null 274 | && backToken.type == TokenTypes.OPERATOR 275 | && backToken.value.equals(".")) { 276 | // This is window().end (or similar) not CASE ... END 277 | return new Token(TokenTypes.WORD, token.value); 278 | } 279 | } 280 | 281 | return token; 282 | } 283 | 284 | public SparkSqlFormatter(FormatConfig cfg) { 285 | super(cfg); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/StandardSqlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class StandardSqlFormatter extends AbstractFormatter { 11 | 12 | // https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#reserved-word 13 | private static final List reservedWords = 14 | Arrays.asList( 15 | "ABS", 16 | "ALL", 17 | "ALLOCATE", 18 | "ALTER", 19 | "AND", 20 | "ANY", 21 | "ARE", 22 | "ARRAY", 23 | "AS", 24 | "ASENSITIVE", 25 | "ASYMMETRIC", 26 | "AT", 27 | "ATOMIC", 28 | "AUTHORIZATION", 29 | "AVG", 30 | "BEGIN", 31 | "BETWEEN", 32 | "BIGINT", 33 | "BINARY", 34 | "BLOB", 35 | "BOOLEAN", 36 | "BOTH", 37 | "BY", 38 | "CALL", 39 | "CALLED", 40 | "CARDINALITY", 41 | "CASCADED", 42 | "CASE", 43 | "CAST", 44 | "CEIL", 45 | "CEILING", 46 | "CHAR", 47 | "CHAR_LENGTH", 48 | "CHARACTER", 49 | "CHARACTER_LENGTH", 50 | "CHECK", 51 | "CLOB", 52 | "CLOSE", 53 | "COALESCE", 54 | "COLLATE", 55 | "COLLECT", 56 | "COLUMN", 57 | "COMMIT", 58 | "CONDITION", 59 | "CONNECT", 60 | "CONSTRAINT", 61 | "CONVERT", 62 | "CORR", 63 | "CORRESPONDING", 64 | "COUNT", 65 | "COVAR_POP", 66 | "COVAR_SAMP", 67 | "CREATE", 68 | "CROSS", 69 | "CUBE", 70 | "CUME_DIST", 71 | "CURRENT", 72 | "CURRENT_CATALOG", 73 | "CURRENT_DATE", 74 | "CURRENT_DEFAULT_TRANSFORM_GROUP", 75 | "CURRENT_PATH", 76 | "CURRENT_ROLE", 77 | "CURRENT_SCHEMA", 78 | "CURRENT_TIME", 79 | "CURRENT_TIMESTAMP", 80 | "CURRENT_TRANSFORM_GROUP_FOR_TYPE", 81 | "CURRENT_USER", 82 | "CURSOR", 83 | "CYCLE", 84 | "DATE", 85 | "DAY", 86 | "DEALLOCATE", 87 | "DEC", 88 | "DECIMAL", 89 | "DECLARE", 90 | "DEFAULT", 91 | "DELETE", 92 | "DENSE_RANK", 93 | "DEREF", 94 | "DESCRIBE", 95 | "DETERMINISTIC", 96 | "DISCONNECT", 97 | "DISTINCT", 98 | "DOUBLE", 99 | "DROP", 100 | "DYNAMIC", 101 | "EACH", 102 | "ELEMENT", 103 | "ELSE", 104 | "END", 105 | "END-EXEC", 106 | "ESCAPE", 107 | "EVERY", 108 | "EXCEPT", 109 | "EXEC", 110 | "EXECUTE", 111 | "EXISTS", 112 | "EXP", 113 | "EXTERNAL", 114 | "EXTRACT", 115 | "FALSE", 116 | "FETCH", 117 | "FILTER", 118 | "FLOAT", 119 | "FLOOR", 120 | "FOR", 121 | "FOREIGN", 122 | "FREE", 123 | "FROM", 124 | "FULL", 125 | "FUNCTION", 126 | "FUSION", 127 | "GET", 128 | "GLOBAL", 129 | "GRANT", 130 | "GROUP", 131 | "GROUPING", 132 | "HAVING", 133 | "HOLD", 134 | "HOUR", 135 | "IDENTITY", 136 | "IN", 137 | "INDICATOR", 138 | "INNER", 139 | "INOUT", 140 | "INSENSITIVE", 141 | "INSERT", 142 | "INT", 143 | "INTEGER", 144 | "INTERSECT", 145 | "INTERSECTION", 146 | "INTERVAL", 147 | "INTO", 148 | "IS", 149 | "JOIN", 150 | "LANGUAGE", 151 | "LARGE", 152 | "LATERAL", 153 | "LEADING", 154 | "LEFT", 155 | "LIKE", 156 | "LIKE_REGEX", 157 | "LN", 158 | "LOCAL", 159 | "LOCALTIME", 160 | "LOCALTIMESTAMP", 161 | "LOWER", 162 | "MATCH", 163 | "MAX", 164 | "MEMBER", 165 | "MERGE", 166 | "METHOD", 167 | "MIN", 168 | "MINUTE", 169 | "MOD", 170 | "MODIFIES", 171 | "MODULE", 172 | "MONTH", 173 | "MULTISET", 174 | "NATIONAL", 175 | "NATURAL", 176 | "NCHAR", 177 | "NCLOB", 178 | "NEW", 179 | "NO", 180 | "NONE", 181 | "NORMALIZE", 182 | "NOT", 183 | "NULL", 184 | "NULLIF", 185 | "NUMERIC", 186 | "OCTET_LENGTH", 187 | "OCCURRENCES_REGEX", 188 | "OF", 189 | "OLD", 190 | "ON", 191 | "ONLY", 192 | "OPEN", 193 | "OR", 194 | "ORDER", 195 | "OUT", 196 | "OUTER", 197 | "OVER", 198 | "OVERLAPS", 199 | "OVERLAY", 200 | "PARAMETER", 201 | "PARTITION", 202 | "PERCENT_RANK", 203 | "PERCENTILE_CONT", 204 | "PERCENTILE_DISC", 205 | "POSITION", 206 | "POSITION_REGEX", 207 | "POWER", 208 | "PRECISION", 209 | "PREPARE", 210 | "PRIMARY", 211 | "PROCEDURE", 212 | "RANGE", 213 | "RANK", 214 | "READS", 215 | "REAL", 216 | "RECURSIVE", 217 | "REF", 218 | "REFERENCES", 219 | "REFERENCING", 220 | "REGR_AVGX", 221 | "REGR_AVGY", 222 | "REGR_COUNT", 223 | "REGR_INTERCEPT", 224 | "REGR_R2", 225 | "REGR_SLOPE", 226 | "REGR_SXX", 227 | "REGR_SXY", 228 | "REGR_SYY", 229 | "RELEASE", 230 | "RESULT", 231 | "RETURN", 232 | "RETURNS", 233 | "REVOKE", 234 | "RIGHT", 235 | "ROLLBACK", 236 | "ROLLUP", 237 | "ROW", 238 | "ROW_NUMBER", 239 | "ROWS", 240 | "SAVEPOINT", 241 | "SCOPE", 242 | "SCROLL", 243 | "SEARCH", 244 | "SECOND", 245 | "SELECT", 246 | "SENSITIVE", 247 | "SESSION_USER", 248 | "SET", 249 | "SIMILAR", 250 | "SMALLINT", 251 | "SOME", 252 | "SPECIFIC", 253 | "SPECIFICTYPE", 254 | "SQL", 255 | "SQLEXCEPTION", 256 | "SQLSTATE", 257 | "SQLWARNING", 258 | "SQRT", 259 | "START", 260 | "STATIC", 261 | "STDDEV_POP", 262 | "STDDEV_SAMP", 263 | "SUBMULTISET", 264 | "SUBSTRING", 265 | "SUBSTRING_REGEX", 266 | "SUM", 267 | "SYMMETRIC", 268 | "SYSTEM", 269 | "SYSTEM_USER", 270 | "TABLE", 271 | "TABLESAMPLE", 272 | "THEN", 273 | "TIME", 274 | "TIMESTAMP", 275 | "TIMEZONE_HOUR", 276 | "TIMEZONE_MINUTE", 277 | "TO", 278 | "TRAILING", 279 | "TRANSLATE", 280 | "TRANSLATE_REGEX", 281 | "TRANSLATION", 282 | "TREAT", 283 | "TRIGGER", 284 | "TRIM", 285 | "TRUE", 286 | "UESCAPE", 287 | "UNION", 288 | "UNIQUE", 289 | "UNKNOWN", 290 | "UNNEST", 291 | "UPDATE", 292 | "UPPER", 293 | "USER", 294 | "USING", 295 | "VALUE", 296 | "VALUES", 297 | "VAR_POP", 298 | "VAR_SAMP", 299 | "VARBINARY", 300 | "VARCHAR", 301 | "VARYING", 302 | "WHEN", 303 | "WHENEVER", 304 | "WHERE", 305 | "WIDTH_BUCKET", 306 | "WINDOW", 307 | "WITH", 308 | "WITHIN", 309 | "WITHOUT", 310 | "YEAR"); 311 | 312 | private static final List reservedTopLevelWords = 313 | Arrays.asList( 314 | "ADD", 315 | "ALTER COLUMN", 316 | "ALTER TABLE", 317 | "CASE", 318 | "DELETE FROM", 319 | "END", 320 | "FETCH FIRST", 321 | "FETCH NEXT", 322 | "FETCH PRIOR", 323 | "FETCH LAST", 324 | "FETCH ABSOLUTE", 325 | "FETCH RELATIVE", 326 | "FROM", 327 | "GROUP BY", 328 | "HAVING", 329 | "INSERT INTO", 330 | "LIMIT", 331 | "ORDER BY", 332 | "SELECT", 333 | "SET SCHEMA", 334 | "SET", 335 | "UPDATE", 336 | "VALUES", 337 | "WHERE"); 338 | 339 | private static final List reservedTopLevelWordsNoIndent = 340 | Arrays.asList( 341 | "INTERSECT", 342 | "INTERSECT ALL", 343 | "INTERSECT DISTINCT", 344 | "UNION", 345 | "UNION ALL", 346 | "UNION DISTINCT", 347 | "EXCEPT", 348 | "EXCEPT ALL", 349 | "EXCEPT DISTINCT"); 350 | 351 | private static final List reservedNewlineWords = 352 | Arrays.asList( 353 | "AND", 354 | "ELSE", 355 | "OR", 356 | "WHEN", 357 | // joins 358 | "JOIN", 359 | "INNER JOIN", 360 | "LEFT JOIN", 361 | "LEFT OUTER JOIN", 362 | "RIGHT JOIN", 363 | "RIGHT OUTER JOIN", 364 | "FULL JOIN", 365 | "FULL OUTER JOIN", 366 | "CROSS JOIN", 367 | "NATURAL JOIN"); 368 | 369 | @Override 370 | public DialectConfig dialectConfig() { 371 | return DialectConfig.builder() 372 | .reservedWords(reservedWords) 373 | .reservedTopLevelWords(reservedTopLevelWords) 374 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 375 | .reservedNewlineWords(reservedNewlineWords) 376 | .stringTypes(Arrays.asList(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE)) 377 | .openParens(Arrays.asList("(", "CASE")) 378 | .closeParens(Arrays.asList(")", "END")) 379 | .indexedPlaceholderTypes(Collections.singletonList("?")) 380 | .namedPlaceholderTypes(Collections.emptyList()) 381 | .lineCommentTypes(Arrays.asList("--")) 382 | .operators(Collections.singletonList("||")) 383 | .build(); 384 | } 385 | 386 | public StandardSqlFormatter(FormatConfig cfg) { 387 | super(cfg); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/StringLiteral.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.stream.Collectors; 7 | 8 | public class StringLiteral { 9 | public static final String BACK_QUOTE = "``"; 10 | public static final String DOUBLE_QUOTE = "\"\""; 11 | public static final String U_DOUBLE_QUOTE = "U&\"\""; 12 | public static final String U_SINGLE_QUOTE = "U&''"; 13 | public static final String E_SINGLE_QUOTE = "E''"; 14 | public static final String N_SINGLE_QUOTE = "N''"; 15 | public static final String Q_SINGLE_QUOTE = "Q''"; 16 | public static final String SINGLE_QUOTE = "''"; 17 | public static final String BRACE = "{}"; 18 | public static final String DOLLAR = "$$"; 19 | public static final String BRACKET = "[]"; 20 | 21 | private static final Map literals; 22 | 23 | static { 24 | literals = 25 | new HashMap<>( 26 | Arrays.stream(Preset.values()) 27 | .collect(Collectors.toMap(Preset::getKey, Preset::getRegex))); 28 | } 29 | 30 | public static String get(String key) { 31 | return literals.get(key); 32 | } 33 | 34 | private enum Preset { 35 | /** `` */ 36 | BACK_QUOTE(StringLiteral.BACK_QUOTE, "((`[^`]*($|`))+)"), 37 | /** "" */ 38 | DOUBLE_QUOTE( 39 | StringLiteral.DOUBLE_QUOTE, 40 | "((\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*(\"|$))+)"), // "((^\"((?:\"\"|[^\"])*)\")+)"), 41 | /** [] */ 42 | BRACKET(StringLiteral.BRACKET, "((\\[[^\\]]*($|\\]))(\\][^\\]]*($|\\]))*)"), 43 | /** {} */ 44 | BRACE(StringLiteral.BRACE, "((\\{[^\\}]*($|\\}))+)"), 45 | /** '' */ 46 | SINGLE_QUOTE( 47 | StringLiteral.SINGLE_QUOTE, 48 | "(('[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)"), // "((^'((?:''|[^'])*)')+)"), 49 | /** N'' */ 50 | N_SINGLE_QUOTE(StringLiteral.N_SINGLE_QUOTE, "((N'[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)"), 51 | /** q'' */ 52 | Q_SINGLE_QUOTE( 53 | StringLiteral.Q_SINGLE_QUOTE, 54 | "(?i)" 55 | + String.join( 56 | "|", 57 | "((n?q'\\{(?:(?!\\}'|\\\\).)*\\}')+)", 58 | "((n?q'\\[(?:(?!\\]'|\\\\).)*\\]')+)", 59 | "((n?q'<(?:(?!>'|\\\\).)*>')+)", 60 | "((n?q'\\((?:(?!\\)'|\\\\).)*\\)')+)")), 61 | // single_quote("((^'((?:''|[^'])*)')+)"), 62 | E_SINGLE_QUOTE(StringLiteral.E_SINGLE_QUOTE, "((E'[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)"), 63 | /** U&'' */ 64 | U_SINGLE_QUOTE(StringLiteral.U_SINGLE_QUOTE, "((U&'[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)"), 65 | /** U&"" */ 66 | U_DOUBLE_QUOTE(StringLiteral.U_DOUBLE_QUOTE, "((U&\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*(\"|$))+)"), 67 | /** $$ */ 68 | DOLLAR(StringLiteral.DOLLAR, "((?\\$\\w*\\$)[\\s\\S]*?(?:\\k|$))"), 69 | ; 70 | 71 | public final String key; 72 | public final String regex; 73 | 74 | Preset(String key, String regex) { 75 | this.key = key; 76 | this.regex = regex; 77 | } 78 | 79 | public String getKey() { 80 | return this.key; 81 | } 82 | 83 | public String getRegex() { 84 | return this.regex; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/github/vertical_blank/sqlformatter/languages/TSqlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.languages; 2 | 3 | import com.github.vertical_blank.sqlformatter.core.AbstractFormatter; 4 | import com.github.vertical_blank.sqlformatter.core.DialectConfig; 5 | import com.github.vertical_blank.sqlformatter.core.FormatConfig; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class TSqlFormatter extends AbstractFormatter { 11 | 12 | private static final List reservedWords = 13 | Arrays.asList( 14 | "ADD", 15 | "EXTERNAL", 16 | "PROCEDURE", 17 | "ALL", 18 | "FETCH", 19 | "PUBLIC", 20 | "ALTER", 21 | "FILE", 22 | "RAISERROR", 23 | "AND", 24 | "FILLFACTOR", 25 | "READ", 26 | "ANY", 27 | "FOR", 28 | "READTEXT", 29 | "AS", 30 | "FOREIGN", 31 | "RECONFIGURE", 32 | "ASC", 33 | "FREETEXT", 34 | "REFERENCES", 35 | "AUTHORIZATION", 36 | "FREETEXTTABLE", 37 | "REPLICATION", 38 | "BACKUP", 39 | "FROM", 40 | "RESTORE", 41 | "BEGIN", 42 | "FULL", 43 | "RESTRICT", 44 | "BETWEEN", 45 | "FUNCTION", 46 | "RETURN", 47 | "BREAK", 48 | "GOTO", 49 | "REVERT", 50 | "BROWSE", 51 | "GRANT", 52 | "REVOKE", 53 | "BULK", 54 | "GROUP", 55 | "RIGHT", 56 | "BY", 57 | "HAVING", 58 | "ROLLBACK", 59 | "CASCADE", 60 | "HOLDLOCK", 61 | "ROWCOUNT", 62 | "CASE", 63 | "IDENTITY", 64 | "ROWGUIDCOL", 65 | "CHECK", 66 | "IDENTITY_INSERT", 67 | "RULE", 68 | "CHECKPOINT", 69 | "IDENTITYCOL", 70 | "SAVE", 71 | "CLOSE", 72 | "IF", 73 | "SCHEMA", 74 | "CLUSTERED", 75 | "IN", 76 | "SECURITYAUDIT", 77 | "COALESCE", 78 | "INDEX", 79 | "SELECT", 80 | "COLLATE", 81 | "INNER", 82 | "SEMANTICKEYPHRASETABLE", 83 | "COLUMN", 84 | "INSERT", 85 | "SEMANTICSIMILARITYDETAILSTABLE", 86 | "COMMIT", 87 | "INTERSECT", 88 | "SEMANTICSIMILARITYTABLE", 89 | "COMPUTE", 90 | "INTO", 91 | "SESSION_USER", 92 | "CONSTRAINT", 93 | "IS", 94 | "SET", 95 | "CONTAINS", 96 | "JOIN", 97 | "SETUSER", 98 | "CONTAINSTABLE", 99 | "KEY", 100 | "SHUTDOWN", 101 | "CONTINUE", 102 | "KILL", 103 | "SOME", 104 | "CONVERT", 105 | "LEFT", 106 | "STATISTICS", 107 | "CREATE", 108 | "LIKE", 109 | "SYSTEM_USER", 110 | "CROSS", 111 | "LINENO", 112 | "TABLE", 113 | "CURRENT", 114 | "LOAD", 115 | "TABLESAMPLE", 116 | "CURRENT_DATE", 117 | "MERGE", 118 | "TEXTSIZE", 119 | "CURRENT_TIME", 120 | "NATIONAL", 121 | "THEN", 122 | "CURRENT_TIMESTAMP", 123 | "NOCHECK", 124 | "TO", 125 | "CURRENT_USER", 126 | "NONCLUSTERED", 127 | "TOP", 128 | "CURSOR", 129 | "NOT", 130 | "TRAN", 131 | "DATABASE", 132 | "NULL", 133 | "TRANSACTION", 134 | "DBCC", 135 | "NULLIF", 136 | "TRIGGER", 137 | "DEALLOCATE", 138 | "OF", 139 | "TRUNCATE", 140 | "DECLARE", 141 | "OFF", 142 | "TRY_CONVERT", 143 | "DEFAULT", 144 | "OFFSETS", 145 | "TSEQUAL", 146 | "DELETE", 147 | "ON", 148 | "UNION", 149 | "DENY", 150 | "OPEN", 151 | "UNIQUE", 152 | "DESC", 153 | "OPENDATASOURCE", 154 | "UNPIVOT", 155 | "DISK", 156 | "OPENQUERY", 157 | "UPDATE", 158 | "DISTINCT", 159 | "OPENROWSET", 160 | "UPDATETEXT", 161 | "DISTRIBUTED", 162 | "OPENXML", 163 | "USE", 164 | "DOUBLE", 165 | "OPTION", 166 | "USER", 167 | "DROP", 168 | "OR", 169 | "VALUES", 170 | "DUMP", 171 | "ORDER", 172 | "VARYING", 173 | "ELSE", 174 | "OUTER", 175 | "VIEW", 176 | "END", 177 | "OVER", 178 | "WAITFOR", 179 | "ERRLVL", 180 | "PERCENT", 181 | "WHEN", 182 | "ESCAPE", 183 | "PIVOT", 184 | "WHERE", 185 | "EXCEPT", 186 | "PLAN", 187 | "WHILE", 188 | "EXEC", 189 | "PRECISION", 190 | "WITH", 191 | "EXECUTE", 192 | "PRIMARY", 193 | "WITHIN GROUP", 194 | "EXISTS", 195 | "PRINT", 196 | "WRITETEXT", 197 | "EXIT", 198 | "PROC"); 199 | 200 | private static final List reservedTopLevelWords = 201 | Arrays.asList( 202 | "ADD", 203 | "ALTER COLUMN", 204 | "ALTER TABLE", 205 | "CASE", 206 | "DELETE FROM", 207 | "END", 208 | "EXCEPT", 209 | "FROM", 210 | "GROUP BY", 211 | "HAVING", 212 | "INSERT INTO", 213 | "INSERT", 214 | "LIMIT", 215 | "ORDER BY", 216 | "SELECT", 217 | "SET CURRENT SCHEMA", 218 | "SET SCHEMA", 219 | "SET", 220 | "UPDATE", 221 | "VALUES", 222 | "WHERE"); 223 | 224 | private static final List reservedTopLevelWordsNoIndent = 225 | Arrays.asList("INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL"); 226 | 227 | private static final List reservedNewlineWords = 228 | Arrays.asList( 229 | "AND", 230 | "ELSE", 231 | "OR", 232 | "WHEN", 233 | // joins 234 | "JOIN", 235 | "INNER JOIN", 236 | "LEFT JOIN", 237 | "LEFT OUTER JOIN", 238 | "RIGHT JOIN", 239 | "RIGHT OUTER JOIN", 240 | "FULL JOIN", 241 | "FULL OUTER JOIN", 242 | "CROSS JOIN"); 243 | 244 | @Override 245 | public DialectConfig dialectConfig() { 246 | return DialectConfig.builder() 247 | .reservedWords(reservedWords) 248 | .reservedTopLevelWords(reservedTopLevelWords) 249 | .reservedTopLevelWordsNoIndent(reservedTopLevelWordsNoIndent) 250 | .reservedNewlineWords(reservedNewlineWords) 251 | .stringTypes( 252 | Arrays.asList( 253 | StringLiteral.DOUBLE_QUOTE, 254 | StringLiteral.N_SINGLE_QUOTE, 255 | StringLiteral.SINGLE_QUOTE, 256 | StringLiteral.BACK_QUOTE, 257 | StringLiteral.BRACKET)) 258 | .openParens(Arrays.asList("(", "CASE")) 259 | .closeParens(Arrays.asList(")", "END")) 260 | .indexedPlaceholderTypes(Collections.emptyList()) 261 | .namedPlaceholderTypes(Arrays.asList("@")) 262 | .lineCommentTypes(Collections.singletonList("--")) 263 | .specialWordChars(Arrays.asList("#", "@")) 264 | .operators( 265 | Arrays.asList( 266 | ">=", "<=", "<>", "!=", "!<", "!>", "+=", "-=", "*=", "/=", "%=", "|=", "&=", "^=", 267 | "::")) 268 | .build(); 269 | } 270 | 271 | public TSqlFormatter(FormatConfig cfg) { 272 | super(cfg); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/test/java/com/github/vertical_blank/sqlformatter/Benchmark.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import org.openjdk.jmh.annotations.*; 5 | import org.openjdk.jmh.runner.Runner; 6 | import org.openjdk.jmh.runner.RunnerException; 7 | import org.openjdk.jmh.runner.options.Options; 8 | import org.openjdk.jmh.runner.options.OptionsBuilder; 9 | 10 | @BenchmarkMode(Mode.AverageTime) 11 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 12 | @State(Scope.Benchmark) 13 | public class Benchmark { 14 | 15 | public static final String SQL = 16 | "SELECT foo, bar, CASE baz WHEN 'one' THEN 1 WHEN 'two' THEN 2 ELSE 3 END FROM table"; 17 | 18 | public static void main(String[] args) throws RunnerException { 19 | Options opt = new OptionsBuilder().include(Benchmark.class.getSimpleName()).forks(1).build(); 20 | 21 | new Runner(opt).run(); 22 | } 23 | 24 | @org.openjdk.jmh.annotations.Benchmark 25 | public void format() { 26 | SqlFormatter.format(SQL); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/github/vertical_blank/sqlformatter/SqlFormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import com.github.vertical_blank.sqlformatter.languages.Dialect; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import org.junit.jupiter.api.Test; 10 | 11 | public class SqlFormatterTest { 12 | 13 | @Test 14 | public void simple() { 15 | String format = 16 | SqlFormatter.format( 17 | "SELECT foo, bar, CASE baz WHEN 'one' THEN 1 WHEN 'two' THEN 2 ELSE 3 END FROM table"); 18 | assertEquals( 19 | format, 20 | "SELECT\n" 21 | + " foo,\n" 22 | + " bar,\n" 23 | + " CASE\n" 24 | + " baz\n" 25 | + " WHEN 'one' THEN 1\n" 26 | + " WHEN 'two' THEN 2\n" 27 | + " ELSE 3\n" 28 | + " END\n" 29 | + "FROM\n" 30 | + " table"); 31 | } 32 | 33 | @Test 34 | public void withIndent() { 35 | String format = 36 | SqlFormatter.format( 37 | "SELECT foo, bar, CASE baz WHEN 'one' THEN 1 WHEN 'two' THEN 2 ELSE 3 END FROM table", 38 | " "); 39 | assertEquals( 40 | format, 41 | "SELECT\n" 42 | + " foo,\n" 43 | + " bar,\n" 44 | + " CASE\n" 45 | + " baz\n" 46 | + " WHEN 'one' THEN 1\n" 47 | + " WHEN 'two' THEN 2\n" 48 | + " ELSE 3\n" 49 | + " END\n" 50 | + "FROM\n" 51 | + " table"); 52 | } 53 | 54 | @Test 55 | public void withNamedParams() { 56 | Map namedParams = new HashMap<>(); 57 | namedParams.put("foo", "'bar'"); 58 | 59 | String format = 60 | SqlFormatter.of(Dialect.TSql).format("SELECT * FROM tbl WHERE foo = @foo", namedParams); 61 | assertEquals(format, "SELECT\n" + " *\n" + "FROM\n" + " tbl\n" + "WHERE\n" + " foo = 'bar'"); 62 | } 63 | 64 | @Test 65 | public void withFatArrow() { 66 | String format = 67 | SqlFormatter.extend(config -> config.plusOperators("=>")) 68 | .format("SELECT * FROM tbl WHERE foo => '123'"); 69 | assertEquals( 70 | format, "SELECT\n" + " *\n" + "FROM\n" + " tbl\n" + "WHERE\n" + " foo => '123'"); 71 | } 72 | 73 | @Test 74 | public void withIndexedParams() { 75 | String format = SqlFormatter.format("SELECT * FROM tbl WHERE foo = ?", Arrays.asList("'bar'")); 76 | assertEquals(format, "SELECT\n" + " *\n" + "FROM\n" + " tbl\n" + "WHERE\n" + " foo = 'bar'"); 77 | } 78 | 79 | @Test 80 | public void withLambdasParams() { 81 | String format = 82 | SqlFormatter.of(Dialect.SparkSql) 83 | .format("SELECT aggregate(array(1, 2, 3), 0, (acc, x) -> acc + x, acc -> acc * 10);"); 84 | assertEquals( 85 | format, 86 | "SELECT\n" + " aggregate(array(1, 2, 3), 0, (acc, x) -> acc + x, acc -> acc * 10);"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/BehavesLikeMariaDbFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 5 | import com.github.vertical_blank.sqlformatter.features.supportsCase 6 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 7 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 8 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 9 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 10 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 11 | import org.spekframework.spek2.style.specification.Suite 12 | 13 | fun Suite.behavesLikeMariaDbFormatter(formatter: SqlFormatter.Formatter) { 14 | with(formatter) { 15 | behavesLikeSqlFormatter(formatter) 16 | supportsCase(formatter) 17 | supportsCreateTable(formatter) 18 | supportsAlterTable(formatter) 19 | supportsStrings( 20 | formatter, 21 | listOf(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE) 22 | ) 23 | supportsBetween(formatter) 24 | supportsOperators( 25 | formatter, 26 | listOf( 27 | "%", 28 | "&", 29 | "|", 30 | "^", 31 | "~", 32 | "!=", 33 | "!", 34 | "<=>", 35 | "<<", 36 | ">>", 37 | "&&", 38 | "||", 39 | ":=", 40 | ) 41 | ) 42 | supportsJoin( 43 | formatter, 44 | without = listOf("FULL"), 45 | additionally = 46 | listOf( 47 | "STRAIGHT_JOIN", 48 | "NATURAL LEFT JOIN", 49 | "NATURAL LEFT OUTER JOIN", 50 | "NATURAL RIGHT JOIN", 51 | "NATURAL RIGHT OUTER JOIN", 52 | ) 53 | ) 54 | 55 | it("supports # comments") { 56 | expect(format("SELECT a # comment\nFROM b # comment")) 57 | .toBe( 58 | """ 59 | SELECT 60 | a # comment 61 | FROM 62 | b # comment 63 | """.trimIndent() 64 | ) 65 | } 66 | 67 | it("supports @variables") { 68 | expect(format("SELECT @foo, @bar")) 69 | .toBe(""" 70 | SELECT 71 | @foo, 72 | @bar 73 | """.trimIndent()) 74 | } 75 | 76 | it("supports setting variables: @var :=") { 77 | expect(format("SET @foo := (SELECT * FROM tbl);")) 78 | .toBe( 79 | """ 80 | SET 81 | @foo := ( 82 | SELECT 83 | * 84 | FROM 85 | tbl 86 | ); 87 | """.trimIndent() 88 | ) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/Db2FormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 5 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 6 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 7 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 8 | import com.github.vertical_blank.sqlformatter.languages.Dialect 9 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 10 | import org.spekframework.spek2.Spek 11 | import org.spekframework.spek2.style.specification.describe 12 | 13 | object Db2FormatterTest : 14 | Spek({ 15 | val formatter = SqlFormatter.of(Dialect.Db2) 16 | 17 | describe("Db2Formatter") { 18 | with(formatter) { 19 | behavesLikeSqlFormatter(formatter) 20 | supportsCreateTable(formatter) 21 | supportsAlterTable(formatter) 22 | supportsStrings( 23 | formatter, 24 | listOf(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE) 25 | ) 26 | supportsBetween(formatter) 27 | supportsSchema(formatter) 28 | 29 | it("formats FETCH FIRST like LIMIT") { 30 | expect(format("SELECT col1 FROM tbl ORDER BY col2 DESC FETCH FIRST 20 ROWS ONLY;")) 31 | .toBe( 32 | """ 33 | SELECT 34 | col1 35 | FROM 36 | tbl 37 | ORDER BY 38 | col2 DESC 39 | FETCH FIRST 40 | 20 ROWS ONLY; 41 | """.trimIndent() 42 | ) 43 | } 44 | } 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/DialectTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.languages.Dialect 4 | import org.spekframework.spek2.Spek 5 | import org.spekframework.spek2.style.specification.describe 6 | 7 | object DialectTest : 8 | Spek({ 9 | describe("Dialect") { 10 | it("Find dialect by name or alias") { 11 | expect(Dialect.nameOf("pl/sql").get()).toBe(Dialect.PlSql) 12 | expect(Dialect.nameOf("plsql").get()).toBe(Dialect.PlSql) 13 | } 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/MariaDbFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.languages.Dialect 4 | import org.spekframework.spek2.Spek 5 | import org.spekframework.spek2.style.specification.describe 6 | 7 | object MariaDbFormatterTest : 8 | Spek({ 9 | val formatter = SqlFormatter.of(Dialect.MariaDb) 10 | 11 | describe("MariaDbFormatter") { with(formatter) { behavesLikeMariaDbFormatter(formatter) } } 12 | }) 13 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/ModifiedFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import org.spekframework.spek2.Spek 4 | import org.spekframework.spek2.style.specification.describe 5 | 6 | object ModifiedFormatterTest : 7 | Spek({ 8 | describe("ModifiedFormatter") { 9 | it("With fat arrow operator") { 10 | { 11 | val result = 12 | SqlFormatter.standard() 13 | .extend { it.plusOperators("=>") } 14 | .format("SELECT * FROM TABLE WHERE A => 4") 15 | expect(result) 16 | .toBe( 17 | """ 18 | SELECT 19 | * 20 | FROM 21 | TABLE 22 | WHERE 23 | A => 4""".trimIndent() 24 | ) 25 | } 26 | } 27 | 28 | it("With := operator") { 29 | val result = 30 | SqlFormatter.standard() 31 | .extend { it.plusOperators(":=") } 32 | .format("SELECT * FROM TABLE WHERE A := 4") 33 | expect(result) 34 | .toBe( 35 | """ 36 | SELECT 37 | * 38 | FROM 39 | TABLE 40 | WHERE 41 | A := 4""".trimIndent() 42 | ) 43 | } 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/MySqlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 4 | import com.github.vertical_blank.sqlformatter.languages.Dialect 5 | import org.spekframework.spek2.Spek 6 | import org.spekframework.spek2.style.specification.describe 7 | 8 | object MySqlFormatterTest : 9 | Spek({ 10 | val formatter = SqlFormatter.of(Dialect.MySql) 11 | 12 | describe("MySqlFormatter") { 13 | with(formatter) { 14 | behavesLikeMariaDbFormatter(formatter) 15 | 16 | describe("additional MySQL operators") { supportsOperators(formatter, listOf("->", "->>")) } 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/N1qlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 4 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 5 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 6 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 7 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 8 | import com.github.vertical_blank.sqlformatter.languages.Dialect 9 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 10 | import org.spekframework.spek2.Spek 11 | import org.spekframework.spek2.style.specification.describe 12 | 13 | object N1qlFormatterTest : 14 | Spek({ 15 | val formatter = SqlFormatter.of(Dialect.N1ql) 16 | 17 | describe("N1qlFormatter") { 18 | with(formatter) { 19 | behavesLikeSqlFormatter(formatter) 20 | supportsStrings( 21 | formatter, 22 | listOf(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE) 23 | ) 24 | supportsBetween(formatter) 25 | supportsSchema(formatter) 26 | supportsOperators(formatter, listOf("%", "==", "!=")) 27 | supportsJoin(formatter, without = listOf("FULL", "CROSS", "NATURAL")) 28 | 29 | it("formats SELECT query with element selection expression") { 30 | val result = format("SELECT order_lines[0].productId FROM orders;") 31 | expect(result) 32 | .toBe( 33 | """ 34 | SELECT 35 | order_lines[0].productId 36 | FROM 37 | orders; 38 | """.trimIndent() 39 | ) 40 | } 41 | 42 | it("formats SELECT query with primary key querying") { 43 | val result = format("SELECT fname, email FROM tutorial USE KEYS ['dave', 'ian'];") 44 | expect(result) 45 | .toBe( 46 | """ 47 | SELECT 48 | fname, 49 | email 50 | FROM 51 | tutorial 52 | USE KEYS 53 | ['dave', 'ian']; 54 | """.trimIndent() 55 | ) 56 | } 57 | 58 | it("formats INSERT with {} object literal") { 59 | val result = 60 | format("INSERT INTO heroes (KEY, VALUE) VALUES ('123', {'id':1,'type':'Tarzan'});") 61 | expect(result) 62 | .toBe( 63 | """ 64 | INSERT INTO 65 | heroes (KEY, VALUE) 66 | VALUES 67 | ('123', {'id': 1, 'type': 'Tarzan'}); 68 | """.trimIndent() 69 | ) 70 | } 71 | 72 | it("formats INSERT with large object and array literals") { 73 | val result = 74 | format( 75 | """ 76 | INSERT INTO heroes (KEY, VALUE) VALUES ('123', {'id': 1, 'type': 'Tarzan', 77 | 'array': [123456789, 123456789, 123456789, 123456789, 123456789], 'hello': 'world'}); 78 | """ 79 | ) 80 | expect(result) 81 | .toBe( 82 | """ 83 | INSERT INTO 84 | heroes (KEY, VALUE) 85 | VALUES 86 | ( 87 | '123', 88 | { 89 | 'id': 1, 90 | 'type': 'Tarzan', 91 | 'array': [ 92 | 123456789, 93 | 123456789, 94 | 123456789, 95 | 123456789, 96 | 123456789 97 | ], 98 | 'hello': 'world' 99 | } 100 | ); 101 | """.trimIndent() 102 | ) 103 | } 104 | 105 | it("formats SELECT query with UNNEST top level reserver word") { 106 | val result = format("SELECT * FROM tutorial UNNEST tutorial.children c;") 107 | expect(result) 108 | .toBe( 109 | """ 110 | SELECT 111 | * 112 | FROM 113 | tutorial 114 | UNNEST 115 | tutorial.children c; 116 | """.trimIndent() 117 | ) 118 | } 119 | 120 | it("formats SELECT query with NEST and USE KEYS") { 121 | val result = 122 | format( 123 | """ 124 | SELECT * FROM usr 125 | USE KEYS 'Elinor_33313792' NEST orders_with_users orders 126 | ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END; 127 | """ 128 | ) 129 | expect(result) 130 | .toBe( 131 | """ 132 | SELECT 133 | * 134 | FROM 135 | usr 136 | USE KEYS 137 | 'Elinor_33313792' 138 | NEST 139 | orders_with_users orders ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END; 140 | """.trimIndent() 141 | ) 142 | } 143 | 144 | it("formats explained DELETE query with USE KEYS and RETURNING") { 145 | val result = format("EXPLAIN DELETE FROM tutorial t USE KEYS 'baldwin' RETURNING t") 146 | expect(result) 147 | .toBe( 148 | """ 149 | EXPLAIN DELETE FROM 150 | tutorial t 151 | USE KEYS 152 | 'baldwin' RETURNING t 153 | """.trimIndent() 154 | ) 155 | } 156 | 157 | it("formats UPDATE query with USE KEYS and RETURNING") { 158 | val result = 159 | format("UPDATE tutorial USE KEYS 'baldwin' SET type = 'actor' RETURNING tutorial.type") 160 | expect(result) 161 | .toBe( 162 | """ 163 | UPDATE 164 | tutorial 165 | USE KEYS 166 | 'baldwin' 167 | SET 168 | type = 'actor' RETURNING tutorial.type 169 | """.trimIndent() 170 | ) 171 | } 172 | 173 | it("recognizes \$variables") { 174 | val result = format("SELECT \$variable, \$'var name', \$\"var name\", \$`var name`;") 175 | expect(result) 176 | .toBe( 177 | """ 178 | SELECT 179 | ${"$"}variable, 180 | ${"$"}'var name', 181 | ${"$"}"var name", 182 | ${"$"}`var name`; 183 | """.trimIndent() 184 | ) 185 | } 186 | 187 | it("replaces \$variables with param values") { 188 | val result = 189 | format( 190 | "SELECT \$variable, $'var name', \$\"var name\", \$`var name`;", 191 | mapOf( 192 | "variable" to """"variable value"""", 193 | "var name" to "'var value'", 194 | ) 195 | ) 196 | expect(result) 197 | .toBe( 198 | """ 199 | SELECT 200 | "variable value", 201 | 'var value', 202 | 'var value', 203 | 'var value'; 204 | """.trimIndent() 205 | ) 206 | } 207 | 208 | it("replaces $ numbered placeholders with param values") { 209 | val result = 210 | format( 211 | "SELECT \$1, \$2, \$0;", 212 | mapOf( 213 | "0" to "first", 214 | "1" to "second", 215 | "2" to "third", 216 | ) 217 | ) 218 | expect(result) 219 | .toBe( 220 | """ 221 | SELECT 222 | second, 223 | third, 224 | first; 225 | """.trimIndent() 226 | ) 227 | } 228 | } 229 | } 230 | }) 231 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/PlSqlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTableModify 5 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 6 | import com.github.vertical_blank.sqlformatter.features.supportsCase 7 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 8 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 9 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 10 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 11 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 12 | import com.github.vertical_blank.sqlformatter.languages.Dialect 13 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 14 | import org.spekframework.spek2.Spek 15 | import org.spekframework.spek2.style.specification.describe 16 | 17 | object PlSqlFormatterTest : 18 | Spek({ 19 | val formatter = SqlFormatter.of(Dialect.PlSql) 20 | 21 | describe("PlSqlFormatter") { 22 | with(formatter) { 23 | behavesLikeSqlFormatter(formatter) 24 | supportsCase(formatter) 25 | supportsCreateTable(formatter) 26 | supportsAlterTable(formatter) 27 | supportsAlterTableModify(formatter) 28 | supportsStrings( 29 | formatter, 30 | listOf( 31 | StringLiteral.DOUBLE_QUOTE, 32 | StringLiteral.SINGLE_QUOTE, 33 | StringLiteral.BACK_QUOTE, 34 | StringLiteral.Q_SINGLE_QUOTE 35 | ) 36 | ) 37 | supportsBetween(formatter) 38 | supportsSchema(formatter) 39 | supportsOperators(formatter, listOf("||", "**", "!=", ":=")) 40 | supportsJoin(formatter) 41 | 42 | it("formats FETCH FIRST like LIMIT") { 43 | expect(format("SELECT col1 FROM tbl ORDER BY col2 DESC FETCH FIRST 20 ROWS ONLY;")) 44 | .toBe( 45 | """ 46 | SELECT 47 | col1 48 | FROM 49 | tbl 50 | ORDER BY 51 | col2 DESC 52 | FETCH FIRST 53 | 20 ROWS ONLY; 54 | """.trimIndent() 55 | ) 56 | } 57 | 58 | it("formats only -- as a line comment") { 59 | val result = format("SELECT col FROM\n-- This is a comment\nMyTable;\n") 60 | expect(result) 61 | .toBe( 62 | """ 63 | SELECT 64 | col 65 | FROM 66 | -- This is a comment 67 | MyTable; 68 | """.trimIndent() 69 | ) 70 | } 71 | 72 | it("recognizes _, $, #, . and @ as part of identifiers") { 73 | val result = format("SELECT my_col\$1#, col.2@ FROM tbl\n") 74 | expect(result) 75 | .toBe( 76 | """ 77 | SELECT 78 | my_col$1#, 79 | col.2@ 80 | FROM 81 | tbl 82 | """.trimIndent() 83 | ) 84 | } 85 | 86 | it("formats INSERT without INTO") { 87 | val result = 88 | format( 89 | "INSERT Customers (ID, MoneyBalance, Address, City) VALUES (12,-123.4, 'Skagen 2111','Stv');" 90 | ) 91 | expect(result) 92 | .toBe( 93 | """ 94 | INSERT 95 | Customers (ID, MoneyBalance, Address, City) 96 | VALUES 97 | (12, -123.4, 'Skagen 2111', 'Stv'); 98 | """.trimIndent() 99 | ) 100 | } 101 | 102 | it("recognizes ?[0-9]* placeholders") { 103 | val result = format("SELECT ?1, ?25, ?;") 104 | expect(result) 105 | .toBe( 106 | """ 107 | SELECT 108 | ?1, 109 | ?25, 110 | ?; 111 | """.trimIndent() 112 | ) 113 | } 114 | 115 | it("replaces ? numbered placeholders with param values") { 116 | val result = 117 | format( 118 | "SELECT ?1, ?2, ?0;", 119 | mapOf( 120 | "0" to "first", 121 | "1" to "second", 122 | "2" to "third", 123 | ) 124 | ) 125 | expect(result).toBe("SELECT\n" + " second,\n" + " third,\n" + " first;") 126 | } 127 | 128 | it("replaces ? indexed placeholders with param values") { 129 | val result = format("SELECT ?, ?, ?;", listOf("first", "second", "third")) 130 | expect(result).toBe("SELECT\n" + " first,\n" + " second,\n" + " third;") 131 | } 132 | 133 | it("formats SELECT query with CROSS APPLY") { 134 | val result = format("SELECT a, b FROM t CROSS APPLY fn(t.id)") 135 | expect(result) 136 | .toBe( 137 | """ 138 | SELECT 139 | a, 140 | b 141 | FROM 142 | t 143 | CROSS APPLY fn(t.id) 144 | """.trimIndent() 145 | ) 146 | } 147 | 148 | it("formats simple SELECT") { 149 | val result = format("SELECT N, M FROM t") 150 | expect(result) 151 | .toBe( 152 | """ 153 | SELECT 154 | N, 155 | M 156 | FROM 157 | t 158 | """.trimIndent() 159 | ) 160 | } 161 | 162 | it("formats simple SELECT with national characters") { 163 | val result = format("SELECT N'value'") 164 | expect(result) 165 | .toBe(""" 166 | SELECT 167 | N'value' 168 | """.trimIndent()) 169 | } 170 | 171 | it("formats SELECT query with OUTER APPLY") { 172 | val result = format("SELECT a, b FROM t OUTER APPLY fn(t.id)") 173 | expect(result) 174 | .toBe( 175 | """ 176 | SELECT 177 | a, 178 | b 179 | FROM 180 | t 181 | OUTER APPLY fn(t.id) 182 | """.trimIndent() 183 | ) 184 | } 185 | 186 | it("formats Oracle recursive sub queries") { 187 | val result = 188 | format( 189 | """ 190 | WITH t1(id, parent_id) AS ( 191 | -- Anchor member. 192 | SELECT 193 | id, 194 | parent_id 195 | FROM 196 | tab1 197 | WHERE 198 | parent_id IS NULL 199 | MINUS 200 | -- Recursive member. 201 | SELECT 202 | t2.id, 203 | t2.parent_id 204 | FROM 205 | tab1 t2, 206 | t1 207 | WHERE 208 | t2.parent_id = t1.id 209 | ) SEARCH BREADTH FIRST BY id SET order1, 210 | another AS (SELECT * FROM dual) 211 | SELECT id, parent_id FROM t1 ORDER BY order1; 212 | """.trimIndent() 213 | ) 214 | expect(result) 215 | .toBe( 216 | """ 217 | WITH t1(id, parent_id) AS ( 218 | -- Anchor member. 219 | SELECT 220 | id, 221 | parent_id 222 | FROM 223 | tab1 224 | WHERE 225 | parent_id IS NULL 226 | MINUS 227 | -- Recursive member. 228 | SELECT 229 | t2.id, 230 | t2.parent_id 231 | FROM 232 | tab1 t2, 233 | t1 234 | WHERE 235 | t2.parent_id = t1.id 236 | ) SEARCH BREADTH FIRST BY id SET order1, 237 | another AS ( 238 | SELECT 239 | * 240 | FROM 241 | dual 242 | ) 243 | SELECT 244 | id, 245 | parent_id 246 | FROM 247 | t1 248 | ORDER BY 249 | order1; 250 | """.trimIndent() 251 | ) 252 | } 253 | 254 | it("formats Oracle recursive sub queries regardless of capitalization") { 255 | val result = 256 | format( 257 | """ 258 | WITH t1(id, parent_id) AS ( 259 | -- Anchor member. 260 | SELECT 261 | id, 262 | parent_id 263 | FROM 264 | tab1 265 | WHERE 266 | parent_id IS NULL 267 | MINUS 268 | -- Recursive member. 269 | SELECT 270 | t2.id, 271 | t2.parent_id 272 | FROM 273 | tab1 t2, 274 | t1 275 | WHERE 276 | t2.parent_id = t1.id 277 | ) SEARCH BREADTH FIRST by id set order1, 278 | another AS (SELECT * FROM dual) 279 | SELECT id, parent_id FROM t1 ORDER BY order1; 280 | """.trimIndent() 281 | ) 282 | expect(result) 283 | .toBe( 284 | """ 285 | WITH t1(id, parent_id) AS ( 286 | -- Anchor member. 287 | SELECT 288 | id, 289 | parent_id 290 | FROM 291 | tab1 292 | WHERE 293 | parent_id IS NULL 294 | MINUS 295 | -- Recursive member. 296 | SELECT 297 | t2.id, 298 | t2.parent_id 299 | FROM 300 | tab1 t2, 301 | t1 302 | WHERE 303 | t2.parent_id = t1.id 304 | ) SEARCH BREADTH FIRST by id set order1, 305 | another AS ( 306 | SELECT 307 | * 308 | FROM 309 | dual 310 | ) 311 | SELECT 312 | id, 313 | parent_id 314 | FROM 315 | t1 316 | ORDER BY 317 | order1; 318 | """.trimIndent() 319 | ) 320 | } 321 | } 322 | } 323 | }) 324 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/PostgreSqlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 5 | import com.github.vertical_blank.sqlformatter.features.supportsCase 6 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 7 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 8 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 9 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 10 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 11 | import com.github.vertical_blank.sqlformatter.languages.Dialect 12 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 13 | import org.spekframework.spek2.Spek 14 | import org.spekframework.spek2.style.specification.describe 15 | 16 | object PostgreSqlFormatterTest : 17 | Spek({ 18 | val formatter = SqlFormatter.of(Dialect.PostgreSql) 19 | 20 | describe("PostgreSqlFormatter") { 21 | with(formatter) { 22 | behavesLikeSqlFormatter(formatter) 23 | supportsCase(formatter) 24 | supportsCreateTable(formatter) 25 | supportsAlterTable(formatter) 26 | supportsStrings( 27 | formatter, 28 | listOf( 29 | StringLiteral.DOUBLE_QUOTE, 30 | StringLiteral.SINGLE_QUOTE, 31 | StringLiteral.U_DOUBLE_QUOTE, 32 | StringLiteral.U_SINGLE_QUOTE, 33 | StringLiteral.DOLLAR 34 | ) 35 | ) 36 | supportsBetween(formatter) 37 | supportsSchema(formatter) 38 | supportsOperators( 39 | formatter, 40 | listOf( 41 | "%", 42 | "^", 43 | "!", 44 | "!!", 45 | "@", 46 | "!=", 47 | "&", 48 | "|", 49 | "~", 50 | "#", 51 | "<<", 52 | ">>", 53 | "||/", 54 | "|/", 55 | "::", 56 | "->>", 57 | "->", 58 | "~~*", 59 | "~~", 60 | "!~~*", 61 | "!~~", 62 | "~*", 63 | "!~*", 64 | "!~", 65 | "@@", 66 | "@@@", 67 | ) 68 | ) 69 | supportsJoin(formatter) 70 | 71 | it("supports \$n placeholders") { 72 | val result = format("SELECT \$1, \$2 FROM tbl") 73 | expect(result) 74 | .toBe( 75 | """ 76 | SELECT 77 | $1, 78 | $2 79 | FROM 80 | tbl 81 | """.trimIndent() 82 | ) 83 | } 84 | 85 | it("replaces \$n placeholders with param values") { 86 | val result = 87 | format( 88 | "SELECT \$1, \$2 FROM tbl", 89 | mapOf("1" to """"variable value"""", "2" to """"blah"""") 90 | ) 91 | expect(result) 92 | .toBe( 93 | """ 94 | SELECT 95 | "variable value", 96 | "blah" 97 | FROM 98 | tbl 99 | """.trimIndent() 100 | ) 101 | } 102 | 103 | it("supports :name placeholders") { expect(format("foo = :bar")).toBe("foo = :bar") } 104 | 105 | it("replaces :name placeholders with param values") { 106 | expect( 107 | format( 108 | """foo = :bar AND :"field" = 10 OR col = :'val'""", 109 | mapOf( 110 | "bar" to "'Hello'", 111 | "field" to "some_col", 112 | "val" to 7, 113 | ) 114 | ) 115 | ) 116 | .toBe( 117 | """ 118 | foo = 'Hello' 119 | AND some_col = 10 120 | OR col = 7 121 | """.trimIndent() 122 | ) 123 | } 124 | } 125 | } 126 | }) 127 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/RedshiftFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTableModify 5 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 6 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 7 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 8 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 9 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 10 | import com.github.vertical_blank.sqlformatter.languages.Dialect 11 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 12 | import org.spekframework.spek2.Spek 13 | import org.spekframework.spek2.dsl.Skip 14 | import org.spekframework.spek2.style.specification.describe 15 | 16 | object RedshiftFormatterTest : 17 | Spek({ 18 | val formatter = SqlFormatter.of(Dialect.Redshift) 19 | 20 | describe("RedshiftFormatter") { 21 | with(formatter) { 22 | behavesLikeSqlFormatter(formatter) 23 | supportsCreateTable(formatter) 24 | supportsAlterTable(formatter) 25 | supportsAlterTableModify(formatter) 26 | supportsStrings( 27 | formatter, 28 | listOf(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE) 29 | ) 30 | supportsSchema(formatter) 31 | supportsOperators( 32 | formatter, 33 | listOf("%", "^", "|/", "||/", "<<", ">>", "&", "|", "~", "!", "!=", "||") 34 | ) 35 | supportsJoin(formatter) 36 | 37 | it("formats LIMIT") { 38 | expect(format("SELECT col1 FROM tbl ORDER BY col2 DESC LIMIT 10;")) 39 | .toBe( 40 | """ 41 | SELECT 42 | col1 43 | FROM 44 | tbl 45 | ORDER BY 46 | col2 DESC 47 | LIMIT 48 | 10; 49 | """.trimIndent() 50 | ) 51 | } 52 | 53 | it("formats only -- as a line comment") { 54 | val result = 55 | format( 56 | """ 57 | SELECT col FROM 58 | -- This is a comment 59 | MyTable; 60 | """ 61 | ) 62 | expect(result) 63 | .toBe( 64 | """ 65 | SELECT 66 | col 67 | FROM 68 | -- This is a comment 69 | MyTable; 70 | """.trimIndent() 71 | ) 72 | } 73 | 74 | it("recognizes @ as part of identifiers") { 75 | val result = format("SELECT @col1 FROM tbl") 76 | expect(result) 77 | .toBe( 78 | """ 79 | SELECT 80 | @col1 81 | FROM 82 | tbl 83 | """.trimIndent() 84 | ) 85 | } 86 | 87 | it("formats DISTKEY and SORTKEY after CREATE TABLE", skip = Skip.Yes()) { 88 | expect( 89 | format( 90 | "CREATE TABLE items (a INT PRIMARY KEY, b TEXT, c INT NOT NULL, d INT NOT NULL) DISTKEY(created_at) SORTKEY(created_at);" 91 | ) 92 | ) 93 | .toBe( 94 | """ 95 | CREATE TABLE items ( 96 | a INT PRIMARY KEY, 97 | b TEXT, 98 | c INT NOT NULL, 99 | d INT NOT NULL 100 | ) 101 | DISTKEY(created_at) 102 | SORTKEY(created_at); 103 | """.trimIndent() 104 | ) 105 | } 106 | 107 | it("formats COPY") { 108 | expect( 109 | format( 110 | """ 111 | COPY schema.table 112 | FROM 's3://bucket/file.csv' 113 | IAM_ROLE 'arn:aws:iam::123456789:role/rolename' 114 | FORMAT AS CSV DELIMITER ',' QUOTE '"' 115 | REGION AS 'us-east-1' 116 | """ 117 | ) 118 | ) 119 | .toBe( 120 | """ 121 | COPY 122 | schema.table 123 | FROM 124 | 's3://bucket/file.csv' 125 | IAM_ROLE 126 | 'arn:aws:iam::123456789:role/rolename' 127 | FORMAT 128 | AS CSV 129 | DELIMITER 130 | ',' QUOTE '"' 131 | REGION 132 | AS 'us-east-1' 133 | """.trimIndent() 134 | ) 135 | } 136 | } 137 | } 138 | }) 139 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/SparkSqlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 5 | import com.github.vertical_blank.sqlformatter.features.supportsCase 6 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 7 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 8 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 9 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 10 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 11 | import com.github.vertical_blank.sqlformatter.languages.Dialect 12 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 13 | import org.spekframework.spek2.Spek 14 | import org.spekframework.spek2.style.specification.describe 15 | 16 | object SparkSqlFormatterTest : 17 | Spek({ 18 | val formatter = SqlFormatter.of(Dialect.SparkSql) 19 | 20 | describe("SparkSqlFormatter") { 21 | with(formatter) { 22 | behavesLikeSqlFormatter(formatter) 23 | supportsCase(formatter) 24 | supportsCreateTable(formatter) 25 | supportsAlterTable(formatter) 26 | supportsStrings( 27 | formatter, 28 | listOf(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE, StringLiteral.BACK_QUOTE) 29 | ) 30 | supportsBetween(formatter) 31 | supportsSchema(formatter) 32 | supportsOperators( 33 | formatter, 34 | listOf("!=", "%", "|", "&", "^", "~", "!", "<=>", "%", "&&", "||", "==", "->") 35 | ) 36 | supportsJoin( 37 | formatter, 38 | additionally = 39 | listOf( 40 | "ANTI JOIN", 41 | "SEMI JOIN", 42 | "LEFT ANTI JOIN", 43 | "LEFT SEMI JOIN", 44 | "RIGHT OUTER JOIN", 45 | "RIGHT SEMI JOIN", 46 | "NATURAL ANTI JOIN", 47 | "NATURAL FULL OUTER JOIN", 48 | "NATURAL INNER JOIN", 49 | "NATURAL LEFT ANTI JOIN", 50 | "NATURAL LEFT OUTER JOIN", 51 | "NATURAL LEFT SEMI JOIN", 52 | "NATURAL OUTER JOIN", 53 | "NATURAL RIGHT OUTER JOIN", 54 | "NATURAL RIGHT SEMI JOIN", 55 | "NATURAL SEMI JOIN", 56 | ) 57 | ) 58 | 59 | it("formats WINDOW specification as top level") { 60 | val result = 61 | format( 62 | "SELECT *, LAG(value) OVER wnd AS next_value FROM tbl WINDOW wnd as (PARTITION BY id ORDER BY time);" 63 | ) 64 | expect(result) 65 | .toBe( 66 | """ 67 | SELECT 68 | *, 69 | LAG(value) OVER wnd AS next_value 70 | FROM 71 | tbl 72 | WINDOW 73 | wnd as ( 74 | PARTITION BY 75 | id 76 | ORDER BY 77 | time 78 | ); 79 | """.trimIndent() 80 | ) 81 | } 82 | 83 | it("formats window function and end as inline") { 84 | val result = 85 | format( 86 | """SELECT window(time, "1 hour").start AS window_start, window(time, "1 hour").end AS window_end FROM tbl;""" 87 | ) 88 | expect(result) 89 | .toBe( 90 | """ 91 | SELECT 92 | window(time, "1 hour").start AS window_start, 93 | window(time, "1 hour").end AS window_end 94 | FROM 95 | tbl; 96 | """.trimIndent() 97 | ) 98 | } 99 | 100 | // eslint-disable-next-line no-template-curly-in-string 101 | it("does not add spaces around \${value} params") { 102 | // eslint-disable-next-line no-template-curly-in-string 103 | val result = format("SELECT \${var_name};") 104 | expect(result) 105 | .toBe( 106 | """ 107 | SELECT 108 | ${"$"}{var_name}; 109 | """.trimIndent() 110 | ) 111 | } 112 | 113 | // eslint-disable-next-line no-template-curly-in-string 114 | it("replaces \$variables and \${variables} with param values") { 115 | // eslint-disable-next-line no-template-curly-in-string 116 | val result = 117 | format( 118 | "SELECT \$var1, \${var2};", 119 | mapOf( 120 | "var1" to "'var one'", 121 | "var2" to "'var two'", 122 | ), 123 | ) 124 | expect(result) 125 | .toBe( 126 | """ 127 | SELECT 128 | 'var one', 129 | 'var two'; 130 | """.trimIndent() 131 | ) 132 | } 133 | } 134 | } 135 | }) 136 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/StandardSqlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 5 | import com.github.vertical_blank.sqlformatter.features.supportsCase 6 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 7 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 8 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 9 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 10 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 11 | import org.spekframework.spek2.Spek 12 | import org.spekframework.spek2.style.specification.describe 13 | 14 | object StandardSqlFormatterTest : 15 | Spek({ 16 | val formatter = SqlFormatter.standard() 17 | 18 | describe("StandardSqlFormatter") { 19 | with(formatter) { 20 | behavesLikeSqlFormatter(formatter) 21 | supportsCase(formatter) 22 | supportsCreateTable(formatter) 23 | supportsAlterTable(formatter) 24 | supportsStrings(formatter, listOf(StringLiteral.DOUBLE_QUOTE, StringLiteral.SINGLE_QUOTE)) 25 | supportsBetween(formatter) 26 | supportsSchema(formatter) 27 | supportsJoin(formatter) 28 | 29 | it("replaces ? indexed placeholders with param values") { 30 | val result = format("SELECT ?, ?, ?;", listOf("first", "second", "third")) 31 | 32 | expect(result) 33 | .toBe( 34 | """ 35 | SELECT 36 | first, 37 | second, 38 | third; 39 | """.trimIndent() 40 | ) 41 | } 42 | 43 | it("formats FETCH FIRST like LIMIT") { 44 | val result = format("SELECT * FETCH FIRST 2 ROWS ONLY;") 45 | expect(result) 46 | .toBe( 47 | """ 48 | SELECT 49 | * 50 | FETCH FIRST 51 | 2 ROWS ONLY; 52 | """.trimIndent() 53 | ) 54 | } 55 | 56 | it("Sql and comments in single line") { 57 | val result = 58 | format( 59 | "SELECT * FROM table/* comment */ WHERE date BETWEEN '2001-01-01' AND '2010-12-31'; -- comment" 60 | ) 61 | expect(result) 62 | .toBe( 63 | """ 64 | SELECT 65 | * 66 | FROM 67 | table 68 | /* comment */ 69 | WHERE 70 | date BETWEEN '2001-01-01' AND '2010-12-31'; 71 | -- comment 72 | """.trimIndent() 73 | ) 74 | } 75 | 76 | it("Formats merge") { 77 | val result = 78 | format( 79 | """merge into DW_STG_USER.ACCOUNT_DIM target using ( select COMMON_NAME m_commonName, ORIGIN m_origin, USAGE_TYPE m_usageType, CATEGORY m_category from MY_TABLE where USAGE_TYPE = :value ) source on source.m_usageType = target.USAGE_TYPE when matched then update set target.COMMON_NAME = source.m_commonName, target.ORIGIN = source.m_origin, target.USAGE_TYPE = source.m_usageType, target.CATEGORY = source.m_category where ((source.m_commonName <> target.COMMON_NAME)or(source.m_origin <> target.ORIGIN)or(source.m_usageType <> target.USAGE_TYPE)or(source.m_category <> target.CATEGORY)) when not matched then insert ( target.COMMON_NAME, target.ORIGIN, target.USAGE_TYPE, target.CATEGORY) values (source.m_commonName, source.m_origin, source.m_usageType, source.m_category)""" 80 | ) 81 | expect(result) 82 | .toBe( 83 | """ 84 | merge into DW_STG_USER.ACCOUNT_DIM target using ( 85 | select 86 | COMMON_NAME m_commonName, 87 | ORIGIN m_origin, 88 | USAGE_TYPE m_usageType, 89 | CATEGORY m_category 90 | from 91 | MY_TABLE 92 | where 93 | USAGE_TYPE =: value 94 | ) source on source.m_usageType = target.USAGE_TYPE 95 | when matched then 96 | update 97 | set 98 | target.COMMON_NAME = source.m_commonName, 99 | target.ORIGIN = source.m_origin, 100 | target.USAGE_TYPE = source.m_usageType, 101 | target.CATEGORY = source.m_category 102 | where 103 | ( 104 | (source.m_commonName <> target.COMMON_NAME) 105 | or (source.m_origin <> target.ORIGIN) 106 | or (source.m_usageType <> target.USAGE_TYPE) 107 | or (source.m_category <> target.CATEGORY) 108 | ) 109 | when not matched then insert ( 110 | target.COMMON_NAME, 111 | target.ORIGIN, 112 | target.USAGE_TYPE, 113 | target.CATEGORY 114 | ) 115 | values 116 | ( 117 | source.m_commonName, 118 | source.m_origin, 119 | source.m_usageType, 120 | source.m_category 121 | ) 122 | """.trimIndent() 123 | ) 124 | } 125 | } 126 | } 127 | }) 128 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/TSqlFormatterTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import com.github.vertical_blank.sqlformatter.features.supportsAlterTable 4 | import com.github.vertical_blank.sqlformatter.features.supportsBetween 5 | import com.github.vertical_blank.sqlformatter.features.supportsCase 6 | import com.github.vertical_blank.sqlformatter.features.supportsCreateTable 7 | import com.github.vertical_blank.sqlformatter.features.supportsJoin 8 | import com.github.vertical_blank.sqlformatter.features.supportsOperators 9 | import com.github.vertical_blank.sqlformatter.features.supportsSchema 10 | import com.github.vertical_blank.sqlformatter.features.supportsStrings 11 | import com.github.vertical_blank.sqlformatter.languages.Dialect 12 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 13 | import org.spekframework.spek2.Spek 14 | import org.spekframework.spek2.style.specification.describe 15 | 16 | object TSqlFormatterTest : 17 | Spek({ 18 | val formatter = SqlFormatter.of(Dialect.TSql) 19 | 20 | describe("TSqlFormatter") { 21 | with(formatter) { 22 | behavesLikeSqlFormatter(formatter) 23 | supportsCase(formatter) 24 | supportsCreateTable(formatter) 25 | supportsAlterTable(formatter) 26 | supportsStrings( 27 | formatter, 28 | listOf( 29 | StringLiteral.DOUBLE_QUOTE, 30 | StringLiteral.SINGLE_QUOTE, 31 | StringLiteral.N_SINGLE_QUOTE, 32 | StringLiteral.BRACKET 33 | ) 34 | ) 35 | supportsBetween(formatter) 36 | supportsSchema(formatter) 37 | supportsOperators( 38 | formatter, 39 | listOf( 40 | "%", 41 | "&", 42 | "|", 43 | "^", 44 | "~", 45 | "!=", 46 | "!<", 47 | "!>", 48 | "+=", 49 | "-=", 50 | "*=", 51 | "/=", 52 | "%=", 53 | "|=", 54 | "&=", 55 | "^=", 56 | "::", 57 | ) 58 | ) 59 | supportsJoin(formatter, without = listOf("NATURAL")) 60 | 61 | // TODO: The following are duplicated from StandardSQLFormatter test 62 | 63 | it("formats INSERT without INTO") { 64 | val result = 65 | format( 66 | "INSERT Customers (ID, MoneyBalance, Address, City) VALUES (12,-123.4, 'Skagen 2111','Stv');" 67 | ) 68 | expect(result) 69 | .toBe( 70 | """ 71 | INSERT 72 | Customers (ID, MoneyBalance, Address, City) 73 | VALUES 74 | (12, -123.4, 'Skagen 2111', 'Stv'); 75 | """.trimIndent() 76 | ) 77 | } 78 | 79 | it("recognizes @variables") { 80 | val result = format("""SELECT @variable, @"var name", @[var name];""") 81 | expect(result) 82 | .toBe( 83 | """ 84 | SELECT 85 | @variable, 86 | @"var name", 87 | @[var name]; 88 | """.trimIndent() 89 | ) 90 | } 91 | 92 | it("replaces @variables with param values") { 93 | val result = 94 | format( 95 | """SELECT @variable, @"var name1", @[var name2];""", 96 | mapOf( 97 | "variable" to "'var value'", 98 | "var name1" to "'var value1'", 99 | "var name2" to "'var value2'", 100 | ) 101 | ) 102 | expect(result) 103 | .toBe( 104 | """ 105 | SELECT 106 | 'var value', 107 | 'var value1', 108 | 'var value2'; 109 | """.trimIndent() 110 | ) 111 | } 112 | 113 | it("formats SELECT query with CROSS JOIN") { 114 | val result = format("SELECT a, b FROM t CROSS JOIN t2 on t.id = t2.id_t") 115 | expect(result) 116 | .toBe( 117 | """ 118 | SELECT 119 | a, 120 | b 121 | FROM 122 | t 123 | CROSS JOIN t2 on t.id = t2.id_t 124 | """.trimIndent() 125 | ) 126 | } 127 | } 128 | } 129 | }) 130 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/TestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter 2 | 3 | import org.amshove.kluent.shouldBeEqualTo 4 | 5 | interface Expect { 6 | fun toBe(v2: T) 7 | } 8 | 9 | fun expect(v: T) = 10 | object : Expect { 11 | override fun toBe(v2: T) { 12 | v shouldBeEqualTo v2 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/AlterTable.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import org.spekframework.spek2.style.specification.Suite 6 | 7 | fun Suite.supportsAlterTable(formatter: SqlFormatter.Formatter) { 8 | with(formatter) { 9 | it("formats ALTER TABLE ... ALTER COLUMN query") { 10 | val result = format("ALTER TABLE supplier ALTER COLUMN supplier_name VARCHAR(100) NOT NULL;") 11 | expect(result) 12 | .toBe( 13 | """ 14 | ALTER TABLE 15 | supplier 16 | ALTER COLUMN 17 | supplier_name VARCHAR(100) NOT NULL; 18 | """.trimIndent() 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/AlterTableModify.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import org.spekframework.spek2.style.specification.Suite 6 | 7 | fun Suite.supportsAlterTableModify(formatter: SqlFormatter.Formatter) { 8 | with(formatter) { 9 | it("formats ALTER TABLE ... MODIFY statement") { 10 | val result = format("ALTER TABLE supplier MODIFY supplier_name char(100) NOT NULL;") 11 | expect(result) 12 | .toBe( 13 | """ 14 | ALTER TABLE 15 | supplier 16 | MODIFY 17 | supplier_name char(100) NOT NULL; 18 | """.trimIndent() 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/Between.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import org.spekframework.spek2.style.specification.Suite 6 | 7 | fun Suite.supportsBetween(formatter: SqlFormatter.Formatter) { 8 | with(formatter) { 9 | it("formats BETWEEN _ AND _ on single line") { 10 | expect(format("foo BETWEEN bar AND baz")).toBe("foo BETWEEN bar AND baz") 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/Case.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.core.FormatConfig 5 | import com.github.vertical_blank.sqlformatter.expect 6 | import org.spekframework.spek2.style.specification.Suite 7 | 8 | fun Suite.supportsCase(formatter: SqlFormatter.Formatter) { 9 | with(formatter) { 10 | it("formats CASE ... WHEN with a blank expression") { 11 | val result = 12 | format( 13 | "CASE WHEN option = 'foo' THEN 1 WHEN option = 'bar' THEN 2 WHEN option = 'baz' THEN 3 ELSE 4 END;" 14 | ) 15 | 16 | expect(result) 17 | .toBe( 18 | """ 19 | CASE 20 | WHEN option = 'foo' THEN 1 21 | WHEN option = 'bar' THEN 2 22 | WHEN option = 'baz' THEN 3 23 | ELSE 4 24 | END; 25 | """.trimIndent() 26 | ) 27 | } 28 | 29 | it("formats CASE ... WHEN with an expression") { 30 | val result = 31 | format( 32 | "CASE toString(getNumber()) WHEN 'one' THEN 1 WHEN 'two' THEN 2 WHEN 'three' THEN 3 ELSE 4 END;" 33 | ) 34 | 35 | expect(result) 36 | .toBe( 37 | """ 38 | CASE 39 | toString(getNumber()) 40 | WHEN 'one' THEN 1 41 | WHEN 'two' THEN 2 42 | WHEN 'three' THEN 3 43 | ELSE 4 44 | END; 45 | """.trimIndent() 46 | ) 47 | } 48 | 49 | it("formats CASE ... WHEN inside SELECT") { 50 | val result = 51 | format( 52 | "SELECT foo, bar, CASE baz WHEN 'one' THEN 1 WHEN 'two' THEN 2 ELSE 3 END FROM table" 53 | ) 54 | 55 | expect(result) 56 | .toBe( 57 | """ 58 | SELECT 59 | foo, 60 | bar, 61 | CASE 62 | baz 63 | WHEN 'one' THEN 1 64 | WHEN 'two' THEN 2 65 | ELSE 3 66 | END 67 | FROM 68 | table 69 | """.trimIndent() 70 | ) 71 | } 72 | 73 | it("recognizes lowercase CASE ... END") { 74 | val result = format("case when option = 'foo' then 1 else 2 end;") 75 | 76 | expect(result) 77 | .toBe( 78 | """ 79 | case 80 | when option = 'foo' then 1 81 | else 2 82 | end; 83 | """.trimIndent() 84 | ) 85 | } 86 | 87 | // Regression test for issue #43 88 | it("ignores words CASE and END inside other strings") { 89 | val result = format("SELECT CASEDATE, ENDDATE FROM table1;") 90 | 91 | expect(result) 92 | .toBe( 93 | """ 94 | SELECT 95 | CASEDATE, 96 | ENDDATE 97 | FROM 98 | table1; 99 | """.trimIndent() 100 | ) 101 | } 102 | 103 | it("properly converts to uppercase in case statements") { 104 | val result = 105 | format( 106 | "case toString(getNumber()) when 'one' then 1 when 'two' then 2 when 'three' then 3 else 4 end;", 107 | FormatConfig.builder().uppercase(true).build() 108 | ) 109 | expect(result) 110 | .toBe( 111 | """ 112 | CASE 113 | toString(getNumber()) 114 | WHEN 'one' THEN 1 115 | WHEN 'two' THEN 2 116 | WHEN 'three' THEN 3 117 | ELSE 4 118 | END; 119 | """.trimIndent() 120 | ) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/CreateTable.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import org.spekframework.spek2.style.specification.Suite 6 | 7 | fun Suite.supportsCreateTable(formatter: SqlFormatter.Formatter) { 8 | with(formatter) { 9 | it("formats short CREATE TABLE") { 10 | expect(format("CREATE TABLE items (a INT PRIMARY KEY, b TEXT);")) 11 | .toBe("CREATE TABLE items (a INT PRIMARY KEY, b TEXT);") 12 | } 13 | 14 | // The decision to place it to multiple lines is made based on the length of text inside 15 | // BRACEs 16 | // ignoring the whitespace. (Which is not quite right :P) 17 | it("formats long CREATE TABLE") { 18 | expect( 19 | format( 20 | "CREATE TABLE items (a INT PRIMARY KEY, b TEXT, c INT NOT NULL, doggie INT NOT NULL);" 21 | ) 22 | ) 23 | .toBe( 24 | """ 25 | CREATE TABLE items ( 26 | a INT PRIMARY KEY, 27 | b TEXT, 28 | c INT NOT NULL, 29 | doggie INT NOT NULL 30 | ); 31 | """.trimIndent() 32 | ) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/Join.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import kotlin.collections.listOf 6 | import org.spekframework.spek2.style.specification.Suite 7 | 8 | fun Suite.supportsJoin( 9 | formatter: SqlFormatter.Formatter, 10 | without: List = listOf(), 11 | additionally: List = listOf() 12 | ) { 13 | with(formatter) { 14 | val unsupportedJoinRegex = 15 | if (without.isNotEmpty()) without.joinToString("|") else "^whateve_!%&$" 16 | val isSupportedJoin = { join: String -> unsupportedJoinRegex.toRegex().find(join) == null } 17 | 18 | listOf("CROSS JOIN", "NATURAL JOIN").filter(isSupportedJoin).forEach { join -> 19 | it("supports $join") { 20 | val result = format("SELECT * FROM tbl1 $join tbl2") 21 | expect(result) 22 | .toBe( 23 | """ 24 | SELECT 25 | * 26 | FROM 27 | tbl1 28 | $join tbl2 29 | """.trimIndent() 30 | ) 31 | } 32 | } 33 | 34 | // ::= [ ] JOIN 35 | // 36 | // ::= INNER | [ OUTER ] 37 | // 38 | // ::= LEFT | RIGHT | FULL 39 | 40 | (listOf( 41 | "JOIN", 42 | "INNER JOIN", 43 | "LEFT JOIN", 44 | "LEFT OUTER JOIN", 45 | "RIGHT JOIN", 46 | "RIGHT OUTER JOIN", 47 | "FULL JOIN", 48 | "FULL OUTER JOIN", 49 | ) + additionally) 50 | .filter(isSupportedJoin) 51 | .forEach { join -> 52 | it("supports $join") { 53 | val result = 54 | format( 55 | """ 56 | SELECT customer_id.from, COUNT(order_id) AS total FROM customers 57 | $join orders ON customers.customer_id = orders.customer_id; 58 | """.trimIndent() 59 | ) 60 | expect(result) 61 | .toBe( 62 | """ 63 | SELECT 64 | customer_id.from, 65 | COUNT(order_id) AS total 66 | FROM 67 | customers 68 | $join orders ON customers.customer_id = orders.customer_id; 69 | """.trimIndent() 70 | ) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/Operators.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import org.spekframework.spek2.style.specification.Suite 6 | 7 | fun Suite.supportsOperators(formatter: SqlFormatter.Formatter, operators: List) { 8 | with(formatter) { 9 | operators.forEach { op -> 10 | it("supports $op operator") { expect(format("foo${op}bar")).toBe("foo $op bar") } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/Schema.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import org.spekframework.spek2.style.specification.Suite 6 | 7 | fun Suite.supportsSchema(formatter: SqlFormatter.Formatter) { 8 | with(formatter) { 9 | it("formats simple SET SCHEMA statements") { 10 | val result = format("SET SCHEMA schema1;") 11 | expect(result).toBe(""" 12 | SET SCHEMA 13 | schema1; 14 | """.trimIndent()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/vertical_blank/sqlformatter/features/Strings.kt: -------------------------------------------------------------------------------- 1 | package com.github.vertical_blank.sqlformatter.features 2 | 3 | import com.github.vertical_blank.sqlformatter.SqlFormatter 4 | import com.github.vertical_blank.sqlformatter.expect 5 | import com.github.vertical_blank.sqlformatter.languages.StringLiteral 6 | import org.spekframework.spek2.style.specification.Suite 7 | 8 | fun Suite.supportsStrings(formatter: SqlFormatter.Formatter, stringTypes: List = listOf()) { 9 | with(formatter) { 10 | if (stringTypes.contains(StringLiteral.DOUBLE_QUOTE)) { 11 | it("supports double-quoted strings") { 12 | expect(format(""""foo JOIN bar"""")).toBe(""""foo JOIN bar"""") 13 | expect(format(""""foo \" JOIN bar"""")).toBe(""""foo \" JOIN bar"""") 14 | } 15 | } 16 | 17 | if (stringTypes.contains(StringLiteral.SINGLE_QUOTE)) { 18 | it("supports single-quoted strings") { 19 | expect(format("'foo JOIN bar'")).toBe("'foo JOIN bar'") 20 | expect(format("'foo \\' JOIN bar'")).toBe("'foo \\' JOIN bar'") 21 | } 22 | } 23 | 24 | if (stringTypes.contains(StringLiteral.BACK_QUOTE)) { 25 | it("supports backtick-quoted strings") { 26 | expect(format("`foo JOIN bar`")).toBe("`foo JOIN bar`") 27 | expect(format("`foo `` JOIN bar`")).toBe("`foo `` JOIN bar`") 28 | } 29 | } 30 | 31 | if (stringTypes.contains(StringLiteral.U_DOUBLE_QUOTE)) { 32 | it("supports unicode double-quoted strings") { 33 | expect(format("""U&"foo JOIN bar"""")).toBe("""U&"foo JOIN bar"""") 34 | expect(format("""U&"foo \" JOIN bar"""")).toBe("""U&"foo \" JOIN bar"""") 35 | } 36 | } 37 | 38 | if (stringTypes.contains(StringLiteral.U_SINGLE_QUOTE)) { 39 | it("supports single-quoted strings") { 40 | expect(format("U&'foo JOIN bar'")).toBe("U&'foo JOIN bar'") 41 | expect(format("U&'foo \\' JOIN bar'")).toBe("U&'foo \\' JOIN bar'") 42 | } 43 | } 44 | 45 | if (stringTypes.contains(StringLiteral.DOLLAR)) { 46 | it("supports DOLLAR-quoted strings") { 47 | expect(format("\$xxx\$foo \$\$ LEFT JOIN \$yyy\$ bar\$xxx\$")) 48 | .toBe("\$xxx\$foo \$\$ LEFT JOIN \$yyy\$ bar\$xxx\$") 49 | expect(format("\$\$foo JOIN bar\$\$")).toBe("\$\$foo JOIN bar\$\$") 50 | expect(format("\$\$foo \$ JOIN bar\$\$")).toBe("\$\$foo $ JOIN bar\$\$") 51 | expect(format("\$\$foo \n bar\$\$")).toBe("\$\$foo \n bar\$\$") 52 | } 53 | } 54 | 55 | if (stringTypes.contains(StringLiteral.BRACKET)) { 56 | it("supports [BRACKET-quoted identifiers]") { 57 | expect(format("[foo JOIN bar]")).toBe("[foo JOIN bar]") 58 | expect(format("[foo ]] JOIN bar]")).toBe("[foo ]] JOIN bar]") 59 | } 60 | } 61 | 62 | if (stringTypes.contains(StringLiteral.N_SINGLE_QUOTE)) { 63 | it("supports T-SQL unicode strings") { 64 | expect(format("N'foo JOIN bar'")).toBe("N'foo JOIN bar'") 65 | expect(format("N'foo \\' JOIN bar'")).toBe("N'foo \\' JOIN bar'") 66 | } 67 | } 68 | 69 | if (stringTypes.contains(StringLiteral.Q_SINGLE_QUOTE)) { 70 | it("supports Oracle quotation operator") { 71 | expect(format("Q'[I'm boy]',Q'{I'm boy}',Q'',Q'(I'm boy)',1")) 72 | .toBe( 73 | """Q'[I'm boy]', 74 | |Q'{I'm boy}', 75 | |Q'', 76 | |Q'(I'm boy)', 77 | |1""".trimMargin() 78 | ) 79 | expect(format("NQ'[I'm boy]',NQ'{I'm boy}',NQ'',NQ'(I'm boy)',1")) 80 | .toBe( 81 | """NQ'[I'm boy]', 82 | |NQ'{I'm boy}', 83 | |NQ'', 84 | |NQ'(I'm boy)', 85 | |1""".trimMargin() 86 | ) 87 | } 88 | } 89 | } 90 | } 91 | --------------------------------------------------------------------------------