Skip to content

Instantly share code, notes, and snippets.

@MariusVolkhart
Last active June 29, 2022 23:53
Show Gist options
  • Save MariusVolkhart/3e2374b5fdbefad17d56 to your computer and use it in GitHub Desktop.
Save MariusVolkhart/3e2374b5fdbefad17d56 to your computer and use it in GitHub Desktop.
A small utility to help you construct SQL query where clauses
/*
* Copyright 2014-2015 Marius Volkhart
*
* 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.
*
* Original at https://gist.github.com/MariusVolkhart/3e2374b5fdbefad17d56
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Representation of a constructed SQL query where clause
*/
public final class WhereClause {
public final String selection;
public final String[] selectionArgs;
private WhereClause(String selection, String[] selectionArgs) {
this.selection = selection;
this.selectionArgs = selectionArgs;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WhereClause that = (WhereClause) o;
return !(selection != null ? !selection.equals(that.selection) : that.selection != null)
&& Arrays.equals(selectionArgs, that.selectionArgs);
}
@Override
public int hashCode() {
int result = selection != null ? selection.hashCode() : 0;
result = 31 * result + (selectionArgs != null ? Arrays.hashCode(selectionArgs) : 0);
return result;
}
@Override
public String toString() {
return WhereClause.class.getSimpleName() + "{" +
"selection='" + selection + '\'' +
", selectionArgs=" + Arrays.toString(selectionArgs) +
'}';
}
/**
* Common SQL operators. For convenience, consider implementing this interface if creating a
* lot of queries.
*/
public interface Operator {
public String EQUALS = "=";
public String NOT_EQUALS = "!=";
public String GREATER_THAN = ">";
public String LESS_THAN = "<";
public String GREATER_THAN_EQUALS = ">=";
public String LESS_THAN_EQUALS = "<=";
public String LIKE = " LIKE ";
public String IS = " IS ";
public String IS_NOT = " IS NOT ";
}
public static class Builder {
private static final String AND = " AND ";
private static final String OR = " OR ";
private final StringBuilder stringBuilder = new StringBuilder();
private final List<String> args = new ArrayList<>();
private String nextOperator;
public Builder where(String column, String operand, String arg) {
setNextOperatorIfNeeded();
stringBuilder.append(column).append(operand).append('?');
args.add(arg);
nextOperator = null;
return this;
}
public Builder where(String column, String operand, boolean arg) {
return where(column, operand, arg ? "1" : "0");
}
public Builder where(String column, String operand, int arg) {
return where(column, operand, Integer.toString(arg));
}
public Builder where(String column, String operand, long arg) {
return where(column, operand, Long.toString(arg));
}
public Builder where(String column, String operand, float arg) {
return where(column, operand, Float.toString(arg));
}
public Builder where(String column, String operand, double arg) {
return where(column, operand, Double.toString(arg));
}
public Builder where(WhereClause whereClause) {
if (whereClause.selectionArgs.length > 0) {
setNextOperatorIfNeeded();
stringBuilder.append('(').append(whereClause.selection).append(')');
args.addAll(Arrays.asList(whereClause.selectionArgs));
}
nextOperator = null;
return this;
}
/**
* Joins two statements with an {@code AND} operator. This is also the implicit behavior.
*/
public Builder and() {
nextOperator = AND;
return this;
}
public Builder or() {
nextOperator = OR;
return this;
}
/**
* Ensures that multiple {@code where} statements can be joined safely. Defaults to using
* {@code AND}.
*/
private void setNextOperatorIfNeeded() {
if (stringBuilder.length() == 0) {
return;
}
if (nextOperator == null) {
stringBuilder.append(AND);
} else {
stringBuilder.append(nextOperator);
nextOperator = null;
}
}
public WhereClause build() {
String[] arguments = args.toArray(new String[args.size()]);
return new WhereClause(stringBuilder.toString(), arguments);
}
}
}
/*
* Copyright 2014-2015 Marius Volkhart
*
* 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.
*
* Original at https://gist.github.com/MariusVolkhart/3e2374b5fdbefad17d56
*/
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class WhereClauseTest {
@Test
public void operator_values_are_correct() {
assertThat(WhereClause.Operator.EQUALS).isEqualTo("=");
assertThat(WhereClause.Operator.NOT_EQUALS).isEqualTo("!=");
assertThat(WhereClause.Operator.GREATER_THAN).isEqualTo(">");
assertThat(WhereClause.Operator.LESS_THAN).isEqualTo("<");
assertThat(WhereClause.Operator.GREATER_THAN_EQUALS).isEqualTo(">=");
assertThat(WhereClause.Operator.LESS_THAN_EQUALS).isEqualTo("<=");
assertThat(WhereClause.Operator.LIKE).isEqualTo(" LIKE ");
assertThat(WhereClause.Operator.IS).isEqualTo(" IS ");
assertThat(WhereClause.Operator.IS_NOT).isEqualTo(" IS NOT ");
}
@Test
public void where_boolean_is_true() {
WhereClause whereClause = new WhereClause.Builder()
.where("isDirty", WhereClause.Operator.EQUALS, true)
.build();
assertThat(whereClause.selection).isEqualTo("isDirty=?");
assertThat(whereClause.selectionArgs).containsExactly("1");
}
@Test
public void where_boolean_is_false() {
WhereClause whereClause = new WhereClause.Builder()
.where("isDirty", WhereClause.Operator.EQUALS, false)
.build();
assertThat(whereClause.selection).isEqualTo("isDirty=?");
assertThat(whereClause.selectionArgs).containsExactly("0");
}
@Test
public void where_integer() {
WhereClause whereClause = new WhereClause.Builder()
.where("age", WhereClause.Operator.EQUALS, 23)
.build();
assertThat(whereClause.selection).isEqualTo("age=?");
assertThat(whereClause.selectionArgs).containsExactly("23");
}
@Test
public void where_long() {
WhereClause whereClause = new WhereClause.Builder()
.where("id", WhereClause.Operator.EQUALS, Long.MAX_VALUE)
.build();
assertThat(whereClause.selection).isEqualTo("id=?");
assertThat(whereClause.selectionArgs).containsExactly(Long.toString(Long.MAX_VALUE));
}
@Test
public void where_float() {
WhereClause whereClause = new WhereClause.Builder()
.where("price", WhereClause.Operator.EQUALS, 9.99f)
.build();
assertThat(whereClause.selection).isEqualTo("price=?");
assertThat(whereClause.selectionArgs).containsExactly("9.99");
}
@Test
public void where_double() {
WhereClause whereClause = new WhereClause.Builder()
.where("price", WhereClause.Operator.EQUALS, 9.99)
.build();
assertThat(whereClause.selection).isEqualTo("price=?");
assertThat(whereClause.selectionArgs).containsExactly("9.99");
}
@Test
public void where_string() {
WhereClause whereClause = new WhereClause.Builder()
.where("name", WhereClause.Operator.EQUALS, "John")
.build();
assertThat(whereClause.selection).isEqualTo("name=?");
assertThat(whereClause.selectionArgs).containsExactly("John");
}
@Test
public void and() {
WhereClause whereClause = new WhereClause.Builder()
.where("first_name", WhereClause.Operator.EQUALS, "John")
.and()
.where("last_name", WhereClause.Operator.EQUALS, "Doe")
.build();
assertThat(whereClause.selection).isEqualTo("first_name=? AND last_name=?");
assertThat(whereClause.selectionArgs).containsExactly("John", "Doe");
}
@Test
public void or() {
WhereClause whereClause = new WhereClause.Builder()
.where("first_name", WhereClause.Operator.EQUALS, "John")
.or()
.where("first_name", WhereClause.Operator.EQUALS, "Jane")
.build();
assertThat(whereClause.selection).isEqualTo("first_name=? OR first_name=?");
assertThat(whereClause.selectionArgs).containsExactly("John", "Jane");
}
@Test
public void implicit_and_on_multiple_wheres() {
WhereClause whereClause = new WhereClause.Builder()
.where("first_name", WhereClause.Operator.EQUALS, "John")
.where("last_name", WhereClause.Operator.EQUALS, "Doe")
.build();
assertThat(whereClause.selection).isEqualTo("first_name=? AND last_name=?");
assertThat(whereClause.selectionArgs).containsExactly("John", "Doe");
}
@Test
public void nested_whereClause() {
WhereClause john = new WhereClause.Builder()
.where("first_name", WhereClause.Operator.EQUALS, "John")
.where("last_name", WhereClause.Operator.EQUALS, "Doe")
.build();
WhereClause jane = new WhereClause.Builder()
.where("first_name", WhereClause.Operator.EQUALS, "Jane")
.where("last_name", WhereClause.Operator.EQUALS, "Doe")
.build();
WhereClause whereClause = new WhereClause.Builder()
.where(john)
.or()
.where(jane)
.build();
assertThat(whereClause.selection).isEqualTo("(first_name=? AND last_name=?) OR (first_name=? AND last_name=?)");
assertThat(whereClause.selectionArgs).containsExactly("John", "Doe", "Jane", "Doe");
}
@Test
public void toStringUsesCorrectClassName() {
assertThat(new WhereClause.Builder().build().toString()).startsWith("WhereClause");
}
}
@fernando1983github
Copy link

