Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0357139
Add Capability BATCH_INSERT
sdelamo Apr 24, 2026
4d9f6a0
Merge branch '5.0.x' into batch-insert-capability
sdelamo Apr 24, 2026
3ccd33b
Update data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/De…
sdelamo Apr 24, 2026
2b2091d
Update doc-examples/jdbc-sqlite/src/test/java/example/PersonRepositor…
sdelamo Apr 24, 2026
9023598
Remove unused Logger/LoggerFactory imports and make SQLITE constant p…
Copilot Apr 24, 2026
cc695af
remove unused imports
sdelamo Apr 24, 2026
b0503d6
Update doc-examples/jdbc-sqlite/src/test/java/example/PersonRepositor…
sdelamo Apr 24, 2026
6e973cb
Update doc-examples/jdbc-sqlite/src/main/java/example/SqliteConnectio…
sdelamo Apr 24, 2026
e8290a7
Update data-connection/src/main/java/io/micronaut/data/connection/Def…
sdelamo Apr 24, 2026
d446860
Update ConnectionCapabilities Javadoc to document supplier-based cont…
Copilot Apr 24, 2026
7cb5a43
sqlite
sdelamo Apr 24, 2026
fe9f3a6
fix
sdelamo Apr 24, 2026
e7e0309
use allow batch
sdelamo Apr 24, 2026
4fb1a29
fix cascade
sdelamo Apr 24, 2026
dde3909
allowBatch in dialect
sdelamo Apr 24, 2026
d5ebed9
remove BATCH_INSERT from Capability
sdelamo Apr 24, 2026
776d099
doc SQLite dialect
sdelamo Apr 24, 2026
bde5150
Update data-r2dbc/build.gradle
sdelamo Apr 24, 2026
6d8d390
Update data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/De…
sdelamo Apr 24, 2026
013012f
Update data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/Defa…
sdelamo Apr 24, 2026
8349abc
Update test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jd…
sdelamo Apr 24, 2026
5ff4779
Update doc-examples/jdbc-sqlite/src/test/java/example/PersonRepositor…
sdelamo Apr 24, 2026
6174dac
Update test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jd…
sdelamo Apr 24, 2026
118441c
Update test-suite-data-jdbc-sqlite/src/test/java/io/micronaut/data/jd…
sdelamo Apr 24, 2026
92cd3bd
rename to dictionary
sdelamo Apr 24, 2026
b2b7d52
remove connectionThrowingMetadataException methods
sdelamo Apr 24, 2026
399b448
Update doc-examples/jdbc-sqlite/src/test/java/example/BookRepositoryT…
sdelamo Apr 24, 2026
f52d114
rename to dictionary
sdelamo Apr 24, 2026
1f93edc
Fix INSERT DEFAULT VALUES to use dialect-specific syntax for MySQL
Copilot Apr 24, 2026
3474525
UUID SQLite
sdelamo Apr 24, 2026
680fa23
fix checkstyle
sdelamo Apr 24, 2026
b7c73e1
override supports(Capability,Connnection)
sdelamo Apr 24, 2026
adf36e1
micronaut-gradle-plugin = "5.0.0-M1"
sdelamo Apr 25, 2026
cebb99c
Merge branch '5.0.x' into sqlite-dialect
sdelamo Apr 29, 2026
857bbb0
sqlite from mnSQL
sdelamo May 3, 2026
a04f68c
Merge branch '5.0.x' into sqlite-dialect
sdelamo May 3, 2026
c6ab17e
sqlite
sdelamo May 3, 2026
fc50519
git: ignore .kotlin
sdelamo May 3, 2026
0632371
blockingAwait not blockingGet
sdelamo May 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data-connection/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ dependencies {
implementation mn.micronaut.core.reactive

testImplementation(mnTest.micronaut.test.junit5)
testImplementation(mnTest.junit.jupiter.params)
compileOnly(mn.kotlinx.coroutines.reactor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,23 @@
import io.micronaut.core.order.Ordered;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
* Defines the capabilities of a {@link Connection}.
* Defines the capabilities of a database connection.
* <p>
* The primary API is {@link #supports(Capability, Supplier)}, which accepts a supplier of
* the database product name so that capability detection works for both JDBC
* ({@link Connection}) and R2DBC ({@code io.r2dbc.spi.Connection}) connections.
* Convenience overloads such as {@link #supports(Capability, Connection)} delegate to the
* supplier-based method.
Comment on lines 28 to +35

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes the public ConnectionCapabilities API/SPI (adds a new primary method signature and shifts implementers to Supplier<String>), which is a significant change beyond 'Add SQLITE Dialect' as described in the PR metadata. Please update the PR description to explicitly call out this API/SPI change (and any compatibility expectations), or split it into a separate PR if the intent is to keep this SQLite-focused.

Copilot uses AI. Check for mistakes.
* <p>
* When the database product name cannot be determined (e.g., the supplier throws or returns
* an unrecognised value), implementations should default to returning {@code true} (capability
* is supported) so that the calling code can fall back to its own default behaviour safely.
* <p>
* You can provide your own implementation via Java SPI by registering
* {@code io.micronaut.data.connection.ConnectionCapabilities} in
Expand Down Expand Up @@ -59,14 +71,41 @@ enum Capability {
*/
ConnectionCapabilities INSTANCE = loadInstance();

/**
* Determines whether the database supports the requested capability.
* <p>
* Implementations receive a {@link Supplier} rather than a direct connection object so
* that this method works uniformly with JDBC ({@link Connection}) and R2DBC
* ({@code io.r2dbc.spi.Connection}) connections. The supplier is called lazily; if
* determining the product name is expensive, implementations may cache the result.
* <p>
* If the supplier throws or returns an unrecognised product name, implementations should
* return {@code true} (capability supported) so that callers can fall back gracefully to
* their own default behaviour.
*
* @param capability The capability to evaluate
* @param databaseProductNameSupplier supplier of the database product name; may throw
* {@link RuntimeException} if the metadata cannot be retrieved
* @return {@code true} if the connection supports the capability; {@code false} otherwise
*/
boolean supports(Capability capability, Supplier<String> databaseProductNameSupplier);

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the SPI contract from supports(Capability, Connection) to supports(Capability, Supplier<String>) is a source/binary breaking change for any external ConnectionCapabilities implementations loaded via Java SPI. To preserve compatibility, consider keeping the original supports(Capability, Connection) as the primary method (or at least as an abstract method) and adding the supplier-based method as a default method (e.g., defaulting to true), then migrate internal call sites to prefer the supplier overload. That way existing third-party implementations continue to compile/run unchanged while new implementations can override the supplier-based method for JDBC/R2DBC parity.

Copilot uses AI. Check for mistakes.

/**
* Determines whether the given JDBC connection supports the requested capability.
*
* @param capability The capability to evaluate
* @param connection The JDBC connection
* @return {@code true} if the connection supports the capability; {@code false} otherwise
*/
boolean supports(Capability capability, Connection connection);
default boolean supports(Capability capability, Connection connection) {
return supports(capability, () -> {
try {
return connection.getMetaData().getDatabaseProductName();
} catch (SQLException e) {
return "Unknown";
}
});
}
Comment on lines +100 to +108

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the SPI contract from supports(Capability, Connection) to supports(Capability, Supplier<String>) is a source/binary breaking change for any external ConnectionCapabilities implementations loaded via Java SPI. To preserve compatibility, consider keeping the original supports(Capability, Connection) as the primary method (or at least as an abstract method) and adding the supplier-based method as a default method (e.g., defaulting to true), then migrate internal call sites to prefer the supplier overload. That way existing third-party implementations continue to compile/run unchanged while new implementations can override the supplier-based method for JDBC/R2DBC parity.

Copilot uses AI. Check for mistakes.

private static ConnectionCapabilities loadInstance() {
List<ConnectionCapabilities> providers = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package io.micronaut.data.connection;

import io.micronaut.core.annotation.Internal;
import java.sql.Connection;
import java.util.function.Supplier;

/**
* Internal fallback {@link ConnectionCapabilities} implementation that assumes all capabilities are supported.
Expand All @@ -25,9 +25,8 @@
*/
@Internal
final class DefaultConnectionCapabilities implements ConnectionCapabilities {

@Override
public boolean supports(ConnectionCapabilities.Capability capability, Connection connection) {
public boolean supports(ConnectionCapabilities.Capability capability, Supplier<String> databaseProductNameSupplier) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package example;

import io.micronaut.data.connection.ConnectionCapabilities;

import java.sql.Connection;
import java.util.function.Supplier;

public class CustomConnectionCapabilities implements ConnectionCapabilities {

@Override
public boolean supports(ConnectionCapabilities.Capability capability, Connection connection) {
public boolean supports(ConnectionCapabilities.Capability capability, Supplier<String> databaseProductNameSupplier) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.micronaut.data.connection;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test contains unused imports (CsvSource, assertEquals) and an unused helper (connectionThrowingMetadataException). Either remove the dead code or add a test that uses connectionThrowingMetadataException() to cover the fallback behavior when metadata lookup fails.

Copilot uses AI. Check for mistakes.
import org.junit.jupiter.params.provider.ValueSource;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

import static org.junit.jupiter.api.Assertions.assertEquals;

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test contains unused imports (CsvSource, assertEquals) and an unused helper (connectionThrowingMetadataException). Either remove the dead code or add a test that uses connectionThrowingMetadataException() to cover the fallback behavior when metadata lookup fails.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +14

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imports appear unused in the file and will fail compilation under javac (unused imports are compilation errors). Remove the unused imports (e.g. CsvSource, SQLException, and assertEquals) or update the test to actually use them.

Suggested change
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;

Copilot uses AI. Check for mistakes.
import static org.junit.jupiter.api.Assertions.assertTrue;

class DefaultConnectionCapabilitiesTest {

private final DefaultConnectionCapabilities capabilities = new DefaultConnectionCapabilities();

@DisabledInNativeImage
@ParameterizedTest
@ValueSource(strings = {
"MySQL",
"MariaDB",
"PostgreSQL",
"H2",
"SQLite",
"Oracle",
"Microsoft SQL Server"
})
void supportsReadOnlyForDifferentDatabaseProducts(String databaseProductName) {
assertTrue(capabilities.supports(ConnectionCapabilities.Capability.READ_ONLY, connection(databaseProductName)));
}

private Connection connection(String databaseProductName) {
DatabaseMetaData metaData = (DatabaseMetaData) Proxy.newProxyInstance(
DatabaseMetaData.class.getClassLoader(),
new Class<?>[]{DatabaseMetaData.class},
(proxy, method, args) -> switch (method.getName()) {
case "getDatabaseProductName" -> databaseProductName;
case "unwrap" -> null;
case "isWrapperFor" -> false;
default -> throw new UnsupportedOperationException(method.getName());
}
);
return connection(metaData);
}

private Connection connection(DatabaseMetaData metaData) {
return (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
new Class<?>[]{Connection.class},
(proxy, method, args) -> switch (method.getName()) {
case "getMetaData" -> metaData;
case "unwrap" -> null;
case "isWrapperFor" -> false;
default -> throw new UnsupportedOperationException(method.getName());
}
);
}
}
1 change: 1 addition & 0 deletions data-model/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ dependencies {
testImplementation mn.micronaut.inject.java.test
testImplementation mnSerde.micronaut.serde.jackson
testImplementation mn.micronaut.jackson.databind
testImplementation(mnTest.micronaut.test.junit5)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,19 @@ public enum Dialect {
/**
* Postgres 9.5 or later.
*/
POSTGRES(true, false, ALL_TYPES, false, true, true, true),
POSTGRES(true, false, ALL_TYPES, false, true, true, true, true),
/**
* SQL server 2012 or above.
*/
SQL_SERVER(false, false, ALL_TYPES),
SQL_SERVER(false, false, ALL_TYPES, false, false, false, false, true),
/**
* Oracle 12c or above.
*/
ORACLE(true, true, ALL_TYPES, true, true, true, true),
ORACLE(true, true, ALL_TYPES, true, true, true, true, true),
/**
* SQLite
*/
SQLITE(false, false, ALL_TYPES, false, true, true, true, false),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SQLITE(false, false, ALL_TYPES, false, true, true, true, false),
SQLITE(false, false, ALL_TYPES, false, false, false, false, false),

I think SQLite does not support RETURNING operations

/**
* Ansi compliant SQL.
*/
Expand All @@ -84,6 +88,8 @@ public enum Dialect {
private final boolean supportsUpdateReturning;
private final boolean supportsInsertReturning;
private final boolean supportsDeleteReturning;
private final boolean supportsReadOnly;


/**
* Allows customization of batch support.
Expand All @@ -93,7 +99,7 @@ public enum Dialect {
* @param joinTypesSupported EnumSet of supported join types.
*/
Dialect(boolean supportsBatch, boolean stringUUID, EnumSet<Join.Type> joinTypesSupported) {
this(supportsBatch, stringUUID, joinTypesSupported, false, false, false, false);
this(supportsBatch, stringUUID, joinTypesSupported, false, false, false, false, true);
}

/**

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new supportsReadOnly ctor parameter increases churn across all enum constants. To reduce future diffs and make the change safer, consider adding an overload matching the previous signature (without supportsReadOnly) that defaults supportsReadOnly to true. That keeps enum constant declarations stable as dialect flags evolve.

Suggested change
/**
/**
* The constructor matching the previous full signature.
*
* @param supportsBatch If batch is supported
* @param stringUUID Does the dialect require a string UUID
* @param joinTypesSupported EnumSet of supported join types.
* @param supportsJsonEntity Whether JSON entity is supported
* @param supportsUpdateReturning Whether the dialect supports UPDATE ... RETURNING clause.
* @param supportsInsertReturning Whether the dialect supports INSERT ... RETURNING clause.
* @param supportsDeleteReturning Whether the dialect supports DELETE ... RETURNING clause.
*/
Dialect(boolean supportsBatch,
boolean stringUUID,
EnumSet<Join.Type> joinTypesSupported,
boolean supportsJsonEntity,
boolean supportsUpdateReturning,
boolean supportsInsertReturning,
boolean supportsDeleteReturning) {
this(supportsBatch, stringUUID, joinTypesSupported, supportsJsonEntity, supportsUpdateReturning, supportsInsertReturning, supportsDeleteReturning, true);
}
/**

Copilot uses AI. Check for mistakes.
Expand All @@ -106,6 +112,7 @@ public enum Dialect {
* @param supportsUpdateReturning Whether the dialect supports UPDATE ... RETURNING clause.
* @param supportsInsertReturning Whether the dialect supports INSERT ... RETURNING clause.
* @param supportsDeleteReturning Whether the dialect supports DELETE ... RETURNING clause.
* @param supportsReadOnly Whether the dialect supports invoking {@link java.sql.Connection#setReadOnly(boolean)}
* @since 4.2.0
*/
Dialect(boolean supportsBatch,
Expand All @@ -114,14 +121,16 @@ public enum Dialect {
boolean supportsJsonEntity,
boolean supportsUpdateReturning,
boolean supportsInsertReturning,
boolean supportsDeleteReturning) {
boolean supportsDeleteReturning,
boolean supportsReadOnly) {
this.supportsBatch = supportsBatch;
this.stringUUID = stringUUID;
this.joinTypesSupported = joinTypesSupported;
this.supportsJsonEntity = supportsJsonEntity;
this.supportsUpdateReturning = supportsUpdateReturning;
this.supportsInsertReturning = supportsInsertReturning;
this.supportsDeleteReturning = supportsDeleteReturning;
this.supportsReadOnly = supportsReadOnly;
}
Comment on lines 117 to 133

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new supportsReadOnly ctor parameter increases churn across all enum constants. To reduce future diffs and make the change safer, consider adding an overload matching the previous signature (without supportsReadOnly) that defaults supportsReadOnly to true. That keeps enum constant declarations stable as dialect flags evolve.

Copilot uses AI. Check for mistakes.

/**
Expand Down Expand Up @@ -210,4 +219,14 @@ public boolean supportsInsertReturning() {
public boolean supportsDeleteReturning() {
return supportsDeleteReturning;
}

/**
* Whether the dialect supports read only. e.g. invoking {@link java.sql.Connection#setReadOnly(boolean)}.
*
* @return true if it does support
* @since 5.0.0
*/
public boolean supportsReadOnly() {
return supportsReadOnly;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1229,9 +1229,13 @@ public String[] getPropertyPath() {
});
}

builder = INSERT_INTO + getTableName(entity) +
" (" + String.join(",", columns) + CLOSE_BRACKET + " " +
"VALUES (" + String.join(String.valueOf(COMMA), values) + CLOSE_BRACKET;
if (columns.isEmpty()) {
builder = INSERT_INTO + getTableName(entity) + " DEFAULT VALUES";
} else {
builder = INSERT_INTO + getTableName(entity) +
" (" + String.join(",", columns) + CLOSE_BRACKET + " " +
"VALUES (" + String.join(String.valueOf(COMMA), values) + CLOSE_BRACKET;
}
Comment on lines +1280 to +1291

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INSERT INTO <table> DEFAULT VALUES is not supported by all SQL dialects (notably MySQL/MariaDB typically require INSERT INTO <table> () VALUES ()). Since this query builder is dialect-agnostic, emitting DEFAULT VALUES unconditionally when columns is empty risks breaking inserts on those dialects. Consider generating dialect-specific SQL here (e.g. DEFAULT VALUES for SQLite/Postgres/H2/SQL Server/Oracle, but () VALUES () for MySQL/MariaDB).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot do the suggested change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 1f93edc. MySQL now generates INSERT INTO <table> () VALUES () while all other dialects (SQLite, Postgres, H2, SQL Server, Oracle, ANSI) continue to use INSERT INTO <table> DEFAULT VALUES.


if (definition.returning()) {
if (dialect == Dialect.ORACLE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ public String getSqlType(Dialect dialect) {
case LONG -> {
if (dialect == Dialect.ORACLE) {
yield "NUMBER(19)";

// In SQLite, INTEGER PRIMARY KEY has special behavior (rowid, auto-generation).
} else if (dialect == Dialect.SQLITE && isAutoGenerated()) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For such a table:

record Person(@Id @GeneratedValue @Nullable Long id, String name, int age) {}

for SQLite the following table should be created:

CREATE TABLE "person" ("id" INTEGER PRIMARY KEY,"name" VARCHAR(255) NOT NULL,"age" INT NOT NULL);

BIG PRIMARY KEY does not work for primary key auto generation.

yield "INTEGER";

} else {
yield "BIGINT";
}
Expand Down
Loading
Loading