[jOOQ/jOOQ#2968] Add support for int4range

This commit is contained in:
Lukas Eder 2022-03-01 14:53:44 +01:00
parent 8aa2784438
commit a8e97eff95
10 changed files with 763 additions and 0 deletions

View File

@ -1529,6 +1529,19 @@ public abstract class AbstractDatabase implements Database {
.withIncludeTypes("_cidr")
.withPriority(Integer.MIN_VALUE)
);
getConfiguredForcedTypes().add(new ForcedType()
.withUserType("org.jooq.postgres.extensions.types.IntegerRange")
.withBinding("org.jooq.postgres.extensions.bindings.IntegerRangeBinding")
.withIncludeTypes("int4range")
.withPriority(Integer.MIN_VALUE)
);
getConfiguredForcedTypes().add(new ForcedType()
.withUserType("org.jooq.postgres.extensions.types.IntegerRange[]")
.withBinding("org.jooq.postgres.extensions.bindings.IntegerRangeArrayBinding")
.withIncludeTypes("_int4range")
.withPriority(Integer.MIN_VALUE)
);
}
catch (ClassNotFoundException ignore) {
log.debug("Built in data types", "org.jooq.postgres.extensions.types.Hstore not found on classpath, ignoring built in data type extensions");

View File

@ -0,0 +1,79 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.bindings;
import java.sql.SQLException;
import java.sql.Types;
import org.jooq.BindingGetResultSetContext;
import org.jooq.BindingGetStatementContext;
import org.jooq.BindingRegisterContext;
import org.jooq.BindingSetStatementContext;
import org.jooq.postgres.extensions.types.AbstractRange;
/**
* A binding for the PostgreSQL <code>range</code> data types.
*
* @author Lukas Eder
*/
abstract class AbstractRangeBinding<U extends AbstractRange<?>> extends AbstractPostgresBinding<Object, U> {
// TODO: This looks just like the AbstractInetBinding. Perhaps rename?
@Override
public void register(final BindingRegisterContext<U> ctx) throws SQLException {
ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR);
}
@Override
public void set(final BindingSetStatementContext<U> ctx) throws SQLException {
Object value = ctx.convert(converter()).value();
ctx.statement().setString(ctx.index(), value == null ? null : "" + value);
}
@Override
public void get(final BindingGetResultSetContext<U> ctx) throws SQLException {
ctx.convert(converter()).value(ctx.resultSet().getString(ctx.index()));
}
@Override
public void get(final BindingGetStatementContext<U> ctx) throws SQLException {
ctx.convert(converter()).value(ctx.statement().getString(ctx.index()));
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.bindings;
import org.jooq.Converter;
import org.jooq.postgres.extensions.converters.IntegerRangeConverter;
import org.jooq.postgres.extensions.types.IntegerRange;
/**
* A binding for the PostgreSQL <code>inet[]</code> data type.
*
* @author Lukas Eder
*/
public class IntegerRangeArrayBinding extends AbstractPostgresArrayBinding<IntegerRange> {
private static final Converter<Object[], IntegerRange[]> CONVERTER = new IntegerRangeConverter().forArrays();
@Override
public Converter<Object[], IntegerRange[]> converter() {
return CONVERTER;
}
@Override
protected String castType() {
return "int4range[]";
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.bindings;
import org.jooq.Converter;
import org.jooq.postgres.extensions.converters.IntegerRangeConverter;
import org.jooq.postgres.extensions.types.IntegerRange;
/**
* A binding for the PostgreSQL <code>int4range</code> data type.
*
* @author Lukas Eder
*/
public class IntegerRangeBinding extends AbstractRangeBinding<IntegerRange> {
private static final Converter<Object, IntegerRange> CONVERTER = new IntegerRangeConverter();
@Override
public Converter<Object, IntegerRange> converter() {
return CONVERTER;
}
@Override
protected String castType() {
return "int4range";
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.converters;
import static org.jooq.tools.StringUtils.isBlank;
import org.jooq.impl.AbstractConverter;
import org.jooq.postgres.extensions.types.AbstractRange;
/**
* A converter for {@link AbstractRange} types.
*
* @author Lukas Eder
*/
abstract class AbstractRangeConverter<X, U extends AbstractRange<X>> extends AbstractConverter<Object, U> {
public AbstractRangeConverter(Class<U> toType) {
super(Object.class, toType);
}
abstract U construct(String lower, boolean lowerIncluding, String upper, boolean upperIncluding);
abstract U empty();
@Override
public U from(Object t) {
if (t == null)
return null;
String s = t.toString();
if ("empty".equals(s))
return empty();
String[] a = s.split(",");
String s0 = a[0];
String s1 = a[1];
String lower = s0.substring(1);
boolean lowerIncluding = s0.charAt(0) == '[';
int l = s1.length() - 1;
String upper = s1.substring(0, l);
boolean upperIncluding = s1.charAt(l) == ']';
return construct(
isBlank(lower) ? null : lower,
lowerIncluding,
isBlank(upper) ? null : upper,
upperIncluding
);
}
@Override
public Object to(U u) {
if (u == null)
return null;
else
return u.toString();
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.converters;
import static org.jooq.postgres.extensions.types.IntegerRange.integerRange;
import org.jooq.postgres.extensions.types.IntegerRange;
/**
* A converter for {@link IntegerRange}.
*
* @author Lukas Eder
*/
public class IntegerRangeConverter extends AbstractRangeConverter<Integer, IntegerRange> {
public IntegerRangeConverter() {
super(IntegerRange.class);
}
@Override
final IntegerRange construct(String lower, boolean lowerIncluding, String upper, boolean upperIncluding) {
return integerRange(
lower == null ? null : Integer.valueOf(lower),
lowerIncluding,
upper == null ? null : Integer.valueOf(upper),
upperIncluding
);
}
@Override
final IntegerRange empty() {
return integerRange(0, 0);
}
}

View File

@ -0,0 +1,118 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.types;
import java.util.Objects;
/**
* A data type representing the PostgreSQL <code>range</code> type for discrete ranges.
*
* @author Lukas Eder
*/
abstract class AbstractDiscreteRange<T, R extends AbstractDiscreteRange<T, R>> extends AbstractRange<T> {
AbstractDiscreteRange(T lower, boolean lowerIncluding, T upper, boolean upperIncluding) {
super(lower, lowerIncluding, upper, upperIncluding);
}
/**
* Given a value t, get the next value.
*/
abstract T next(T t);
/**
* Given a value t, get the previous value.
*/
abstract T prev(T t);
/**
* Construct a new instance of this type.
*/
abstract R construct(T lower, T upper);
final boolean isCanonical() {
return lowerIncluding() && !upperIncluding();
}
@SuppressWarnings("unchecked")
final R canonical() {
if (isCanonical())
return (R) this;
T l = lower();
T u = upper();
if (!lowerIncluding() && l != null)
l = next(l);
// This can overflow for Integer and Long. In PostgreSQL, an overflow
// will cause an error. We might deal with this too, in the future
if (upperIncluding() && u != null)
u = next(u);
return construct(l, u);
}
@Override
public int hashCode() {
if (isCanonical())
return Objects.hash(lower(), upper());
else
return canonical().hashCode();
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
R other = (R) obj;
boolean c1 = isCanonical();
boolean c2 = other.isCanonical();
R r1 = c1 ? (R) this : canonical();
R r2 = c2 ? other : other.canonical();
return Objects.equals(r1.lower(), r2.lower()) && Objects.equals(r1.upper(), r2.upper());
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.types;
import java.io.Serializable;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
/**
* A data type representing the PostgreSQL <code>range</code> type.
*
* @author Lukas Eder
*/
public abstract class AbstractRange<T> implements Serializable {
private final T lower;
private final boolean lowerIncluding;
private final T upper;
private final boolean upperIncluding;
AbstractRange(T lower, boolean lowerIncluding, T upper, boolean upperIncluding) {
this.lower = lower;
this.lowerIncluding = lowerIncluding;
this.upper = upper;
this.upperIncluding = upperIncluding;
}
@Nullable
public final T lower() {
return lower;
}
public final boolean lowerIncluding() {
return lowerIncluding;
}
@Nullable
public final T upper() {
return upper;
}
public final boolean upperIncluding() {
return upperIncluding;
}
@Override
public int hashCode() {
return Objects.hash(lower, lowerIncluding, upper, upperIncluding);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractRange<?> other = (AbstractRange<?>) obj;
return Objects.equals(lower, other.lower) && lowerIncluding == other.lowerIncluding
&& Objects.equals(upper, other.upper) && upperIncluding == other.upperIncluding;
}
@Override
public String toString() {
return (lowerIncluding ? "[" : "(")
+ (lower == null ? "" : "" + lower)
+ ","
+ (upper == null ? "" : "" + upper)
+ (upperIncluding ? "]" : ")");
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.types;
/**
* A data type representing the PostgreSQL <code>int4range</code> type.
*
* @author Lukas Eder
*/
public final class IntegerRange extends AbstractDiscreteRange<Integer, IntegerRange> {
private IntegerRange(Integer lower, boolean lowerIncluding, Integer upper, boolean upperIncluding) {
super(lower, lowerIncluding, upper, upperIncluding);
}
/**
* Create a new {@link IntegerRange} with a inclusive lower bound and an
* exclusive upper bound.
*/
public static final IntegerRange integerRange(Integer lower, Integer upper) {
return new IntegerRange(lower, true, upper, false);
}
/**
* Create a new {@link IntegerRange}.
*/
public static final IntegerRange integerRange(Integer lower, boolean lowerIncluding, Integer upper, boolean upperIncluding) {
return new IntegerRange(lower, lowerIncluding, upper, upperIncluding);
}
@Override
final IntegerRange construct(Integer lower, Integer upper) {
return new IntegerRange(lower, true, upper, false);
}
@Override
final Integer next(Integer t) {
return t.intValue() + 1;
}
@Override
final Integer prev(Integer t) {
return t.intValue() - 1;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.postgres.extensions.test;
import static org.jooq.postgres.extensions.types.IntegerRange.integerRange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import org.junit.Test;
public class IntegerRangeTest {
@Test
public void testEqualsHashCode() {
assertEqualsHashCode(integerRange(0, 0), integerRange(0, 0));
assertEqualsHashCode(integerRange(0, 1), integerRange(0, 1));
assertEqualsHashCode(integerRange(0, 2), integerRange(0, true, 1, true));
assertEqualsHashCode(integerRange(0, 2), integerRange(0, true, 2, false));
assertEqualsHashCode(integerRange(0, 2), integerRange(-1, false, 1, true));
assertEqualsHashCode(integerRange(0, 2), integerRange(-1, false, 2, false));
assertEqualsHashCode(integerRange(0, null), integerRange(0, true, null, true));
assertEqualsHashCode(integerRange(0, null), integerRange(0, true, null, false));
assertEqualsHashCode(integerRange(0, null), integerRange(-1, false, null, true));
assertEqualsHashCode(integerRange(0, null), integerRange(-1, false, null, false));
assertEqualsHashCode(integerRange(null, 2), integerRange(null, true, 1, true));
assertEqualsHashCode(integerRange(null, 2), integerRange(null, true, 2, false));
assertEqualsHashCode(integerRange(null, 2), integerRange(null, false, 1, true));
assertEqualsHashCode(integerRange(null, 2), integerRange(null, false, 2, false));
assertEqualsHashCode(integerRange(null, null), integerRange(null, true, null, true));
assertEqualsHashCode(integerRange(null, null), integerRange(null, true, null, false));
assertEqualsHashCode(integerRange(null, null), integerRange(null, false, null, true));
assertEqualsHashCode(integerRange(null, null), integerRange(null, false, null, false));
assertNotEquals(integerRange(0, 1), integerRange(1, 0));
}
private void assertEqualsHashCode(Object expected, Object actual) {
assertEquals(expected, actual);
assertEquals(expected.hashCode(), actual.hashCode());
}
}