-
Notifications
You must be signed in to change notification settings - Fork 227
Add SQLITE Dialect #3830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 5.0.x
Are you sure you want to change the base?
Add SQLITE Dialect #3830
Changes from 33 commits
0357139
4d9f6a0
3ccd33b
2b2091d
9023598
cc695af
b0503d6
6e973cb
e8290a7
d446860
7cb5a43
fe9f3a6
e7e0309
4fb1a29
dde3909
d5ebed9
776d099
bde5150
6d8d390
013012f
8349abc
5ff4779
6174dac
118441c
92cd3bd
b2b7d52
399b448
f52d114
1f93edc
3474525
680fa23
b7c73e1
adf36e1
cebb99c
857bbb0
a04f68c
c6ab17e
fc50519
0632371
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
| * <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 | ||
|
|
@@ -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); | ||
|
||
|
|
||
| /** | ||
| * 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
|
||
|
|
||
| private static ConnectionCapabilities loadInstance() { | ||
| List<ConnectionCapabilities> providers = new ArrayList<>(); | ||
|
|
||
| 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; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
| 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.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; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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), | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think SQLite does not support RETURNING operations |
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Ansi compliant SQL. | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -84,6 +88,7 @@ public enum Dialect { | |||||||||||||||||||||||||||||||||||||||||||||||
| private final boolean supportsUpdateReturning; | ||||||||||||||||||||||||||||||||||||||||||||||||
| private final boolean supportsInsertReturning; | ||||||||||||||||||||||||||||||||||||||||||||||||
| private final boolean supportsDeleteReturning; | ||||||||||||||||||||||||||||||||||||||||||||||||
| private final boolean supportsReadOnly; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Allows customization of batch support. | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -93,7 +98,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); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |
| /** | |
| * 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
AI
Apr 24, 2026
There was a problem hiding this comment.
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.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -945,6 +945,11 @@ private String addGeneratedStatementToColumn(GeneratedValue.Type type, DataType | |
| column += " AUTO_INCREMENT"; | ||
| } | ||
| break; | ||
| case SQLITE: | ||
| if (type == UUID) { | ||
| column += " NOT NULL DEFAULT (lower(hex(randomblob(4))||'-'||hex(randomblob(2))||'-'||'4'||substr(hex(randomblob(2)),2)||'-'||substr('89ab',abs(random())%4+1,1)||substr(hex(randomblob(2)),2)||'-'||hex(randomblob(6))))"; | ||
| } | ||
| break; | ||
|
Comment on lines
+991
to
+995
|
||
| default: | ||
| if (type == UUID) { | ||
| column += " NOT NULL DEFAULT random_uuid()"; | ||
|
|
@@ -1229,9 +1234,18 @@ 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()) { | ||
| // MySQL/MariaDB do not support DEFAULT VALUES syntax | ||
| if (dialect == Dialect.MYSQL) { | ||
| builder = INSERT_INTO + getTableName(entity) + " () VALUES ()"; | ||
| } else { | ||
| 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
|
||
|
|
||
| if (definition.returning()) { | ||
| if (dialect == Dialect.ORACLE) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,6 +209,8 @@ public String getSqlType(Dialect dialect) { | |
| case UUID -> { | ||
| if (dialect == Dialect.ORACLE || dialect == Dialect.MYSQL) { | ||
| yield varcharType(36); | ||
| } else if (dialect == Dialect.SQLITE) { | ||
| yield "TEXT"; | ||
| } else if (dialect == Dialect.SQL_SERVER) { | ||
| yield "UNIQUEIDENTIFIER"; | ||
| } else { | ||
|
|
@@ -250,6 +252,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()) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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);
|
||
| yield "INTEGER"; | ||
|
|
||
| } else { | ||
| yield "BIGINT"; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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
ConnectionCapabilitiesAPI/SPI (adds a new primary method signature and shifts implementers toSupplier<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.