Skip to content

Commit 001c07c

Browse files
committed
Merge remote-tracking branch 'upstream/52-stable' into 52-stable
2 parents 311eb92 + d1b97a6 commit 001c07c

File tree

8 files changed

+207
-49
lines changed

8 files changed

+207
-49
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ bundler_args: --without development
1111
script: bundle exec rake ${TEST_PREFIX}test_$DB
1212
before_install:
1313
- unset _JAVA_OPTIONS
14+
- rvm @default,@global do gem uninstall bundler -a -x -I || true
15+
- gem install bundler -v "~>1.17.3"
1416
before_script:
1517
- echo "JAVA_OPTS=$JAVA_OPTS"
1618
- export JRUBY_OPTS="-J-Xms64M -J-Xmx1024M"

jdbc-mysql/lib/jdbc/mysql/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Jdbc
22
module MySQL
3-
DRIVER_VERSION = '5.1.46'
3+
DRIVER_VERSION = '5.1.47'
44
VERSION = DRIVER_VERSION
55
end
66
end

src/java/arjdbc/jdbc/RubyJdbcConnection.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,7 +2687,7 @@ protected String internedTypeFor(final ThreadContext context, final IRubyObject
26872687
}
26882688

26892689
protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final IRubyObject value) {
2690-
return timeInDefaultTimeZone(context, toTime(context, value));
2690+
return timeInDefaultTimeZone(context, DateTimeUtils.toTime(context, value));
26912691
}
26922692

26932693
protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final RubyTime time) {
@@ -2706,10 +2706,7 @@ protected final DateTime dateTimeInDefaultTimeZone(final ThreadContext context,
27062706
}
27072707

27082708
public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
2709-
if ( ! ( value instanceof RubyTime ) ) { // unlikely
2710-
return (RubyTime) TypeConverter.convertToTypeWithCheck(value, context.runtime.getTime(), "to_time");
2711-
}
2712-
return (RubyTime) value;
2709+
return DateTimeUtils.toTime(context, value);
27132710
}
27142711

