diff --git a/CHANGES.md b/CHANGES.md
index 1cecc7f922..c0152b69af 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,6 +10,9 @@ You might be looking for:
 * Updated default eclipse-jdt version to `4.7.1` from `4.6.3`.
 * Updated jgit from `4.5.0.201609210915-r` to `4.9.0.201710071750-r`.
 * Updated concurrent-trees from `2.6.0` to `2.6.1` (performance improvement).
+* Added `dbeaverSql` formatter step, for formatting sql scripts. ([#166](https://github.com/diffplug/spotless/pull/166))
+	+ Many thanks to [Baptiste Mesta](https://github.com/baptistemesta) for porting to Spotless.
+	+ Many thanks to [DBeaver](https://dbeaver.jkiss.org/) and the [DBeaver contributors](https://github.com/serge-rider/dbeaver/graphs/contributors) for building the implementation.
 
 ### Version 1.6.0 - September 29th 2017 (javadoc [lib](https://diffplug.github.io/spotless/javadoc/spotless-lib/1.6.0/) [lib-extra](https://diffplug.github.io/spotless/javadoc/spotless-lib-extra/1.6.0/), artifact [lib]([jcenter](https://bintray.com/diffplug/opensource/spotless-lib), [lib-extra]([jcenter](https://bintray.com/diffplug/opensource/spotless-lib-extra)))
 
diff --git a/README.md b/README.md
index 1b23d87570..2faac7d958 100644
--- a/README.md
+++ b/README.md
@@ -73,12 +73,13 @@ lib('scala.ScalaFmtStep')                        +'{{yes}}       | {{no}}
 
 ## Acknowledgements
 
-* Huge thanks to [Jonathan Bluett-Duncan](https://github.com/jbduncan) for
+* Thanks to [Baptiste Mesta](https://github.com/baptistemesta) for porting the DBeaver formatter to Spotless, and thanks to [DBeaver](https://dbeaver.jkiss.org/) and [its authors](https://github.com/serge-rider/dbeaver/graphs/contributors) for their excellent SQL formatter.
+* Thanks to [Jonathan Bluett-Duncan](https://github.com/jbduncan) for
 	+ implementing up-to-date checking [#31](https://github.com/diffplug/spotless/issues/31)
 	+ breaking spotless into libraries [#56](https://github.com/diffplug/spotless/issues/56)
 	+ lots of other things, but especially the diff support in `spotlessCheck`
-* Huge thanks to [Frank Vennemeyer](https://github.com/fvgh) for [Groovy support via greclipse](https://github.com/diffplug/spotless/issues/13).
-* Huge thanks to [Stefan Oehme](https://github.com/oehme) for tons of help on the internal mechanics of Gradle.
+* Thanks to [Frank Vennemeyer](https://github.com/fvgh) for [Groovy support via greclipse](https://github.com/diffplug/spotless/issues/13).
+* Thanks to [Stefan Oehme](https://github.com/oehme) for tons of help on the internal mechanics of Gradle.
 * Formatting by Eclipse
 	+ Special thanks to [Mateusz Matela](https://waynebeaton.wordpress.com/2015/03/15/great-fixes-for-mars-winners-part-i/) for huge improvements to the eclipse code formatter!
 * Thanks to [Stanley Shyiko](https://github.com/shyiko) for his help integrating [ktlint](https://github.com/shyiko/ktlint).
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatter.java b/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatter.java
new file mode 100644
index 0000000000..a213140796
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql;
+
+import java.util.Properties;
+
+import com.diffplug.spotless.sql.dbeaver.SQLFormatterConfiguration;
+import com.diffplug.spotless.sql.dbeaver.SQLTokenizedFormatter;
+
+/**
+ * @author Baptiste Mesta.
+ */
+public class DBeaverSQLFormatter {
+
+	private final SQLTokenizedFormatter sqlTokenizedFormatter;
+
+	DBeaverSQLFormatter(Properties properties) {
+		SQLFormatterConfiguration configuration = new SQLFormatterConfiguration(properties);
+		sqlTokenizedFormatter = new SQLTokenizedFormatter(configuration);
+	}
+
+	public String format(String input) {
+		return sqlTokenizedFormatter.format(input);
+	}
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java
new file mode 100644
index 0000000000..2968e71aca
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql;
+
+import java.io.File;
+import java.io.Serializable;
+
+import com.diffplug.spotless.FileSignature;
+import com.diffplug.spotless.FormatterFunc;
+import com.diffplug.spotless.FormatterProperties;
+import com.diffplug.spotless.FormatterStep;
+
+/** Wraps up [BasicFormatterImpl](https://docs.jboss.org/hibernate/orm/4.1/javadocs/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.html) as a FormatterStep. */
+public class DBeaverSQLFormatterStep {
+
+	static final String NAME = "dbeaverSql";
+
+	// prevent direct instantiation
+	private DBeaverSQLFormatterStep() {}
+
+	public static FormatterStep create(Iterable<File> files) {
+		return FormatterStep.createLazy(NAME,
+				() -> new State(files),
+				State::createFormat);
+	}
+
+	static final class State implements Serializable {
+		private static final long serialVersionUID = 1L;
+
+		/** The signature of the settings file. */
+		final FileSignature settings;
+
+		State(final Iterable<File> settingsFiles) throws Exception {
+			this.settings = FileSignature.signAsList(settingsFiles);
+		}
+
+		FormatterFunc createFormat() throws Exception {
+			FormatterProperties preferences = FormatterProperties.from(settings.files());
+			DBeaverSQLFormatter DBeaverSqlFormatter = new DBeaverSQLFormatter(preferences.getProperties());
+			return DBeaverSqlFormatter::format;
+		}
+	}
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java
new file mode 100644
index 0000000000..2219fd160d
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+/**
+ * Forked from
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
+ *
+* Database keyword type
+*/
+public enum DBPKeywordType {
+	KEYWORD, FUNCTION, TYPE, OTHER
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/FormatterToken.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/FormatterToken.java
new file mode 100644
index 0000000000..977b3e5b49
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/FormatterToken.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+class FormatterToken {
+
+	private TokenType fType;
+	private String fString;
+	private int fPos = -1;
+
+	public FormatterToken(final TokenType argType, final String argString, final int argPos) {
+		fType = argType;
+		fString = argString;
+		fPos = argPos;
+	}
+
+	public FormatterToken(final TokenType argType, final String argString) {
+		this(argType, argString, -1);
+	}
+
+	public void setType(final TokenType argType) {
+		fType = argType;
+	}
+
+	public TokenType getType() {
+		return fType;
+	}
+
+	public void setString(final String argString) {
+		fString = argString;
+	}
+
+	public String getString() {
+		return fString;
+	}
+
+	public void setPos(final int argPos) {
+		fPos = argPos;
+	}
+
+	public int getPos() {
+		return fPos;
+	}
+
+	public String toString() {
+		final StringBuilder buf = new StringBuilder();
+		buf.append(getClass().getName());
+		buf.append("type=").append(fType);
+		buf.append(",string=").append(fString);
+		buf.append(",pos=").append(fPos);
+		buf.append("]");
+		return buf.toString();
+	}
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/KeywordCase.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/KeywordCase.java
new file mode 100644
index 0000000000..5840a4503c
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/KeywordCase.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+import java.util.Locale;
+
+/**
+ * @author Baptiste Mesta.
+ */
+public enum KeywordCase {
+	UPPER {
+		public String transform(String value) {
+			return value.toUpperCase(Locale.ENGLISH);
+		}
+	},
+	LOWER {
+		public String transform(String value) {
+			return value.toLowerCase(Locale.ENGLISH);
+		}
+	},
+	ORIGINAL {
+		public String transform(String value) {
+			return value;
+		}
+	};
+
+	public abstract String transform(String value);
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java
new file mode 100644
index 0000000000..87385f2999
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+/**
+ * Pair
+ */
+public class Pair<T1, T2> {
+	private T1 first;
+	private T2 second;
+
+	public Pair(T1 first, T2 second) {
+		this.first = first;
+		this.second = second;
+	}
+
+	public T1 getFirst() {
+		return first;
+	}
+
+	public void setFirst(T1 first) {
+		this.first = first;
+	}
+
+	public T2 getSecond() {
+		return second;
+	}
+
+	public void setSecond(T2 second) {
+		this.second = second;
+	}
+
+	@Override
+	public String toString() {
+		return first + "=" + second;
+	}
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java
new file mode 100644
index 0000000000..0fe6b914ef
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+/**
+ * Forked from
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
+ *
+ * SQL editor constants
+ */
+class SQLConstants {
+
+	static final String ML_COMMENT_START = "/*";
+	static final String ML_COMMENT_END = "*/";
+	static final String SL_COMMENT = "--";
+
+	private static final String KEYWORD_SELECT = "SELECT";
+	private static final String KEYWORD_ON = "ON";
+
+	static final String[] SQL2003_RESERVED_KEYWORDS = {
+			"ALL",
+			"ALLOCATE",
+			"ALTER",
+			"AND",
+			"ANY",
+			"ARE",
+			"ARRAY",
+			"AS",
+			"ASENSITIVE",
+			"ASYMMETRIC",
+			"AT",
+			"ATOMIC",
+			"AUTHORIZATION",
+			"BEGIN",
+			"BETWEEN",
+			//"BIGINT",
+			"BINARY",
+			"BOTH",
+			"BY",
+			"CALL",
+			"CALLED",
+			"CARDINALITY",
+			"CASCADE",
+			"CASCADED",
+			"CASE",
+			"CAST",
+			"CEIL",
+			"CEILING",
+			"CHARACTER",
+			"CHECK",
+			"CLOSE",
+			"COALESCE",
+			"COLLATE",
+			"COLLECT",
+			"COLUMN",
+			"COMMIT",
+			"CONDITION",
+			"CONNECT",
+			"CONSTRAINT",
+			"CONVERT",
+			"CORR",
+			"CORRESPONDING",
+			"COVAR_POP",
+			"COVAR_SAMP",
+			"CREATE",
+			"CROSS",
+			"CUBE",
+			"CUME_DIST",
+			"CURRENT",
+			"CURSOR",
+			"CYCLE",
+			"DAY",
+			"DEALLOCATE",
+			"DEC",
+			"DECLARE",
+			"DEFAULT",
+			"DELETE",
+			"DENSE_RANK",
+			"DEREF",
+			"DESCRIBE",
+			"DETERMINISTIC",
+			"DISCONNECT",
+			"DISTINCT",
+			"DROP",
+			"DYNAMIC",
+			"EACH",
+			"ELEMENT",
+			"ELSE",
+			"END",
+			"END-EXEC",
+			"ESCAPE",
+			"EVERY",
+			"EXCEPT",
+			"EXEC",
+			"EXECUTE",
+			"EXISTS",
+			"EXP",
+			"EXTERNAL",
+			"EXTRACT",
+			"FALSE",
+			"FETCH",
+			"FILTER",
+			"FOR",
+			"FOREIGN",
+			"FREE",
+			"FROM",
+			"FULL",
+			"FUNCTION",
+			"FUSION",
+			"GET",
+			"GLOBAL",
+			"GRANT",
+			"GROUP",
+			"GROUPING",
+			"HAVING",
+			"HOLD",
+			"HOUR",
+			"IDENTITY",
+			"IF",
+			"IN",
+			"INDEX",
+			"INDICATOR",
+			"INNER",
+			"INOUT",
+			"INSENSITIVE",
+			"INSERT",
+			"INTERSECT",
+			"INTERSECTION",
+			"INTERVAL",
+			"INTO",
+			"IS",
+			"JOIN",
+			"LANGUAGE",
+			"LARGE",
+			"LATERAL",
+			"LEFT",
+			"LIKE",
+			"LN",
+			"LOCAL",
+			"LOCALTIME",
+			"LOCALTIMESTAMP",
+			"MATCH",
+			"MEMBER",
+			"MERGE",
+			"METHOD",
+			"MINUTE",
+			"MOD",
+			"MODIFIES",
+			//        "MODULE", // too common for column names
+			"MONTH",
+			"MULTISET",
+			"NATIONAL",
+			"NATURAL",
+			//"NCHAR",
+			//"NCLOB",
+			"NEW",
+			"NO",
+			"NONE",
+			"NORMALIZE",
+			"NOT",
+			"NULL",
+			"NULLIF",
+			"NUMERIC",
+			"OF",
+			"OLD",
+			KEYWORD_ON,
+			"ONLY",
+			"OPEN",
+			"OR",
+			"ORDER",
+			"OUT",
+			"OUTER",
+			"OVER",
+			"OVERLAPS",
+			"OVERLAY",
+			"PARAMETER",
+			"PARTITION",
+			"POSITION",
+			"PRECISION",
+			"PREPARE",
+			"PRIMARY",
+			"PROCEDURE",
+			"RANGE",
+			"RANK",
+			"READS",
+			"REAL",
+			"RECURSIVE",
+			"REF",
+			"REFERENCES",
+			"REFERENCING",
+			"RELEASE",
+			"RENAME",
+			"RESULT",
+			"RETURN",
+			"RETURNS",
+			"REVOKE",
+			"RIGHT",
+			"ROLLBACK",
+			"ROLLUP",
+			"ROW",
+			"ROW_NUMBER",
+			"ROWS",
+			"SAVEPOINT",
+			"SCOPE",
+			"SCROLL",
+			"SEARCH",
+			"SECOND",
+			KEYWORD_SELECT,
+			"SENSITIVE",
+			"SESSION_USER",
+			"SET",
+			"SIMILAR",
+			"SMALLINT",
+			"SOME",
+			"SPECIFIC",
+			"SPECIFICTYPE",
+			"SQL",
+			"SQLEXCEPTION",
+			"SQLSTATE",
+			"SQLWARNING",
+			"START",
+			"STATIC",
+			"STDDEV_POP",
+			"STDDEV_SAMP",
+			"SUBMULTISET",
+			"SYMMETRIC",
+			"SYSTEM",
+			"SYSTEM_USER",
+			"TABLE",
+			"TABLESAMPLE",
+			"THEN",
+			"TIMEZONE_HOUR",
+			"TIMEZONE_MINUTE",
+			"TO",
+			"TRAILING",
+			"TRANSLATE",
+			"TRANSLATION",
+			"TREAT",
+			"TRIGGER",
+			"TRUE",
+			"UNION",
+			"UNIQUE",
+			"UNKNOWN",
+			"UNNEST",
+			"UPDATE",
+			"USER",
+			"USING",
+			//"VALUE", // too common for column names
+			"VALUES",
+			"VAR_POP",
+			"VAR_SAMP",
+			//"VARCHAR",
+			"VARYING",
+			"WHEN",
+			"WHENEVER",
+			"WHERE",
+			"WIDTH_BUCKET",
+			"WINDOW",
+			"WITH",
+			"WITHIN",
+			"WITHOUT",
+			"YEAR",
+
+			"NULLS",
+			"FIRST",
+			"LAST",
+
+			"FOLLOWING",
+			"PRECEDING",
+			"UNBOUNDED",
+
+			"LENGTH",
+			"KEY",
+			"LEVEL",
+
+			"VIEW",
+			"SEQUENCE",
+			"SCHEMA",
+			"ROLE",
+			"RESTRICT",
+			"ASC",
+			"DESC",
+
+			// Not actually standard but widely used
+			"LIMIT",
+
+			// Extended keywords
+			//        "A",
+			"ABSOLUTE",
+			"ACTION",
+			//        "ADA",
+			"ADD",
+			//        "ADMIN",
+			"AFTER",
+			"ALWAYS",
+			//        "ASC",
+			"ASSERTION",
+			"ASSIGNMENT",
+			"ATTRIBUTE",
+			"ATTRIBUTES",
+			"BEFORE",
+			//        "BERNOULLI",
+			//        "BREADTH",
+			//        "C",
+			"CASCADE",
+			"CATALOG",
+			//        "CATALOG_NAME",
+			"CHAIN",
+			//        "CHARACTER_SET_CATALOG",
+			//        "CHARACTER_SET_NAME",
+			//        "CHARACTER_SET_SCHEMA",
+			"CHARACTERISTICS",
+			"CHARACTERS",
+			//        "CLASS_ORIGIN",
+			//        "COBOL",
+			"COLLATION",
+			//        "COLLATION_CATALOG",
+			//        "COLLATION_NAME",
+			//        "COLLATION_SCHEMA",
+			//        "COLUMN_NAME",
+			//        "COMMAND_FUNCTION",
+			//        "COMMAND_FUNCTION_CODE",
+			"COMMITTED",
+			//        "CONDITION_NUMBER",
+			"CONNECTION",
+			//        "CONNECTION_NAME",
+			//        "CONSTRAINT_CATALOG",
+			//        "CONSTRAINT_NAME",
+			//        "CONSTRAINT_SCHEMA",
+			"CONSTRAINTS",
+			"CONSTRUCTOR",
+			"CONTAINS",
+			"CONTINUE",
+			"CURSOR_NAME",
+			"DATA",
+			//        "DATETIME_INTERVAL_CODE",
+			//        "DATETIME_INTERVAL_PRECISION",
+			"DEFAULTS",
+			"DEFERRABLE",
+			"DEFERRED",
+			"DEFINED",
+			"DEFINER",
+			"DEGREE",
+			"DEPTH",
+			"DERIVED",
+			//        "DESC",
+			"DESCRIPTOR",
+			"DIAGNOSTICS",
+			"DISPATCH",
+			"DOMAIN",
+			//        "DYNAMIC_FUNCTION",
+			//        "DYNAMIC_FUNCTION_CODE",
+			"EQUALS",
+			"EXCEPTION",
+			"EXCLUDE",
+			"EXCLUDING",
+			"FINAL",
+			"FIRST",
+			//        "FORTRAN",
+			"FOUND",
+			//        "G",
+			"GENERAL",
+			"GENERATED",
+			"GO",
+			"GOTO",
+			"GRANTED",
+			"HIERARCHY",
+			"IMMEDIATE",
+			"IMPLEMENTATION",
+			"INCLUDING",
+			"INCREMENT",
+			"INITIALLY",
+			"INPUT",
+			"INSTANCE",
+			"INSTANTIABLE",
+			"INVOKER",
+			"ISOLATION",
+			//        "K",
+			//        "KEY_MEMBER",
+			"KEY_TYPE",
+			"LAST",
+			"LOCATOR",
+			//        "M",
+			"MAP",
+			"MATCHED",
+			"MAXVALUE",
+			//        "MESSAGE_LENGTH",
+			//        "MESSAGE_OCTET_LENGTH",
+			//        "MESSAGE_TEXT",
+			"MINVALUE",
+			"MORE",
+			"MUMPS",
+			//        "NAME",
+			//        "NAMES",
+			"NESTING",
+			"NEXT",
+			"NORMALIZED",
+			//        "NULLABLE",
+			//        "NULLS",
+			//        "NUMBER",
+			"OBJECT",
+			"OCTETS",
+			"OPTION",
+			"OPTIONS",
+			"ORDERING",
+			"ORDINALITY",
+			"OTHERS",
+			"OUTPUT",
+			"OVERRIDING",
+			"PAD",
+			//        "PARAMETER_MODE",
+			//        "PARAMETER_NAME",
+			//        "PARAMETER_ORDINAL_POSITION",
+			//        "PARAMETER_SPECIFIC_CATALOG",
+			//        "PARAMETER_SPECIFIC_NAME",
+			//        "PARAMETER_SPECIFIC_SCHEMA",
+			"PARTIAL",
+			//        "PASCAL",
+			"PATH",
+			"PLACING",
+			//        "PLI",
+			"PRESERVE",
+			"PRIOR",
+			"PRIVILEGES",
+			//        "PUBLIC",
+			"READ",
+			"RELATIVE",
+			"REPEATABLE",
+			"RESTART",
+			//        "RETURNED_CARDINALITY",
+			//        "RETURNED_LENGTH",
+			//        "RETURNED_OCTET_LENGTH",
+			//        "RETURNED_SQLSTATE",
+			"ROUTINE",
+			//        "ROUTINE_CATALOG",
+			//        "ROUTINE_NAME",
+			//        "ROUTINE_SCHEMA",
+			//        "ROW_COUNT",
+			"SCALE",
+			//        "SCHEMA_NAME",
+			//        "SCOPE_CATALOG",
+			//        "SCOPE_NAME",
+			//        "SCOPE_SCHEMA",
+			"SECTION",
+			"SECURITY",
+			"SELF",
+			"SERIALIZABLE",
+			//        "SERVER_NAME",
+			"SESSION",
+			"SETS",
+			//        "SIMPLE",
+			"SIZE",
+			"SOURCE",
+			"SPACE",
+			//        "SPECIFIC_NAME",
+			//        "STATE", // too common for column names
+			"STATEMENT",
+			"STRUCTURE",
+			"STYLE",
+			//        "SUBCLASS_ORIGIN",
+			//        "TABLE_NAME",
+			"TEMPORARY",
+			"TIES",
+			//        "TOP_LEVEL_COUNT",
+			"TRANSACTION",
+			//        "TRANSACTION_ACTIVE",
+			//        "TRANSACTIONS_COMMITTED",
+			//        "TRANSACTIONS_ROLLED_BACK",
+			"TRANSFORM",
+			"TRANSFORMS",
+			//        "TRIGGER_CATALOG",
+			//        "TRIGGER_NAME",
+			//        "TRIGGER_SCHEMA",
+			"TYPE",
+			"UNCOMMITTED",
+			"UNDER",
+			"UNNAMED",
+			"USAGE",
+			//        "USER_DEFINED_TYPE_CATALOG",
+			//        "USER_DEFINED_TYPE_CODE",
+			//        "USER_DEFINED_TYPE_NAME",
+			//        "USER_DEFINED_TYPE_SCHEMA",
+			"WORK",
+			"WRITE",
+			"ZONE"
+	};
+
+	static final String[] SQL2003_FUNCTIONS = {
+			"ABS",
+			"AVG",
+			"CHAR_LENGTH",
+			"CHARACTER_LENGTH",
+			"COUNT",
+			"CURRENT_DATE",
+			"CURRENT_DEFAULT_TRANSFORM_GROUP",
+			"CURRENT_PATH",
+			"CURRENT_ROLE",
+			"CURRENT_TIME",
+			"CURRENT_TIMESTAMP",
+			"CURRENT_TRANSFORM_GROUP_FOR_TYPE",
+			"CURRENT_USER",
+			"FLOOR",
+			"LEADING",
+			"LOWER",
+			"MAX",
+			"MIN",
+			"OCTET_LENGTH",
+			"PERCENT_RANK",
+			"PERCENTILE_CONT",
+			"PERCENTILE_DISC",
+			"POWER",
+			"REGR_AVGX",
+			"REGR_AVGY",
+			"REGR_COUNT",
+			"REGR_INTERCEPT",
+			"REGR_R2",
+			"REGR_SLOPE",
+			"REGR_SXX",
+			"REGR_SXY",
+			"REGR_SYY",
+			"SQRT",
+			"SUBSTRING",
+			"SUM",
+			"TRIM",
+			"UESCAPE",
+			"UPPER",
+	};
+
+	static final String[] SQL_EX_KEYWORDS = {
+			"CHANGE",
+			"MODIFY",
+	};
+	static final String[] DEFAULT_TYPES = {
+			"BOOLEAN",
+			"CHAR",
+			"VARCHAR",
+			"BINARY",
+			"VARBINARY",
+			"INT",
+			"INTEGER",
+			"SMALLINT",
+			"BIGINT",
+			"NUMBER",
+			"NUMERIC",
+			"DECIMAL",
+			"FLOAT",
+			"DOUBLE",
+			"DATE",
+			"TIME",
+			"TIMESTAMP",
+			"CLOB",
+			"BLOB",
+	};
+
+	static final char STRUCT_SEPARATOR = '.'; //$NON-NLS-1$
+
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java
new file mode 100644
index 0000000000..096daf9359
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+import java.util.*;
+
+/**
+ * Basic SQL Dialect
+ */
+class SQLDialect {
+
+	private static final String[] DEFAULT_LINE_COMMENTS = {SQLConstants.SL_COMMENT};
+	private static final String[] EXEC_KEYWORDS = new String[0];
+
+	private static final String[][] DEFAULT_QUOTE_STRINGS = {{"\"", "\""}};
+
+	// Keywords
+	private TreeMap<String, DBPKeywordType> allKeywords = new TreeMap<>();
+
+	private final TreeSet<String> functions = new TreeSet<>();
+	private final TreeSet<String> types = new TreeSet<>();
+	// Comments
+	private Pair<String, String> multiLineComments = new Pair<>(SQLConstants.ML_COMMENT_START, SQLConstants.ML_COMMENT_END);
+
+	static final SQLDialect INSTANCE = new SQLDialect();
+
+	private SQLDialect() {
+		loadStandardKeywords();
+	}
+
+	String[][] getIdentifierQuoteStrings() {
+		return DEFAULT_QUOTE_STRINGS;
+	}
+
+	private String[] getExecuteKeywords() {
+		return EXEC_KEYWORDS;
+	}
+
+	private void addSQLKeyword(String keyword) {
+		allKeywords.put(keyword, DBPKeywordType.KEYWORD);
+	}
+
+	/**
+	 * Add keywords.
+	 * @param set     keywords. Must be in upper case.
+	 * @param type    keyword type
+	 */
+	private void addKeywords(Collection<String> set, DBPKeywordType type) {
+		for (String keyword : set) {
+			keyword = keyword.toUpperCase(Locale.ENGLISH);
+			DBPKeywordType oldType = allKeywords.get(keyword);
+			if (oldType != DBPKeywordType.KEYWORD) {
+				// We can't mark keywords as functions or types because keywords are reserved and
+				// if some identifier conflicts with keyword it must be quoted.
+				allKeywords.put(keyword, type);
+			}
+		}
+	}
+
+	DBPKeywordType getKeywordType(String word) {
+		return allKeywords.get(word.toUpperCase(Locale.ENGLISH));
+	}
+
+	String getCatalogSeparator() {
+		return String.valueOf(SQLConstants.STRUCT_SEPARATOR);
+	}
+
+	char getStructSeparator() {
+		return SQLConstants.STRUCT_SEPARATOR;
+	}
+
+	String getScriptDelimiter() {
+		return ";";
+	}
+
+	Pair<String, String> getMultiLineComments() {
+		return multiLineComments;
+	}
+
+	String[] getSingleLineComments() {
+		return DEFAULT_LINE_COMMENTS;
+	}
+
+	private void loadStandardKeywords() {
+		// Add default set of keywords
+		Set<String> all = new HashSet<>();
+		Collections.addAll(all, SQLConstants.SQL2003_RESERVED_KEYWORDS);
+		Collections.addAll(all, SQLConstants.SQL_EX_KEYWORDS);
+		Collections.addAll(functions, SQLConstants.SQL2003_FUNCTIONS);
+
+		for (String executeKeyword : getExecuteKeywords()) {
+			addSQLKeyword(executeKeyword);
+		}
+
+		// Add default types
+		Collections.addAll(types, SQLConstants.DEFAULT_TYPES);
+
+		addKeywords(all, DBPKeywordType.KEYWORD);
+		addKeywords(types, DBPKeywordType.TYPE);
+		addKeywords(functions, DBPKeywordType.FUNCTION);
+	}
+
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLFormatterConfiguration.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLFormatterConfiguration.java
new file mode 100644
index 0000000000..5aec381438
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLFormatterConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+import java.util.Properties;
+
+/**
+ * SQLFormatterConfiguration
+ */
+public class SQLFormatterConfiguration {
+
+	/**
+	 * UPPER, LOWER or ORIGINAL
+	 */
+	public static final String SQL_FORMATTER_KEYWORD_CASE = "sql.formatter.keyword.case";
+
+	/**
+	 * ';' by default
+	 */
+	public static final String SQL_FORMATTER_STATEMENT_DELIMITER = "sql.formatter.statement.delimiter";
+	/**
+	 * space or tag
+	 */
+	public static final String SQL_FORMATTER_INDENT_TYPE = "sql.formatter.indent.type";
+	/**
+	 * 4 by default
+	 */
+	public static final String SQL_FORMATTER_INDENT_SIZE = "sql.formatter.indent.size";
+
+	private String statementDelimiters;
+	private KeywordCase keywordCase;
+	private String indentString;
+
+	public SQLFormatterConfiguration(Properties properties) {
+		this.keywordCase = KeywordCase.valueOf(properties.getProperty(SQL_FORMATTER_KEYWORD_CASE, "UPPER"));
+		this.statementDelimiters = properties.getProperty(SQL_FORMATTER_STATEMENT_DELIMITER, SQLDialect.INSTANCE
+				.getScriptDelimiter());
+		String indentType = properties.getProperty(SQL_FORMATTER_INDENT_TYPE, "space");
+		int indentSize = Integer.parseInt(properties.getProperty(SQL_FORMATTER_INDENT_SIZE, "4"));
+		indentString = getIndentString(indentType, indentSize);
+	}
+
+	private String getIndentString(String indentType, int indentSize) {
+		char indentChar = indentType.equals("space") ? ' ' : '\t';
+		StringBuilder stringBuilder = new StringBuilder();
+		for (int i = 0; i < indentSize; i++) {
+			stringBuilder.append(indentChar);
+		}
+		return stringBuilder.toString();
+	}
+
+	String getStatementDelimiter() {
+		return statementDelimiters;
+	}
+
+	String getIndentString() {
+		return indentString;
+	}
+
+	KeywordCase getKeywordCase() {
+		return keywordCase;
+	}
+
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java
new file mode 100644
index 0000000000..44fd0019ec
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Forked from
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
+ *
+ * SQL formatter
+ */
+public class SQLTokenizedFormatter {
+
+	private static final String[] JOIN_BEGIN = {"LEFT", "RIGHT", "INNER", "OUTER", "JOIN"};
+	private static final SQLDialect sqlDialect = SQLDialect.INSTANCE;
+	private SQLFormatterConfiguration formatterCfg;
+	private List<Boolean> functionBracket = new ArrayList<>();
+	private List<String> statementDelimiters = new ArrayList<>(2);
+
+	public SQLTokenizedFormatter(SQLFormatterConfiguration formatterCfg) {
+		this.formatterCfg = formatterCfg;
+	}
+
+	public String format(final String argSql) {
+
+		statementDelimiters.add(formatterCfg.getStatementDelimiter());
+		SQLTokensParser fParser = new SQLTokensParser();
+
+		functionBracket.clear();
+
+		boolean isSqlEndsWithNewLine = false;
+		if (argSql.endsWith("\n")) {
+			isSqlEndsWithNewLine = true;
+		}
+
+		List<FormatterToken> list = fParser.parse(argSql);
+		list = format(list);
+
+		StringBuilder after = new StringBuilder(argSql.length() + 20);
+		for (FormatterToken token : list) {
+			after.append(token.getString());
+		}
+
+		if (isSqlEndsWithNewLine) {
+			after.append(getDefaultLineSeparator());
+		}
+
+		return after.toString();
+	}
+
+	private List<FormatterToken> format(final List<FormatterToken> argList) {
+		if (argList.isEmpty()) {
+			return argList;
+		}
+
+		FormatterToken token = argList.get(0);
+		if (token.getType() == TokenType.SPACE) {
+			argList.remove(0);
+			if (argList.isEmpty()) {
+				return argList;
+			}
+		}
+
+		token = argList.get(argList.size() - 1);
+		if (token.getType() == TokenType.SPACE) {
+			argList.remove(argList.size() - 1);
+			if (argList.isEmpty()) {
+				return argList;
+			}
+		}
+
+		final KeywordCase keywordCase = formatterCfg.getKeywordCase();
+		for (FormatterToken anArgList : argList) {
+			token = anArgList;
+			if (token.getType() == TokenType.KEYWORD) {
+				token.setString(keywordCase.transform(token.getString()));
+			}
+		}
+
+		// Remove extra tokens (spaces, etc)
+		for (int index = argList.size() - 1; index >= 1; index--) {
+			token = argList.get(index);
+			FormatterToken prevToken = argList.get(index - 1);
+			if (token.getType() == TokenType.SPACE && (prevToken.getType() == TokenType.SYMBOL || prevToken.getType() == TokenType.COMMENT)) {
+				argList.remove(index);
+			} else if ((token.getType() == TokenType.SYMBOL || token.getType() == TokenType.COMMENT) && prevToken.getType() == TokenType.SPACE) {
+				argList.remove(index - 1);
+			} else if (token.getType() == TokenType.SPACE) {
+				token.setString(" ");
+			}
+		}
+
+		for (int index = 0; index < argList.size() - 2; index++) {
+			FormatterToken t0 = argList.get(index);
+			FormatterToken t1 = argList.get(index + 1);
+			FormatterToken t2 = argList.get(index + 2);
+
+			String tokenString = t0.getString().toUpperCase(Locale.ENGLISH);
+			String token2String = t2.getString().toUpperCase(Locale.ENGLISH);
+			// Concatenate tokens
+			if (t0.getType() == TokenType.KEYWORD && t1.getType() == TokenType.SPACE && t2.getType() == TokenType.KEYWORD) {
+				if (((tokenString.equals("ORDER") || tokenString.equals("GROUP") || tokenString.equals("CONNECT")) && token2String.equals("BY")) ||
+						((tokenString.equals("START")) && token2String.equals("WITH"))) {
+					t0.setString(t0.getString() + " " + t2.getString());
+					argList.remove(index + 1);
+					argList.remove(index + 1);
+				}
+			}
+
+			// Oracle style joins
+			if (tokenString.equals("(") && t1.getString().equals("+") && token2String.equals(")")) {  //$NON-NLS-2$ //$NON-NLS-3$
+				t0.setString("(+)");
+				argList.remove(index + 1);
+				argList.remove(index + 1);
+			}
+		}
+
+		int indent = 0;
+		final List<Integer> bracketIndent = new ArrayList<>();
+		FormatterToken prev = new FormatterToken(TokenType.SPACE, " ");
+		boolean encounterBetween = false;
+		for (int index = 0; index < argList.size(); index++) {
+			token = argList.get(index);
+			String tokenString = token.getString().toUpperCase(Locale.ENGLISH);
+			if (token.getType() == TokenType.SYMBOL) {
+				if (tokenString.equals("(")) {
+					functionBracket.add(isFunction(prev.getString()) ? Boolean.TRUE : Boolean.FALSE);
+					bracketIndent.add(indent);
+					indent++;
+					index += insertReturnAndIndent(argList, index + 1, indent);
+				} else if (tokenString.equals(")") && !bracketIndent.isEmpty() && !functionBracket.isEmpty()) {
+					indent = bracketIndent.remove(bracketIndent.size() - 1);
+					index += insertReturnAndIndent(argList, index, indent);
+					functionBracket.remove(functionBracket.size() - 1);
+				} else if (tokenString.equals(",")) {
+					index += insertReturnAndIndent(argList, index + 1, indent);
+				} else if (statementDelimiters.contains(tokenString)) {
+					indent = 0;
+					index += insertReturnAndIndent(argList, index, indent);
+				}
+			} else if (token.getType() == TokenType.KEYWORD) {
+				switch (tokenString) {
+				case "DELETE":
+				case "SELECT":
+				case "UPDATE":
+				case "INSERT":
+				case "INTO":
+				case "CREATE":
+				case "DROP":
+				case "TRUNCATE":
+				case "TABLE":
+				case "CASE":
+					indent++;
+					index += insertReturnAndIndent(argList, index + 1, indent);
+					break;
+				case "FROM":
+				case "WHERE":
+				case "SET":
+				case "START WITH":
+				case "CONNECT BY":
+				case "ORDER BY":
+				case "GROUP BY":
+				case "HAVING":
+					index += insertReturnAndIndent(argList, index, indent - 1);
+					index += insertReturnAndIndent(argList, index + 1, indent);
+					break;
+				case "LEFT":
+				case "RIGHT":
+				case "INNER":
+				case "OUTER":
+				case "JOIN":
+					if (isJoinStart(argList, index)) {
+						index += insertReturnAndIndent(argList, index, indent - 1);
+					}
+					break;
+				case "VALUES":
+				case "END":
+					indent--;
+					index += insertReturnAndIndent(argList, index, indent);
+					break;
+				case "OR":
+				case "WHEN":
+				case "ELSE":
+					index += insertReturnAndIndent(argList, index, indent);
+					break;
+				case "ON":
+					//indent++;
+					index += insertReturnAndIndent(argList, index + 1, indent);
+					break;
+				case "USING":   //$NON-NLS-2$
+					index += insertReturnAndIndent(argList, index, indent + 1);
+					break;
+				case "TOP":   //$NON-NLS-2$
+					// SQL Server specific
+					index += insertReturnAndIndent(argList, index, indent);
+					if (argList.size() < index + 3) {
+						index += insertReturnAndIndent(argList, index + 3, indent);
+					}
+					break;
+				case "UNION":
+				case "INTERSECT":
+				case "EXCEPT":
+					indent -= 2;
+					index += insertReturnAndIndent(argList, index, indent);
+					//index += insertReturnAndIndent(argList, index + 1, indent);
+					indent++;
+					break;
+				case "BETWEEN":
+					encounterBetween = true;
+					break;
+				case "AND":
+					if (!encounterBetween) {
+						index += insertReturnAndIndent(argList, index, indent);
+					}
+					encounterBetween = false;
+					break;
+				default:
+					break;
+				}
+			} else if (token.getType() == TokenType.COMMENT) {
+				boolean isComment = false;
+				String[] slComments = sqlDialect.getSingleLineComments();
+				for (String slc : slComments) {
+					if (token.getString().startsWith(slc)) {
+						isComment = true;
+						break;
+					}
+				}
+				if (!isComment) {
+					Pair<String, String> mlComments = sqlDialect.getMultiLineComments();
+					if (token.getString().startsWith(mlComments.getFirst())) {
+						index += insertReturnAndIndent(argList, index + 1, indent);
+					}
+				}
+			} else if (token.getType() == TokenType.COMMAND) {
+				indent = 0;
+				if (index > 0) {
+					index += insertReturnAndIndent(argList, index, 0);
+				}
+				index += insertReturnAndIndent(argList, index + 1, 0);
+			} else {
+				if (statementDelimiters.contains(tokenString)) {
+					indent = 0;
+					index += insertReturnAndIndent(argList, index + 1, indent);
+				}
+			}
+			prev = token;
+		}
+
+		for (int index = argList.size() - 1; index >= 4; index--) {
+			if (index >= argList.size()) {
+				continue;
+			}
+
+			FormatterToken t0 = argList.get(index);
+			FormatterToken t1 = argList.get(index - 1);
+			FormatterToken t2 = argList.get(index - 2);
+			FormatterToken t3 = argList.get(index - 3);
+			FormatterToken t4 = argList.get(index - 4);
+
+			if (t4.getString().equals("(")
+					&& t3.getString().trim().isEmpty()
+					&& t1.getString().trim().isEmpty()
+					&& t0.getString().equalsIgnoreCase(")")) {
+				t4.setString(t4.getString() + t2.getString() + t0.getString());
+				argList.remove(index);
+				argList.remove(index - 1);
+				argList.remove(index - 2);
+				argList.remove(index - 3);
+			}
+		}
+
+		for (int index = 1; index < argList.size(); index++) {
+			prev = argList.get(index - 1);
+			token = argList.get(index);
+
+			if (prev.getType() != TokenType.SPACE &&
+					token.getType() != TokenType.SPACE &&
+					!token.getString().startsWith("(")) {
+				if (token.getString().equals(",") || statementDelimiters.contains(token.getString())) {
+					continue;
+				}
+				if (isFunction(prev.getString())
+						&& token.getString().equals("(")) {
+					continue;
+				}
+				if (token.getType() == TokenType.VALUE && prev.getType() == TokenType.NAME) {
+					// Do not add space between name and value [JDBC:MSSQL]
+					continue;
+				}
+				if (token.getType() == TokenType.SYMBOL && isEmbeddedToken(token) ||
+						prev.getType() == TokenType.SYMBOL && isEmbeddedToken(prev)) {
+					// Do not insert spaces around colons
+					continue;
+				}
+				if (token.getType() == TokenType.SYMBOL && prev.getType() == TokenType.SYMBOL) {
+					// Do not add space between symbols
+					continue;
+				}
+				argList.add(index, new FormatterToken(TokenType.SPACE, " "));
+			}
+		}
+
+		return argList;
+	}
+
+	private static boolean isEmbeddedToken(FormatterToken token) {
+		return ":".equals(token.getString()) || ".".equals(token.getString());
+	}
+
+	private boolean isJoinStart(List<FormatterToken> argList, int index) {
+		// Keyword sequence must start from LEFT, RIGHT, INNER, OUTER or JOIN and must end with JOIN
+		// And we must be in the beginning of sequence
+
+		// check current token
+		if (!contains(JOIN_BEGIN, argList.get(index).getString())) {
+			return false;
+		}
+		// check previous token
+		for (int i = index - 1; i >= 0; i--) {
+			FormatterToken token = argList.get(i);
+			if (token.getType() == TokenType.SPACE) {
+				continue;
+			}
+			if (contains(JOIN_BEGIN, token.getString())) {
+				// It is not the begin of sequence
+				return false;
+			} else {
+				break;
+			}
+		}
+		// check last token
+		for (int i = index; i < argList.size(); i++) {
+			FormatterToken token = argList.get(i);
+			if (token.getType() == TokenType.SPACE) {
+				continue;
+			}
+			if (token.getString().equals("JOIN")) {
+				return true;
+			}
+			if (!contains(JOIN_BEGIN, token.getString())) {
+				// It is not the begin of sequence
+				return false;
+			}
+		}
+		return false;
+	}
+
+	boolean isFunction(String name) {
+		return sqlDialect.getKeywordType(name) == DBPKeywordType.FUNCTION;
+	}
+
+	private static String getDefaultLineSeparator() {
+		return System.getProperty("line.separator", "\n");
+	}
+
+	private int insertReturnAndIndent(final List<FormatterToken> argList, final int argIndex, final int argIndent) {
+		if (functionBracket.contains(Boolean.TRUE))
+			return 0;
+		try {
+			StringBuilder s = new StringBuilder(getDefaultLineSeparator());
+			if (argIndex > 0) {
+				final FormatterToken prevToken = argList.get(argIndex - 1);
+				if (prevToken.getType() == TokenType.COMMENT &&
+						isCommentLine(sqlDialect, prevToken.getString())) {
+					s = new StringBuilder();
+				}
+			}
+			for (int index = 0; index < argIndent; index++) {
+				s.append(formatterCfg.getIndentString());
+			}
+
+			FormatterToken token = argList.get(argIndex);
+			if (token.getType() == TokenType.SPACE) {
+				token.setString(s.toString());
+				return 0;
+			}
+			boolean isDelimiter = statementDelimiters.contains(token.getString().toUpperCase(Locale.ENGLISH));
+
+			if (!isDelimiter) {
+				token = argList.get(argIndex - 1);
+				if (token.getType() == TokenType.SPACE) {
+					token.setString(s.toString());
+					return 0;
+				}
+			}
+
+			if (isDelimiter) {
+				if (argList.size() > argIndex + 1) {
+					String string = s.toString();
+					argList.add(argIndex + 1, new FormatterToken(TokenType.SPACE, string + string));
+				}
+			} else {
+				argList.add(argIndex, new FormatterToken(TokenType.SPACE, s.toString()));
+			}
+			return 1;
+		} catch (IndexOutOfBoundsException e) {
+			e.printStackTrace();
+			return 0;
+		}
+	}
+
+	private static boolean isCommentLine(SQLDialect dialect, String line) {
+		for (String slc : dialect.getSingleLineComments()) {
+			if (line.startsWith(slc)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private static <OBJECT_TYPE> boolean contains(OBJECT_TYPE[] array, OBJECT_TYPE value) {
+		if (array == null || array.length == 0)
+			return false;
+		for (OBJECT_TYPE anArray : array) {
+			if (Objects.equals(value, anArray))
+				return true;
+		}
+		return false;
+	}
+
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java
new file mode 100644
index 0000000000..c388a99e42
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Forked from
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
+ *
+ * SQLTokensParser
+ */
+class SQLTokensParser {
+
+	private static final String[] twoCharacterSymbol = {"<>", "<=", ">=", "||", "()", "!=", ":=", ".*"};
+	private static final SQLDialect sqlDialect = SQLDialect.INSTANCE;
+
+	private final String[][] quoteStrings;
+	private String fBefore = null;
+	private int fPos;
+	private char structSeparator;
+	private String catalogSeparator;
+	private Set<String> commands = new HashSet<>();
+	private String[] singleLineComments;
+	private char[] singleLineCommentStart;
+
+	public SQLTokensParser() {
+		this.structSeparator = sqlDialect.getStructSeparator();
+		this.catalogSeparator = sqlDialect.getCatalogSeparator();
+		this.quoteStrings = sqlDialect.getIdentifierQuoteStrings();
+		this.singleLineComments = sqlDialect.getSingleLineComments();
+		this.singleLineCommentStart = new char[this.singleLineComments.length];
+		for (int i = 0; i < singleLineComments.length; i++) {
+			if (singleLineComments[i].isEmpty())
+				singleLineCommentStart[i] = 0;
+			else
+				singleLineCommentStart[i] = singleLineComments[i].charAt(0);
+		}
+	}
+
+	public static boolean isSpace(final char argChar) {
+		return Character.isWhitespace(argChar);
+	}
+
+	public static boolean isLetter(final char argChar) {
+		return !isSpace(argChar) && !isDigit(argChar) && !isSymbol(argChar);
+	}
+
+	public static boolean isDigit(final char argChar) {
+		return Character.isDigit(argChar);
+	}
+
+	public static boolean isSymbol(final char argChar) {
+		switch (argChar) {
+		case '"': // double quote
+		case '?': // question mark
+		case '%': // percent
+		case '&': // ampersand
+		case '\'': // quote
+		case '(': // left paren
+		case ')': // right paren
+		case '|': // vertical bar
+		case '*': // asterisk
+		case '+': // plus sign
+		case ',': // comma
+		case '-': // minus sign
+		case '.': // period
+		case '/': // solidus
+		case ':': // colon
+		case ';': // semicolon
+		case '<': // less than operator
+		case '=': // equals operator
+		case '>': // greater than operator
+		case '!': // greater than operator
+		case '~': // greater than operator
+		case '`': // apos
+		case '[': // bracket open
+		case ']': // bracket close
+			return true;
+		default:
+			return false;
+		}
+	}
+
+	FormatterToken nextToken() {
+		int start_pos = fPos;
+		if (fPos >= fBefore.length()) {
+			fPos++;
+			return new FormatterToken(TokenType.END, "", start_pos);
+		}
+
+		char fChar = fBefore.charAt(fPos);
+
+		if (isSpace(fChar)) {
+			StringBuilder workString = new StringBuilder();
+			for (;;) {
+				workString.append(fChar);
+				fChar = fBefore.charAt(fPos);
+				if (!isSpace(fChar)) {
+					return new FormatterToken(TokenType.SPACE, workString.toString(), start_pos);
+				}
+				fPos++;
+				if (fPos >= fBefore.length()) {
+					return new FormatterToken(TokenType.SPACE, workString.toString(), start_pos);
+				}
+			}
+		} else if (fChar == ';') {
+			fPos++;
+			return new FormatterToken(TokenType.SYMBOL, ";", start_pos);
+		} else if (isDigit(fChar)) {
+			StringBuilder s = new StringBuilder();
+			while (isDigit(fChar) || fChar == '.' || fChar == 'e' || fChar == 'E') {
+				// if (ch == '.') type = Token.REAL;
+				s.append(fChar);
+				fPos++;
+
+				if (fPos >= fBefore.length()) {
+					break;
+				}
+
+				fChar = fBefore.charAt(fPos);
+			}
+			return new FormatterToken(TokenType.VALUE, s.toString(), start_pos);
+		}
+		// single line comment
+		else if (contains(singleLineCommentStart, fChar)) {
+			fPos++;
+			String commentString = null;
+			for (String slc : singleLineComments) {
+				if (fBefore.length() >= start_pos + slc.length() && slc.equals(fBefore.substring(start_pos, start_pos + slc.length()))) {
+					commentString = slc;
+					break;
+				}
+			}
+			if (commentString == null) {
+				return new FormatterToken(TokenType.SYMBOL, String.valueOf(fChar), start_pos);
+			}
+			fPos += commentString.length() - 1;
+			while (fPos < fBefore.length()) {
+				fPos++;
+				if (fBefore.charAt(fPos - 1) == '\n') {
+					break;
+				}
+			}
+			commentString = fBefore.substring(start_pos, fPos);
+			return new FormatterToken(TokenType.COMMENT, commentString, start_pos);
+		} else if (isLetter(fChar)) {
+			StringBuilder s = new StringBuilder();
+			while (isLetter(fChar) || isDigit(fChar) || fChar == '*' || structSeparator == fChar || catalogSeparator.indexOf(fChar) != -1) {
+				s.append(fChar);
+				fPos++;
+				if (fPos >= fBefore.length()) {
+					break;
+				}
+
+				fChar = fBefore.charAt(fPos);
+			}
+			String word = s.toString();
+			if (commands.contains(word.toUpperCase(Locale.ENGLISH))) {
+				s.setLength(0);
+				for (; fPos < fBefore.length(); fPos++) {
+					fChar = fBefore.charAt(fPos);
+					if (fChar == '\n' || fChar == '\r') {
+						break;
+					} else {
+						s.append(fChar);
+					}
+				}
+				return new FormatterToken(TokenType.COMMAND, word + s.toString(), start_pos);
+			}
+			if (sqlDialect.getKeywordType(word) != null) {
+				return new FormatterToken(TokenType.KEYWORD, word, start_pos);
+			}
+			return new FormatterToken(TokenType.NAME, word, start_pos);
+		} else if (fChar == '/') {
+			fPos++;
+			char ch2 = fBefore.charAt(fPos);
+			if (ch2 != '*') {
+				return new FormatterToken(TokenType.SYMBOL, "/", start_pos);
+			}
+
+			StringBuilder s = new StringBuilder("/*");
+			fPos++;
+			for (;;) {
+				int ch0 = fChar;
+				fChar = fBefore.charAt(fPos);
+				s.append(fChar);
+				fPos++;
+				if (ch0 == '*' && fChar == '/') {
+					return new FormatterToken(TokenType.COMMENT, s.toString(), start_pos);
+				}
+			}
+		} else {
+			if (fChar == '\'' || isQuoteChar(fChar)) {
+				fPos++;
+				char endQuoteChar = fChar;
+				// Close quote char may differ
+				if (quoteStrings != null) {
+					for (String[] quoteString : quoteStrings) {
+						if (quoteString[0].charAt(0) == endQuoteChar) {
+							endQuoteChar = quoteString[1].charAt(0);
+							break;
+						}
+					}
+				}
+
+				StringBuilder s = new StringBuilder();
+				s.append(fChar);
+				for (;;) {
+					fChar = fBefore.charAt(fPos);
+					s.append(fChar);
+					fPos++;
+					char fNextChar = fPos >= fBefore.length() - 1 ? 0 : fBefore.charAt(fPos);
+					if (fChar == endQuoteChar && fNextChar == endQuoteChar) {
+						// Escaped quote
+						s.append(fChar);
+						fPos++;
+						continue;
+					}
+					if (fChar == endQuoteChar) {
+						return new FormatterToken(TokenType.VALUE, s.toString(), start_pos);
+					}
+				}
+			}
+
+			else if (isSymbol(fChar)) {
+				StringBuilder s = new StringBuilder(String.valueOf(fChar));
+				fPos++;
+				if (fPos >= fBefore.length()) {
+					return new FormatterToken(TokenType.SYMBOL, s.toString(), start_pos);
+				}
+				char ch2 = fBefore.charAt(fPos);
+				for (String aTwoCharacterSymbol : twoCharacterSymbol) {
+					if (aTwoCharacterSymbol.charAt(0) == fChar && aTwoCharacterSymbol.charAt(1) == ch2) {
+						fPos++;
+						s.append(ch2);
+						break;
+					}
+				}
+				return new FormatterToken(TokenType.SYMBOL, s.toString(), start_pos);
+			} else {
+				fPos++;
+				return new FormatterToken(TokenType.UNKNOWN, String.valueOf(fChar), start_pos);
+			}
+		}
+	}
+
+	private boolean isQuoteChar(char fChar) {
+		if (quoteStrings != null) {
+			for (String[] quoteString : quoteStrings) {
+				if (quoteString[0].charAt(0) == fChar) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	List<FormatterToken> parse(final String argSql) {
+		fPos = 0;
+		fBefore = argSql;
+
+		final List<FormatterToken> list = new ArrayList<>();
+		for (;;) {
+			final FormatterToken token = nextToken();
+			if (token.getType() == TokenType.END) {
+				break;
+			}
+
+			list.add(token);
+		}
+		return list;
+	}
+
+	private static boolean contains(char[] array, char value) {
+		if (array == null || array.length == 0)
+			return false;
+		for (char aChar : array) {
+			if (aChar == value)
+				return true;
+		}
+		return false;
+	}
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java
new file mode 100644
index 0000000000..8043555dff
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql.dbeaver;
+
+enum TokenType {
+
+	SPACE, SYMBOL, KEYWORD, NAME, VALUE, COMMAND, COMMENT, END, UNKNOWN
+}
diff --git a/lib/src/main/java/com/diffplug/spotless/sql/package-info.java b/lib/src/main/java/com/diffplug/spotless/sql/package-info.java
new file mode 100644
index 0000000000..77088bf36c
--- /dev/null
+++ b/lib/src/main/java/com/diffplug/spotless/sql/package-info.java
@@ -0,0 +1,7 @@
+@ParametersAreNonnullByDefault
+@ReturnValuesAreNonnullByDefault
+package com.diffplug.spotless.sql;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault;
diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md
index 7644ac1966..6f837ec98d 100644
--- a/plugin-gradle/CHANGES.md
+++ b/plugin-gradle/CHANGES.md
@@ -4,6 +4,9 @@
 
 * Updated default eclipse-jdt version to `4.7.1` from `4.6.3`.
 * All spotless tasks now run before the `clean` task. ([#159](https://github.com/diffplug/spotless/issues/159))
+* Added `sql` ([#166](https://github.com/diffplug/spotless/pull/166)) and `dbeaverSql`. ([#166](https://github.com/diffplug/spotless/pull/166))
+	+ Many thanks to [Baptiste Mesta](https://github.com/baptistemesta) for porting to Spotless.
+	+ Many thanks to [DBeaver](https://dbeaver.jkiss.org/) and the [DBeaver contributors](https://github.com/serge-rider/dbeaver/graphs/contributors) for building the implementation.
 
 ### Version 3.6.0 - September 29th 2017 ([javadoc](https://diffplug.github.io/spotless/javadoc/spotless-plugin-gradle/3.6.0/), [jcenter](https://bintray.com/diffplug/opensource/spotless-plugin-gradle/3.6.0))
 
diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md
index 78c57d85bd..e695d105bf 100644
--- a/plugin-gradle/README.md
+++ b/plugin-gradle/README.md
@@ -243,6 +243,21 @@ spotless {
 }
 ```
 
+<a name="sql-dbeaver"></a>
+
+## Applying [DBeaver](https://dbeaver.jkiss.org/) to SQL scripts
+
+```gradle
+spotless {
+	sql {
+		// you have to specify the target
+		target '**/*.sql'
+		// configFile is optional, arguments available here: https://github.com/diffplug/spotless/blob/master/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java
+		dbeaverSql().configFile('dbeaver.props')
+	}
+}
+```
+
 <a name="custom"></a>
 
 ## Custom rules
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java
index 355c1a0075..15877034d9 100644
--- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java
@@ -113,6 +113,11 @@ public void groovyGradle(Action<GroovyGradleExtension> closure) {
 		configure(GroovyGradleExtension.NAME, GroovyGradleExtension.class, closure);
 	}
 
+	/** Configures the special sql-specific extension for SQL files. */
+	public void sql(Action<SqlExtension> closure) {
+		configure(SqlExtension.NAME, SqlExtension.class, closure);
+	}
+
 	/** Configures a custom extension. */
 	public void format(String name, Action<FormatExtension> closure) {
 		requireNonNull(name, "name");
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java
new file mode 100644
index 0000000000..a19222715a
--- /dev/null
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.gradle.spotless;
+
+import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull;
+
+import org.gradle.api.Project;
+
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.sql.DBeaverSQLFormatterStep;
+
+public class SqlExtension extends FormatExtension {
+	static final String NAME = "sql";
+
+	public SqlExtension(SpotlessExtension rootExtension) {
+		super(rootExtension);
+	}
+
+	public DBeaverSQLFormatterConfig dbeaver() {
+		return new DBeaverSQLFormatterConfig();
+	}
+
+	public class DBeaverSQLFormatterConfig {
+		Object[] configFiles;
+
+		DBeaverSQLFormatterConfig() {
+			configFiles = new Object[0];
+			addStep(createStep());
+		}
+
+		public void configFile(Object... configFiles) {
+			this.configFiles = requireElementsNonNull(configFiles);
+			replaceStep(createStep());
+		}
+
+		private FormatterStep createStep() {
+			Project project = getProject();
+			return DBeaverSQLFormatterStep.create(project.files(configFiles).getFiles());
+		}
+	}
+
+	/** If the user hasn't specified the files yet, we'll assume he/she means all of the sql files. */
+	@Override
+	protected void setupTask(SpotlessTask task) {
+		if (target == null) {
+			target("**/*.sql");
+		}
+		super.setupTask(task);
+	}
+}
diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SqlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SqlExtensionTest.java
new file mode 100644
index 0000000000..bc18116fa3
--- /dev/null
+++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SqlExtensionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.gradle.spotless;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class SqlExtensionTest extends GradleIntegrationTest {
+
+	@Test
+	public void should_format_sql_with_default_configuration() throws IOException {
+		write("build.gradle",
+				"plugins {",
+				"    id 'com.diffplug.gradle.spotless'",
+				"}",
+				"spotless {",
+				"    sql {",
+				"       dbeaver()",
+				"    }",
+				"}");
+
+		File sqlFile = write("src/main/resources/aFolder/create.sql", getTestResource("sql/dbeaver/create.dirty"));
+
+		// Run
+		gradleRunner().withArguments("spotlessApply").build();
+
+		// Common checks
+		assertFileContent(getTestResource("sql/dbeaver/create.clean"), sqlFile);
+	}
+
+	@Test
+	public void should_format_sql_with_alternative_configuration() throws IOException {
+		write("build.gradle",
+				"plugins {",
+				"    id 'com.diffplug.gradle.spotless'",
+				"}",
+				"spotless {",
+				"    sql {",
+				"       dbeaver().configFile 'myConfig.properties'",
+				"    }",
+				"}");
+
+		File sqlFile = write("src/main/resources/aFolder/create.sql", getTestResource("sql/dbeaver/create.dirty"));
+		write("myConfig.properties", getTestResource("sql/dbeaver/myConfig.properties"));
+
+		// Run
+		gradleRunner().withArguments("spotlessApply").build();
+
+		// Common checks
+		assertFileContent(getTestResource("sql/dbeaver/create.clean.alternative"), sqlFile);
+	}
+
+}
diff --git a/plugin-gradle/src/test/resources/sql/dbeaver/create.clean b/plugin-gradle/src/test/resources/sql/dbeaver/create.clean
new file mode 100644
index 0000000000..38d9d560cb
--- /dev/null
+++ b/plugin-gradle/src/test/resources/sql/dbeaver/create.clean
@@ -0,0 +1,26 @@
+CREATE
+    TABLE
+        films(
+            code CHAR(5) CONSTRAINT firstkey PRIMARY KEY,
+            title VARCHAR(40) NOT NULL,
+            did INTEGER NOT NULL,
+            date_prod DATE,
+            kind VARCHAR(10),
+            len INTERVAL HOUR TO MINUTE
+        );
+
+CREATE
+    TABLE
+        distributors(
+            did INTEGER PRIMARY KEY DEFAULT nextval('serial'),
+            name VARCHAR(40) NOT NULL CHECK(
+                name <> ''
+            )
+        );
+
+-- Create a table with a 2-dimensional array:
+ CREATE
+    TABLE
+        array_int(
+            vector INT [][]
+        );
diff --git a/plugin-gradle/src/test/resources/sql/dbeaver/create.clean.alternative b/plugin-gradle/src/test/resources/sql/dbeaver/create.clean.alternative
new file mode 100644
index 0000000000..1cf1668ee4
--- /dev/null
+++ b/plugin-gradle/src/test/resources/sql/dbeaver/create.clean.alternative
@@ -0,0 +1,26 @@
+create
+		table
+				films(
+						code char(5) constraint firstkey primary key,
+						title varchar(40) not null,
+						did integer not null,
+						date_prod date,
+						kind varchar(10),
+						len interval hour to minute
+				);
+
+create
+		table
+				distributors(
+						did integer primary key default nextval('serial'),
+						name varchar(40) not null check(
+								name <> ''
+						)
+				);
+
+-- Create a table with a 2-dimensional array:
+ create
+		table
+				array_int(
+						vector int [][]
+				);
diff --git a/plugin-gradle/src/test/resources/sql/dbeaver/create.dirty b/plugin-gradle/src/test/resources/sql/dbeaver/create.dirty
new file mode 100644
index 0000000000..60b852ba64
--- /dev/null
+++ b/plugin-gradle/src/test/resources/sql/dbeaver/create.dirty
@@ -0,0 +1,25 @@
+CREATE TABLE films (
+    code        char(5) CONSTRAINT firstkey PRIMARY KEY,
+    title       varchar(40) NOT NULL,
+
+
+    did    integer NOT NULL,
+    date_prod   date,
+    kind        varchar(10),
+    len         interval hour to minute
+);
+
+CREATE TABLE distributors (
+
+
+
+
+     did    integer PRIMARY KEY DEFAULT nextval('serial'),
+     name   varchar(40) NOT NULL CHECK (name <> '')
+);
+
+-- Create a table with a 2-dimensional array:
+
+                                CREATE TABLE array_int (
+                vector  int[][]
+);
diff --git a/plugin-gradle/src/test/resources/sql/dbeaver/myConfig.properties b/plugin-gradle/src/test/resources/sql/dbeaver/myConfig.properties
new file mode 100644
index 0000000000..4d1ac4c935
--- /dev/null
+++ b/plugin-gradle/src/test/resources/sql/dbeaver/myConfig.properties
@@ -0,0 +1,4 @@
+sql.formatter.keyword.case=LOWER
+sql.formatter.statement.delimiter=;
+sql.formatter.indent.type=tab
+sql.formatter.indent.size=2
\ No newline at end of file
diff --git a/testlib/src/test/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStepTest.java
new file mode 100644
index 0000000000..5941cc84b1
--- /dev/null
+++ b/testlib/src/test/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStepTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 DiffPlug
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.diffplug.spotless.sql;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.diffplug.spotless.FormatterStep;
+import com.diffplug.spotless.ResourceHarness;
+import com.diffplug.spotless.SerializableEqualityTester;
+import com.diffplug.spotless.StepHarness;
+
+public class DBeaverSQLFormatterStepTest extends ResourceHarness {
+
+	@Test
+	public void behavior() throws Exception {
+		FormatterStep step = DBeaverSQLFormatterStep.create(Collections.emptySet());
+		StepHarness.forStep(step)
+				.testResource("sql/dbeaver/full.dirty", "sql/dbeaver/full.clean")
+				.testResource("sql/dbeaver/V1_initial.sql.dirty", "sql/dbeaver/V1_initial.sql.clean")
+				.testResource("sql/dbeaver/alter-table.dirty", "sql/dbeaver/alter-table.clean")
+				.testResource("sql/dbeaver/create.dirty", "sql/dbeaver/create.clean");
+	}
+
+	@Test
+	public void behaviorWithConfigFile() throws Exception {
+		FormatterStep step = DBeaverSQLFormatterStep.create(createTestFiles("sql/dbeaver/sqlConfig.properties"));
+		StepHarness.forStep(step)
+				.testResource("sql/dbeaver/create.dirty", "sql/dbeaver/create.clean");
+	}
+
+	@Test
+	public void behaviorWithAlternativeConfigFile() throws Exception {
+		FormatterStep step = DBeaverSQLFormatterStep.create(createTestFiles("sql/dbeaver/sqlConfig2.properties"));
+		StepHarness.forStep(step)
+				.testResource("sql/dbeaver/create.dirty", "sql/dbeaver/create.clean.alternative");
+	}
+
+	@Test
+	public void equality() throws Exception {
+		List<File> sqlConfig1 = createTestFiles("sql/dbeaver/sqlConfig.properties");
+		List<File> sqlConfig2 = createTestFiles("sql/dbeaver/sqlConfig2.properties");
+		new SerializableEqualityTester() {
+			List<File> settingsFiles;
+
+			@Override
+			protected void setupTest(API api) {
+				settingsFiles = sqlConfig1;
+				api.areDifferentThan();
+
+				settingsFiles = sqlConfig2;
+				api.areDifferentThan();
+			}
+
+			@Override
+			protected FormatterStep create() {
+				return DBeaverSQLFormatterStep.create(settingsFiles);
+			}
+		}.testEquals();
+	}
+
+}
diff --git a/testlib/src/test/resources/sql/dbeaver/V1_initial.sql.clean b/testlib/src/test/resources/sql/dbeaver/V1_initial.sql.clean
new file mode 100644
index 0000000000..20d18b61ef
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/V1_initial.sql.clean
@@ -0,0 +1,112 @@
+--- Account management
+ CREATE
+    TABLE
+        account(
+            id serial PRIMARY KEY,
+            username VARCHAR(60) NOT NULL UNIQUE,
+            email VARCHAR(513) NOT NULL UNIQUE,
+            name VARCHAR(255),
+            --NULLABLE
+ created_at TIMESTAMP NOT NULL,
+            created_ip inet NOT NULL,
+            updated_at TIMESTAMP NOT NULL,
+            updated_ip inet NOT NULL,
+            last_seen_at TIMESTAMP NOT NULL,
+            last_seen_ip inet NOT NULL,
+            last_emailed_at TIMESTAMP NOT NULL
+        );
+
+CREATE
+    TABLE
+        loginlink(
+            code CHAR(44) PRIMARY KEY,
+            created_at TIMESTAMP NOT NULL,
+            expires_at TIMESTAMP NOT NULL,
+            requestor_ip inet NOT NULL,
+            account_id INT NOT NULL REFERENCES account(id)
+        );
+
+CREATE
+    TABLE
+        confirmaccountlink(
+            code CHAR(44) PRIMARY KEY,
+            created_at TIMESTAMP NOT NULL,
+            expires_at TIMESTAMP NOT NULL,
+            requestor_ip inet NOT NULL,
+            username VARCHAR(60) NOT NULL,
+            email VARCHAR(513) NOT NULL
+        );
+
+--- Takes
+ CREATE
+    TABLE
+        takerevision(
+            id serial PRIMARY KEY,
+            parent_id INT REFERENCES takerevision(id),
+            --NULLABLE (null for root)
+ created_at TIMESTAMP NOT NULL,
+            created_ip inet NOT NULL,
+            title VARCHAR(255) NOT NULL,
+            blocks jsonb NOT NULL
+        );
+
+CREATE
+    TABLE
+        takedraft(
+            id serial PRIMARY KEY,
+            user_id INT NOT NULL REFERENCES account(id),
+            last_revision INT NOT NULL REFERENCES takerevision(id)
+        );
+
+CREATE
+    TABLE
+        takepublished(
+            id serial PRIMARY KEY,
+            user_id INT NOT NULL REFERENCES account(id),
+            title VARCHAR(255) NOT NULL,
+            title_slug VARCHAR(255) NOT NULL,
+            blocks jsonb NOT NULL,
+            published_at TIMESTAMP NOT NULL,
+            published_ip inet NOT NULL,
+            deleted_at TIMESTAMP,
+            --NULLABLE
+ deleted_ip inet,
+            --NULLABLE
+ count_view INT NOT NULL DEFAULT 0,
+            count_like INT NOT NULL DEFAULT 0,
+            count_bookmark INT NOT NULL DEFAULT 0,
+            count_spam INT NOT NULL DEFAULT 0,
+            count_illegal INT NOT NULL DEFAULT 0
+        );
+
+-- /user/title must be unique, and fast to lookup
+ CREATE
+    UNIQUE INDEX takepublished_title_user ON
+    takepublished(
+        title_slug,
+        user_id
+    );
+
+CREATE
+    TYPE reaction AS ENUM(
+        'like',
+        'bookmark',
+        'spam',
+        'illegal'
+    );
+
+CREATE
+    TABLE
+        takereaction(
+            take_id INT NOT NULL REFERENCES takepublished(id),
+            user_id INT NOT NULL REFERENCES account(id),
+            kind reaction NOT NULL,
+            PRIMARY KEY(
+                take_id,
+                user_id,
+                kind
+            ),
+            --user can only have one of each kind of reaction per take
+ reacted_at TIMESTAMP NOT NULL,
+            reacted_ip inet NOT NULL
+        );
\ No newline at end of file
diff --git a/testlib/src/test/resources/sql/dbeaver/V1_initial.sql.dirty b/testlib/src/test/resources/sql/dbeaver/V1_initial.sql.dirty
new file mode 100644
index 0000000000..7626d8d0d6
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/V1_initial.sql.dirty
@@ -0,0 +1,76 @@
+--- Account management
+CREATE TABLE account (
+    id              serial PRIMARY KEY,
+    username        varchar(60) NOT NULL UNIQUE,
+    email           varchar(513) NOT NULL UNIQUE,
+    name            varchar(255), --NULLABLE
+    created_at      timestamp NOT NULL,
+    created_ip      inet NOT NULL,
+    updated_at      timestamp NOT NULL,
+    updated_ip      inet NOT NULL,
+    last_seen_at    timestamp NOT NULL,
+    last_seen_ip    inet NOT NULL,
+    last_emailed_at timestamp NOT NULL
+);
+
+CREATE TABLE loginlink (
+    code            char(44) PRIMARY KEY,
+    created_at      timestamp NOT NULL,
+    expires_at      timestamp NOT NULL,
+    requestor_ip    inet NOT NULL,
+    account_id      int NOT NULL REFERENCES account (id)
+);
+
+CREATE TABLE confirmaccountlink (
+    code            char(44) PRIMARY KEY,
+    created_at      timestamp NOT NULL,
+    expires_at      timestamp NOT NULL,
+    requestor_ip    inet NOT NULL,
+    username        varchar(60) NOT NULL,
+    email           varchar(513) NOT NULL
+);
+
+--- Takes
+CREATE TABLE takerevision (
+    id          serial PRIMARY KEY,
+    parent_id   int REFERENCES takerevision (id), --NULLABLE (null for root)
+    created_at  timestamp NOT NULL,
+    created_ip  inet NOT NULL,
+    title       varchar(255) NOT NULL,
+    blocks      jsonb NOT NULL
+);
+
+CREATE TABLE takedraft (
+    id              serial PRIMARY KEY,
+    user_id         int NOT NULL REFERENCES account (id),
+    last_revision   int NOT NULL REFERENCES takerevision (id)
+);
+
+CREATE TABLE takepublished (
+    id              serial PRIMARY KEY,
+    user_id         int NOT NULL REFERENCES account (id),
+    title           varchar(255) NOT NULL,
+    title_slug      varchar(255) NOT NULL,
+    blocks          jsonb NOT NULL,
+    published_at    timestamp NOT NULL,
+    published_ip    inet NOT NULL,
+    deleted_at      timestamp, --NULLABLE
+    deleted_ip      inet, --NULLABLE
+    count_view      int NOT NULL DEFAULT 0,
+    count_like      int NOT NULL DEFAULT 0,
+    count_bookmark  int NOT NULL DEFAULT 0,
+    count_spam      int NOT NULL DEFAULT 0,
+    count_illegal   int NOT NULL DEFAULT 0
+);
+-- /user/title must be unique, and fast to lookup
+CREATE UNIQUE INDEX takepublished_title_user ON takepublished (title_slug, user_id);
+
+CREATE TYPE reaction AS ENUM ('like', 'bookmark', 'spam', 'illegal');
+CREATE TABLE takereaction (
+    take_id         int NOT NULL REFERENCES takepublished (id),
+    user_id         int NOT NULL REFERENCES account (id),
+    kind            reaction NOT NULL,
+    PRIMARY KEY (take_id, user_id, kind), --user can only have one of each kind of reaction per take
+    reacted_at      timestamp NOT NULL,
+    reacted_ip      inet NOT NULL
+);
\ No newline at end of file
diff --git a/testlib/src/test/resources/sql/dbeaver/alter-table.clean b/testlib/src/test/resources/sql/dbeaver/alter-table.clean
new file mode 100644
index 0000000000..7b12a355b3
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/alter-table.clean
@@ -0,0 +1,7 @@
+ALTER TABLE
+    userinfo ALTER COLUMN id DROP
+        DEFAULT;
+
+ALTER TABLE
+    userinfo DROP
+        CONSTRAINT userinfo_pkey CASCADE;
\ No newline at end of file
diff --git a/testlib/src/test/resources/sql/dbeaver/alter-table.dirty b/testlib/src/test/resources/sql/dbeaver/alter-table.dirty
new file mode 100644
index 0000000000..1a8324e9f9
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/alter-table.dirty
@@ -0,0 +1,2 @@
+ALTER TABLE userinfo ALTER COLUMN id drop DEFAULT;
+ALTER TABLE userinfo drop constraint userinfo_pkey cascade;
\ No newline at end of file
diff --git a/testlib/src/test/resources/sql/dbeaver/create.clean b/testlib/src/test/resources/sql/dbeaver/create.clean
new file mode 100644
index 0000000000..38d9d560cb
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/create.clean
@@ -0,0 +1,26 @@
+CREATE
+    TABLE
+        films(
+            code CHAR(5) CONSTRAINT firstkey PRIMARY KEY,
+            title VARCHAR(40) NOT NULL,
+            did INTEGER NOT NULL,
+            date_prod DATE,
+            kind VARCHAR(10),
+            len INTERVAL HOUR TO MINUTE
+        );
+
+CREATE
+    TABLE
+        distributors(
+            did INTEGER PRIMARY KEY DEFAULT nextval('serial'),
+            name VARCHAR(40) NOT NULL CHECK(
+                name <> ''
+            )
+        );
+
+-- Create a table with a 2-dimensional array:
+ CREATE
+    TABLE
+        array_int(
+            vector INT [][]
+        );
diff --git a/testlib/src/test/resources/sql/dbeaver/create.clean.alternative b/testlib/src/test/resources/sql/dbeaver/create.clean.alternative
new file mode 100644
index 0000000000..1cf1668ee4
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/create.clean.alternative
@@ -0,0 +1,26 @@
+create
+		table
+				films(
+						code char(5) constraint firstkey primary key,
+						title varchar(40) not null,
+						did integer not null,
+						date_prod date,
+						kind varchar(10),
+						len interval hour to minute
+				);
+
+create
+		table
+				distributors(
+						did integer primary key default nextval('serial'),
+						name varchar(40) not null check(
+								name <> ''
+						)
+				);
+
+-- Create a table with a 2-dimensional array:
+ create
+		table
+				array_int(
+						vector int [][]
+				);
diff --git a/testlib/src/test/resources/sql/dbeaver/create.dirty b/testlib/src/test/resources/sql/dbeaver/create.dirty
new file mode 100644
index 0000000000..60b852ba64
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/create.dirty
@@ -0,0 +1,25 @@
+CREATE TABLE films (
+    code        char(5) CONSTRAINT firstkey PRIMARY KEY,
+    title       varchar(40) NOT NULL,
+
+
+    did    integer NOT NULL,
+    date_prod   date,
+    kind        varchar(10),
+    len         interval hour to minute
+);
+
+CREATE TABLE distributors (
+
+
+
+
+     did    integer PRIMARY KEY DEFAULT nextval('serial'),
+     name   varchar(40) NOT NULL CHECK (name <> '')
+);
+
+-- Create a table with a 2-dimensional array:
+
+                                CREATE TABLE array_int (
+                vector  int[][]
+);
diff --git a/testlib/src/test/resources/sql/dbeaver/full.clean b/testlib/src/test/resources/sql/dbeaver/full.clean
new file mode 100644
index 0000000000..bd53dad562
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/full.clean
@@ -0,0 +1,59 @@
+SELECT
+    *
+FROM
+    TABLE1 t
+WHERE
+    a > 100
+    AND b BETWEEN 12 AND 45;
+
+SELECT
+    t.*,
+    j1.x,
+    j2.y
+FROM
+    TABLE1 t
+JOIN JT1 j1 ON
+    j1.a = t.a
+LEFT OUTER JOIN JT2 j2 ON
+    j2.a = t.a
+    AND j2.b = j1.b
+WHERE
+    t.xxx NOT NULL;
+
+DELETE
+FROM
+    TABLE1
+WHERE
+    a = 1;
+
+UPDATE
+    TABLE1
+SET
+    a = 2
+WHERE
+    a = 1;
+
+SELECT
+    table1.id,
+    table2.number,
+    SUM( table1.amount )
+FROM
+    table1
+INNER JOIN table2 ON
+    table.id = table2.table1_id
+WHERE
+    table1.id IN(
+        SELECT
+            table1_id
+        FROM
+            table3
+        WHERE
+            table3.name = 'Foo Bar'
+            AND table3.type = 'unknown_type'
+            AND table3.param =:aParam
+    )
+GROUP BY
+    table1.id,
+    table2.number
+ORDER BY
+    table1.id;
\ No newline at end of file
diff --git a/testlib/src/test/resources/sql/dbeaver/full.dirty b/testlib/src/test/resources/sql/dbeaver/full.dirty
new file mode 100644
index 0000000000..03b722747a
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/full.dirty
@@ -0,0 +1,13 @@
+Select * From TABLE1 t Where a > 100 AND b between 12 AND 45;
+
+SELECT t.*,j1.x,j2.y FROM TABLE1 t JOIN JT1 j1 ON j1.a = t.a
+LEFT OUTER JOIN JT2 j2 ON j2.a=t.a AND j2.b=j1.b
+WHERE t.xxx NOT NULL;
+
+DELETE FROM TABLE1 WHERE a=1;
+
+UPDATE TABLE1 SET a=2 WHERE a=1;
+
+SELECT table1.id, table2.number, SUM(table1.amount) FROM table1 INNER JOIN table2 ON table.id = table2.table1_id
+WHERE table1.id IN (SELECT table1_id FROM table3 WHERE table3.name = 'Foo Bar' and table3.type = 'unknown_type' And        table3.param = :aParam)
+GROUP BY table1.id, table2.number ORDER BY table1.id;
\ No newline at end of file
diff --git a/testlib/src/test/resources/sql/dbeaver/sqlConfig.properties b/testlib/src/test/resources/sql/dbeaver/sqlConfig.properties
new file mode 100644
index 0000000000..855fdb5c27
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/sqlConfig.properties
@@ -0,0 +1,4 @@
+sql.formatter.keyword.case=UPPER
+sql.formatter.statement.delimiter=;
+sql.formatter.indent.type=space
+sql.formatter.indent.size=4
diff --git a/testlib/src/test/resources/sql/dbeaver/sqlConfig2.properties b/testlib/src/test/resources/sql/dbeaver/sqlConfig2.properties
new file mode 100644
index 0000000000..4d1ac4c935
--- /dev/null
+++ b/testlib/src/test/resources/sql/dbeaver/sqlConfig2.properties
@@ -0,0 +1,4 @@
+sql.formatter.keyword.case=LOWER
+sql.formatter.statement.delimiter=;
+sql.formatter.indent.type=tab
+sql.formatter.indent.size=2
\ No newline at end of file