http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java new file mode 100644 index 0000000..a578c23 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java @@ -0,0 +1,43 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ +package org.apache.cayenne.dba.sqlite; + +import org.apache.cayenne.access.DataNode; +import org.apache.cayenne.access.OperationObserver; +import org.apache.cayenne.access.jdbc.SelectAction; +import org.apache.cayenne.query.SQLAction; +import org.apache.cayenne.query.SelectQuery; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * @since 4.1 + */ +public class SQLiteSelectAction extends SelectAction { + + public SQLiteSelectAction(SelectQuery<?> query, DataNode dataNode) { + super(query, dataNode); + } + + @Override + protected int getInMemoryOffset(int queryOffset) { + return 0; + } +}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java new file mode 100644 index 0000000..d20bc66 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java @@ -0,0 +1,100 @@ +package org.apache.cayenne.dba.sqlite; + +import org.apache.cayenne.access.sqlbuilder.QuotingAppendable; +import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; +import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode; +import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor; +import org.apache.cayenne.dba.mysql.sqltree.MysqlLimitOffsetNode; + +/** + * @since 4.2 + */ +public class SQLiteTreeProcessor extends BaseSQLTreeProcessor { + + @Override + protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) { + replaceChild(parent, index, new MysqlLimitOffsetNode(child.getLimit(), child.getOffset()), false); + } + + @Override + protected void onFunctionNode(Node parent, FunctionNode child, int index) { + String functionName = child.getFunctionName(); + Node replacement = null; + switch (functionName) { + case "LOCATE": + replacement = new FunctionNode("INSTR", child.getAlias(), true); + for (int i = 0; i <= 1; i++) { + replacement.addChild(child.getChild(1 - i)); + } + parent.replaceChild(index, replacement); + return; + case "DAY_OF_YEAR": + replaceExtractFunction(parent, child, index, "'%j'"); + return; + case "DAY_OF_WEEK": + replaceExtractFunction(parent, child, index, "'%w'"); + return; + case "WEEK": + replaceExtractFunction(parent, child, index, "'%W'"); + return; + case "YEAR": + replaceExtractFunction(parent, child, index, "'%Y'"); + return; + case "MONTH": + replaceExtractFunction(parent, child, index, "'%m'"); + return; + case "DAY": + case "DAY_OF_MONTH": + replaceExtractFunction(parent, child, index, "'%d'"); + return; + case "HOUR": + replaceExtractFunction(parent, child, index, "'%H'"); + return; + case "MINUTE": + replaceExtractFunction(parent, child, index, "'%M'"); + return; + case "SECOND": + replaceExtractFunction(parent, child, index, "'%S'"); + return; + + case "SUBSTRING": + replacement = new FunctionNode("SUBSTR", child.getAlias(), true); + break; + case "CONCAT": + replacement = new OpExpressionNode("||"); + break; + case "MOD": + replacement = new OpExpressionNode("%"); + break; + case "CURRENT_DATE": + case "CURRENT_TIMESTAMP": + case "CURRENT_TIME": + replacement = new FunctionNode(functionName, child.getAlias(), false); + break; + } + + if(replacement != null) { + replaceChild(parent, index, replacement); + } + } + + private void replaceExtractFunction(Node parent, FunctionNode original, int index, String format) { + Node replacement = new FunctionNode("cast", original.getAlias(), true) { + @Override + public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) { + buffer.append(" as "); + } + }; + + FunctionNode strftime = new FunctionNode("strftime", null, true); + strftime.addChild(new TextNode(format)); + strftime.addChild(original.getChild(0)); + replacement.addChild(strftime); + replacement.addChild(new TextNode("integer")); + + parent.replaceChild(index, replacement); + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java index a94bf62..6a99dcb 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java @@ -20,11 +20,10 @@ package org.apache.cayenne.dba.sqlserver; import java.util.List; +import java.util.function.Function; import org.apache.cayenne.access.DataNode; -import org.apache.cayenne.access.translator.select.QualifierTranslator; -import org.apache.cayenne.access.translator.select.QueryAssembler; -import org.apache.cayenne.access.translator.select.SelectTranslator; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.access.types.ExtendedTypeFactory; import org.apache.cayenne.access.types.ValueObjectTypeRegistry; @@ -32,10 +31,8 @@ import org.apache.cayenne.configuration.Constants; import org.apache.cayenne.configuration.RuntimeProperties; import org.apache.cayenne.dba.sybase.SybaseAdapter; import org.apache.cayenne.di.Inject; -import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; -import org.apache.cayenne.query.SelectQuery; import org.apache.cayenne.resource.ResourceLocator; /** @@ -91,11 +88,11 @@ public class SQLServerAdapter extends SybaseAdapter { } /** - * @since 4.0 + * @since 4.2 */ @Override - public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) { - return new SQLServerSelectTranslator(query, this, entityResolver); + public Function<Node, Node> getSqlTreeProcessor() { + return new SQLServerTreeProcessor(); } /** @@ -108,14 +105,4 @@ public class SQLServerAdapter extends SybaseAdapter { return query.createSQLAction(new SQLServerActionBuilder(node)); } - /** - * Returns a trimming translator. - */ - @Override - public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) { - QualifierTranslator translator = new SQLServerTrimmingQualifierTranslator(queryAssembler, - SQLServerAdapter.TRIM_FUNCTION); - translator.setCaseInsensitive(caseInsensitiveCollations); - return translator; - } } http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java deleted file mode 100644 index b78877c..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java +++ /dev/null @@ -1,54 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ -package org.apache.cayenne.dba.sqlserver; - -import org.apache.cayenne.access.translator.select.DefaultSelectTranslator; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.query.Query; - -public class SQLServerSelectTranslator extends DefaultSelectTranslator { - - /** - * @since 4.0 - */ - public SQLServerSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) { - super(query, adapter, entityResolver); - } - - @Override - protected void appendLimitAndOffsetClauses(StringBuilder buffer) { - - int limit = queryMetadata.getFetchLimit(); - int offset = queryMetadata.getFetchOffset(); - - if (limit > 0) { - String sql = buffer.toString(); - - // If contains distinct insert top limit after - if (sql.startsWith("SELECT DISTINCT ")) { - buffer.replace(0, 15, "SELECT DISTINCT TOP " + (offset + limit)); - - } else { - buffer.replace(0, 6, "SELECT TOP " + (offset + limit)); - } - } - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java new file mode 100644 index 0000000..2681a53 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java @@ -0,0 +1,128 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.dba.sqlserver; + +import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; +import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType; +import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.TopNode; +import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor; +import org.apache.cayenne.dba.sqlserver.sqltree.SQLServerColumnNode; + +/** + * @since 4.2 + */ +public class SQLServerTreeProcessor extends BaseSQLTreeProcessor { + + @Override + protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) { + // SQLServer uses "SELECT DISTINCT TOP N" or "SELECT TOP N" instead of LIMIT + // Offset will be calculated in-memory + replaceChild(parent, index, new EmptyNode(), false); + if(child.getLimit() > 0) { + int limit = child.getLimit() + child.getOffset(); + // we have root actually as input for processor, but it's better to keep processor stateless + // root shouldn't be far from limit's parent (likely it will be parent itself) + Node root = getRoot(parent); + int idx = 0; + if(root.getChild(0).getType() == NodeType.DISTINCT) { + idx = 1; + } + root.addChild(idx, new TopNode(limit)); + } + } + + private Node getRoot(Node node) { + while(node.getParent() != null) { + node = node.getParent(); + } + return node; + } + + @Override + protected void onColumnNode(Node parent, ColumnNode child, int index) { + replaceChild(parent, index, new SQLServerColumnNode(child)); + } + + @Override + protected void onFunctionNode(Node parent, FunctionNode child, int index) { + String functionName = child.getFunctionName(); + Node replacement = null; + switch (functionName) { + case "LENGTH": + replacement = new FunctionNode("LEN", child.getAlias(), true); + break; + case "LOCATE": + replacement = new FunctionNode("CHARINDEX", child.getAlias(), true); + break; + case "MOD": + replacement = new OpExpressionNode("%"); + break; + case "TRIM": + Node rtrim = new FunctionNode("RTRIM", null, true); + replacement = new FunctionNode("LTRIM", child.getAlias(), true); + for(int i=0; i<child.getChildrenCount(); i++) { + rtrim.addChild(child.getChild(i)); + } + replacement.addChild(rtrim); + parent.replaceChild(index, replacement); + return; + case "CURRENT_DATE": + replacement = new FunctionNode("{fn CURDATE()}", child.getAlias(), false); + break; + case "CURRENT_TIME": + replacement = new FunctionNode("{fn CURTIME()}", child.getAlias(), false); + break; + case "CURRENT_TIMESTAMP": + replacement = new FunctionNode("CURRENT_TIMESTAMP", child.getAlias(), false); + break; + + case "YEAR": + case "MONTH": + case "WEEK": + case "DAY_OF_YEAR": + case "DAY": + case "DAY_OF_MONTH": + case "DAY_OF_WEEK": + case "HOUR": + case "MINUTE": + case "SECOND": + replacement = new FunctionNode("DATEPART", child.getAlias(), true); + if("DAY_OF_MONTH".equals(functionName)) { + functionName = "DAY"; + } else if("DAY_OF_WEEK".equals(functionName)) { + functionName = "WEEKDAY"; + } else if("DAY_OF_YEAR".equals(functionName)) { + functionName = "DAYOFYEAR"; + } + replacement.addChild(new TextNode(functionName)); + break; + } + + if(replacement != null) { + replaceChild(parent, index, replacement); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java deleted file mode 100644 index 9847742..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java +++ /dev/null @@ -1,207 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ -package org.apache.cayenne.dba.sqlserver; - -import org.apache.cayenne.access.translator.select.QueryAssembler; -import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator; -import org.apache.cayenne.exp.Expression; -import org.apache.cayenne.exp.parser.ASTExtract; -import org.apache.cayenne.exp.parser.ASTFunctionCall; -import org.apache.cayenne.map.DbAttribute; - -import java.sql.Types; -import java.util.ArrayList; -import java.util.List; - -/** - * @since 3.0 - */ -class SQLServerTrimmingQualifierTranslator extends TrimmingQualifierTranslator { - - // since LIKE IGNORE CASE requires more contextual information than the - // super - // translator can provide, we are using an internal element stack to trace - // translation - // context.. Maybe it is a good idea to introduce it in the superclass? - private List<Expression> expressionStack; - - SQLServerTrimmingQualifierTranslator(QueryAssembler queryAssembler, String trimFunction) { - super(queryAssembler, trimFunction); - expressionStack = new ArrayList<>(); - } - - @Override - public void startNode(Expression node, Expression parentNode) { - push(node); - super.startNode(node, parentNode); - } - - @Override - protected void processColumn(DbAttribute dbAttr) { - - Expression node = peek(1); - - boolean likeCI = node != null && dbAttr.getType() == Types.CLOB - && (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE); - - if (likeCI) { - out.append("CAST("); - } - - super.processColumn(dbAttr); - - if (likeCI) { - out.append(" AS NVARCHAR(MAX))"); - } - } - - @Override - protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) { - Expression node = peek(1); - - boolean likeCI = node != null && dbAttr.getType() == Types.CLOB - && (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE); - - if (likeCI) { - out.append("CAST("); - } - - super.processColumnWithQuoteSqlIdentifiers(dbAttr, node); - - if (likeCI) { - out.append(" AS NVARCHAR(MAX))"); - } - } - - @Override - public void endNode(Expression node, Expression parentNode) { - super.endNode(node, parentNode); - pop(); - } - - private void push(Expression node) { - expressionStack.add(node); - } - - private void pop() { - int len = expressionStack.size(); - if (len > 0) { - expressionStack.remove(len - 1); - } - } - - private Expression peek(int tailIndex) { - int index = expressionStack.size() - tailIndex - 1; - if (index < 0) { - return null; - } - - return expressionStack.get(index); - } - - /** - * @since 4.0 - */ - @Override - protected void appendFunction(ASTFunctionCall functionExpression) { - switch (functionExpression.getFunctionName()) { - case "LENGTH": - out.append("LEN"); - break; - case "LOCATE": - out.append("CHARINDEX"); - break; - case "MOD": - // noop - break; - case "TRIM": - out.append("LTRIM(RTRIM"); - break; - case "CURRENT_DATE": - out.append("{fn CURDATE()}"); - break; - case "CURRENT_TIME": - out.append("{fn CURTIME()}"); - break; - default: - super.appendFunction(functionExpression); - } - } - - /** - * @since 4.0 - */ - @Override - protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) { - if("MOD".equals(functionExpression.getFunctionName())) { - out.append(" % "); - } else { - super.appendFunctionArgDivider(functionExpression); - } - } - - /** - * @since 4.0 - */ - @Override - protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) { - if("MOD".equals(functionExpression.getFunctionName())) { - out.delete(out.length() - " % ".length(), out.length()); - } else { - super.clearLastFunctionArgDivider(functionExpression); - if("TRIM".equals(functionExpression.getFunctionName())) { - out.append(")"); - } - } - - if(functionExpression instanceof ASTExtract) { - out.append(")"); - } - } - - @Override - protected boolean parenthesisNeeded(Expression node, Expression parentNode) { - if (node.getType() == Expression.FUNCTION_CALL) { - if (node instanceof ASTExtract) { - return false; - } - } - - return super.parenthesisNeeded(node, parentNode); - } - - @Override - protected void appendExtractFunction(ASTExtract functionExpression) { - out.append("DATEPART("); - switch (functionExpression.getPart()) { - case DAY_OF_MONTH: - out.append("DAY"); - break; - case DAY_OF_WEEK: - out.append("WEEKDAY"); - break; - case DAY_OF_YEAR: - out.append("DAYOFYEAR"); - break; - default: - out.append(functionExpression.getPart().name()); - } - out.append(" , "); - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java new file mode 100644 index 0000000..b629840 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java @@ -0,0 +1,46 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.dba.sqlserver.sqltree; + +import org.apache.cayenne.access.sqlbuilder.QuotingAppendable; +import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode; + +/** + * @since 4.2 + */ +public class SQLServerColumnNode extends TrimmingColumnNode { + + public SQLServerColumnNode(ColumnNode columnNode) { + super(columnNode); + } + + @Override + protected void appendClobColumnNode(QuotingAppendable buffer) { + buffer.append(" CAST("); + appendColumnNode(buffer); + buffer.append(" AS NVARCHAR(MAX))"); + } + + @Override + public SQLServerColumnNode copy() { + return new SQLServerColumnNode(columnNode.deepCopy()); + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java index 71fa2d9..2aaff9d 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java @@ -23,12 +23,11 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; import java.util.List; +import java.util.function.Function; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory; -import org.apache.cayenne.access.translator.select.QualifierTranslator; -import org.apache.cayenne.access.translator.select.QueryAssembler; -import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.ByteArrayType; import org.apache.cayenne.access.types.ByteType; import org.apache.cayenne.access.types.CharType; @@ -43,15 +42,13 @@ import org.apache.cayenne.dba.DefaultQuotingStrategy; import org.apache.cayenne.dba.JdbcAdapter; import org.apache.cayenne.dba.PkGenerator; import org.apache.cayenne.dba.QuotingStrategy; +import org.apache.cayenne.dba.sqlserver.SQLServerTreeProcessor; import org.apache.cayenne.di.Inject; import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.query.SelectQuery; import org.apache.cayenne.resource.ResourceLocator; /** - * DbAdapter implementation for <a href="http://www.sybase.com">Sybase - * RDBMS</a>. + * DbAdapter implementation for <a href="http://www.sybase.com">Sybase RDBMS</a>. */ public class SybaseAdapter extends JdbcAdapter { @@ -79,20 +76,12 @@ public class SybaseAdapter extends JdbcAdapter { return new SybaseEJBQLTranslatorFactory(); } - /** - * @since 4.0 - */ - @Override - public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) { - return new SybaseSelectTranslator(query, this, entityResolver); - } - /** - * @since 4.0 + * @since 4.2 */ @Override - public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) { - return new SybaseQualifierTranslator(queryAssembler); + public Function<Node, Node> getSqlTreeProcessor() { + return new SQLServerTreeProcessor(); } /** http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java deleted file mode 100644 index 0f6e239..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java +++ /dev/null @@ -1,114 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ - -package org.apache.cayenne.dba.sybase; - -import org.apache.cayenne.access.translator.select.QualifierTranslator; -import org.apache.cayenne.access.translator.select.QueryAssembler; -import org.apache.cayenne.exp.Expression; -import org.apache.cayenne.exp.parser.ASTExtract; -import org.apache.cayenne.exp.parser.ASTFunctionCall; - -/** - * @since 4.0 - */ -public class SybaseQualifierTranslator extends QualifierTranslator { - - public SybaseQualifierTranslator(QueryAssembler queryAssembler) { - super(queryAssembler); - } - - @Override - protected void appendFunction(ASTFunctionCall functionExpression) { - switch (functionExpression.getFunctionName()) { - case "MOD": - case "CONCAT": - // noop - break; - case "LENGTH": - out.append("LEN"); - break; - case "LOCATE": - out.append("CHARINDEX"); - break; - default: - super.appendFunction(functionExpression); - } - } - - @Override - protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) { - switch (functionExpression.getFunctionName()) { - case "MOD": - out.append(" % "); - break; - case "CONCAT": - out.append(" + "); - break; - default: - super.appendFunctionArgDivider(functionExpression); - } - } - - @Override - protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) { - switch (functionExpression.getFunctionName()) { - case "MOD": - case "CONCAT": - out.delete(out.length() - 3, out.length()); - break; - default: - super.clearLastFunctionArgDivider(functionExpression); - } - - if(functionExpression instanceof ASTExtract) { - out.append(")"); - } - } - - @Override - protected boolean parenthesisNeeded(Expression node, Expression parentNode) { - if (node.getType() == Expression.FUNCTION_CALL) { - if (node instanceof ASTExtract) { - return false; - } - } - - return super.parenthesisNeeded(node, parentNode); - } - - @Override - protected void appendExtractFunction(ASTExtract functionExpression) { - out.append("datepart("); - switch (functionExpression.getPart()) { - case DAY_OF_MONTH: - out.append("day"); - break; - case DAY_OF_WEEK: - out.append("weekday"); - break; - case DAY_OF_YEAR: - out.append("dayofyear"); - break; - default: - out.append(functionExpression.getPart().name().toLowerCase()); - } - out.append(" , "); - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java deleted file mode 100644 index 93af596..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java +++ /dev/null @@ -1,52 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ -package org.apache.cayenne.dba.sybase; - -import org.apache.cayenne.access.translator.select.DefaultSelectTranslator; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.query.Query; - -public class SybaseSelectTranslator extends DefaultSelectTranslator { - /** - * @since 4.0 - */ - public SybaseSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) { - super(query, adapter, entityResolver); - } - - @Override - protected void appendLimitAndOffsetClauses(StringBuilder buffer) { - - int limit = queryMetadata.getFetchLimit(); - int offset = queryMetadata.getFetchOffset(); - - if (limit > 0) { - String sql = buffer.toString(); - - // If contains distinct insert top limit after - if (sql.startsWith("SELECT DISTINCT ")) { - buffer.replace(0, 15, "SELECT DISTINCT TOP " + (offset + limit)); - - } else { - buffer.replace(0, 6, "SELECT TOP " + (offset + limit)); - } - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java index 35c36f2..fafd258 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java @@ -686,6 +686,13 @@ public class ColumnSelect<T> extends FluentSelect<T> { return having; } + /** + * @since 4.2 + */ + public boolean isDistinct() { + return distinct; + } + @Override public T selectFirst(ObjectContext context) { return context.selectFirst(limit(1)); http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java index 8a9e7d9..9ee83bf 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java @@ -232,7 +232,7 @@ class SelectQueryMetadata extends BaseQueryMetadata { */ @Override public Map<String, String> getPathSplitAliases() { - return pathSplitAliases != null ? pathSplitAliases : Collections.<String, String> emptyMap(); + return pathSplitAliases != null ? pathSplitAliases : Collections.emptyMap(); } /** @@ -328,18 +328,17 @@ class SelectQueryMetadata extends BaseQueryMetadata { * (possibly including joint prefetch). * This information will be used to correctly create Persistent object back from raw result. * - * This method is actually repeating logic of - * {@link org.apache.cayenne.access.translator.select.DefaultSelectTranslator#appendQueryColumns}. - * Here we don't care about intermediate joins and few other things so it's shorter. - * Logic of these methods should be unified and simplified, possibly to a single source of metadata, - * generated only once and used everywhere. - * * @param query original query * @param column full object column * @param resolver entity resolver to get ObjEntity and ClassDescriptor * @return Entity result */ private EntityResult buildEntityResultForColumn(SelectQuery<?> query, BaseProperty<?> column, EntityResolver resolver) { + // This method is actually repeating logic of DescriptorColumnExtractor. + // Here we don't care about intermediate joins and few other things so it's shorter. + // Logic of these methods should be unified and simplified, possibly to a single source of metadata, + // generated only once and used everywhere. + final EntityResult result = new EntityResult(column.getType()); // Collecting visitor for ObjAttributes and toOne relationships http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java b/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java new file mode 100644 index 0000000..80df070 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java @@ -0,0 +1,260 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.util; + +/** + * @since 4.1 + */ +public final class ArrayUtil { + + public static int[][] sliceArray(int[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new int[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + int[][] result = new int[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new int[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static long[][] sliceArray(long[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new long[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + long[][] result = new long[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new long[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static float[][] sliceArray(float[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new float[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + float[][] result = new float[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new float[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static double[][] sliceArray(double[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new double[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + double[][] result = new double[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new double[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static short[][] sliceArray(short[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new short[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + short[][] result = new short[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new short[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static char[][] sliceArray(char[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new char[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + char[][] result = new char[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new char[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static boolean[][] sliceArray(boolean[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new boolean[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + boolean[][] result = new boolean[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new boolean[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static byte[][] sliceArray(byte[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new byte[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + byte[][] result = new byte[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new byte[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } + + public static Object[][] sliceArray(Object[] array, int batchSize) { + if(array == null) { + return null; + } + int length = array.length; + + if(length <= batchSize) { + return new Object[][]{array}; + } + + int batches = length / batchSize; + if(length % batchSize > 0) { + batches++; + } + + Object[][] result = new Object[batches][]; + int offset = 0; + for(int i=0; i<batches; i++) { + int nextSize = i < batches - 1 ? batchSize : length - offset; + result[i] = new Object[nextSize]; + System.arraycopy(array, offset, result[i], 0, nextSize); + offset += nextSize; + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java b/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java index 7a10d10..03d3774 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java @@ -76,8 +76,7 @@ public class FlattenedRelationshipsIT extends ServerCase { tFlattenedTest2.setColumns("FT2_ID", "FT1_ID", "NAME"); tFlattenedTest3 = new TableHelper(dbHelper, "FLATTENED_TEST_3"); - tFlattenedTest3.setColumns("FT3_ID", "FT2_ID", "NAME").setColumnTypes( - Types.INTEGER, Types.INTEGER, Types.VARCHAR); + tFlattenedTest3.setColumns("FT3_ID", "FT2_ID", "NAME"); tComplexJoin = new TableHelper(dbHelper, "COMPLEX_JOIN"); tComplexJoin.setColumns("PK", "FT1_FK", "FT3_FK", "EXTRA_COLUMN"); http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java new file mode 100644 index 0000000..155c968 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java @@ -0,0 +1,43 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import java.sql.Types; + +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbEntity; + +/** + * @since 4.2 + */ +public class BaseColumnExtractorTest { + + DbEntity createMockDbEntity(String entityName) { + DbEntity entity = new DbEntity(entityName); + DbAttribute id = new DbAttribute("id"); + id.setPrimaryKey(true); + id.setType(Types.BIGINT); + DbAttribute name = new DbAttribute("name"); + name.setType(Types.VARBINARY); + entity.addAttribute(id); + entity.addAttribute(name); + return entity; + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java new file mode 100644 index 0000000..dd1c7e7 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java @@ -0,0 +1,55 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.access.jdbc.ColumnDescriptor; +import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode; +import org.apache.cayenne.exp.Property; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @since 4.2 + */ +public class ColumnDescriptorStageTest { + + @Test + public void perform() { + TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder() + .withDistinct(true) + .withMetaData(new MockQueryMetadataBuilder() + .withSuppressDistinct() + .build()) + .build(); + TranslatorContext context = new MockTranslatorContext(wrapper); + + context.addResultNode(new EmptyNode()); + context.addResultNode(new EmptyNode(), "key"); + context.addResultNode(new EmptyNode(), false, Property.COUNT, "key2"); + + ColumnDescriptorStage stage = new ColumnDescriptorStage(); + stage.perform(context); + + assertEquals(1, context.getColumnDescriptors().size()); + ColumnDescriptor descriptor = context.getColumnDescriptors().iterator().next(); + assertEquals("key", descriptor.getDataRowKey()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java new file mode 100644 index 0000000..2836ab8 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java @@ -0,0 +1,94 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import java.sql.Types; +import java.util.Collection; +import java.util.Collections; + +import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode; +import org.apache.cayenne.exp.ExpressionFactory; +import org.apache.cayenne.exp.property.BaseProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.map.ObjAttribute; +import org.apache.cayenne.map.ObjEntity; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.*; + +/** + * @since 4.2 + */ +public class CustomColumnSetExtractorTest extends BaseColumnExtractorTest { + + @Test + public void testExtractWithoutPrefix() { + DbEntity mockDbEntity = createMockDbEntity("mock"); + TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder() + .withMetaData(new MockQueryMetadataBuilder() + .withDbEntity(mockDbEntity) + .build()) + .build(); + TranslatorContext context = new MockTranslatorContext(wrapper); + + DataMap dataMap = new DataMap(); + dataMap.addDbEntity(mockDbEntity); + + ObjEntity entity = new ObjEntity(); + entity.setName("mock"); + entity.setDataMap(dataMap); + entity.setDbEntity(mockDbEntity); + + ObjAttribute attribute = new ObjAttribute(); + attribute.setName("not_name"); + attribute.setDbAttributePath("name"); + attribute.setType("my.type"); + entity.addAttribute(attribute); + + dataMap.addObjEntity(entity); + + EntityResolver resolver = new EntityResolver(); + resolver.addDataMap(dataMap); + + BaseProperty<?> property0 = PropertyFactory.createBase(ExpressionFactory.dbPathExp("name"), String.class); + Collection<BaseProperty<?>> properties = Collections.singleton(property0); + + CustomColumnSetExtractor extractor = new CustomColumnSetExtractor(context, properties); + extractor.extract(); + + assertEquals(1, context.getResultNodeList().size()); + + ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0); + + assertSame(property0, descriptor0.getProperty()); + assertNotNull(descriptor0.getNode()); + assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class)); + assertFalse(descriptor0.isAggregate()); + assertTrue(descriptor0.isInDataRow()); + assertNotNull(descriptor0.getDbAttribute()); + assertNull(descriptor0.getDataRowKey()); + assertEquals(Types.VARBINARY, descriptor0.getJdbcType()); + assertEquals("java.lang.String", descriptor0.getJavaType()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java new file mode 100644 index 0000000..4585347 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java @@ -0,0 +1,121 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode; +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.DbRelationship; +import org.apache.cayenne.map.JoinType; +import org.junit.Test; + +import java.sql.Types; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.*; + +public class DbEntityColumnExtractorTest extends BaseColumnExtractorTest { + + @Test + public void testExtractNoPrefix() { + TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder() + .withMetaData(new MockQueryMetadataBuilder() + .withDbEntity(createMockDbEntity("mock")) + .build()) + .build(); + TranslatorContext context = new MockTranslatorContext(wrapper); + + DbEntityColumnExtractor extractor = new DbEntityColumnExtractor(context); + extractor.extract(null); + + assertEquals(2, context.getResultNodeList().size()); + + ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0); + ResultNodeDescriptor descriptor1 = context.getResultNodeList().get(1); + + assertNull(descriptor0.getProperty()); + assertNotNull(descriptor0.getNode()); + assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class)); + assertFalse(descriptor0.isAggregate()); + assertTrue(descriptor0.isInDataRow()); + assertEquals("id", descriptor0.getDataRowKey()); + assertNotNull(descriptor0.getDbAttribute()); + assertEquals(Types.BIGINT, descriptor0.getJdbcType()); + + assertNull(descriptor1.getProperty()); + assertNotNull(descriptor1.getNode()); + assertThat(descriptor1.getNode(), instanceOf(ColumnNode.class)); + assertFalse(descriptor1.isAggregate()); + assertTrue(descriptor1.isInDataRow()); + assertNotNull(descriptor1.getDbAttribute()); + assertEquals("name", descriptor1.getDataRowKey()); + assertEquals(Types.VARBINARY, descriptor1.getJdbcType()); + assertEquals("byte[]", descriptor1.getJavaType()); + } + + @Test + public void testExtractWithPrefix() { + DbEntity mockDbEntity = createMockDbEntity("mock1"); + DbEntity mock2DbEntity = createMockDbEntity("mock2"); + DataMap dataMap = new DataMap(); + dataMap.addDbEntity(mockDbEntity); + dataMap.addDbEntity(mock2DbEntity); + mockDbEntity.setDataMap(dataMap); + + TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder() + .withMetaData(new MockQueryMetadataBuilder() + .withDbEntity(mockDbEntity) + .build()) + .build(); + TranslatorContext context = new MockTranslatorContext(wrapper); + + DbRelationship relationship = new DbRelationship(); + relationship.setSourceEntity(mockDbEntity); + relationship.setTargetEntityName("mock1"); + context.getTableTree().addJoinTable("prefix", relationship, JoinType.INNER); + + DbEntityColumnExtractor extractor = new DbEntityColumnExtractor(context); + extractor.extract("prefix"); + + assertEquals(2, context.getResultNodeList().size()); + + ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0); + ResultNodeDescriptor descriptor1 = context.getResultNodeList().get(1); + + assertNull(descriptor0.getProperty()); + assertNotNull(descriptor0.getNode()); + assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class)); + assertFalse(descriptor0.isAggregate()); + assertTrue(descriptor0.isInDataRow()); + assertEquals("prefix.id", descriptor0.getDataRowKey()); + assertNotNull(descriptor0.getDbAttribute()); + assertEquals(Types.BIGINT, descriptor0.getJdbcType()); + + assertNull(descriptor1.getProperty()); + assertNotNull(descriptor1.getNode()); + assertThat(descriptor1.getNode(), instanceOf(ColumnNode.class)); + assertFalse(descriptor1.isAggregate()); + assertTrue(descriptor1.isInDataRow()); + assertNotNull(descriptor1.getDbAttribute()); + assertEquals("prefix.name", descriptor1.getDataRowKey()); + assertEquals(Types.VARBINARY, descriptor1.getJdbcType()); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java new file mode 100644 index 0000000..04b156c --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java @@ -0,0 +1,136 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.access.DataContext; +import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.query.SelectQuery; +import org.apache.cayenne.testdo.testmap.Artist; +import org.apache.cayenne.testdo.testmap.Painting; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @since 4.2 + */ +@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) +public class DefaultObjectSelectTranslatorIT extends ServerCase { + + @Inject + DataContext context; + + @Inject + DbAdapter adapter; + + @Test + public void simpleSql() { + SelectQuery<Artist> select = SelectQuery.query(Artist.class); + DefaultSelectTranslator translator = new DefaultSelectTranslator(select, adapter, context.getEntityResolver()); + + String sql = translator.getSql(); + assertTrue(sql.startsWith("SELECT ")); + assertTrue(sql.contains("t0.ARTIST_NAME")); + assertTrue(sql.contains("t0.DATE_OF_BIRTH")); + assertTrue(sql.contains("t0.ARTIST_ID")); + assertTrue(sql.indexOf("FROM ARTIST t0") > sql.indexOf("t0.ARTIST_ID")); + + assertEquals(0, translator.getBindings().length); + assertEquals(3, translator.getResultColumns().length); + assertEquals("ARTIST_NAME", translator.getResultColumns()[0].getDataRowKey()); + assertEquals("DATE_OF_BIRTH", translator.getResultColumns()[1].getDataRowKey()); + assertEquals("ARTIST_ID", translator.getResultColumns()[2].getDataRowKey()); + assertFalse(translator.hasJoins()); + assertFalse(translator.isSuppressingDistinct()); + } + + @Test + public void selectWithComplexWhere() { + SelectQuery<Artist> select = SelectQuery.query(Artist.class, Artist.ARTIST_NAME.eq("artist") + .andExp(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).eq("painting"))); + + DefaultSelectTranslator translator = new DefaultSelectTranslator(select, adapter, context.getEntityResolver()); + + String sql = translator.getSql(); + assertTrue(sql.startsWith("SELECT DISTINCT")); + assertTrue(sql.contains("t0.ARTIST_NAME")); + assertTrue(sql.contains("t0.DATE_OF_BIRTH")); + assertTrue(sql.contains("t0.ARTIST_ID")); + assertTrue(sql.indexOf("FROM ARTIST t0") > sql.indexOf("t0.ARTIST_ID")); + assertTrue(sql.indexOf("JOIN PAINTING t1") > sql.indexOf("FROM ARTIST t0")); + assertTrue(sql.indexOf("WHERE") > sql.indexOf("JOIN PAINTING t1")); + + assertEquals(2, translator.getBindings().length); + assertEquals("ARTIST_NAME", translator.getBindings()[0].getAttribute().getName()); + assertEquals("PAINTING_TITLE", translator.getBindings()[1].getAttribute().getName()); + + assertEquals(3, translator.getResultColumns().length); + assertEquals("ARTIST_NAME", translator.getResultColumns()[0].getDataRowKey()); + assertEquals("DATE_OF_BIRTH", translator.getResultColumns()[1].getDataRowKey()); + assertEquals("ARTIST_ID", translator.getResultColumns()[2].getDataRowKey()); + + assertTrue(translator.hasJoins()); + assertFalse(translator.isSuppressingDistinct()); + } + + @Test + public void selectWithJointPrefetch() { + SelectQuery<Painting> select = SelectQuery.query(Painting.class); + select.addPrefetch(Painting.TO_ARTIST.joint()); + + DefaultSelectTranslator translator = new DefaultSelectTranslator(select, adapter, context.getEntityResolver()); + + String sql = translator.getSql(); + + assertTrue(sql.startsWith("SELECT DISTINCT")); + assertTrue(sql.contains("t0.ESTIMATED_PRICE")); + assertTrue(sql.contains("t0.PAINTING_DESCRIPTION")); + assertTrue(sql.contains("t0.PAINTING_TITLE")); + assertTrue(sql.contains("t0.ARTIST_ID")); + assertTrue(sql.contains("t0.GALLERY_ID")); + assertTrue(sql.contains("t0.PAINTING_ID")); + assertTrue(sql.contains("t1.ARTIST_ID")); + assertTrue(sql.contains("t1.DATE_OF_BIRTH")); + assertTrue(sql.contains("t1.ARTIST_NAME")); + assertTrue(sql.indexOf("FROM PAINTING t0") > sql.indexOf("t1.ARTIST_NAME")); + assertTrue(sql.indexOf("LEFT JOIN ARTIST t1") > sql.indexOf("FROM PAINTING t0")); + + assertEquals(0, translator.getBindings().length); + + assertEquals(9, translator.getResultColumns().length); + assertEquals("ESTIMATED_PRICE", translator.getResultColumns()[0].getDataRowKey()); + assertEquals("PAINTING_DESCRIPTION", translator.getResultColumns()[1].getDataRowKey()); + assertEquals("PAINTING_TITLE", translator.getResultColumns()[2].getDataRowKey()); + assertEquals("ARTIST_ID", translator.getResultColumns()[3].getDataRowKey()); + assertEquals("GALLERY_ID", translator.getResultColumns()[4].getDataRowKey()); + assertEquals("PAINTING_ID", translator.getResultColumns()[5].getDataRowKey()); + + assertEquals("toArtist.ARTIST_ID", translator.getResultColumns()[6].getDataRowKey()); + assertEquals("toArtist.DATE_OF_BIRTH", translator.getResultColumns()[7].getDataRowKey()); + assertEquals("toArtist.ARTIST_NAME", translator.getResultColumns()[8].getDataRowKey()); + + assertTrue(translator.hasJoins()); + assertFalse(translator.isSuppressingDistinct()); + } +} \ No newline at end of file