Skip to content

Commit 4ad9ceb

Browse files
committed
[WIP][OPENJPA-2940] Implementing JPQL CAST TO STRING
1 parent ffb548b commit 4ad9ceb

File tree

11 files changed

+356
-6
lines changed

11 files changed

+356
-6
lines changed

openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,11 @@ public Value getDateTimeField(DateTimeExtractField field, Value value) {
380380
public Value getDateTimePart(DateTimeExtractPart part, Value value) {
381381
return new ExtractDateTimePart((Val) value, part);
382382
}
383+
384+
@Override
385+
public Value newTypecastAsString(Value value) {
386+
return new TypecastAsString((Val) value);
387+
}
383388

384389
@Override
385390
public Parameter newParameter(Object name, Class type) {
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.openjpa.jdbc.kernel.exps;
20+
21+
import java.sql.SQLException;
22+
23+
import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
24+
import org.apache.openjpa.jdbc.sql.DBDictionary;
25+
import org.apache.openjpa.jdbc.sql.Joins;
26+
import org.apache.openjpa.jdbc.sql.Result;
27+
import org.apache.openjpa.jdbc.sql.SQLBuffer;
28+
import org.apache.openjpa.jdbc.sql.Select;
29+
import org.apache.openjpa.kernel.Filters;
30+
import org.apache.openjpa.kernel.exps.ExpressionVisitor;
31+
import org.apache.openjpa.meta.ClassMetaData;
32+
33+
/**
34+
* Returns the temporal field of a given date or time.
35+
*
36+
*/
37+
public class TypecastAsString
38+
extends AbstractVal {
39+
40+
private static final long serialVersionUID = 1L;
41+
private final Val _val;
42+
private ClassMetaData _meta = null;
43+
44+
/**
45+
* Constructor. Provides the value to be casted to string.
46+
*/
47+
public TypecastAsString(Val val) {
48+
_val = val;
49+
}
50+
51+
public Val getVal() {
52+
return _val;
53+
}
54+
55+
@Override
56+
public ClassMetaData getMetaData() {
57+
return _meta;
58+
}
59+
60+
@Override
61+
public void setMetaData(ClassMetaData meta) {
62+
_meta = meta;
63+
}
64+
65+
@Override
66+
public Class getType() {
67+
return String.class;
68+
}
69+
70+
@Override
71+
public void setImplicitType(Class type) {
72+
}
73+
74+
@Override
75+
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
76+
ExpState valueState = _val.initialize(sel, ctx, 0);
77+
return new TypecastAsStringExpState(valueState.joins, valueState);
78+
}
79+
80+
/**
81+
* Expression state.
82+
*/
83+
private static class TypecastAsStringExpState
84+
extends ExpState {
85+
86+
public final ExpState valueState;
87+
88+
public TypecastAsStringExpState(Joins joins, ExpState valueState) {
89+
super(joins);
90+
this.valueState = valueState;
91+
}
92+
}
93+
94+
@Override
95+
public void select(Select sel, ExpContext ctx, ExpState state,
96+
boolean pks) {
97+
sel.select(newSQLBuffer(sel, ctx, state), this);
98+
}
99+
100+
@Override
101+
public void selectColumns(Select sel, ExpContext ctx, ExpState state, boolean pks) {
102+
TypecastAsStringExpState casstate = (TypecastAsStringExpState) state;
103+
_val.selectColumns(sel, ctx, casstate.valueState, true);
104+
}
105+
106+
@Override
107+
public void groupBy(Select sel, ExpContext ctx, ExpState state) {
108+
sel.groupBy(newSQLBuffer(sel, ctx, state));
109+
}
110+
111+
@Override
112+
public void orderBy(Select sel, ExpContext ctx, ExpState state,
113+
boolean asc) {
114+
sel.orderBy(newSQLBuffer(sel, ctx, state), asc, false, getSelectAs());
115+
}
116+
117+
private SQLBuffer newSQLBuffer(Select sel, ExpContext ctx, ExpState state) {
118+
calculateValue(sel, ctx, state, null, null);
119+
SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary());
120+
appendTo(sel, ctx, state, buf, 0);
121+
return buf;
122+
}
123+
124+
@Override
125+
public Object load(ExpContext ctx, ExpState state, Result res)
126+
throws SQLException {
127+
return Filters.convert(res.getObject(this,
128+
JavaSQLTypes.JDBC_DEFAULT, null), getType());
129+
}
130+
131+
@Override
132+
public void calculateValue(Select sel, ExpContext ctx, ExpState state,
133+
Val other, ExpState otherState) {
134+
TypecastAsStringExpState casstate = (TypecastAsStringExpState) state;
135+
_val.calculateValue(sel, ctx, casstate.valueState, null, null);
136+
}
137+
138+
@Override
139+
public int length(Select sel, ExpContext ctx, ExpState state) {
140+
return 1;
141+
}
142+
143+
@Override
144+
public void appendTo(Select sel, ExpContext ctx, ExpState state,
145+
SQLBuffer sql, int index) {
146+
DBDictionary dict = ctx.store.getDBDictionary();
147+
String func = dict.castFunction;
148+
149+
int fieldPart = func.indexOf("{0}");
150+
int targetPart = func.indexOf("{1}");
151+
String part1 = func.substring(0, fieldPart);
152+
String part2 = func.substring(fieldPart + 3, targetPart);
153+
String part3 = func.substring(targetPart + 3);
154+
155+
TypecastAsStringExpState casstate = (TypecastAsStringExpState) state;
156+
sql.append(part1);
157+
_val.appendTo(sel, ctx, casstate.valueState, sql, 0);
158+
sql.append(part2);
159+
sql.append(dict.varcharTypeName);
160+
sql.append(part3);
161+
}
162+
163+
@Override
164+
public void acceptVisit(ExpressionVisitor visitor) {
165+
visitor.enter(this);
166+
_val.acceptVisit(visitor);
167+
visitor.exit(this);
168+
}
169+
170+
@Override
171+
public int getId() {
172+
return Val.EXTRACTDTF_VAL;
173+
}
174+
}
175+

openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ public class DBDictionary
315315
public int maxEmbeddedBlobSize = -1;
316316
public int maxEmbeddedClobSize = -1;
317317
public int inClauseLimit = -1;
318-
318+
319319
/**
320320
* Attention, while this is named datePrecision it actually only get used for Timestamp handling!
321321
* @see StateManagerImpl#roundTimestamp(Timestamp, int)

openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DerbyDictionary.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public DerbyDictionary() {
6969
supportsNullUniqueColumn = false;
7070

7171
supportsComments = true;
72-
72+
7373
// Derby does still not support 'WITH TIMEZONE' from the SQL92 standard
7474

7575
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{

openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*/
1919
package org.apache.openjpa.kernel.exps;
2020

21-
import java.time.temporal.ChronoField;
2221
import java.time.temporal.Temporal;
2322
import java.util.Date;
2423

@@ -258,6 +257,12 @@ Subquery newSubquery(ClassMetaData candidate, boolean subs,
258257
* Return the Date or time part of the given temporal value
259258
*/
260259
Value getDateTimePart(DateTimeExtractPart part, Value value);
260+
261+
/**
262+
* Returns the value typecasted as string
263+
*
264+
*/
265+
Value newTypecastAsString(Value value);
261266

262267
/**
263268
* Return a value representing a parameter for the given value. The

openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,11 @@ public Value getDateTimeField(DateTimeExtractField field, Value value) {
554554
public Value getDateTimePart(DateTimeExtractPart part, Value value) {
555555
return new ExtractDateTimePart(part, (Val) value);
556556
}
557+
558+
@Override
559+
public Value newTypecastAsString(Value value) {
560+
return new TypecastAsString((Val) value);
561+
}
557562

558563
@Override
559564
public Parameter newParameter(Object name, Class type) {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.openjpa.kernel.exps;
20+
21+
import java.sql.Date;
22+
import java.sql.Time;
23+
import java.sql.Timestamp;
24+
import java.time.Instant;
25+
import java.time.LocalDate;
26+
import java.time.LocalDateTime;
27+
import java.time.LocalTime;
28+
import java.time.ZoneId;
29+
import java.time.format.DateTimeFormatter;
30+
31+
import org.apache.openjpa.kernel.StoreContext;
32+
33+
/**
34+
* Casts a given value as string
35+
*
36+
*/
37+
class TypecastAsString
38+
extends Val {
39+
40+
41+
private static final long serialVersionUID = 1L;
42+
private final Val _val;
43+
44+
/**
45+
* Constructor. Provide target field and the value.
46+
*/
47+
public TypecastAsString(Val val) {
48+
_val = val;
49+
}
50+
51+
@Override
52+
public Class getType() {
53+
return String.class;
54+
}
55+
56+
@Override
57+
public void setImplicitType(Class type) {
58+
}
59+
60+
@Override
61+
protected Object eval(Object candidate, Object orig,
62+
StoreContext ctx, Object[] params) {
63+
64+
Object r = _val.eval(candidate, orig, ctx, params);
65+
Class<?> clazz = r.getClass();
66+
if (Time.class.isAssignableFrom(clazz)) {
67+
return ((Time) r).toLocalTime().format(DateTimeFormatter.ISO_TIME);
68+
} else if (Timestamp.class.isAssignableFrom(clazz)) {
69+
return ((Timestamp) r).toLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
70+
} else if (LocalDateTime.class.isAssignableFrom(clazz)) {
71+
return ((LocalDateTime) r).toLocalTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
72+
} else if (LocalTime.class.isAssignableFrom(clazz)) {
73+
return ((LocalTime) r).format(DateTimeFormatter.ISO_LOCAL_TIME);
74+
} else if (Instant.class.isAssignableFrom(clazz)) {
75+
return LocalDateTime.ofInstant((Instant) r, ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
76+
} else if (Date.class.isAssignableFrom(clazz)) {
77+
return LocalDateTime.ofInstant(((Date) r).toInstant(), ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
78+
} else if (LocalDate.class.isAssignableFrom(clazz)) {
79+
return ((LocalDate) r).format(DateTimeFormatter.ISO_LOCAL_DATE);
80+
} else if (Number.class.isAssignableFrom(clazz)) {
81+
return String.valueOf((Number) r);
82+
} else if (Boolean.class.isAssignableFrom(clazz)) {
83+
return String.valueOf((Boolean) r);
84+
}
85+
throw new IllegalArgumentException();
86+
}
87+
88+
@Override
89+
public void acceptVisit(ExpressionVisitor visitor) {
90+
visitor.enter(this);
91+
_val.acceptVisit(visitor);
92+
visitor.exit(this);
93+
}
94+
95+
}
96+

openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,9 @@ else if (node.getChildCount() == 2
15211521

15221522
case JJTTIMESTAMPLITERAL:
15231523
return factory.newLiteral(node.text, Literal.TYPE_TIMESTAMP);
1524+
1525+
case JJTSTRINGCAST:
1526+
return factory.newTypecastAsString(getValue(onlyChild(node)));
15241527

15251528
default:
15261529
throw parseException(EX_FATAL, "bad-tree",

openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ TOKEN [ IGNORE_CASE ]: /* basics */
170170
| < ENTRY: "ENTRY" >
171171
| < LOCAL: "LOCAL" >
172172
| < EXTRACT: "EXTRACT" >
173+
| < CAST: "CAST" >
174+
| < STRING: "STRING" >
173175
}
174176

175177
TOKEN [ IGNORE_CASE ]: /* aggregates */
@@ -1122,7 +1124,7 @@ void string_value() : { }
11221124

11231125
void string_expression() : { }
11241126
{
1125-
input_parameter() | string_primary()
1127+
input_parameter() | string_primary()
11261128
}
11271129

11281130

@@ -1133,8 +1135,9 @@ void string_primary() : { }
11331135
LOOKAHEAD(general_identification_variable()) general_identification_variable() |
11341136
LOOKAHEAD(identification_variable()) identification_variable() |
11351137
LOOKAHEAD("(" string_expression()) "(" string_expression() ")" |
1136-
functions_returning_strings() | LOOKAHEAD("(" subquery()) "(" subquery() ")"
1137-
| case_expression()
1138+
functions_returning_strings() |
1139+
string_cast_function() |
1140+
LOOKAHEAD("(" subquery()) "(" subquery() ")" | case_expression()
11381141
}
11391142

11401143

@@ -1222,6 +1225,10 @@ void functions_returning_strings() : { }
12221225
concat() | substring() | trim() | lower() | upper()
12231226
}
12241227

1228+
void string_cast_function() #STRINGCAST : { }
1229+
{
1230+
<CAST> "(" scalar_expression() <AS> <STRING> ")"
1231+
}
12251232

12261233
void concat() #CONCAT : { }
12271234
{

0 commit comments

Comments
 (0)