27152712
protected boolean isDefaultTimeZoneUTC(final ThreadContext context) {
@@ -2809,7 +2806,7 @@ protected void setTimestampParameter(final ThreadContext context,
28092806
final int index, IRubyObject value,
28102807
final IRubyObject attribute, final int type) throws SQLException {
28112808

2812-
final RubyTime timeValue = toTime(context, value);
2809+
final RubyTime timeValue = DateTimeUtils.toTime(context, value);
28132810
final DateTime dateTime = dateTimeInDefaultTimeZone(context, timeValue.getDateTime());
28142811
final Timestamp timestamp = new Timestamp(dateTime.getMillis());
28152812
// 1942-11-30T01:02:03.123_456
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package arjdbc.postgresql;
2+
3+
import arjdbc.util.DateTimeUtils;
4+
import org.joda.time.DateTimeZone;
5+
import org.jruby.RubyArray;
6+
import org.jruby.RubyFloat;
7+
import org.jruby.runtime.ThreadContext;
8+
import org.jruby.runtime.builtin.IRubyObject;
9+
10+
/**
11+
* PostgreSQL specific DateTime/Timestamp helpers
12+
* @author dritz
13+
*/
14+
public abstract class PgDateTimeUtils extends DateTimeUtils {
15+
/**
16+
* Convert a ruby value to a PostgreSQL timestamp string
17+
* @param context
18+
* @param value Ruby value, typically a Time instance
19+
* @param zone DateTimeZone to adjust to, optional
20+
* @param withZone include timezone in string?
21+
* @return A string fit for PostgreSQL
22+
*/
23+
public static String timestampValueToString(final ThreadContext context, IRubyObject value, DateTimeZone zone,
24+
boolean withZone) {
25+
if (value instanceof RubyFloat) {
26+
final double dv = ((RubyFloat) value).getValue();
27+
if (dv == Double.POSITIVE_INFINITY) {
28+
return "infinity";
29+
} else if (dv == Double.NEGATIVE_INFINITY) {
30+
return "-infinity";
31+
}
32+
}
33+
return timestampTimeToString(context, value, zone, withZone);
34+
}
35+
36+
/**
37+
* Converts a RubyArray with timestamp values to a java array of PostgreSQL timestamp strings
38+
* @param context
39+
* @param rubyArray
40+
* @return Array of timestamp strings
41+
*/
42+
public static String[] timestampStringArray(final ThreadContext context, final RubyArray rubyArray) {
43+
int size = rubyArray.size();
44+
String[] values = new String[size];
45+
46+
for (int i = 0; i < size; i++) {
47+
IRubyObject elem = rubyArray.eltInternal(i);
48+
values[i] = timestampValueToString(context, elem, DateTimeZone.UTC, false);
49+
}
50+
return values;
51+
}
52+
}

src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,30 @@ protected IRubyObject mapQueryResult(final ThreadContext context, final Connecti
282282
return mapExecuteResult(context, connection, resultSet).toARResult(context);
283283
}
284284

285+
@Override
286+
protected void setArrayParameter(final ThreadContext context,
287+
final Connection connection, final PreparedStatement statement,
288+
final int index, final IRubyObject value,
289+
final IRubyObject attribute, final int type) throws SQLException {
290+
291+
final String typeName = resolveArrayBaseTypeName(context, attribute);
292+
final RubyArray valueForDB = (RubyArray) value.callMethod(context, "values");
293+
294+
Object[] values;
295+
switch (typeName) {
296+
case "datetime":
297+
case "timestamp": {
298+
values = PgDateTimeUtils.timestampStringArray(context, valueForDB);
299+
break;
300+
}
301+
default:
302+
values = valueForDB.toArray();
303+
break;
304+
}
305+
306+
statement.setArray(index, connection.createArrayOf(typeName, values));
307+
}
308+
285309
@Override
286310
protected void setBlobParameter(final ThreadContext context,
287311
final Connection connection, final PreparedStatement statement,
@@ -305,47 +329,9 @@ protected void setTimestampParameter(final ThreadContext context,
305329
final Connection connection, final PreparedStatement statement,
306330
final int index, IRubyObject value,
307331
final IRubyObject attribute, final int type) throws SQLException {
308-
309-
if ( value instanceof RubyFloat ) {
310-
final double doubleValue = ( (RubyFloat) value ).getValue();
311-
if ( Double.isInfinite(doubleValue) ) {
312-
setTimestampInfinity(statement, index, doubleValue);
313-
return;
314-
}
315-
}
316-
317-
RubyTime timeValue = toTime(context, value);
318-
319-
final Timestamp timestamp;
320-
321-
if (timeValue.getDateTime().getYear() > 0) {
322-
timeValue = timeInDefaultTimeZone(context, timeValue);
323-
DateTime dateTime = timeValue.getDateTime();
324-
timestamp = new Timestamp(dateTime.getMillis());
325-
326-
if (timeValue.getNSec() > 0) timestamp.setNanos((int) (timestamp.getNanos() + timeValue.getNSec()));
327-
328-
statement.setTimestamp(index, timestamp, getCalendar(dateTime.getZone()));
329-
}
330-
else {
331-
setTimestampBC(statement, index, timeValue);
332-
}
333-
}
334-
335-
private static void setTimestampBC(final PreparedStatement statement,
336-
final int index, final RubyTime timeValue) throws SQLException {
337-
DateTime dateTime = timeValue.getDateTime();
338-
@SuppressWarnings("deprecated")
339-
Timestamp timestamp = new Timestamp(dateTime.getYear() - 1900,
340-
dateTime.getMonthOfYear() - 1,
341-
dateTime.getDayOfMonth(),
342-
dateTime.getHourOfDay(),
343-
dateTime.getMinuteOfHour(),
344-
dateTime.getSecondOfMinute(),
345-
dateTime.getMillisOfSecond() * 1_000_000 + (int) timeValue.getNSec()
346-
);
347-
348-
statement.setObject(index, timestamp);
332+
// PGJDBC uses strings internally anyway, so using Timestamp doesn't do any good
333+
String tsString = PgDateTimeUtils.timestampValueToString(context, value, null, true);
334+
statement.setObject(index, tsString, Types.OTHER);
349335
}
350336

351337
private static void setTimestampInfinity(final PreparedStatement statement,
@@ -367,7 +353,8 @@ protected void setTimeParameter(final ThreadContext context,
367353
final int index, IRubyObject value,
368354
final IRubyObject attribute, final int type) throws SQLException {
369355
// to handle more fractional second precision than (default) 59.123 only
370-
super.setTimestampParameter(context, connection, statement, index, value, attribute, type);
356+
String timeStr = DateTimeUtils.timeString(context, value, getDefaultTimeZone(context), true);
357+
statement.setObject(index, timeStr, Types.OTHER);
371358
}
372359

373360
@Override

src/java/arjdbc/util/DateTimeUtils.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.sql.Timestamp;
2929
import java.util.TimeZone;
3030

31+
import org.joda.time.Chronology;
3132
import org.joda.time.DateTime;
3233
import org.joda.time.DateTimeZone;
3334
import org.joda.time.chrono.GJChronology;
@@ -41,6 +42,7 @@
4142
import org.jruby.runtime.ThreadContext;
4243
import org.jruby.runtime.builtin.IRubyObject;
4344
import org.jruby.util.ByteList;
45+
import org.jruby.util.TypeConverter;
4446

4547
import static arjdbc.util.StringHelper.decByte;
4648

@@ -49,6 +51,17 @@
4951
* @author kares
5052
*/
5153
public abstract class DateTimeUtils {
54+
public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
55+
if (!(value instanceof RubyTime)) { // unlikely
56+
return (RubyTime) TypeConverter.convertToTypeWithCheck(value, context.runtime.getTime(), "to_time");
57+
}
58+
return (RubyTime) value;
59+
}
60+
61+
public static DateTime dateTimeInZone(final DateTime dateTime, final DateTimeZone zone) {
62+
if (zone == dateTime.getZone()) return dateTime;
63+
return dateTime.withZone(zone);
64+
}
5265

5366
@SuppressWarnings("deprecation")
5467
public static ByteList timeToString(final Time time) {
@@ -583,4 +596,110 @@ private static int extractIntValue(final CharSequence str, int beg, int end) {
583596
return n;
584597
}
585598

599+
600+
private static final char[] ZEROS = {'0', '0', '0', '0', '0', '0'};
601+
private static final char[][] NUMBERS;
602+
603+
static {
604+
// maximum value is 60 (seconds)
605+
NUMBERS = new char[60][];
606+
for (int i = 0; i < NUMBERS.length; i++) {
607+
NUMBERS[i] = ((i < 10 ? "0" : "") + Integer.toString(i)).toCharArray();
608+
}
609+
}
610+
611+
/**
612+
* Converts a ruby timestamp to a java string, optionally with timezone and timezone adjustment
613+
* @param context
614+
* @param value the ruby value, typically a Time
615+
* @param zone DateTimeZone to adjust to, optional
616+
* @param withZone include timezone in the string?
617+
* @return timestamp as string
618+
*/
619+
public static String timestampTimeToString(final ThreadContext context,
620+
final IRubyObject value, DateTimeZone zone, boolean withZone) {
621+
RubyTime timeValue = toTime(context, value);
622+
DateTime dt = timeValue.getDateTime();
623+
624+
StringBuilder sb = new StringBuilder(36);
625+
626+
int year = dt.getYear();
627+
if (year <= 0) {
628+
year--;
629+
} else if (zone != null) {
630+
dt = dateTimeInZone(dt, zone);
631+
year = dt.getYear();
632+
}
633+
634+
Chronology chrono = dt.getChronology();
635+
long millis = dt.getMillis();
636+
637+
// always use 4 digits for year to avoid short dates being misinterpreted
638+
sb.append(Math.abs(year));
639+
int lead = 4 - sb.length();
640+
if (lead > 0) sb.insert(0, ZEROS, 0, lead);
641+
sb.append('-');
642+
sb.append(NUMBERS[chrono.monthOfYear().get(millis)]);
643+
sb.append('-');
644+
sb.append(NUMBERS[chrono.dayOfMonth().get(millis)]);
645+
if (year < 0) sb.append(" BC");
646+
sb.append(' ');
647+
648+
appendTime(sb, chrono, millis, (int) timeValue.getUSec(), withZone);
649+
650+
return sb.toString();
651+
}
652+
653+
/**
654+
* Converts a ruby time to a java string, optionally with timezone and timezone adjustment
655+
* @param context
656+
* @param value the ruby value, typically a Time
657+
* @param zone DateTimeZone to adjust to, optional
658+
* @param withZone include timezone in the string?
659+
* @return time as string
660+
*/
661+
public static String timeString(final ThreadContext context,
662+
final IRubyObject value, DateTimeZone zone, boolean withZone) {
663+
StringBuilder sb = new StringBuilder(21);
664+
RubyTime timeValue = toTime(context, value);
665+
DateTime dt = timeValue.getDateTime();
666+
if (zone != null) dt = dateTimeInZone(dt, zone);
667+
668+
appendTime(sb, dt.getChronology(), dt.getMillis(), (int) timeValue.getUSec(), withZone);
669+
return sb.toString();
670+
}
671+
672+
private static void appendTime(StringBuilder sb, Chronology chrono,
673+
long millis, int usec, boolean withZone) {
674+
sb.append(NUMBERS[chrono.hourOfDay().get(millis)]);
675+
sb.append(':');
676+
sb.append(NUMBERS[chrono.minuteOfHour().get(millis)]);
677+
sb.append(':');
678+
sb.append(NUMBERS[chrono.secondOfMinute().get(millis)]);
679+
680+
// PG has microsecond resolution. Change when nanos are required
681+
int micros = chrono.millisOfSecond().get(millis) * 1000 + usec;
682+
if (micros > 0) {
683+
sb.append('.');
684+
685+
int len = sb.length();
686+
sb.append(micros);
687+
int lead = 6 - (sb.length() - len);
688+
if (lead > 0) sb.insert(len, ZEROS, 0, lead);
689+
690+
for (int end = sb.length() - 1; sb.charAt(end) == '0'; end--) {
691+
sb.setLength(end);
692+
}
693+
}
694+
695+
if (withZone) {
696+
int offset = chrono.getZone().getOffset(millis) / 1000;
697+
int absoff = Math.abs(offset);
698+
int hours = absoff / 3600;
699+
int mins = (absoff - hours * 3600) / 60;
700+
701+
sb.append(offset < 0 ? '-' : '+');
702+
sb.append(NUMBERS[hours]).append(':').append(NUMBERS[mins]);
703+
}
704+
}
586705
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exclude :test_via_to_sql_with_complicating_connection, "non standard conforming string quoting not implemented"

0 commit comments

Comments
 (0)