From 834307d6671b723f4f38d08d54058d702620fe08 Mon Sep 17 00:00:00 2001 From: Philip Z Date: Wed, 23 Sep 2026 15:42:47 +0800 Subject: [PATCH 1/3] Fix selected SQL dialect in portable schema reconciliation --- .../core/dm8/Dm8DataServiceExecutor.java | 1 + .../core/mssql/MssqlDataServiceExecutor.java | 2 +- .../core/mysql/MysqlDataServiceExecutor.java | 1 + .../io/teaql/mysql/MysqlIntegrationTest.java | 42 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/teaql-dm8/src/main/java/io/teaql/core/dm8/Dm8DataServiceExecutor.java b/teaql-dm8/src/main/java/io/teaql/core/dm8/Dm8DataServiceExecutor.java index 8170718a..4a2f5d1a 100644 --- a/teaql-dm8/src/main/java/io/teaql/core/dm8/Dm8DataServiceExecutor.java +++ b/teaql-dm8/src/main/java/io/teaql/core/dm8/Dm8DataServiceExecutor.java @@ -59,6 +59,7 @@ public List> getTableColumns(String tableName) { for (EntityDescriptor descriptor : descriptors) { PortableSQLRepository repository = new PortableSQLRepository(descriptor, dbAdapter, null); + repository.setDialect(this.dialect); repository.ensurePhysicalSchema(context); } } diff --git a/teaql-mssql/src/main/java/io/teaql/core/mssql/MssqlDataServiceExecutor.java b/teaql-mssql/src/main/java/io/teaql/core/mssql/MssqlDataServiceExecutor.java index db3402a7..08333a59 100644 --- a/teaql-mssql/src/main/java/io/teaql/core/mssql/MssqlDataServiceExecutor.java +++ b/teaql-mssql/src/main/java/io/teaql/core/mssql/MssqlDataServiceExecutor.java @@ -17,7 +17,6 @@ public MssqlDataServiceExecutor(String name, SqlExecutionAdapter executionAdapte super(name, executionAdapter); this.dialect = new MssqlDialect(); this.debugDatabaseKind = "mssql"; - this.dialect = new MssqlDialect(); } @Override @@ -60,6 +59,7 @@ public List> getTableColumns(String tableName) { for (EntityDescriptor descriptor : descriptors) { PortableSQLRepository repository = new PortableSQLRepository(descriptor, dbAdapter, null); + repository.setDialect(this.dialect); repository.ensurePhysicalSchema(context); } } diff --git a/teaql-mysql/src/main/java/io/teaql/core/mysql/MysqlDataServiceExecutor.java b/teaql-mysql/src/main/java/io/teaql/core/mysql/MysqlDataServiceExecutor.java index 297a16a5..850d135e 100644 --- a/teaql-mysql/src/main/java/io/teaql/core/mysql/MysqlDataServiceExecutor.java +++ b/teaql-mysql/src/main/java/io/teaql/core/mysql/MysqlDataServiceExecutor.java @@ -75,6 +75,7 @@ public Optional indexExists( for (EntityDescriptor descriptor : descriptors) { PortableSQLRepository repository = new PortableSQLRepository(descriptor, dbAdapter, null); + repository.setDialect(this.dialect); repository.ensurePhysicalSchema(context); } } diff --git a/teaql-mysql/src/test/java/io/teaql/mysql/MysqlIntegrationTest.java b/teaql-mysql/src/test/java/io/teaql/mysql/MysqlIntegrationTest.java index 2102ff0a..a404f13d 100644 --- a/teaql-mysql/src/test/java/io/teaql/mysql/MysqlIntegrationTest.java +++ b/teaql-mysql/src/test/java/io/teaql/mysql/MysqlIntegrationTest.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Collections; +import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -287,6 +288,47 @@ public void testSchemaUsesInvokingContextMetadata() { jdbc.queryForList("SELECT id FROM context_probe_data WHERE 1 = 0", new Object[0]).isEmpty()); } + @Test + public void testEnsureSchemaUsesMysqlLongTextInsteadOfPostgresText() { + String table = "mysql_long_text_" + UUID.randomUUID().toString().substring(0, 8); + SimpleEntityMetaFactory isolated = new SimpleEntityMetaFactory(); + SQLEntityDescriptor probe = new SQLEntityDescriptor(); + probe.setType("MysqlLongTextProbe"); + probe.setTargetType(Task.class); + probe.setEntitySupplier(Task::new); + probe.setDataService("mysql"); + for (String name : List.of("id", "version")) { + io.teaql.core.sql.GenericSQLProperty field = + (io.teaql.core.sql.GenericSQLProperty) probe.addSimpleProperty(name, Long.class); + field.setTableName(table); + field.setColumnName(name); + field.setColumnType("BIGINT"); + } + io.teaql.core.sql.GenericSQLProperty payload = + (io.teaql.core.sql.GenericSQLProperty) probe.addSimpleProperty("payload", String.class); + payload.setTableName(table); + payload.setColumnName("payload"); + payload.setColumnType("LARGE_TEXT"); + isolated.register(probe); + + JdbcSqlExecutor jdbc = new JdbcSqlExecutor(dataSource); + TeaQLRuntime isolatedRuntime = TeaQLRuntime.builder() + .metadata(isolated) + .dataService("mysql", new MysqlDataServiceExecutor("mysql", jdbc, dataSource)) + .build(); + UserContext isolatedContext = new DefaultUserContext(isolatedRuntime); + isolatedContext.ensureSchema(); + isolatedContext.ensureSchema(); + + List> columns = jdbc.queryForList( + "SELECT data_type FROM information_schema.columns " + + "WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?", + new Object[] {table, "payload"}); + assertEquals(1, columns.size()); + assertEquals("longtext", String.valueOf(columns.get(0).values().iterator().next()) + .toLowerCase(java.util.Locale.ROOT)); + } + @Test public void testCanonicalRelationIndexIsIdempotentOnLiveMysql() { SimpleEntityMetaFactory isolated = relationMetadata("mysql"); From f211217005bc9762e9eaf81cea296a5d7bd8cf26 Mon Sep 17 00:00:00 2001 From: Philip Z Date: Wed, 23 Sep 2026 15:47:33 +0800 Subject: [PATCH 2/3] Test selected schema dialect on live SQL Server and DM8 --- .../teaql/dm8/Dm8SchemaIntegrationTest.java | 101 ++++++++++++++++++ teaql-mssql/pom.xml | 29 +++++ .../mssql/MssqlSchemaIntegrationTest.java | 101 ++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 teaql-dm8/src/test/java/io/teaql/dm8/Dm8SchemaIntegrationTest.java create mode 100644 teaql-mssql/src/test/java/io/teaql/core/mssql/MssqlSchemaIntegrationTest.java diff --git a/teaql-dm8/src/test/java/io/teaql/dm8/Dm8SchemaIntegrationTest.java b/teaql-dm8/src/test/java/io/teaql/dm8/Dm8SchemaIntegrationTest.java new file mode 100644 index 00000000..845890b1 --- /dev/null +++ b/teaql-dm8/src/test/java/io/teaql/dm8/Dm8SchemaIntegrationTest.java @@ -0,0 +1,101 @@ +package io.teaql.dm8; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import io.teaql.core.BaseEntity; +import io.teaql.core.UserContext; +import io.teaql.core.dm8.Dm8DataServiceExecutor; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.core.sql.GenericSQLProperty; +import io.teaql.core.sql.SQLEntityDescriptor; +import io.teaql.provider.jdbc.JdbcSqlExecutor; +import io.teaql.runtime.DefaultUserContext; +import io.teaql.runtime.TeaQLRuntime; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Logger; +import javax.sql.DataSource; +import org.junit.Assume; +import org.junit.Test; + +public class Dm8SchemaIntegrationTest { + + private record TestDataSource(String url, String user, String password) implements DataSource { + @Override public Connection getConnection() throws SQLException { + return DriverManager.getConnection(url, user, password); + } + @Override public Connection getConnection(String username, String secret) throws SQLException { + return DriverManager.getConnection(url, username, secret); + } + @Override public PrintWriter getLogWriter() { return null; } + @Override public void setLogWriter(PrintWriter writer) {} + @Override public void setLoginTimeout(int seconds) {} + @Override public int getLoginTimeout() { return 0; } + @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + @Override public T unwrap(Class type) throws SQLException { + throw new SQLException("Not a wrapper for " + type.getName()); + } + @Override public boolean isWrapperFor(Class type) { return false; } + } + + @Test + public void ensureSchemaUsesSelectedDialectForLargeText() { + String url = System.getenv("TEAQL_TEST_DM8_URL"); + String user = System.getenv("TEAQL_TEST_DM8_USER"); + String password = System.getenv("TEAQL_TEST_DM8_PASSWORD"); + if ("true".equalsIgnoreCase(System.getenv("TEAQL_REQUIRE_LIVE_DB"))) { + assertTrue("TEAQL_TEST_DM8_URL must be set", url != null && !url.isBlank()); + assertTrue("TEAQL_TEST_DM8_USER must be set", user != null && !user.isBlank()); + assertTrue("TEAQL_TEST_DM8_PASSWORD must be set", password != null && !password.isBlank()); + } + Assume.assumeTrue("DM8 live test credentials are not configured", + url != null && user != null && password != null); + + DataSource dataSource = new TestDataSource(url, user, password); + JdbcSqlExecutor jdbc = new JdbcSqlExecutor(dataSource); + String table = "dm8_long_text_" + UUID.randomUUID().toString().substring(0, 8); + + SQLEntityDescriptor descriptor = new SQLEntityDescriptor(); + descriptor.setType("Dm8LongTextProbe"); + descriptor.setTargetType(BaseEntity.class); + descriptor.setEntitySupplier(BaseEntity::new); + descriptor.setDataService("dm8"); + for (String name : List.of("id", "version")) { + GenericSQLProperty property = (GenericSQLProperty) descriptor.addSimpleProperty(name, Long.class); + property.setTableName(table); + property.setColumnName(name); + property.setColumnType("BIGINT"); + } + GenericSQLProperty payload = (GenericSQLProperty) descriptor.addSimpleProperty("payload", String.class); + payload.setTableName(table); + payload.setColumnName("payload"); + payload.setColumnType("LARGE_TEXT"); + SimpleEntityMetaFactory metadata = new SimpleEntityMetaFactory(); + metadata.register(descriptor); + + TeaQLRuntime runtime = TeaQLRuntime.builder() + .metadata(metadata) + .dataService("dm8", new Dm8DataServiceExecutor("dm8", jdbc)) + .build(); + UserContext context = new DefaultUserContext(runtime); + context.ensureSchema(); + context.ensureSchema(); + + List> columns = jdbc.queryForList( + "SELECT data_type FROM all_tab_columns WHERE owner = USER " + + "AND table_name = UPPER(?) AND column_name = 'PAYLOAD'", + new Object[] {table}); + assertEquals(1, columns.size()); + assertEquals("CLOB", String.valueOf(columns.get(0).values().iterator().next()) + .toUpperCase(java.util.Locale.ROOT)); + } +} diff --git a/teaql-mssql/pom.xml b/teaql-mssql/pom.xml index 687d6de4..5e060112 100644 --- a/teaql-mssql/pom.xml +++ b/teaql-mssql/pom.xml @@ -24,5 +24,34 @@ junit test + + com.microsoft.sqlserver + mssql-jdbc + 13.4.0.jre11 + test + + + io.teaql + teaql-provider-jdbc + ${project.version} + test + + + io.teaql + teaql-runtime + ${project.version} + test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --add-reads io.teaql.mssql=io.teaql.runtime + + + + diff --git a/teaql-mssql/src/test/java/io/teaql/core/mssql/MssqlSchemaIntegrationTest.java b/teaql-mssql/src/test/java/io/teaql/core/mssql/MssqlSchemaIntegrationTest.java new file mode 100644 index 00000000..8285f404 --- /dev/null +++ b/teaql-mssql/src/test/java/io/teaql/core/mssql/MssqlSchemaIntegrationTest.java @@ -0,0 +1,101 @@ +package io.teaql.core.mssql; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import io.teaql.core.BaseEntity; +import io.teaql.core.UserContext; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.core.sql.GenericSQLProperty; +import io.teaql.core.sql.SQLEntityDescriptor; +import io.teaql.provider.jdbc.JdbcSqlExecutor; +import io.teaql.runtime.DefaultUserContext; +import io.teaql.runtime.TeaQLRuntime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; +import org.junit.Assume; +import org.junit.Test; + +public class MssqlSchemaIntegrationTest { + + private record TestDataSource(String url, String user, String password) implements DataSource { + @Override public Connection getConnection() throws SQLException { + return DriverManager.getConnection(url, user, password); + } + @Override public Connection getConnection(String username, String secret) throws SQLException { + return DriverManager.getConnection(url, username, secret); + } + @Override public PrintWriter getLogWriter() { return null; } + @Override public void setLogWriter(PrintWriter writer) {} + @Override public void setLoginTimeout(int seconds) {} + @Override public int getLoginTimeout() { return 0; } + @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + @Override public T unwrap(Class type) throws SQLException { + throw new SQLException("Not a wrapper for " + type.getName()); + } + @Override public boolean isWrapperFor(Class type) { return false; } + } + + @Test + public void ensureSchemaUsesSelectedDialectForLargeText() { + String url = System.getenv("TEAQL_TEST_MSSQL_URL"); + String user = System.getenv("TEAQL_TEST_MSSQL_USER"); + String password = System.getenv("TEAQL_TEST_MSSQL_PASSWORD"); + if ("true".equalsIgnoreCase(System.getenv("TEAQL_REQUIRE_LIVE_DB"))) { + assertTrue("TEAQL_TEST_MSSQL_URL must be set", url != null && !url.isBlank()); + assertTrue("TEAQL_TEST_MSSQL_USER must be set", user != null && !user.isBlank()); + assertTrue("TEAQL_TEST_MSSQL_PASSWORD must be set", password != null && !password.isBlank()); + } + Assume.assumeTrue("SQL Server live test credentials are not configured", + url != null && user != null && password != null); + + DataSource dataSource = new TestDataSource(url, user, password); + JdbcSqlExecutor jdbc = new JdbcSqlExecutor(dataSource); + String table = "mssql_long_text_" + UUID.randomUUID().toString().substring(0, 8); + + SQLEntityDescriptor descriptor = new SQLEntityDescriptor(); + descriptor.setType("MssqlLongTextProbe"); + descriptor.setTargetType(BaseEntity.class); + descriptor.setEntitySupplier(BaseEntity::new); + descriptor.setDataService("mssql"); + for (String name : List.of("id", "version")) { + GenericSQLProperty property = (GenericSQLProperty) descriptor.addSimpleProperty(name, Long.class); + property.setTableName(table); + property.setColumnName(name); + property.setColumnType("BIGINT"); + } + GenericSQLProperty payload = (GenericSQLProperty) descriptor.addSimpleProperty("payload", String.class); + payload.setTableName(table); + payload.setColumnName("payload"); + payload.setColumnType("LARGE_TEXT"); + SimpleEntityMetaFactory metadata = new SimpleEntityMetaFactory(); + metadata.register(descriptor); + + TeaQLRuntime runtime = TeaQLRuntime.builder() + .metadata(metadata) + .dataService("mssql", new MssqlDataServiceExecutor("mssql", jdbc)) + .build(); + UserContext context = new DefaultUserContext(runtime); + context.ensureSchema(); + context.ensureSchema(); + + List> columns = jdbc.queryForList( + "SELECT data_type, character_maximum_length FROM information_schema.columns " + + "WHERE table_schema = SCHEMA_NAME() AND table_name = ? AND column_name = ?", + new Object[] {table, "payload"}); + assertEquals(1, columns.size()); + Map column = columns.get(0); + assertEquals("nvarchar", String.valueOf(column.get("data_type")).toLowerCase(java.util.Locale.ROOT)); + assertEquals(-1, ((Number) column.get("character_maximum_length")).intValue()); + } +} From e0e1769eeffd01f0704373a158212ce61221da5a Mon Sep 17 00:00:00 2001 From: Philip Z Date: Wed, 23 Sep 2026 15:49:38 +0800 Subject: [PATCH 3/3] Run SQL Server schema test in live dialect CI --- .github/workflows/live-dialects.yml | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/live-dialects.yml b/.github/workflows/live-dialects.yml index 8c973202..e4bd15f0 100644 --- a/.github/workflows/live-dialects.yml +++ b/.github/workflows/live-dialects.yml @@ -52,3 +52,34 @@ jobs: mvn -B -ntp -pl teaql-postgres,teaql-mysql -am -Dtest=PostgresIntegrationTest,MysqlIntegrationTest -Dsurefire.failIfNoSpecifiedTests=false test + + sqlserver: + runs-on: ubuntu-latest + services: + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + env: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: TeaqlSchemaLiveTest123! + ports: + - 1433:1433 + options: >- + --health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P TeaqlSchemaLiveTest123! -C -Q 'SELECT 1' -b" + --health-interval 10s --health-timeout 5s --health-retries 20 + env: + TEAQL_REQUIRE_LIVE_DB: "true" + TEAQL_TEST_MSSQL_URL: "jdbc:sqlserver://127.0.0.1:1433;databaseName=master;encrypt=false;trustServerCertificate=true" + TEAQL_TEST_MSSQL_USER: sa + TEAQL_TEST_MSSQL_PASSWORD: TeaqlSchemaLiveTest123! + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + cache: maven + - name: Verify live SQL Server schema dialect (skips are forbidden) + run: >- + mvn -B -ntp -pl teaql-mssql -am + -Dtest=MssqlSchemaIntegrationTest + -Dsurefire.failIfNoSpecifiedTests=false test