Just tweaked it a bit, hope you don't mind
Changed selectionArgs to map<String, Object> to allow to pass Object instead of only just strings.
added IN operator

package com.schneiderelectric.volumetria.util;

/*
 * Copyright 2014-2015 Marius Volkhart
 *
 * 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.
 *
 * Original at https://gist.github.com/MariusVolkhart/3e2374b5fdbefad17d56
 */

import java.util.HashMap;
import java.util.Map;

/**
 * Representation of a constructed SQL query where clause
 */
public final class WhereClause {

    public final String selection;
    public final Map<String, Object> selectionArgs;

    private WhereClause(String selection, Map<String, Object> selectionArgs) {
        this.selection = selection;
        this.selectionArgs = selectionArgs;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        WhereClause that = (WhereClause) o;

        return !(selection != null ? !selection.equals(that.selection) : that.selection != null)
                && selectionArgs.equals(that.selectionArgs);
    }

    @Override
    public int hashCode() {
        int result = selection != null ? selection.hashCode() : 0;
        result = 31 * result + (selectionArgs != null ? selectionArgs.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return WhereClause.class.getSimpleName() + "{" +
                "selection='" + selection + '\'' +
                ", selectionArgs=" + selectionArgs +
                '}';
    }

    /**
     * Common SQL operators. For convenience, consider implementing this interface if creating a
     * lot of queries.
     */
    public interface Operator {
        public String EQUALS = "=";
        public String NOT_EQUALS = "!=";
        public String GREATER_THAN = ">";
        public String LESS_THAN = "<";
        public String GREATER_THAN_EQUALS = ">=";
        public String LESS_THAN_EQUALS = "<=";
        public String LIKE = " LIKE ";
        public String IS = " IS ";
        public String IS_NOT = " IS NOT ";
        public String IN = " IN ";
    }

    public static class Builder {

        private static final String AND = " AND ";
        private static final String OR = " OR ";

        private final StringBuilder stringBuilder = new StringBuilder();
        private final Map<String, Object> args = new HashMap<String, Object>();
        private String nextOperator;

        public Builder where(String column, String operand, Object arg) {
        	return where(column, null, operand, arg);
        }
        
        public Builder where(String column, String expression, String operand, Object arg) {
            setNextOperatorIfNeeded();
            if(arg instanceof Boolean) {
            	arg = (Boolean) arg ? "1" : "0";
            }
            
            String consecutiveColumn = "";
            consecutiveColumn = column + "_" + args.size();
            args.put(consecutiveColumn, arg);
            
            if(operand.equalsIgnoreCase(Operator.IN)) {
            	stringBuilder.append(column).append(operand).append("(:" + consecutiveColumn + ")");
            } else {
            	stringBuilder.append(expression == null ? column : expression).append(operand).append(":" + consecutiveColumn);
            }
            
            
            
            nextOperator = null;

            return this;
        }

        public Builder where(WhereClause whereClause) {

            if (whereClause.selectionArgs.size() > 0) {
                setNextOperatorIfNeeded();
                stringBuilder.append('(').append(whereClause.selection).append(')');
                args.putAll(whereClause.selectionArgs);
            }

            nextOperator = null;
            return this;
        }

        /**
         * Joins two statements with an {@code AND} operator. This is also the implicit behavior.
         */
        public Builder and() {
            nextOperator = AND;
            return this;
        }

        public Builder or() {
            nextOperator = OR;
            return this;
        }

        /**
         * Ensures that multiple {@code where} statements can be joined safely. Defaults to using
         * {@code AND}.
         */
        private void setNextOperatorIfNeeded() {
            if (stringBuilder.length() == 0) {
                return;
            }

            if (nextOperator == null) {
                stringBuilder.append(AND);
            } else {
                stringBuilder.append(nextOperator);
                nextOperator = null;
            }
        }

        public WhereClause build() {
        	Map<String, Object> arguments = args;
            return new WhereClause(stringBuilder.toString(), arguments);
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment