Skip to content
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

SQL Criteria Implementation #1394

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
17.0.4.1
9 changes: 9 additions & 0 deletions criteria/sql/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[*]
indent_style = space
indent_size = 2
continuation_indent_size = 4
max_line_length = 120
charset = utf-8
end_of_line = lf
insert_final_newline = true

72 changes: 72 additions & 0 deletions criteria/sql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Criteria Adapter for SQL databases.

**Note:** _This module is highly_ ***experimental*** _and is subject to change in the future.
The underlying implementation and APIs are likely to change in the future. Use discretion when using
this module for anything more than experimentation._

This is a minimal implementation of criteria support for SQL databases, supporting the basic
CRUD operations of entities persisted in single tables. It is intended to be a lightweight
alternative to other ORMs but does not compete directly with them or support all the features of SQL databases.
There are no dependencies in this implementation beyond basic JDBC support.

## General approach

The approach is a pretty straightforward mapping from Criteria APIs to the underlying
JDBC concepts. A Criteria operation is converted into a SQL command (in `commands`) which
call to the SQL compiler to generate a SQL statement corresponding to the operation, and
which also sets up mapping from supplied data to the underlying named parameters. When
statements are executed, the parameters are converted, then bound into position in
within `FluentStatement`. Inserts and updates of entities are done in a batch.

### Annotations

There are some SQL-specific annotations supported, which help with mapping to the
underlying tables.

* `SQL.Table` - specify the target table for an entity
* `SQL.Column` - map a property to a column and optionally a column type

### Type conversion

Object and properties are converted to underlying SQL data types using classes
in the `conversion` package. At the lower level rows are mapped to objects by calling to
the associated `RowMapper` which is registered in `RowMappers`. A `RowMapper` class and a `Setup`
class are generated for each class annotated with `SQL.Table` - for example a `Note` class would
generate a `SqlNoteSetup` class and a `SqlNoteRowMapper` class - the latter of which will be registered
into `RowMappers` when the setup class us called to get a `Backend`. Currently, this assumes a standard
immutable class builder being generated, which may or may not work if `Style` is used extensively.
Type conversion happens my looking up registered converters in `TypeConverters`. All
conversion lookups support registration of custom conversions.

#### Generic Jackson conversion
A default `RowMapper` is generated however you can also use `Jackson` to convert
from a row to objects, with the Jackson annotations having an impact on the mapping (`JsonSerializeAs` etc).
As an example, the following could be used to generate a RowMapper used in a custom setup.

```java
static <T> RowMapper<T> newRowMapper(final SqlTypeMetadata metadata) {
return row -> {
final Map<String, Object> data = new HashMap<>();
final ResultSetMetaData rm = row.getMetaData();
for (int i = 1; i <= rm.getColumnCount(); ++i) {
final String name = rm.getColumnName(i).toLowerCase(Locale.ROOT);
final SqlPropertyMetadata property = metadata.columns().get(name);
if (property != null) {
data.put(property.name(), TypeConverters.convert(property.mapping().type(), property.type(),
property.mapping().fetcher().apply(row, rm.getColumnName(i))));
}
}
return (T) MAPPER.convertValue(data, metadata.type());
};
}
```

## Limitations

- Watch,GetByKey,DeleteByKey,Upsert are not implemented
- Handling of autogenerated keys and returning keys is not supported
- Joins, sub-graphs etc. are not supported.
- Projections, aggregations, groupby etc are not supported

---
**Note:** _This module is highly_ ***experimental*** _and is subject to change in the future._
136 changes: 93 additions & 43 deletions criteria/sql/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>criteria</artifactId>
<groupId>org.immutables</groupId>
<version>2.9.4-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>criteria-sql</artifactId>
<name>${project.groupId}.${project.artifactId}</name>
<description>SQL Criteria backend</description>

<properties>
<module.name>${project.groupId}.criteria.sql</module.name>
<h2.version>2.1.214</h2.version>
<liquibase.version>4.15.0</liquibase.version>
</properties>

<dependencies>
Expand All @@ -36,58 +40,25 @@
<type>jar</type>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<!-- Jackson is needed for our bson to jackson bridge API
Color? -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava2.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
<groupId>org.immutables</groupId>
<artifactId>generator</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
<groupId>org.immutables</groupId>
<artifactId>metainf</artifactId>
<version>${project.version}</version>
</dependency>


<dependency>
<groupId>org.immutables</groupId>
<artifactId>testing</artifactId>
Expand All @@ -101,5 +72,84 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<configuration>
<compilerArgs combine.children="append">
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -Xep:CheckReturnValue:ERROR -Xep:MethodCanBeStatic:ERROR -Xep:BadImport:ERROR -Xep:MissingOverride:ERROR -Xep:OrphanedFormatString:ERROR -Xep:RedundantOverride:ERROR -Xep:RedundantThrows:ERROR -Xep:RemoveUnusedImports:ERROR -Xep:UnusedMethod:ERROR</arg>
<arg>-Xlint:unchecked</arg>
</compilerArgs>

<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>${errorprone.version}</version>
</path>
<path>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>org.immutables</groupId>
<artifactId>metainf</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>org.immutables</groupId>
<artifactId>generator-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<configuration>
<compilerArgs combine.children="append">
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -Xep:CheckReturnValue:ERROR -Xep:MethodCanBeStatic:ERROR -Xep:BadImport:ERROR -Xep:MissingOverride:ERROR -Xep:OrphanedFormatString:ERROR -Xep:RedundantOverride:ERROR -Xep:RedundantThrows:ERROR -Xep:RemoveUnusedImports:ERROR -Xep:UnusedMethod:ERROR</arg>
<arg>-Xlint:unchecked</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>${errorprone.version}</version>
</path>
<path>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>org.immutables</groupId>
<artifactId>criteria-sql</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
48 changes: 48 additions & 0 deletions criteria/sql/src/org/immutables/criteria/sql/SQL.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2022 Immutables Authors and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.immutables.criteria.sql;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A set of annotations specific to the SQL backend
*/
public @interface SQL {
/**
* Used to define the table an entity is mapped to. If not defined the table name will be the same as
* {@code Class.getSimpleName() }
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value() default "";
}

/**
* Used to map a property to a column and the target column type. This will drive the use of
* {@code TypeConverters.convert()} for mapping values to/from the database.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Column {
String name() default "";

Class<?> type();
}
}