Skip to content

Commit

Permalink
[NOID] Fixes #3496: Simple mysql select now(); Throws class java.time…
Browse files Browse the repository at this point in the history
….LocalDateTime cannot be cast to class java.sql.Timestamp (#3975)

* Fixes #3496: Simple mysql select now(); Throws class java.time.LocalDateTime cannot be cast to class java.sql.Timestamp

* Fix tests and updated implementation package

* Removed unused imports

* Fixed mysql test
  • Loading branch information
vga91 committed Apr 3, 2024
1 parent 8ec6391 commit a2d661d
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 86 deletions.
21 changes: 13 additions & 8 deletions core/src/main/java/apoc/load/util/JdbcUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
*/
package apoc.load.util;

import us.fatehi.utility.datasource.DatabaseConnectionSource;
import us.fatehi.utility.datasource.DatabaseConnectionSources;
import us.fatehi.utility.datasource.MultiUseUserCredentials;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginContext;
import apoc.util.Util;
import java.net.URI;
import java.security.PrivilegedActionException;
Expand All @@ -37,12 +46,9 @@ public class JdbcUtil {

private JdbcUtil() {}

public static Connection getConnection(String jdbcUrl, LoadJdbcConfig config) throws Exception {
if (config.hasCredentials()) {
return createConnection(
jdbcUrl,
config.getCredentials().getUser(),
config.getCredentials().getPassword());
public static DatabaseConnectionSource getConnection(String jdbcUrl, LoadJdbcConfig config) throws Exception {
if(config.hasCredentials()) {
return createConnection(jdbcUrl, config.getCredentials().getUser(), config.getCredentials().getPassword());
} else {
URI uri = new URI(jdbcUrl.substring("jdbc:".length()));
String userInfo = uri.getUserInfo();
Expand All @@ -68,8 +74,7 @@ private static Connection createConnection(String jdbcUrl, String userName, Stri
lc.login();
Subject subject = lc.getSubject();
try {
return Subject.doAs(subject, (PrivilegedExceptionAction<Connection>)
() -> DriverManager.getConnection(jdbcUrl, userName, password));
return Subject.doAs(subject, (PrivilegedExceptionAction<DatabaseConnectionSource>) () -> DatabaseConnectionSources.newDatabaseConnectionSource(jdbcUrl, new MultiUseUserCredentials(userName, password)));
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
Expand Down
4 changes: 2 additions & 2 deletions full/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ dependencies {
compileOnly group: 'org.ow2.asm', name: 'asm', version: '5.0.2'

// schemacrawler
implementation group: 'us.fatehi', name: 'schemacrawler', version: '15.04.01'
testImplementation group: 'us.fatehi', name: 'schemacrawler-mysql', version: '15.04.01'
implementation group: 'us.fatehi', name: 'schemacrawler', version: '16.20.8'
testImplementation group: 'us.fatehi', name: 'schemacrawler-mysql', version: '16.20.8'

testImplementation group: 'org.apache.hive', name: 'hive-jdbc', version: '1.2.2', withoutServers

Expand Down
20 changes: 10 additions & 10 deletions full/src/main/java/apoc/load/Jdbc.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.sql.*;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
Expand Down Expand Up @@ -106,7 +107,7 @@ private Stream<RowResult> executeQuery(
String url = getUrlOrKey(urlOrKey);
String query = getSqlOrKey(tableOrSelect);
try {
Connection connection = getConnection(url, loadJdbcConfig);
Connection connection = getConnection(url, loadJdbcConfig).get();
// see https://jdbc.postgresql.org/documentation/91/query.html#query-with-cursors
connection.setAutoCommit(loadJdbcConfig.isAutoCommit());
try {
Expand Down Expand Up @@ -162,7 +163,7 @@ private Stream<RowResult> executeUpdate(
String url = getUrlOrKey(urlOrKey);
LoadJdbcConfig jdbcConfig = new LoadJdbcConfig(config);
try {
Connection connection = getConnection(url, jdbcConfig);
Connection connection = getConnection(url, jdbcConfig).get();
try {
PreparedStatement stmt =
connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
Expand Down Expand Up @@ -277,21 +278,20 @@ private Object convert(Object value, int sqlType) {
if (Types.TIME_WITH_TIMEZONE == sqlType) {
return OffsetTime.parse(value.toString());
}
ZoneId zoneId = config.getZoneId();
if (Types.TIMESTAMP == sqlType) {
if (config.getZoneId() != null) {
return ((java.sql.Timestamp) value)
.toInstant()
.atZone(config.getZoneId())
if (zoneId != null) {
return ((java.sql.Timestamp)value).toInstant()
.atZone(zoneId)
.toOffsetDateTime();
} else {
return ((java.sql.Timestamp) value).toLocalDateTime();
}
}
if (Types.TIMESTAMP_WITH_TIMEZONE == sqlType) {
if (config.getZoneId() != null) {
return ((java.sql.Timestamp) value)
.toInstant()
.atZone(config.getZoneId())
if (zoneId != null) {
return ((java.sql.Timestamp)value).toInstant()
.atZone(zoneId)
.toOffsetDateTime();
} else {
return OffsetDateTime.parse(value.toString());
Expand Down
4 changes: 1 addition & 3 deletions full/src/main/java/apoc/model/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ public Stream<DatabaseModel> jdbc(
throws Exception {
String url = getUrlOrKey(urlOrKey);

SchemaCrawlerOptionsBuilder optionsBuilder =
SchemaCrawlerOptionsBuilder.builder().withSchemaInfoLevel(SchemaInfoLevelBuilder.standard());
SchemaCrawlerOptions options = optionsBuilder.toOptions();
SchemaCrawlerOptions options = SchemaCrawlerOptionsBuilder.newSchemaCrawlerOptions();

Catalog catalog = SchemaCrawlerUtility.getCatalog(getConnection(url, new LoadJdbcConfig(config)), options);

Expand Down
128 changes: 106 additions & 22 deletions full/src/test/java/apoc/load/MySQLJdbcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,91 @@
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.neo4j.test.rule.DbmsRule;
import org.neo4j.test.rule.ImpermanentDbmsRule;

public class MySQLJdbcTest extends AbstractJdbcTest {
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.Map;

@ClassRule
public static MySQLContainerExtension mysql = new MySQLContainerExtension();
import static apoc.util.TestUtil.testCall;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@ClassRule
public static DbmsRule db = new ImpermanentDbmsRule();
@RunWith(Enclosed.class)
public class MySQLJdbcTest extends AbstractJdbcTest {

public static class MySQLJdbcLatestVersionTest {

@ClassRule
public static MySQLContainerExtension mysql = new MySQLContainerExtension("mysql:8.0.31");

@ClassRule
public static DbmsRule db = new ImpermanentDbmsRule();

@BeforeClass
public static void setUpContainer() {
mysql.start();
TestUtil.registerProcedure(db, Jdbc.class);
}
@AfterClass
public static void tearDown() {
mysql.stop();
db.shutdown();
}
@Test
public void testLoadJdbc() {
MySQLJdbcTest.testLoadJdbc(db, mysql);
}

@BeforeClass
public static void setUpContainer() {
mysql.start();
TestUtil.registerProcedure(db, Jdbc.class);
@Test
public void testIssue3496() {
MySQLJdbcTest.testIssue3496(db, mysql);
}
}

public static class MySQLJdbcFiveVersionTest {

@ClassRule
public static MySQLContainerExtension mysql = new MySQLContainerExtension("mysql:5.7");

@ClassRule
public static DbmsRule db = new ImpermanentDbmsRule();

@AfterClass
public static void tearDown() {
mysql.stop();
db.shutdown();
@BeforeClass
public static void setUpContainer() {
mysql.start();
TestUtil.registerProcedure(db, Jdbc.class);
}

@AfterClass
public static void tearDown() {
mysql.stop();
db.shutdown();
}

@Test
public void testLoadJdbc() {
MySQLJdbcTest.testLoadJdbc(db, mysql);
}

@Test
public void testIssue3496() {
MySQLJdbcTest.testIssue3496(db, mysql);
}
}

@Test
public void testLoadJdbc() {
testCall(
db,
"CALL apoc.load.jdbc($url, $table, [])",
Util.map("url", mysql.getJdbcUrl(), "table", "country"),
private static void testLoadJdbc(DbmsRule db, MySQLContainerExtension mysql) {
// with the config {timezone: 'UTC'} and `preserveInstants=true&connectionTimeZone=SERVER` to make the result deterministic,
// since `TIMESTAMP` values are automatically converted from the session time zone to UTC for storage, and vice versa.
testCall(db, "CALL apoc.load.jdbc($url, $table, [], {timezone: 'UTC'})",
Util.map(
"url", mysql.getJdbcUrl() + "&preserveInstants=true&connectionTimeZone=SERVER",
"table", "country"),
row -> {
Map<String, Object> expected = Util.map(
"Code", "NLD",
Expand All @@ -56,8 +112,36 @@ public void testLoadJdbc() {
"GovernmentForm", "Constitutional Monarchy",
"HeadOfState", "Beatrix",
"Capital", 5,
"Code2", "NL");
assertEquals(expected, row.get("row"));
"Code2", "NL",
"myTime", LocalTime.of(1, 0, 0),
"myTimeStamp", ZonedDateTime.parse("2003-01-01T01:00Z"),
"myDate", LocalDate.parse("2003-01-01"),
"myYear", LocalDate.parse("2003-01-01")
);
Map actual = (Map) row.get("row");
Object myDateTime = actual.remove("myDateTime");
assertTrue(myDateTime instanceof LocalDateTime);
assertEquals(expected, actual);
});
}

private static void testIssue3496(DbmsRule db, MySQLContainerExtension mysql) {
testCall(db, "CALL apoc.load.jdbc($url,'SELECT DATE(NOW()), NOW(), CURDATE(), CURTIME(), UTC_DATE(), UTC_TIME(), UTC_TIMESTAMP(), DATE(UTC_TIMESTAMP());')",
Util.map("url", mysql.getJdbcUrl()),
r -> {
Map row = (Map) r.get("row");
assertEquals(8, row.size());

assertTrue(row.get("UTC_DATE()") instanceof LocalDate);
assertTrue(row.get("CURDATE()") instanceof LocalDate);

assertTrue(row.get("UTC_TIMESTAMP()") instanceof LocalDateTime);
assertTrue(row.get("NOW()") instanceof LocalDateTime);
assertTrue(row.get("DATE(UTC_TIMESTAMP())") instanceof LocalDate);
assertTrue(row.get("DATE(NOW())") instanceof LocalDate);

assertTrue(row.get("CURTIME()") instanceof LocalTime);
assertTrue(row.get("UTC_TIME()") instanceof LocalTime);
});
}
}
}
48 changes: 10 additions & 38 deletions full/src/test/java/apoc/model/ModelTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ public void testLoadJdbcSchema() {
assertEquals(0L, count.longValue());
List<Node> nodes = (List<Node>) row.get("nodes");
List<Relationship> rels = (List<Relationship>) row.get("relationships");
assertEquals(28, nodes.size());
assertEquals(27, rels.size());
assertEquals(33, nodes.size());
assertEquals(32, rels.size());

// schema
Node schema = nodes.stream()
Expand Down Expand Up @@ -123,25 +123,11 @@ public void testLoadJdbcSchema() {
List<Node> columns = nodes.stream()
.filter(node -> node.hasLabel(Label.label("Column")))
.collect(Collectors.toList());
assertEquals(24, columns.size());
assertEquals(29, columns.size());

List<String> countryNodes = filterColumnsByTableName(columns, "country");
List<String> expectedCountryCols = Arrays.asList(
"Code",
"Name",
"Continent",
"Region",
"SurfaceArea",
"IndepYear",
"Population",
"LifeExpectancy",
"GNP",
"GNPOld",
"LocalName",
"GovernmentForm",
"HeadOfState",
"Capital",
"Code2");
List<String> expectedCountryCols = Arrays.asList("Code", "Name", "Continent", "Region", "SurfaceArea", "IndepYear", "Population", "LifeExpectancy", "GNP", "GNPOld", "LocalName", "GovernmentForm", "HeadOfState", "Capital", "Code2",
"myTime", "myDateTime", "myTimeStamp", "myDate", "myYear");
assertEquals(expectedCountryCols, countryNodes);

List<String> cityNodes = filterColumnsByTableName(columns, "city");
Expand Down Expand Up @@ -178,8 +164,8 @@ public void testLoadJdbcSchemaWithWriteOperation() {
tx.execute("MATCH (n) RETURN collect(distinct n) AS nodes").columnAs("nodes"));
List<Relationship> rels = Iterators.single(tx.execute("MATCH ()-[r]-() RETURN collect(distinct r) AS rels")
.columnAs("rels"));
assertEquals(28, nodes.size());
assertEquals(27, rels.size());
assertEquals(33, nodes.size());
assertEquals(32, rels.size());

// schema
Node schema = nodes.stream()
Expand All @@ -206,25 +192,11 @@ public void testLoadJdbcSchemaWithWriteOperation() {
List<Node> columns = nodes.stream()
.filter(node -> node.hasLabel(Label.label("Column")))
.collect(Collectors.toList());
assertEquals(24, columns.size());
assertEquals(29, columns.size());

List<String> countryNodes = filterColumnsByTableName(columns, "country");
List<String> expectedCountryCols = Arrays.asList(
"Code",
"Name",
"Continent",
"Region",
"SurfaceArea",
"IndepYear",
"Population",
"LifeExpectancy",
"GNP",
"GNPOld",
"LocalName",
"GovernmentForm",
"HeadOfState",
"Capital",
"Code2");
List<String> expectedCountryCols = Arrays.asList("Code", "Name", "Continent", "Region", "SurfaceArea", "IndepYear", "Population", "LifeExpectancy", "GNP", "GNPOld", "LocalName", "GovernmentForm", "HeadOfState", "Capital", "Code2",
"myTime", "myDateTime", "myTimeStamp", "myDate", "myYear");
assertEquals(expectedCountryCols, countryNodes);

List<String> cityNodes = filterColumnsByTableName(columns, "city");
Expand Down
13 changes: 12 additions & 1 deletion full/src/test/resources/init_mysql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ CREATE TABLE `country` (
`HeadOfState` CHAR(60) DEFAULT NULL,
`Capital` INT(11) DEFAULT NULL,
`Code2` CHAR(2) NOT NULL DEFAULT '',
`myTime` TIME NOT NULL DEFAULT '0',
`myDateTime` DATETIME NOT NULL,
`myTimeStamp` TIMESTAMP NOT NULL,
`myDate` DATE NOT NULL,
`myYear` YEAR NOT NULL,
PRIMARY KEY (`Code`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Expand All @@ -32,7 +37,13 @@ CREATE TABLE `country` (
--
-- ORDER BY: `Code`

INSERT INTO `country` VALUES ('NLD','Netherlands','Europe','Western Europe',41526.00,1581,15864000,78.3,371362.00,360478.00,'Nederland','Constitutional Monarchy','Beatrix',5,'NL');
INSERT INTO `country` VALUES ('NLD','Netherlands','Europe','Western Europe',41526.00,1581,15864000,78.3,371362.00,360478.00,'Nederland','Constitutional Monarchy','Beatrix',5,'NL',
TIME('01:00:00'),
DATE('2003-01-01 01:00:00'),
TIMESTAMP('2003-01-01 01:00:00'),
DATE('2003-01-01 01:00:00'),
YEAR('2003-01-01 01:00:00')
);
COMMIT;

--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

public class MySQLContainerExtension extends MySQLContainer<MySQLContainerExtension> {

public MySQLContainerExtension() {
super("mysql:5.7");
public MySQLContainerExtension(String imageName) {
super(imageName);
this.withInitScript("init_mysql.sql");
this.withUrlParam("user", "test");
this.withUrlParam("password", "test");
Expand Down

0 comments on commit a2d661d

Please sign in to comment.