Skip to content

Commit 2b5055a

Browse files
committed
Enhance MySQLTextResultSetRowPacket and MySQLDateBinaryProtocolValue to support LocalDateTime and LocalTime when value contains scale
1 parent 729e822 commit 2b5055a

File tree

16 files changed

+290
-85
lines changed

16 files changed

+290
-85
lines changed

RELEASE-NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Release 5.5.4-SNAPSHOT
22

3+
### Enhancements
4+
5+
1. Enhance MySQLTextResultSetRowPacket and MySQLDateBinaryProtocolValue to support LocalDateTime and LocalTime when value contains scale - [#37881](https://github.com/apache/shardingsphere/pull/37881)
6+
37
### Bug Fixes
48

59
## Release 5.5.3

database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/protocol/MySQLDateBinaryProtocolValue.java

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.shardingsphere.database.protocol.mysql.packet.command.query.binary.execute.protocol;
1919

20+
import lombok.RequiredArgsConstructor;
2021
import org.apache.shardingsphere.database.protocol.mysql.payload.MySQLPacketPayload;
2122

2223
import java.sql.SQLException;
@@ -31,6 +32,8 @@
3132
*/
3233
public final class MySQLDateBinaryProtocolValue implements MySQLBinaryProtocolValue {
3334

35+
private static final long NANOS_PER_SECOND = 1_000_000_000L;
36+
3437
@Override
3538
public Object read(final MySQLPacketPayload payload, final boolean unsigned) throws SQLException {
3639
int length = payload.readInt1();
@@ -60,42 +63,30 @@ private Timestamp getTimestampForDatetime(final MySQLPacketPayload payload) {
6063

6164
@Override
6265
public void write(final MySQLPacketPayload payload, final Object value) {
63-
LocalDateTime dateTime = getLocalDateTime(value);
64-
int year = dateTime.getYear();
65-
int month = dateTime.getMonthValue();
66-
int dayOfMonth = dateTime.getDayOfMonth();
67-
int hours = dateTime.getHour();
68-
int minutes = dateTime.getMinute();
69-
int seconds = dateTime.getSecond();
70-
int nanos = dateTime.getNano();
71-
boolean isTimeAbsent = 0 == hours && 0 == minutes && 0 == seconds;
72-
boolean isNanosAbsent = 0 == nanos;
66+
LocalDateTime dateTime;
67+
if (value instanceof LocalDate) {
68+
dateTime = ((LocalDate) value).atStartOfDay();
69+
} else {
70+
dateTime = value instanceof LocalDateTime ? (LocalDateTime) value : new Timestamp(((Date) value).getTime()).toLocalDateTime();
71+
}
72+
DateTimeValues values = buildDateTimeValues(dateTime);
73+
boolean isTimeAbsent = 0 == values.hours && 0 == values.minutes && 0 == values.seconds;
74+
boolean isNanosAbsent = 0 == values.nanos;
7375
if (isTimeAbsent && isNanosAbsent) {
7476
payload.writeInt1(4);
75-
writeDate(payload, year, month, dayOfMonth);
77+
writeDate(payload, values.year, values.month, values.dayOfMonth);
7678
return;
7779
}
7880
if (isNanosAbsent) {
7981
payload.writeInt1(7);
80-
writeDate(payload, year, month, dayOfMonth);
81-
writeTime(payload, hours, minutes, seconds);
82+
writeDate(payload, values.year, values.month, values.dayOfMonth);
83+
writeTime(payload, values.hours, values.minutes, values.seconds);
8284
return;
8385
}
8486
payload.writeInt1(11);
85-
writeDate(payload, year, month, dayOfMonth);
86-
writeTime(payload, hours, minutes, seconds);
87-
writeNanos(payload, nanos);
88-
}
89-
90-
@SuppressWarnings("UseOfObsoleteDateTimeApi")
91-
private LocalDateTime getLocalDateTime(final Object value) {
92-
if (value instanceof LocalDate) {
93-
return ((LocalDate) value).atStartOfDay();
94-
}
95-
if (value instanceof LocalDateTime) {
96-
return (LocalDateTime) value;
97-
}
98-
return new Timestamp(((Date) value).getTime()).toLocalDateTime();
87+
writeDate(payload, values.year, values.month, values.dayOfMonth);
88+
writeTime(payload, values.hours, values.minutes, values.seconds);
89+
writeNanos(payload, values.nanos);
9990
}
10091

10192
private void writeDate(final MySQLPacketPayload payload, final int year, final int month, final int dayOfMonth) {
@@ -113,4 +104,47 @@ private void writeTime(final MySQLPacketPayload payload, final int hourOfDay, fi
113104
private void writeNanos(final MySQLPacketPayload payload, final int nanos) {
114105
payload.writeInt4(nanos / 1000);
115106
}
107+
108+
private DateTimeValues buildDateTimeValues(final LocalDateTime dateTime) {
109+
int year = dateTime.getYear();
110+
int month = dateTime.getMonthValue();
111+
int dayOfMonth = dateTime.getDayOfMonth();
112+
int hours = dateTime.getHour();
113+
int minutes = dateTime.getMinute();
114+
int seconds = dateTime.getSecond();
115+
int nanos = dateTime.getNano();
116+
if (nanos >= NANOS_PER_SECOND) {
117+
long overflowNanos = nanos;
118+
seconds = (int) (seconds + overflowNanos / NANOS_PER_SECOND);
119+
nanos = (int) (overflowNanos % NANOS_PER_SECOND);
120+
if (seconds >= 60) {
121+
LocalDateTime normalized = dateTime.plusSeconds(seconds - dateTime.getSecond());
122+
year = normalized.getYear();
123+
month = normalized.getMonthValue();
124+
dayOfMonth = normalized.getDayOfMonth();
125+
hours = normalized.getHour();
126+
minutes = normalized.getMinute();
127+
seconds = normalized.getSecond();
128+
}
129+
}
130+
return new DateTimeValues(year, month, dayOfMonth, hours, minutes, seconds, nanos);
131+
}
132+
133+
@RequiredArgsConstructor
134+
private static class DateTimeValues {
135+
136+
private final int year;
137+
138+
private final int month;
139+
140+
private final int dayOfMonth;
141+
142+
private final int hours;
143+
144+
private final int minutes;
145+
146+
private final int seconds;
147+
148+
private final int nanos;
149+
}
116150
}

database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/MySQLTextResultSetRowPacket.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,21 @@
1717

1818
package org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text;
1919

20+
import com.google.common.io.ByteStreams;
2021
import lombok.Getter;
2122
import lombok.RequiredArgsConstructor;
2223
import org.apache.shardingsphere.database.protocol.mysql.packet.MySQLPacket;
2324
import org.apache.shardingsphere.database.protocol.mysql.payload.MySQLPacketPayload;
25+
import org.apache.shardingsphere.infra.exception.generic.UnknownSQLException;
2426
import org.apache.shardingsphere.infra.util.datetime.DateTimeFormatterFactory;
2527

28+
import java.io.IOException;
2629
import java.math.BigDecimal;
30+
import java.sql.Clob;
31+
import java.sql.SQLException;
2732
import java.sql.Timestamp;
2833
import java.time.LocalDateTime;
34+
import java.time.LocalTime;
2935
import java.util.ArrayList;
3036
import java.util.Collection;
3137

@@ -71,6 +77,15 @@ private void writeDataIntoPayload(final MySQLPacketPayload payload, final Object
7177
payload.writeBytesLenenc((boolean) data ? new byte[]{1} : new byte[]{0});
7278
} else if (data instanceof LocalDateTime) {
7379
payload.writeStringLenenc(formatLocalDateTime((LocalDateTime) data));
80+
} else if (data instanceof LocalTime) {
81+
payload.writeStringLenenc(formatLocalTime((LocalTime) data));
82+
} else if (data instanceof Clob) {
83+
try {
84+
// TODO Verify the correct approach for this in MySQL.
85+
payload.writeBytesLenenc(ByteStreams.toByteArray(((Clob) data).getAsciiStream()));
86+
} catch (final IOException | SQLException ex) {
87+
throw new UnknownSQLException(ex);
88+
}
7489
} else {
7590
payload.writeStringLenenc(data.toString());
7691
}
@@ -93,4 +108,22 @@ private String formatLocalDateTime(final LocalDateTime value) {
93108
result.append(microsecondsText, 0, endIndex);
94109
return result.toString();
95110
}
111+
112+
private String formatLocalTime(final LocalTime value) {
113+
int nanos = value.getNano();
114+
if (0 == nanos) {
115+
return DateTimeFormatterFactory.getTimeFormatter().format(value);
116+
}
117+
StringBuilder result = new StringBuilder(DateTimeFormatterFactory.getTimeFormatter().format(value)).append('.');
118+
String microsecondsText = String.format("%06d", nanos / 1000);
119+
int endIndex = microsecondsText.length();
120+
while (endIndex > 0 && '0' == microsecondsText.charAt(endIndex - 1)) {
121+
endIndex--;
122+
}
123+
if (0 == endIndex) {
124+
return result.substring(0, result.length() - 1);
125+
}
126+
result.append(microsecondsText, 0, endIndex);
127+
return result.toString();
128+
}
96129
}

database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/protocol/MySQLDateBinaryProtocolValueTest.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,57 @@ void assertWriteWithSevenBytes() {
140140
@Test
141141
void assertWriteWithElevenBytes() {
142142
MySQLDateBinaryProtocolValue actual = new MySQLDateBinaryProtocolValue();
143-
actual.write(payload, Timestamp.valueOf("1970-01-14 12:10:30.1"));
143+
actual.write(payload, Timestamp.valueOf("1970-01-14 12:10:30.123"));
144144
verify(payload).writeInt1(11);
145145
verify(payload).writeInt2(1970);
146146
verify(payload).writeInt1(1);
147147
verify(payload).writeInt1(14);
148148
verify(payload).writeInt1(12);
149149
verify(payload).writeInt1(10);
150150
verify(payload).writeInt1(30);
151-
verify(payload).writeInt4(100000);
151+
verify(payload).writeInt4(123000);
152+
}
153+
154+
@Test
155+
void assertWriteLocalDateTimeWithMaxNanos() {
156+
MySQLDateBinaryProtocolValue actual = new MySQLDateBinaryProtocolValue();
157+
LocalDateTime dateTime = LocalDateTime.of(1970, 1, 14, 12, 10, 29, 999_999_999);
158+
actual.write(payload, dateTime);
159+
verify(payload).writeInt1(11);
160+
verify(payload).writeInt2(1970);
161+
verify(payload).writeInt1(1);
162+
verify(payload).writeInt1(14);
163+
verify(payload).writeInt1(12);
164+
verify(payload).writeInt1(10);
165+
verify(payload).writeInt1(29);
166+
verify(payload).writeInt4(999999);
167+
}
168+
169+
@Test
170+
void assertWriteLocalDateTimeWithBoundaryNanos() {
171+
MySQLDateBinaryProtocolValue actual = new MySQLDateBinaryProtocolValue();
172+
actual.write(payload, LocalDateTime.of(1970, 1, 14, 12, 10, 29, 1000));
173+
verify(payload).writeInt1(11);
174+
verify(payload).writeInt2(1970);
175+
verify(payload).writeInt1(1);
176+
verify(payload).writeInt1(14);
177+
verify(payload).writeInt1(12);
178+
verify(payload).writeInt1(10);
179+
verify(payload).writeInt1(29);
180+
verify(payload).writeInt4(1);
181+
}
182+
183+
@Test
184+
void assertWriteLocalDateTimeWithMicrosecondPrecision() {
185+
MySQLDateBinaryProtocolValue actual = new MySQLDateBinaryProtocolValue();
186+
actual.write(payload, LocalDateTime.of(1970, 1, 14, 12, 10, 29, 123456789));
187+
verify(payload).writeInt1(11);
188+
verify(payload).writeInt2(1970);
189+
verify(payload).writeInt1(1);
190+
verify(payload).writeInt1(14);
191+
verify(payload).writeInt1(12);
192+
verify(payload).writeInt1(10);
193+
verify(payload).writeInt1(29);
194+
verify(payload).writeInt4(123456);
152195
}
153196
}

features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/merge/dal/EncryptDALResultDecorator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.shardingsphere.encrypt.merge.dal;
1919

20-
import com.sphereex.dbplusengine.sql.parser.statement.core.statement.attribute.type.ViewInResultSetSQLStatementAttribute;
20+
import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.ViewInResultSetSQLStatementAttribute;
2121
import lombok.RequiredArgsConstructor;
2222
import org.apache.shardingsphere.encrypt.merge.dal.show.EncryptShowColumnsMergedResult;
2323
import org.apache.shardingsphere.encrypt.merge.dal.show.EncryptShowCreateTableMergedResult;

features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/decider/ShardingSQLFederationDecider.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ private boolean decide0(final SelectStatementContext selectStatementContext, fin
7474
return false;
7575
}
7676
appendTableDataNodes(rule, database, tableNames, includedDataNodes);
77-
if (isAllShardingTables(selectStatementContext, tableNames) && isSubqueryAllSameShardingConditions(selectStatementContext, parameters, globalRuleMetaData, database, rule)) {
77+
boolean allShardingTables = isAllShardingTables(selectStatementContext, tableNames);
78+
if (allShardingTables && isSubqueryAllSameShardingConditions(selectStatementContext, parameters, globalRuleMetaData, database, rule)) {
7879
return false;
7980
}
80-
if (isAllShardingTables(selectStatementContext, tableNames) && isJoinWithSameEqualityShardingCondition(selectStatementContext, parameters, globalRuleMetaData, database, rule, tableNames)) {
81+
if (allShardingTables && isSingleOrJoinWithSameEqualityShardingCondition(selectStatementContext, parameters, globalRuleMetaData, database, rule, tableNames)) {
8182
return false;
8283
}
8384
if (selectStatementContext.isContainsSubquery() || selectStatementContext.isContainsHaving()
@@ -93,9 +94,9 @@ private boolean decide0(final SelectStatementContext selectStatementContext, fin
9394
return tableNames.size() > 1 && !rule.isBindingTablesUseShardingColumnsJoin(selectStatementContext, tableNames);
9495
}
9596

96-
private boolean isJoinWithSameEqualityShardingCondition(final SelectStatementContext selectStatementContext, final List<Object> parameters, final RuleMetaData globalRuleMetaData,
97-
final ShardingSphereDatabase database, final ShardingRule rule, final Collection<String> tableNames) {
98-
if (!selectStatementContext.isContainsJoinQuery()) {
97+
private boolean isSingleOrJoinWithSameEqualityShardingCondition(final SelectStatementContext selectStatementContext, final List<Object> parameters, final RuleMetaData globalRuleMetaData,
98+
final ShardingSphereDatabase database, final ShardingRule rule, final Collection<String> tableNames) {
99+
if (selectStatementContext.isContainsSubquery() || selectStatementContext.isContainsCombine()) {
99100
return false;
100101
}
101102
// TODO consider supporting JOIN optimization when config database and table sharding strategy @duanzhengqiang
@@ -110,6 +111,9 @@ private boolean isJoinWithSameEqualityShardingCondition(final SelectStatementCon
110111
if (!isAllEqualitySameShardingValues(shardingConditions, tableNames)) {
111112
return false;
112113
}
114+
if (1 == tableNames.size() && !selectStatementContext.isContainsJoinQuery()) {
115+
return true;
116+
}
113117
Collection<ShardingTableReferenceRuleConfiguration> bindingTableGroups = Collections.singleton(new ShardingTableReferenceRuleConfiguration("", Joiner.on(",").join(tableNames)));
114118
BindingTableCheckedConfiguration configuration = new BindingTableCheckedConfiguration(rule.getDataSourceNames(), rule.getShardingAlgorithms(), rule.getConfiguration().getShardingAlgorithms(),
115119
bindingTableGroups, rule.getDefaultDatabaseShardingStrategyConfig(), rule.getDefaultTableShardingStrategyConfig(), rule.getDefaultShardingColumn());

infra/executor/src/main/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/stream/JDBCStreamQueryResult.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.sql.SQLException;
3535
import java.sql.Time;
3636
import java.sql.Timestamp;
37+
import java.time.LocalTime;
3738
import java.time.ZonedDateTime;
3839
import java.util.Calendar;
3940
import java.util.Optional;
@@ -101,6 +102,9 @@ public Object getValue(final int columnIndex, final Class<?> type) throws SQLExc
101102
if (Time.class == type) {
102103
return resultSet.getTime(columnIndex);
103104
}
105+
if (LocalTime.class == type) {
106+
return resultSet.getObject(columnIndex, LocalTime.class);
107+
}
104108
if (Timestamp.class == type) {
105109
return resultSet.getTimestamp(columnIndex);
106110
}

kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/exception/SQLFederationUnsupportedSQLException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ public final class SQLFederationUnsupportedSQLException extends SQLFederationSQL
2727
private static final long serialVersionUID = -8571244162760408846L;
2828

2929
public SQLFederationUnsupportedSQLException(final String sql, final String reason) {
30-
super(XOpenSQLState.SYNTAX_ERROR, 1, reason, "SQL federation does not support SQL '%s'.", sql);
30+
super(XOpenSQLState.SYNTAX_ERROR, 1, reason.replace("%", "%%"), "SQL federation does not support SQL '" + sql + "'.");
3131
}
3232
}

kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/expression/ExpressionConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public static Optional<SqlNode> convert(final ExpressionSegment segment) {
9292
return Optional.empty();
9393
}
9494
if (segment instanceof LiteralExpressionSegment) {
95-
return LiteralExpressionConverter.convert((LiteralExpressionSegment) segment);
95+
return LiteralExpressionConverter.convert((LiteralExpressionSegment) segment, null);
9696
}
9797
if (segment instanceof CommonExpressionSegment) {
9898
// TODO

0 commit comments

Comments
 (0)