DATAJDBC-357 - Dialect support pagination and array support.
We now support Dialect abstractions to consider vendor-specific deviations in SQL query rendering. Dialects support right now: Feature flags for array support Limit and Offset handling The rendering is extended by considering rendering callback hook functions. Current dialects: Postgres SQL SQL Server (2012) MySQL Original pull request: #125.
This commit is contained in:
committed by
Jens Schauder
parent
141f2d77ac
commit
488ef53157
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
|
||||
|
||||
/**
|
||||
* Base class for {@link Dialect} implementations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public abstract class AbstractDialect implements Dialect {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.Dialect#getSelectContext()
|
||||
*/
|
||||
@Override
|
||||
public SelectRenderContext getSelectContext() {
|
||||
|
||||
Function<Select, ? extends CharSequence> afterOrderBy = getAfterOrderBy();
|
||||
|
||||
return new DialectSelectRenderContext(afterOrderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Function afterOrderBy Function}. Typically used for pagination.
|
||||
*
|
||||
* @return the {@link Function} called on {@code afterOrderBy}.
|
||||
*/
|
||||
protected Function<Select, CharSequence> getAfterOrderBy() {
|
||||
|
||||
Function<Select, ? extends CharSequence> afterOrderBy;
|
||||
|
||||
LimitClause limit = limit();
|
||||
|
||||
switch (limit.getClausePosition()) {
|
||||
|
||||
case AFTER_ORDER_BY:
|
||||
afterOrderBy = new AfterOrderByLimitRenderFunction(limit);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException(String.format("Clause position %s not supported!", limit));
|
||||
}
|
||||
|
||||
return afterOrderBy.andThen(PrependWithLeadingWhitespace.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SelectRenderContext} derived from {@link Dialect} specifics.
|
||||
*/
|
||||
class DialectSelectRenderContext implements SelectRenderContext {
|
||||
|
||||
private final Function<Select, ? extends CharSequence> afterOrderBy;
|
||||
|
||||
DialectSelectRenderContext(Function<Select, ? extends CharSequence> afterOrderBy) {
|
||||
this.afterOrderBy = afterOrderBy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.sql.render.SelectRenderContext#afterOrderBy(boolean)
|
||||
*/
|
||||
@Override
|
||||
public Function<Select, ? extends CharSequence> afterOrderBy(boolean hasOrderBy) {
|
||||
return afterOrderBy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After {@code ORDER BY} function rendering the {@link LimitClause}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class AfterOrderByLimitRenderFunction implements Function<Select, CharSequence> {
|
||||
|
||||
private final LimitClause clause;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.function.Function#apply(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public CharSequence apply(Select select) {
|
||||
|
||||
OptionalLong limit = select.getLimit();
|
||||
OptionalLong offset = select.getOffset();
|
||||
|
||||
if (limit.isPresent() && offset.isPresent()) {
|
||||
return clause.getLimitOffset(limit.getAsLong(), offset.getAsLong());
|
||||
}
|
||||
|
||||
if (limit.isPresent()) {
|
||||
return clause.getLimit(limit.getAsLong());
|
||||
}
|
||||
|
||||
if (offset.isPresent()) {
|
||||
return clause.getOffset(offset.getAsLong());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends a non-empty rendering result with a leading whitespace,
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
enum PrependWithLeadingWhitespace implements Function<CharSequence, CharSequence> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.function.Function#apply(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public CharSequence apply(CharSequence charSequence) {
|
||||
|
||||
if (charSequence.length() == 0) {
|
||||
return charSequence;
|
||||
}
|
||||
|
||||
return " " + charSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
/**
|
||||
* Interface declaring methods that express how a dialect supports array-typed columns.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface ArrayColumns {
|
||||
|
||||
/**
|
||||
* Returns {@literal true} if the dialect supports array-typed columns.
|
||||
*
|
||||
* @return {@literal true} if the dialect supports array-typed columns.
|
||||
*/
|
||||
boolean isSupported();
|
||||
|
||||
/**
|
||||
* Translate the {@link Class user type} of an array into the dialect-specific type. This method considers only the
|
||||
* component type.
|
||||
*
|
||||
* @param userType component type of the array.
|
||||
* @return the dialect-supported array type.
|
||||
* @throws UnsupportedOperationException if array typed columns are not supported.
|
||||
* @throws IllegalArgumentException if the {@code userType} is not a supported array type.
|
||||
*/
|
||||
Class<?> getArrayType(Class<?> userType);
|
||||
|
||||
/**
|
||||
* Default {@link ArrayColumns} implementation for dialects that do not support array-typed columns.
|
||||
*/
|
||||
enum Unsupported implements ArrayColumns {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.ArrayColumns#isSupported()
|
||||
*/
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.ArrayColumns#getArrayType(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getArrayType(Class<?> userType) {
|
||||
throw new UnsupportedOperationException("Array types not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
|
||||
|
||||
/**
|
||||
* Represents a dialect that is implemented by a particular database. Please note that not all features are supported by
|
||||
* all vendors. Dialects typically express this with feature flags. Methods for unsupported functionality may throw
|
||||
* {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface Dialect {
|
||||
|
||||
/**
|
||||
* Return the {@link LimitClause} used by this dialect.
|
||||
*
|
||||
* @return the {@link LimitClause} used by this dialect.
|
||||
*/
|
||||
LimitClause limit();
|
||||
|
||||
/**
|
||||
* Returns the array support object that describes how array-typed columns are supported by this dialect.
|
||||
*
|
||||
* @return the array support object that describes how array-typed columns are supported by this dialect.
|
||||
*/
|
||||
default ArrayColumns getArraySupport() {
|
||||
return ArrayColumns.Unsupported.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link SelectRenderContext}.
|
||||
*
|
||||
* @return the {@link SelectRenderContext}.
|
||||
*/
|
||||
SelectRenderContext getSelectContext();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
/**
|
||||
* A clause representing Dialect-specific {@code LIMIT}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface LimitClause {
|
||||
|
||||
/**
|
||||
* Returns the {@code LIMIT} clause to limit results.
|
||||
*
|
||||
* @param limit the actual limit to use.
|
||||
* @return rendered limit clause.
|
||||
* @see #getLimitOffset(long, long)
|
||||
*/
|
||||
String getLimit(long limit);
|
||||
|
||||
/**
|
||||
* Returns the {@code OFFSET} clause to consume rows at a given offset.
|
||||
*
|
||||
* @param limit the actual limit to use.
|
||||
* @return rendered limit clause.
|
||||
* @see #getLimitOffset(long, long)
|
||||
*/
|
||||
String getOffset(long limit);
|
||||
|
||||
/**
|
||||
* Returns a combined {@code LIMIT/OFFSET} clause that limits results and starts consumption at the given
|
||||
* {@code offset}.
|
||||
*
|
||||
* @param limit the actual limit to use.
|
||||
* @param offset the offset to start from.
|
||||
* @return rendered limit clause.
|
||||
*/
|
||||
String getLimitOffset(long limit, long offset);
|
||||
|
||||
/**
|
||||
* Returns the {@link Position} where to apply the {@link #getOffset(long) clause}.
|
||||
*/
|
||||
Position getClausePosition();
|
||||
|
||||
/**
|
||||
* Enumeration of where to render the clause within the SQL statement.
|
||||
*/
|
||||
enum Position {
|
||||
|
||||
/**
|
||||
* Append the clause at the end of the statement.
|
||||
*/
|
||||
AFTER_ORDER_BY
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
/**
|
||||
* An SQL dialect for MySQL.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class MySqlDialect extends AbstractDialect {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final MySqlDialect INSTANCE = new MySqlDialect();
|
||||
|
||||
private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getLimit(long)
|
||||
*/
|
||||
@Override
|
||||
public String getLimit(long limit) {
|
||||
return "LIMIT " + limit;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getOffset(long)
|
||||
*/
|
||||
@Override
|
||||
public String getOffset(long offset) {
|
||||
throw new UnsupportedOperationException("MySQL does not support OFFSET without LIMIT");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getClause(long, long)
|
||||
*/
|
||||
@Override
|
||||
public String getLimitOffset(long limit, long offset) {
|
||||
|
||||
// LIMIT {[offset,] row_count}
|
||||
return String.format("LIMIT %d, %d", offset, limit);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getClausePosition()
|
||||
*/
|
||||
@Override
|
||||
public Position getClausePosition() {
|
||||
return Position.AFTER_ORDER_BY;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.Dialect#limit()
|
||||
*/
|
||||
@Override
|
||||
public LimitClause limit() {
|
||||
return LIMIT_CLAUSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* An SQL dialect for Postgres.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class PostgresDialect extends AbstractDialect {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final PostgresDialect INSTANCE = new PostgresDialect();
|
||||
|
||||
private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getLimit(long)
|
||||
*/
|
||||
@Override
|
||||
public String getLimit(long limit) {
|
||||
return "LIMIT " + limit;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getOffset(long)
|
||||
*/
|
||||
@Override
|
||||
public String getOffset(long offset) {
|
||||
return "OFFSET " + offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getClause(long, long)
|
||||
*/
|
||||
@Override
|
||||
public String getLimitOffset(long limit, long offset) {
|
||||
return String.format("LIMIT %d OFFSET %d", limit, offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getClausePosition()
|
||||
*/
|
||||
@Override
|
||||
public Position getClausePosition() {
|
||||
return Position.AFTER_ORDER_BY;
|
||||
}
|
||||
};
|
||||
|
||||
private final PostgresArrayColumns ARRAY_COLUMNS = new PostgresArrayColumns();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.Dialect#limit()
|
||||
*/
|
||||
@Override
|
||||
public LimitClause limit() {
|
||||
return LIMIT_CLAUSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.Dialect#getArraySupport()
|
||||
*/
|
||||
@Override
|
||||
public ArrayColumns getArraySupport() {
|
||||
return ARRAY_COLUMNS;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
static class PostgresArrayColumns implements ArrayColumns {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.ArrayColumns#isSupported()
|
||||
*/
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.ArrayColumns#getArrayType(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getArrayType(Class<?> userType) {
|
||||
|
||||
Assert.notNull(userType, "Array component type must not be null");
|
||||
|
||||
return ClassUtils.resolvePrimitiveIfNecessary(userType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.relational.core.sql.render.NamingStrategies;
|
||||
import org.springframework.data.relational.core.sql.render.RenderContext;
|
||||
import org.springframework.data.relational.core.sql.render.RenderNamingStrategy;
|
||||
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Factory for {@link RenderContext} based on {@link Dialect}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class RenderContextFactory {
|
||||
|
||||
public final Dialect dialect;
|
||||
|
||||
public RenderNamingStrategy namingStrategy = NamingStrategies.asIs();
|
||||
|
||||
/**
|
||||
* Creates a new {@link RenderContextFactory} given {@link Dialect}.
|
||||
*
|
||||
* @param dialect must not be {@literal null}.
|
||||
*/
|
||||
public RenderContextFactory(Dialect dialect) {
|
||||
|
||||
Assert.notNull(dialect, "Dialect must not be null!");
|
||||
|
||||
this.dialect = dialect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link RenderNamingStrategy}.
|
||||
*
|
||||
* @param namingStrategy must not be {@literal null}.
|
||||
*/
|
||||
public void setNamingStrategy(RenderNamingStrategy namingStrategy) {
|
||||
|
||||
Assert.notNull(namingStrategy, "RenderNamingStrategy must not be null");
|
||||
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RenderContext} configured with {@link Dialect} specifics.
|
||||
*
|
||||
* @return the {@link RenderContext}.
|
||||
*/
|
||||
public RenderContext createRenderContext() {
|
||||
|
||||
SelectRenderContext select = dialect.getSelectContext();
|
||||
|
||||
return new DialectRenderContext(namingStrategy, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RenderContext} derived from {@link Dialect} specifics.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class DialectRenderContext implements RenderContext {
|
||||
|
||||
private final RenderNamingStrategy renderNamingStrategy;
|
||||
|
||||
private final SelectRenderContext selectRenderContext;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.sql.render.RenderContext#getNamingStrategy()
|
||||
*/
|
||||
@Override
|
||||
public RenderNamingStrategy getNamingStrategy() {
|
||||
return renderNamingStrategy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.sql.render.RenderContext#getSelect()
|
||||
*/
|
||||
@Override
|
||||
public SelectRenderContext getSelect() {
|
||||
return selectRenderContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
|
||||
import org.springframework.data.util.Lazy;
|
||||
|
||||
/**
|
||||
* An SQL dialect for Microsoft SQL Server.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class SqlServerDialect extends AbstractDialect {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final SqlServerDialect INSTANCE = new SqlServerDialect();
|
||||
|
||||
private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getLimit(long)
|
||||
*/
|
||||
@Override
|
||||
public String getLimit(long limit) {
|
||||
return "OFFSET 0 ROWS FETCH NEXT " + limit + " ROWS ONLY";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getOffset(long)
|
||||
*/
|
||||
@Override
|
||||
public String getOffset(long offset) {
|
||||
return "OFFSET " + offset + " ROWS";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getClause(long, long)
|
||||
*/
|
||||
@Override
|
||||
public String getLimitOffset(long limit, long offset) {
|
||||
return String.format("OFFSET %d ROWS FETCH NEXT %d ROWS ONLY", offset, limit);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.LimitClause#getClausePosition()
|
||||
*/
|
||||
@Override
|
||||
public Position getClausePosition() {
|
||||
return Position.AFTER_ORDER_BY;
|
||||
}
|
||||
};
|
||||
|
||||
private final Lazy<SelectRenderContext> selectRenderContext = Lazy
|
||||
.of(() -> new SqlServerSelectRenderContext(getAfterOrderBy()));
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.Dialect#limit()
|
||||
*/
|
||||
@Override
|
||||
public LimitClause limit() {
|
||||
return LIMIT_CLAUSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.dialect.AbstractDialect#getSelectContext()
|
||||
*/
|
||||
@Override
|
||||
public SelectRenderContext getSelectContext() {
|
||||
return selectRenderContext.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
|
||||
|
||||
/**
|
||||
* SQL-Server specific {@link SelectRenderContext}. Summary of SQL-specifics:
|
||||
* <ul>
|
||||
* <li>Appends a synthetic ROW_NUMBER when using pagination and the query does not specify ordering</li>
|
||||
* <li>Append synthetic ordering if query uses pagination and the query does not specify ordering</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SqlServerSelectRenderContext implements SelectRenderContext {
|
||||
|
||||
private static final String SYNTHETIC_ORDER_BY_FIELD = "__relational_row_number__";
|
||||
|
||||
private static final String SYNTHETIC_SELECT_LIST = ", ROW_NUMBER() over (ORDER BY CURRENT_TIMESTAMP) AS "
|
||||
+ SYNTHETIC_ORDER_BY_FIELD;
|
||||
|
||||
private final Function<Select, CharSequence> afterOrderBy;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SqlServerSelectRenderContext}.
|
||||
*
|
||||
* @param afterOrderBy the delegate {@code afterOrderBy} function.
|
||||
*/
|
||||
protected SqlServerSelectRenderContext(Function<Select, CharSequence> afterOrderBy) {
|
||||
this.afterOrderBy = afterOrderBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Select, ? extends CharSequence> afterSelectList() {
|
||||
|
||||
return select -> {
|
||||
|
||||
if (usesPagination(select) && select.getOrderBy().isEmpty()) {
|
||||
return SYNTHETIC_SELECT_LIST;
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Select, ? extends CharSequence> afterOrderBy(boolean hasOrderBy) {
|
||||
|
||||
if (hasOrderBy) {
|
||||
return afterOrderBy;
|
||||
}
|
||||
|
||||
return select -> {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (usesPagination(select)) {
|
||||
builder.append(" ORDER BY " + SYNTHETIC_ORDER_BY_FIELD);
|
||||
}
|
||||
|
||||
builder.append(afterOrderBy.apply(select));
|
||||
|
||||
return builder;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean usesPagination(Select select) {
|
||||
return select.getOffset().isPresent() || select.getLimit().isPresent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Dialects abstract the SQL dialect of the underlying database.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.data.relational.core.dialect;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.relational.core.sql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
@@ -48,10 +49,19 @@ class DefaultSelect implements Select {
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
this.joins = new ArrayList<>(joins);
|
||||
this.orderBy = new ArrayList<>(orderBy);
|
||||
this.orderBy = Collections.unmodifiableList(new ArrayList<>(orderBy));
|
||||
this.where = where != null ? new Where(where) : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.sql.Select#getOrderBy()
|
||||
*/
|
||||
@Override
|
||||
public List<OrderByField> getOrderBy() {
|
||||
return this.orderBy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.relational.core.sql.Select#getLimit()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
/**
|
||||
@@ -45,6 +46,11 @@ public interface Select extends Segment, Visitable {
|
||||
return new DefaultSelectBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link List} of {@link OrderByField ORDER BY} fields.
|
||||
*/
|
||||
List<OrderByField> getOrderBy();
|
||||
|
||||
/**
|
||||
* Optional limit. Used for limit/offset paging.
|
||||
*
|
||||
|
||||
@@ -29,4 +29,9 @@ public interface RenderContext {
|
||||
* @return the {@link RenderNamingStrategy}.
|
||||
*/
|
||||
RenderNamingStrategy getNamingStrategy();
|
||||
|
||||
/**
|
||||
* @return the {@link SelectRenderContext}.
|
||||
*/
|
||||
SelectRenderContext getSelect();
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ class SelectListVisitor extends TypedSubtreeVisitor<SelectList> implements PartR
|
||||
|
||||
insideFunction = false;
|
||||
requiresComma = true;
|
||||
} else if (segment instanceof AsteriskFromTable) {
|
||||
builder.append("*");
|
||||
requiresComma = true;
|
||||
} else if (segment instanceof Column) {
|
||||
|
||||
builder.append(context.getNamingStrategy().getName((Column) segment));
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
|
||||
/**
|
||||
* Render context specifically for {@code SELECT} statements. This interface declares rendering hooks that are called
|
||||
* before/after a specific {@code SELECT} clause part. The rendering content is appended directly after/before an
|
||||
* element without further whitespace processing. Hooks are responsible for adding required surrounding whitespaces.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface SelectRenderContext {
|
||||
|
||||
/**
|
||||
* Customization hook: Rendition of a part after the {@code SELECT} list and before any {@code FROM} renderings.
|
||||
* Renders an empty string by default.
|
||||
*
|
||||
* @return render {@link Function} invoked after rendering {@code SELECT} list.
|
||||
*/
|
||||
default Function<Select, ? extends CharSequence> afterSelectList() {
|
||||
return select -> "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook: Rendition of a part after {@code ORDER BY}. The rendering function is called always, regardless
|
||||
* whether {@code ORDER BY} exists or not. Renders an empty string by default.
|
||||
*
|
||||
* @param hasOrderBy the actual value whether the {@link Select} statement has a {@code ORDER BY} clause.
|
||||
* @return render {@link Function} invoked after rendering {@code ORDER BY}.
|
||||
*/
|
||||
default Function<Select, ? extends CharSequence> afterOrderBy(boolean hasOrderBy) {
|
||||
return select -> "";
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import org.springframework.data.relational.core.sql.From;
|
||||
import org.springframework.data.relational.core.sql.Join;
|
||||
import org.springframework.data.relational.core.sql.OrderByField;
|
||||
@@ -35,6 +33,7 @@ import org.springframework.data.relational.core.sql.Where;
|
||||
class SelectStatementVisitor extends DelegatingVisitor implements PartRenderer {
|
||||
|
||||
private final RenderContext context;
|
||||
private final SelectRenderContext selectRenderContext;
|
||||
|
||||
private StringBuilder builder = new StringBuilder();
|
||||
private StringBuilder selectList = new StringBuilder();
|
||||
@@ -50,6 +49,7 @@ class SelectStatementVisitor extends DelegatingVisitor implements PartRenderer {
|
||||
SelectStatementVisitor(RenderContext context) {
|
||||
|
||||
this.context = context;
|
||||
this.selectRenderContext = context.getSelect();
|
||||
this.selectListVisitor = new SelectListVisitor(context, selectList::append);
|
||||
this.orderByClauseVisitor = new OrderByClauseVisitor(context);
|
||||
this.fromClauseVisitor = new FromClauseVisitor(context, it -> {
|
||||
@@ -110,12 +110,16 @@ class SelectStatementVisitor extends DelegatingVisitor implements PartRenderer {
|
||||
|
||||
if (segment instanceof Select) {
|
||||
|
||||
Select select = (Select) segment;
|
||||
|
||||
builder.append("SELECT ");
|
||||
if (((Select) segment).isDistinct()) {
|
||||
|
||||
if (select.isDistinct()) {
|
||||
builder.append("DISTINCT ");
|
||||
}
|
||||
|
||||
builder.append(selectList);
|
||||
builder.append(selectRenderContext.afterSelectList().apply(select));
|
||||
|
||||
if (from.length() != 0) {
|
||||
builder.append(" FROM ").append(from);
|
||||
@@ -130,18 +134,11 @@ class SelectStatementVisitor extends DelegatingVisitor implements PartRenderer {
|
||||
}
|
||||
|
||||
CharSequence orderBy = orderByClauseVisitor.getRenderedPart();
|
||||
if (orderBy.length() != 0)
|
||||
if (orderBy.length() != 0) {
|
||||
builder.append(" ORDER BY ").append(orderBy);
|
||||
|
||||
OptionalLong limit = ((Select) segment).getLimit();
|
||||
if (limit.isPresent()) {
|
||||
builder.append(" LIMIT ").append(limit.getAsLong());
|
||||
}
|
||||
|
||||
OptionalLong offset = ((Select) segment).getOffset();
|
||||
if (offset.isPresent()) {
|
||||
builder.append(" OFFSET ").append(offset.getAsLong());
|
||||
}
|
||||
builder.append(selectRenderContext.afterOrderBy(orderBy.length() != 0).apply(select));
|
||||
|
||||
return Delegation.leave();
|
||||
}
|
||||
|
||||
@@ -28,4 +28,13 @@ class SimpleRenderContext implements RenderContext {
|
||||
|
||||
private final RenderNamingStrategy namingStrategy;
|
||||
|
||||
@Override
|
||||
public SelectRenderContext getSelect() {
|
||||
return DefaultSelectRenderContext.INSTANCE;
|
||||
}
|
||||
|
||||
enum DefaultSelectRenderContext implements SelectRenderContext {
|
||||
INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.StatementBuilder;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sql.render.NamingStrategies;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
|
||||
/**
|
||||
* Tests for {@link MySqlDialect}-specific rendering.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MySqlDialectRenderingTests {
|
||||
|
||||
private final RenderContextFactory factory = new RenderContextFactory(MySqlDialect.INSTANCE);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
factory.setNamingStrategy(NamingStrategies.asIs());
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimit() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).limit(10).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo LIMIT 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithOffset() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).offset(10).build();
|
||||
|
||||
assertThatThrownBy(() -> SqlRenderer.create(factory.createRenderContext()).render(select))
|
||||
.isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimitOffset() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).limit(10).offset(20).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo LIMIT 20, 10");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MySqlDialect}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MySqlDialectUnitTests {
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldNotSupportArrays() {
|
||||
|
||||
ArrayColumns arrayColumns = MySqlDialect.INSTANCE.getArraySupport();
|
||||
|
||||
assertThat(arrayColumns.isSupported()).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimit() {
|
||||
|
||||
LimitClause limit = MySqlDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getClausePosition()).isEqualTo(LimitClause.Position.AFTER_ORDER_BY);
|
||||
assertThat(limit.getLimit(10)).isEqualTo("LIMIT 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderOffset() {
|
||||
|
||||
LimitClause limit = MySqlDialect.INSTANCE.limit();
|
||||
|
||||
assertThatThrownBy(() -> limit.getOffset(10)).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimitOffset() {
|
||||
|
||||
LimitClause limit = MySqlDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getLimitOffset(20, 10)).isEqualTo("LIMIT 10, 20");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.StatementBuilder;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sql.render.NamingStrategies;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
|
||||
/**
|
||||
* Tests for {@link PostgresDialect}-specific rendering.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PostgresDialectRenderingTests {
|
||||
|
||||
private final RenderContextFactory factory = new RenderContextFactory(PostgresDialect.INSTANCE);
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
factory.setNamingStrategy(NamingStrategies.asIs());
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSimpleSelect() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldApplyNamingStrategy() {
|
||||
|
||||
factory.setNamingStrategy(NamingStrategies.toUpper());
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT FOO.* FROM FOO");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimit() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).limit(10).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo LIMIT 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithOffset() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).offset(10).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo OFFSET 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimitOffset() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).limit(10).offset(20).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo LIMIT 10 OFFSET 20");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.SoftAssertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PostgresDialect}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PostgresDialectUnitTests {
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldSupportArrays() {
|
||||
|
||||
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
|
||||
|
||||
assertThat(arrayColumns.isSupported()).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldUseBoxedArrayTypesForPrimitiveTypes() {
|
||||
|
||||
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
|
||||
|
||||
assertSoftly(it -> {
|
||||
it.assertThat(arrayColumns.getArrayType(int.class)).isEqualTo(Integer.class);
|
||||
it.assertThat(arrayColumns.getArrayType(double.class)).isEqualTo(Double.class);
|
||||
it.assertThat(arrayColumns.getArrayType(String.class)).isEqualTo(String.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimit() {
|
||||
|
||||
LimitClause limit = PostgresDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getClausePosition()).isEqualTo(LimitClause.Position.AFTER_ORDER_BY);
|
||||
assertThat(limit.getLimit(10)).isEqualTo("LIMIT 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderOffset() {
|
||||
|
||||
LimitClause limit = PostgresDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getOffset(10)).isEqualTo("OFFSET 10");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimitOffset() {
|
||||
|
||||
LimitClause limit = PostgresDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getLimitOffset(20, 10)).isEqualTo("LIMIT 20 OFFSET 10");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.StatementBuilder;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sql.render.NamingStrategies;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
|
||||
/**
|
||||
* Tests for {@link SqlServerDialect}-specific rendering.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SqlServerDialectRenderingTests {
|
||||
|
||||
private final RenderContextFactory factory = new RenderContextFactory(SqlServerDialect.INSTANCE);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
factory.setNamingStrategy(NamingStrategies.asIs());
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSimpleSelect() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldApplyNamingStrategy() {
|
||||
|
||||
factory.setNamingStrategy(NamingStrategies.toUpper());
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT FOO.* FROM FOO");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimit() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).limit(10).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo(
|
||||
"SELECT foo.*, ROW_NUMBER() over (ORDER BY CURRENT_TIMESTAMP) AS __relational_row_number__ FROM foo ORDER BY __relational_row_number__ OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithOffset() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).offset(10).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo(
|
||||
"SELECT foo.*, ROW_NUMBER() over (ORDER BY CURRENT_TIMESTAMP) AS __relational_row_number__ FROM foo ORDER BY __relational_row_number__ OFFSET 10 ROWS");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimitOffset() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).limit(10).offset(20).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo(
|
||||
"SELECT foo.*, ROW_NUMBER() over (ORDER BY CURRENT_TIMESTAMP) AS __relational_row_number__ FROM foo ORDER BY __relational_row_number__ OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderSelectWithLimitOffsetAndOrderBy() {
|
||||
|
||||
Table table = Table.create("foo");
|
||||
Select select = StatementBuilder.select(table.asterisk()).from(table).orderBy(table.column("column_1")).limit(10)
|
||||
.offset(20).build();
|
||||
|
||||
String sql = SqlRenderer.create(factory.createRenderContext()).render(select);
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT foo.* FROM foo ORDER BY column_1 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.relational.core.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SqlServerDialect}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SqlServerDialectUnitTests {
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldNotSupportArrays() {
|
||||
|
||||
ArrayColumns arrayColumns = SqlServerDialect.INSTANCE.getArraySupport();
|
||||
|
||||
assertThat(arrayColumns.isSupported()).isFalse();
|
||||
assertThatThrownBy(() -> arrayColumns.getArrayType(String.class)).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimit() {
|
||||
|
||||
LimitClause limit = SqlServerDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getClausePosition()).isEqualTo(LimitClause.Position.AFTER_ORDER_BY);
|
||||
assertThat(limit.getLimit(10)).isEqualTo("OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderOffset() {
|
||||
|
||||
LimitClause limit = SqlServerDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getOffset(10)).isEqualTo("OFFSET 10 ROWS");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-278
|
||||
public void shouldRenderLimitOffset() {
|
||||
|
||||
LimitClause limit = SqlServerDialect.INSTANCE.limit();
|
||||
|
||||
assertThat(limit.getLimitOffset(20, 10)).isEqualTo("OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY");
|
||||
}
|
||||
}
|
||||
@@ -37,13 +37,13 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class SelectRendererUnitTests {
|
||||
|
||||
@Test // DATAJDBC-309
|
||||
@Test // DATAJDBC-309, DATAJDBC-278
|
||||
public void shouldRenderSingleColumn() {
|
||||
|
||||
Table bar = SQL.table("bar");
|
||||
Column foo = bar.column("foo");
|
||||
|
||||
Select select = Select.builder().select(foo).from(bar).build();
|
||||
Select select = Select.builder().select(foo).from(bar).limitOffset(1, 2).build();
|
||||
|
||||
assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT bar.foo FROM bar");
|
||||
}
|
||||
@@ -180,17 +180,6 @@ public class SelectRendererUnitTests {
|
||||
.isEqualTo("SELECT emp.name AS emp_name FROM employee AS emp ORDER BY emp_name ASC");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-309
|
||||
public void shouldRenderOrderLimitOffset() {
|
||||
|
||||
Table table = SQL.table("foo");
|
||||
Column bar = table.column("bar");
|
||||
|
||||
Select select = Select.builder().select(bar).from("foo").limitOffset(10, 20).build();
|
||||
|
||||
assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo LIMIT 10 OFFSET 20");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-309
|
||||
public void shouldRenderIsNull() {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user