Skip to content

Commit

Permalink
net-sf-ucanaccess-fork: Add helper class Sql
Browse files Browse the repository at this point in the history
  • Loading branch information
spannm committed Nov 27, 2023
1 parent ccca5d1 commit 388a801
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
115 changes: 115 additions & 0 deletions src/main/java/net/ucanaccess/util/Sql.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package net.ucanaccess.util;

import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* <p>
* An immutable sql statement string created from multiple tokens
* in order to write inline sql statements in an easy-to-read fashion
* spread out over multiple lines of code.
* </p>
*
* <p>
* The class implements {@link CharSequence} and thus can be used as a drop-in
* alternative wherever API supports {@code CharSequence} rather than {@code String}.
* </p>
*
* Please note that the validity of the statement is never checked,
* and that {@code null} or empty inputs are permitted (no run-time exceptions).<br>
*
* The input of multiple tokens is formatted into a single String by
* removing leading and trailing whitespace and concatenating
* non-empty tokens by a single space character.
* Further, any trailing semicolons are removed from the resulting sql string.
*
* <p>Example:</p>
*
* <pre>
* String tblName = "table";
* Sql.of("SELECT COUNT(*)",
* "FROM", tblName,
* " WHERE cond1 = :cond1",
* " AND cond2 = :cond2");
* </pre>
*
* @author Markus Spann
* @since v5.1.0
*/
public final class Sql implements CharSequence {

private static final Sql EMPTY_SQL = new Sql("");

/** The internal sql string. Cannot be null. */
private final String str;

private Sql(String sql) {
str = sql;
}

public static Sql of(CharSequence... _tokens) {
return _tokens == null ? EMPTY_SQL : of(Arrays.asList(_tokens));
}

public static Sql of(Iterable<? extends CharSequence> _tokens) {
return _tokens == null ? EMPTY_SQL : new Sql(format(_tokens));
}

/**
* Formats an sql statement from multiple tokens.<br>
* Leading and trailing whitespace is removed from each token and empty tokens ignored.<br>
* The tokens are joined using a single blank character to create the sql string.<br>
* Finally, any trailing semicolons are removed from the resulting sql.
*
* @param _tokens collection of tokens
* @return formatted sql string
*/
static String format(Iterable<? extends CharSequence> _tokens) {
String sql = StreamSupport.stream(Objects.requireNonNull(_tokens).spliterator(), false)
.filter(Objects::nonNull)
.map(CharSequence::toString)
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(" "));
while (sql.endsWith(";")) {
sql = sql.substring(0, sql.length() - 1);
}
return sql;
}

@Override
public int length() {
return str.length();
}

@Override
public char charAt(int _index) {
return str.charAt(_index);
}

@Override
public CharSequence subSequence(int _start, int _end) {
return str.subSequence(_start, _end);
}

@Override
public int hashCode() {
return str.hashCode();
}

@Override
public boolean equals(Object _obj) {
if (_obj == null || getClass() != _obj.getClass()) {
return false;
}
return str.equals(((Sql) _obj).str);
}

@Override
public String toString() {
return str;
}

}
69 changes: 69 additions & 0 deletions src/test/java/net/ucanaccess/util/SqlTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.ucanaccess.util;

import static org.assertj.core.api.Assertions.assertThat;

import net.ucanaccess.test.AbstractBaseTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

class SqlTest extends AbstractBaseTest {

@Test
void testEmptyInput() {
assertThat(Sql.of((CharSequence[]) null).toString()).isBlank();
assertThat(Sql.of((CharSequence) null).toString()).isBlank();
assertThat(Sql.of((Collection<CharSequence>) null).toString()).isBlank();
assertThat(Sql.of(new CharSequence[0]).toString()).isBlank();
assertThat(Sql.of(new ArrayList<>()).toString()).isBlank();
assertThat(Sql.of().toString()).isBlank();
}

@Test
void testMultipleStrings() {
assertThat(Sql.of(List.of("A", "B", "C")))
.hasToString("A B C");
assertThat(Sql.of("A", "B", "C"))
.hasToString("A B C");
}

@Test
void testMultipleTokens() {
assertThat(Sql.of("SELECT COUNT(*) FROM table",
"WHERE cond1 = :cond1; "))
.hasToString("SELECT COUNT(*) FROM table WHERE cond1 = :cond1");
}

@ParameterizedTest
@ValueSource(strings = {" ", "\n", "\t", ";"})
@NullAndEmptySource
void testStrip(String str) {
Sql sql = Sql.of(str);
assertThat(sql).hasToString("");
}

@Test
void testCharSequenceMethods() {
Sql sql = Sql.of("SELECT * FROM table");
assertThat(sql).hasSize(19);
assertThat(sql.charAt(0)).isEqualTo('S');
assertThat(sql.subSequence(0, 3)).isEqualTo("SEL");
}

@Test
void testEqualsAndHashCode() {
Sql sql1 = Sql.of("DROP TABLE table;");
Sql sql2 = Sql.of(sql1);
assertThat(sql1).isNotNull()
.isEqualTo(sql2)
.hasSameHashCodeAs(sql2)
.isNotEqualTo(Sql.of());
assertThat(sql1.toString()).isNotBlank();
}

}

0 comments on commit 388a801

Please sign in to comment.