Skip to content

Commit 9ff4101

Browse files
philipgreatPhilip Z
andauthored
Fix selected SQL dialect in portable schema reconciliation (#168)
* Fix selected SQL dialect in portable schema reconciliation * Test selected schema dialect on live SQL Server and DM8 * Run SQL Server schema test in live dialect CI --------- Co-authored-by: Philip Z <hello@teaql.io>
1 parent 20738a3 commit 9ff4101

8 files changed

Lines changed: 307 additions & 1 deletion

File tree

‎.github/workflows/live-dialects.yml‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,34 @@ jobs:
5252
mvn -B -ntp -pl teaql-postgres,teaql-mysql,examples/business-id-runtime -am
5353
-Dtest=PostgresIntegrationTest,MysqlIntegrationTest,JdbcBusinessIdLiveDialectIT
5454
-Dsurefire.failIfNoSpecifiedTests=false test
55+
56+
sqlserver:
57+
runs-on: ubuntu-latest
58+
services:
59+
sqlserver:
60+
image: mcr.microsoft.com/mssql/server:2022-latest
61+
env:
62+
ACCEPT_EULA: "Y"
63+
MSSQL_SA_PASSWORD: TeaqlSchemaLiveTest123!
64+
ports:
65+
- 1433:1433
66+
options: >-
67+
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P TeaqlSchemaLiveTest123! -C -Q 'SELECT 1' -b"
68+
--health-interval 10s --health-timeout 5s --health-retries 20
69+
env:
70+
TEAQL_REQUIRE_LIVE_DB: "true"
71+
TEAQL_TEST_MSSQL_URL: "jdbc:sqlserver://127.0.0.1:1433;databaseName=master;encrypt=false;trustServerCertificate=true"
72+
TEAQL_TEST_MSSQL_USER: sa
73+
TEAQL_TEST_MSSQL_PASSWORD: TeaqlSchemaLiveTest123!
74+
steps:
75+
- uses: actions/checkout@v4
76+
- uses: actions/setup-java@v4
77+
with:
78+
distribution: temurin
79+
java-version: "21"
80+
cache: maven
81+
- name: Verify live SQL Server schema dialect (skips are forbidden)
82+
run: >-
83+
mvn -B -ntp -pl teaql-mssql -am
84+
-Dtest=MssqlSchemaIntegrationTest
85+
-Dsurefire.failIfNoSpecifiedTests=false test

‎teaql-dm8/src/main/java/io/teaql/core/dm8/Dm8DataServiceExecutor.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public List<Map<String, Object>> getTableColumns(String tableName) {
5959

6060
for (EntityDescriptor descriptor : descriptors) {
6161
PortableSQLRepository repository = new PortableSQLRepository(descriptor, dbAdapter, null);
62+
repository.setDialect(this.dialect);
6263
repository.ensurePhysicalSchema(context);
6364
}
6465
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.teaql.dm8;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import io.teaql.core.BaseEntity;
7+
import io.teaql.core.UserContext;
8+
import io.teaql.core.dm8.Dm8DataServiceExecutor;
9+
import io.teaql.core.meta.SimpleEntityMetaFactory;
10+
import io.teaql.core.sql.GenericSQLProperty;
11+
import io.teaql.core.sql.SQLEntityDescriptor;
12+
import io.teaql.provider.jdbc.JdbcSqlExecutor;
13+
import io.teaql.runtime.DefaultUserContext;
14+
import io.teaql.runtime.TeaQLRuntime;
15+
import java.io.PrintWriter;
16+
import java.sql.Connection;
17+
import java.sql.DriverManager;
18+
import java.sql.SQLException;
19+
import java.sql.SQLFeatureNotSupportedException;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.UUID;
23+
import java.util.logging.Logger;
24+
import javax.sql.DataSource;
25+
import org.junit.Assume;
26+
import org.junit.Test;
27+
28+
public class Dm8SchemaIntegrationTest {
29+
30+
private record TestDataSource(String url, String user, String password) implements DataSource {
31+
@Override public Connection getConnection() throws SQLException {
32+
return DriverManager.getConnection(url, user, password);
33+
}
34+
@Override public Connection getConnection(String username, String secret) throws SQLException {
35+
return DriverManager.getConnection(url, username, secret);
36+
}
37+
@Override public PrintWriter getLogWriter() { return null; }
38+
@Override public void setLogWriter(PrintWriter writer) {}
39+
@Override public void setLoginTimeout(int seconds) {}
40+
@Override public int getLoginTimeout() { return 0; }
41+
@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException {
42+
throw new SQLFeatureNotSupportedException();
43+
}
44+
@Override public <T> T unwrap(Class<T> type) throws SQLException {
45+
throw new SQLException("Not a wrapper for " + type.getName());
46+
}
47+
@Override public boolean isWrapperFor(Class<?> type) { return false; }
48+
}
49+
50+
@Test
51+
public void ensureSchemaUsesSelectedDialectForLargeText() {
52+
String url = System.getenv("TEAQL_TEST_DM8_URL");
53+
String user = System.getenv("TEAQL_TEST_DM8_USER");
54+
String password = System.getenv("TEAQL_TEST_DM8_PASSWORD");
55+
if ("true".equalsIgnoreCase(System.getenv("TEAQL_REQUIRE_LIVE_DB"))) {
56+
assertTrue("TEAQL_TEST_DM8_URL must be set", url != null && !url.isBlank());
57+
assertTrue("TEAQL_TEST_DM8_USER must be set", user != null && !user.isBlank());
58+
assertTrue("TEAQL_TEST_DM8_PASSWORD must be set", password != null && !password.isBlank());
59+
}
60+
Assume.assumeTrue("DM8 live test credentials are not configured",
61+
url != null && user != null && password != null);
62+
63+
DataSource dataSource = new TestDataSource(url, user, password);
64+
JdbcSqlExecutor jdbc = new JdbcSqlExecutor(dataSource);
65+
String table = "dm8_long_text_" + UUID.randomUUID().toString().substring(0, 8);
66+
67+
SQLEntityDescriptor descriptor = new SQLEntityDescriptor();
68+
descriptor.setType("Dm8LongTextProbe");
69+
descriptor.setTargetType(BaseEntity.class);
70+
descriptor.setEntitySupplier(BaseEntity::new);
71+
descriptor.setDataService("dm8");
72+
for (String name : List.of("id", "version")) {
73+
GenericSQLProperty property = (GenericSQLProperty) descriptor.addSimpleProperty(name, Long.class);
74+
property.setTableName(table);
75+
property.setColumnName(name);
76+
property.setColumnType("BIGINT");
77+
}
78+
GenericSQLProperty payload = (GenericSQLProperty) descriptor.addSimpleProperty("payload", String.class);
79+
payload.setTableName(table);
80+
payload.setColumnName("payload");
81+
payload.setColumnType("LARGE_TEXT");
82+
SimpleEntityMetaFactory metadata = new SimpleEntityMetaFactory();
83+
metadata.register(descriptor);
84+
85+
TeaQLRuntime runtime = TeaQLRuntime.builder()
86+
.metadata(metadata)
87+
.dataService("dm8", new Dm8DataServiceExecutor("dm8", jdbc))
88+
.build();
89+
UserContext context = new DefaultUserContext(runtime);
90+
context.ensureSchema();
91+
context.ensureSchema();
92+
93+
List<Map<String, Object>> columns = jdbc.queryForList(
94+
"SELECT data_type FROM all_tab_columns WHERE owner = USER "
95+
+ "AND table_name = UPPER(?) AND column_name = 'PAYLOAD'",
96+
new Object[] {table});
97+
assertEquals(1, columns.size());
98+
assertEquals("CLOB", String.valueOf(columns.get(0).values().iterator().next())
99+
.toUpperCase(java.util.Locale.ROOT));
100+
}
101+
}

‎teaql-mssql/pom.xml‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,34 @@
2424
<artifactId>junit</artifactId>
2525
<scope>test</scope>
2626
</dependency>
27+
<dependency>
28+
<groupId>com.microsoft.sqlserver</groupId>
29+
<artifactId>mssql-jdbc</artifactId>
30+
<version>13.4.0.jre11</version>
31+
<scope>test</scope>
32+
</dependency>
33+
<dependency>
34+
<groupId>io.teaql</groupId>
35+
<artifactId>teaql-provider-jdbc</artifactId>
36+
<version>${project.version}</version>
37+
<scope>test</scope>
38+
</dependency>
39+
<dependency>
40+
<groupId>io.teaql</groupId>
41+
<artifactId>teaql-runtime</artifactId>
42+
<version>${project.version}</version>
43+
<scope>test</scope>
44+
</dependency>
2745
</dependencies>
46+
<build>
47+
<plugins>
48+
<plugin>
49+
<groupId>org.apache.maven.plugins</groupId>
50+
<artifactId>maven-surefire-plugin</artifactId>
51+
<configuration>
52+
<argLine>--add-reads io.teaql.mssql=io.teaql.runtime</argLine>
53+
</configuration>
54+
</plugin>
55+
</plugins>
56+
</build>
2857
</project>

‎teaql-mssql/src/main/java/io/teaql/core/mssql/MssqlDataServiceExecutor.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public MssqlDataServiceExecutor(String name, SqlExecutionAdapter executionAdapte
1717
super(name, executionAdapter);
1818
this.dialect = new MssqlDialect();
1919
this.debugDatabaseKind = "mssql";
20-
this.dialect = new MssqlDialect();
2120
}
2221

2322
@Override
@@ -60,6 +59,7 @@ public List<Map<String, Object>> getTableColumns(String tableName) {
6059

6160
for (EntityDescriptor descriptor : descriptors) {
6261
PortableSQLRepository repository = new PortableSQLRepository(descriptor, dbAdapter, null);
62+
repository.setDialect(this.dialect);
6363
repository.ensurePhysicalSchema(context);
6464
}
6565
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.teaql.core.mssql;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import io.teaql.core.BaseEntity;
7+
import io.teaql.core.UserContext;
8+
import io.teaql.core.meta.SimpleEntityMetaFactory;
9+
import io.teaql.core.sql.GenericSQLProperty;
10+
import io.teaql.core.sql.SQLEntityDescriptor;
11+
import io.teaql.provider.jdbc.JdbcSqlExecutor;
12+
import io.teaql.runtime.DefaultUserContext;
13+
import io.teaql.runtime.TeaQLRuntime;
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.UUID;
17+
import javax.sql.DataSource;
18+
import java.io.PrintWriter;
19+
import java.sql.Connection;
20+
import java.sql.DriverManager;
21+
import java.sql.SQLException;
22+
import java.sql.SQLFeatureNotSupportedException;
23+
import java.util.logging.Logger;
24+
import org.junit.Assume;
25+
import org.junit.Test;
26+
27+
public class MssqlSchemaIntegrationTest {
28+
29+
private record TestDataSource(String url, String user, String password) implements DataSource {
30+
@Override public Connection getConnection() throws SQLException {
31+
return DriverManager.getConnection(url, user, password);
32+
}
33+
@Override public Connection getConnection(String username, String secret) throws SQLException {
34+
return DriverManager.getConnection(url, username, secret);
35+
}
36+
@Override public PrintWriter getLogWriter() { return null; }
37+
@Override public void setLogWriter(PrintWriter writer) {}
38+
@Override public void setLoginTimeout(int seconds) {}
39+
@Override public int getLoginTimeout() { return 0; }
40+
@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException {
41+
throw new SQLFeatureNotSupportedException();
42+
}
43+
@Override public <T> T unwrap(Class<T> type) throws SQLException {
44+
throw new SQLException("Not a wrapper for " + type.getName());
45+
}
46+
@Override public boolean isWrapperFor(Class<?> type) { return false; }
47+
}
48+
49+
@Test
50+
public void ensureSchemaUsesSelectedDialectForLargeText() {
51+
String url = System.getenv("TEAQL_TEST_MSSQL_URL");
52+
String user = System.getenv("TEAQL_TEST_MSSQL_USER");
53+
String password = System.getenv("TEAQL_TEST_MSSQL_PASSWORD");
54+
if ("true".equalsIgnoreCase(System.getenv("TEAQL_REQUIRE_LIVE_DB"))) {
55+
assertTrue("TEAQL_TEST_MSSQL_URL must be set", url != null && !url.isBlank());
56+
assertTrue("TEAQL_TEST_MSSQL_USER must be set", user != null && !user.isBlank());
57+
assertTrue("TEAQL_TEST_MSSQL_PASSWORD must be set", password != null && !password.isBlank());
58+
}
59+
Assume.assumeTrue("SQL Server live test credentials are not configured",
60+
url != null && user != null && password != null);
61+
62+
DataSource dataSource = new TestDataSource(url, user, password);
63+
JdbcSqlExecutor jdbc = new JdbcSqlExecutor(dataSource);
64+
String table = "mssql_long_text_" + UUID.randomUUID().toString().substring(0, 8);
65+
66+
SQLEntityDescriptor descriptor = new SQLEntityDescriptor();
67+
descriptor.setType("MssqlLongTextProbe");
68+
descriptor.setTargetType(BaseEntity.class);
69+
descriptor.setEntitySupplier(BaseEntity::new);
70+
descriptor.setDataService("mssql");
71+
for (String name : List.of("id", "version")) {
72+
GenericSQLProperty property = (GenericSQLProperty) descriptor.addSimpleProperty(name, Long.class);
73+
property.setTableName(table);
74+
property.setColumnName(name);
75+
property.setColumnType("BIGINT");
76+
}
77+
GenericSQLProperty payload = (GenericSQLProperty) descriptor.addSimpleProperty("payload", String.class);
78+
payload.setTableName(table);
79+
payload.setColumnName("payload");
80+
payload.setColumnType("LARGE_TEXT");
81+
SimpleEntityMetaFactory metadata = new SimpleEntityMetaFactory();
82+
metadata.register(descriptor);
83+
84+
TeaQLRuntime runtime = TeaQLRuntime.builder()
85+
.metadata(metadata)
86+
.dataService("mssql", new MssqlDataServiceExecutor("mssql", jdbc))
87+
.build();
88+
UserContext context = new DefaultUserContext(runtime);
89+
context.ensureSchema();
90+
context.ensureSchema();
91+
92+
List<Map<String, Object>> columns = jdbc.queryForList(
93+
"SELECT data_type, character_maximum_length FROM information_schema.columns "
94+
+ "WHERE table_schema = SCHEMA_NAME() AND table_name = ? AND column_name = ?",
95+
new Object[] {table, "payload"});
96+
assertEquals(1, columns.size());
97+
Map<String, Object> column = columns.get(0);
98+
assertEquals("nvarchar", String.valueOf(column.get("data_type")).toLowerCase(java.util.Locale.ROOT));
99+
assertEquals(-1, ((Number) column.get("character_maximum_length")).intValue());
100+
}
101+
}

‎teaql-mysql/src/main/java/io/teaql/core/mysql/MysqlDataServiceExecutor.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public Optional<Boolean> indexExists(
7575

7676
for (EntityDescriptor descriptor : descriptors) {
7777
PortableSQLRepository repository = new PortableSQLRepository(descriptor, dbAdapter, null);
78+
repository.setDialect(this.dialect);
7879
repository.ensurePhysicalSchema(context);
7980
}
8081
}

‎teaql-mysql/src/test/java/io/teaql/mysql/MysqlIntegrationTest.java‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.ArrayList;
2626
import java.util.List;
2727
import java.util.Collections;
28+
import java.util.UUID;
2829
import java.util.concurrent.Callable;
2930
import java.util.concurrent.ExecutorService;
3031
import java.util.concurrent.Executors;
@@ -287,6 +288,47 @@ public void testSchemaUsesInvokingContextMetadata() {
287288
jdbc.queryForList("SELECT id FROM context_probe_data WHERE 1 = 0", new Object[0]).isEmpty());
288289
}
289290

291+
@Test
292+
public void testEnsureSchemaUsesMysqlLongTextInsteadOfPostgresText() {
293+
String table = "mysql_long_text_" + UUID.randomUUID().toString().substring(0, 8);
294+
SimpleEntityMetaFactory isolated = new SimpleEntityMetaFactory();
295+
SQLEntityDescriptor probe = new SQLEntityDescriptor();
296+
probe.setType("MysqlLongTextProbe");
297+
probe.setTargetType(Task.class);
298+
probe.setEntitySupplier(Task::new);
299+
probe.setDataService("mysql");
300+
for (String name : List.of("id", "version")) {
301+
io.teaql.core.sql.GenericSQLProperty field =
302+
(io.teaql.core.sql.GenericSQLProperty) probe.addSimpleProperty(name, Long.class);
303+
field.setTableName(table);
304+
field.setColumnName(name);
305+
field.setColumnType("BIGINT");
306+
}
307+
io.teaql.core.sql.GenericSQLProperty payload =
308+
(io.teaql.core.sql.GenericSQLProperty) probe.addSimpleProperty("payload", String.class);
309+
payload.setTableName(table);
310+
payload.setColumnName("payload");
311+
payload.setColumnType("LARGE_TEXT");
312+
isolated.register(probe);
313+
314+
JdbcSqlExecutor jdbc = new JdbcSqlExecutor(dataSource);
315+
TeaQLRuntime isolatedRuntime = TeaQLRuntime.builder()
316+
.metadata(isolated)
317+
.dataService("mysql", new MysqlDataServiceExecutor("mysql", jdbc, dataSource))
318+
.build();
319+
UserContext isolatedContext = new DefaultUserContext(isolatedRuntime);
320+
isolatedContext.ensureSchema();
321+
isolatedContext.ensureSchema();
322+
323+
List<java.util.Map<String, Object>> columns = jdbc.queryForList(
324+
"SELECT data_type FROM information_schema.columns "
325+
+ "WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
326+
new Object[] {table, "payload"});
327+
assertEquals(1, columns.size());
328+
assertEquals("longtext", String.valueOf(columns.get(0).values().iterator().next())
329+
.toLowerCase(java.util.Locale.ROOT));
330+
}
331+
290332
@Test
291333
public void testCanonicalRelationIndexIsIdempotentOnLiveMysql() {
292334
SimpleEntityMetaFactory isolated = relationMetadata("mysql");

0 commit comments

Comments
 (0)