From d5c35bb8b26d9ee927070541df00eab3f96ab799 Mon Sep 17 00:00:00 2001 From: Gavin Nicol Date: Wed, 17 Aug 2022 13:21:25 -0400 Subject: [PATCH 1/6] SQL Criteria Implementation Initial SQL implementation of Criteria API limited to basic CRUD. --- criteria/sql/README.md | 38 ++ criteria/sql/pom.xml | 192 ++++++----- .../src/org/immutables/criteria/sql/SQL.java | 33 ++ .../immutables/criteria/sql/SQLBackend.java | 111 ++++++ .../immutables/criteria/sql/SQLException.java | 7 + .../org/immutables/criteria/sql/SQLSetup.java | 45 +++ .../immutables/criteria/sql/SqlBackend.java | 132 ++++--- .../criteria/sql/commands/SQLCommand.java | 56 +++ .../sql/commands/SQLCountCommand.java | 80 +++++ .../sql/commands/SQLDeleteCommand.java | 75 ++++ .../sql/commands/SQLInsertCommand.java | 92 +++++ .../criteria/sql/commands/SQLSaveCommand.java | 94 +++++ .../sql/commands/SQLSelectCommand.java | 75 ++++ .../sql/commands/SQLUpdateCommand.java | 83 +++++ .../criteria/sql/compiler/SQLCompiler.java | 156 +++++++++ .../sql/compiler/SQLConstantExpression.java | 27 ++ .../sql/compiler/SQLCountStatement.java | 39 +++ .../sql/compiler/SQLDeleteStatement.java | 25 ++ .../criteria/sql/compiler/SQLExpression.java | 20 ++ .../sql/compiler/SQLFilterExpression.java | 25 ++ .../sql/compiler/SQLInsertStatement.java | 29 ++ .../sql/compiler/SQLNameExpression.java | 24 ++ .../criteria/sql/compiler/SQLNode.java | 20 ++ .../criteria/sql/compiler/SQLPathNaming.java | 12 + .../sql/compiler/SQLQueryVisitor.java | 124 +++++++ .../sql/compiler/SQLSaveStatement.java | 31 ++ .../sql/compiler/SQLSelectStatement.java | 37 ++ .../sql/compiler/SQLSortExpression.java | 7 + .../criteria/sql/compiler/SQLStatement.java | 20 ++ .../sql/compiler/SQLUpdateStatement.java | 28 ++ .../sql/conversion/ColumnFetcher.java | 8 + .../sql/conversion/ColumnFetchers.java | 38 ++ .../sql/conversion/ColumnMapping.java | 12 + .../criteria/sql/conversion/RowMapper.java | 9 + .../criteria/sql/conversion/RowMappers.java | 73 ++++ .../sql/conversion/TypeConverter.java | 6 + .../sql/conversion/TypeConverters.java | 207 +++++++++++ .../criteria/sql/dialects/SQL92Dialect.java | 210 +++++++++++ .../criteria/sql/dialects/SQLDialect.java | 42 +++ .../criteria/sql/jdbc/FluentStatement.java | 239 +++++++++++++ .../sql/reflection/PropertyExtractor.java | 6 + .../sql/reflection/SQLContainerNaming.java | 30 ++ .../sql/reflection/SQLPropertyMetadata.java | 15 + .../sql/reflection/SQLTypeMetadata.java | 156 +++++++++ .../criteria/sql/util/TypeKeyHashMap.java | 39 +++ .../criteria/sql/compiler/Dummy.java | 13 + .../sql/compiler/SQLCompilerTests.java | 282 +++++++++++++++ .../criteria/sql/note/AbstractTestBase.java | 72 ++++ .../immutables/criteria/sql/note/Note.java | 43 +++ .../sql/note/NoteRepositoryTests.java | 325 ++++++++++++++++++ .../criteria/sql/note/NoteSetup.java | 33 ++ criteria/sql/test/test-migrations.xml | 41 +++ 52 files changed, 3508 insertions(+), 128 deletions(-) create mode 100644 criteria/sql/README.md create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQL.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQLException.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/Note.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java create mode 100644 criteria/sql/test/test-migrations.xml diff --git a/criteria/sql/README.md b/criteria/sql/README.md new file mode 100644 index 000000000..93dc7b25a --- /dev/null +++ b/criteria/sql/README.md @@ -0,0 +1,38 @@ +# Criteria Adapter for SQL databases. + +This is a minimal implementation of criteria support for SQL databases, supporting the basic +CRUD operations of entities persistend in single tables. It is intended to be a lightweight +alternative to other ORMs but does not compete directly or support all the features of SQL databases. +There are no dependencies in this implementation beyond basic JDBC support and Jackson. + +## 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. The default `RowMapper` uses `jackson` to convert +from a row to objects, with the jackson annotations having an impact on the mapping. +Type conversion happens my looking up registered converters in `TypeConverters`. All +conversion lookups support registration of custom conversions. + +## 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 diff --git a/criteria/sql/pom.xml b/criteria/sql/pom.xml index 1a8aa95b4..e9f2d25f2 100644 --- a/criteria/sql/pom.xml +++ b/criteria/sql/pom.xml @@ -14,92 +14,112 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - criteria - org.immutables - 2.9.1-SNAPSHOT - - 4.0.0 - criteria-sql - ${project.groupId}.${project.artifactId} - SQL Criteria backend + + + criteria + org.immutables + 2.9.1-SNAPSHOT + + 4.0.0 + criteria-sql + ${project.groupId}.${project.artifactId} + SQL Criteria backend + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + + - - ${project.groupId}.criteria.sql - + + ${project.groupId}.criteria.sql + 4.15.0 + 2.1.214 + 3.32.0 + 2.13.3 + - - - org.immutables - criteria-common - jar - ${project.version} - - - com.google.guava - guava - ${guava.version} - - - com.google.code.findbugs - jsr305 - ${jsr305.version} - provided - - - org.immutables - value - ${project.version} - true - - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson-databind.version} - - - io.reactivex.rxjava2 - rxjava - ${rxjava2.version} - - - com.fasterxml.jackson.datatype - jackson-datatype-guava - ${jackson.version} - test - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson.version} - test - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - ${jackson.version} - test - - - org.immutables - testing - ${project.version} - test - - - org.immutables - criteria-common - ${project.version} - test-jar - test - - + + + org.immutables + criteria-common + jar + ${project.version} + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + provided + + + org.immutables + value + ${project.version} + true + + + io.reactivex.rxjava2 + rxjava + ${rxjava2.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + com.fasterxml.jackson.module + jackson-module-parameter-names + ${jackson.version} + + + + org.immutables + testing + ${project.version} + test + + + org.immutables + criteria-common + ${project.version} + test-jar + test + + + com.h2database + h2 + ${h2.version} + test + + + org.liquibase + liquibase-core + ${liquibase.version} + test + + diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQL.java b/criteria/sql/src/org/immutables/criteria/sql/SQL.java new file mode 100644 index 000000000..38fcb6630 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SQL.java @@ -0,0 +1,33 @@ +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.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @interface Column { + String name() default ""; + + Class type(); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java b/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java new file mode 100644 index 000000000..6ee92c082 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java @@ -0,0 +1,111 @@ +/* + * 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 io.reactivex.Flowable; +import org.immutables.criteria.backend.Backend; +import org.immutables.criteria.backend.DefaultResult; +import org.immutables.criteria.backend.StandardOperations; +import org.immutables.criteria.sql.commands.*; +import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.reactivestreams.Publisher; + +/** + * Implementation of {@code Backend} which delegates to the underlying SQL specific commands. + */ +public class SQLBackend implements Backend { + private final SQLSetup setup; + + public SQLBackend(final SQLSetup setup) { + this.setup = setup; + } + + public static SQLBackend of(final SQLSetup setup) { + return new SQLBackend(setup); + } + + @Override + public Session open(final Class type) { + return new SQLSession(type, setup); + } + + public static class SQLSession implements Backend.Session { + private final Class type; + private final SQLSetup setup; + + private final SQLTypeMetadata metadata; + + SQLSession(final Class type, final SQLSetup setup) { + this.type = type; + this.setup = setup; + + metadata = SQLTypeMetadata.of(type); + } + + public SQLSetup setup() { + return setup; + } + + public SQLTypeMetadata metadata() { + return metadata; + } + + @Override + public Class entityType() { + return type; + } + + @Override + public Result execute(final Operation operation) { + return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); + } + + private Publisher executeInternal(final Operation operation) { + if (operation instanceof StandardOperations.Select) { + final StandardOperations.Select select = (StandardOperations.Select) operation; + final SQLCommand command = select.query().count() + ? new SQLCountCommand(this, setup, select) + : new SQLSelectCommand(this, setup, select); + return command.execute(); + } else if (operation instanceof StandardOperations.Update) { + final StandardOperations.Update update = (StandardOperations.Update) operation; + final SQLCommand command = new SQLSaveCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.UpdateByQuery) { + final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; + final SQLCommand command = new SQLUpdateCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.Insert) { + final StandardOperations.Insert insert = (StandardOperations.Insert) operation; + final SQLCommand command = new SQLInsertCommand(this, setup, insert); + return command.execute(); + } else if (operation instanceof StandardOperations.Delete) { + final StandardOperations.Delete delete = (StandardOperations.Delete) operation; + final SQLCommand command = new SQLDeleteCommand(this, setup, delete); + return command.execute(); + } else if (operation instanceof StandardOperations.Watch) { + throw new UnsupportedOperationException("Watch"); + } else if (operation instanceof StandardOperations.DeleteByKey) { + throw new UnsupportedOperationException("DeleteByKey"); + } else if (operation instanceof StandardOperations.GetByKey) { + throw new UnsupportedOperationException("GetByKey"); + } + + return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", + operation, SQLBackend.class.getSimpleName()))); + } + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java new file mode 100644 index 000000000..cf7bb5ea2 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java @@ -0,0 +1,7 @@ +package org.immutables.criteria.sql; + +public class SQLException extends RuntimeException { + public SQLException(String message) { + super(message); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java b/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java new file mode 100644 index 000000000..3ce34e402 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java @@ -0,0 +1,45 @@ +/* + * 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 org.immutables.criteria.sql.dialects.SQL92Dialect; +import org.immutables.criteria.sql.dialects.SQLDialect; +import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.value.Value; + +import javax.sql.DataSource; + +@Value.Immutable +public interface SQLSetup { + static SQLSetup of(final DataSource datasource, final SQLTypeMetadata metadata) { + + return of(datasource, new SQL92Dialect(), metadata); + } + + static SQLSetup of(final DataSource datasource, final SQLDialect dialect, final SQLTypeMetadata metadata) { + return ImmutableSQLSetup.builder() + .datasource(datasource) + .dialect(dialect) + .metadata(metadata) + .build(); + } + + SQLTypeMetadata metadata(); + + DataSource datasource(); + + SQLDialect dialect(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java b/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java index b728f9f88..6ee92c082 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java @@ -1,63 +1,111 @@ +/* + * 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 io.reactivex.Flowable; import org.immutables.criteria.backend.Backend; import org.immutables.criteria.backend.DefaultResult; -import org.immutables.criteria.backend.StandardOperations.Delete; -import org.immutables.criteria.backend.StandardOperations.Insert; -import org.immutables.criteria.backend.StandardOperations.Select; -import org.immutables.criteria.backend.StandardOperations.Update; -import org.immutables.criteria.backend.StandardOperations.UpdateByQuery; +import org.immutables.criteria.backend.StandardOperations; +import org.immutables.criteria.sql.commands.*; +import org.immutables.criteria.sql.reflection.SQLTypeMetadata; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -public final class SqlBackend implements Backend { +/** + * Implementation of {@code Backend} which delegates to the underlying SQL specific commands. + */ +public class SQLBackend implements Backend { + private final SQLSetup setup; - @Override - public Session open(Class entityType) { - return new Session(entityType); - } - - private static class Session implements Backend.Session { - - private final Class type; + public SQLBackend(final SQLSetup setup) { + this.setup = setup; + } - Session(Class type) { - this.type = type; + public static SQLBackend of(final SQLSetup setup) { + return new SQLBackend(setup); } @Override - public Class entityType() { - return type; + public Session open(final Class type) { + return new SQLSession(type, setup); } - @Override - public Result execute(Operation operation) { - Publisher publisher = new Publisher() { - @Override - public void subscribe(Subscriber s) { - System.err.println("not subscribed"); + public static class SQLSession implements Backend.Session { + private final Class type; + private final SQLSetup setup; + + private final SQLTypeMetadata metadata; + + SQLSession(final Class type, final SQLSetup setup) { + this.type = type; + this.setup = setup; + + metadata = SQLTypeMetadata.of(type); } - }; - if (operation instanceof Insert) { - Insert insert = (Insert) operation; - } else if (operation instanceof Delete) { - Delete delete = (Delete) operation; + public SQLSetup setup() { + return setup; + } + + public SQLTypeMetadata metadata() { + return metadata; + } - } else if (operation instanceof Select) { - Select select = (Select) operation; - System.out.println(select); + @Override + public Class entityType() { + return type; + } - } else if (operation instanceof Update) { - Update update = (Update) operation; + @Override + public Result execute(final Operation operation) { + return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); + } - } else if (operation instanceof UpdateByQuery) { - UpdateByQuery update = (UpdateByQuery) operation; + private Publisher executeInternal(final Operation operation) { + if (operation instanceof StandardOperations.Select) { + final StandardOperations.Select select = (StandardOperations.Select) operation; + final SQLCommand command = select.query().count() + ? new SQLCountCommand(this, setup, select) + : new SQLSelectCommand(this, setup, select); + return command.execute(); + } else if (operation instanceof StandardOperations.Update) { + final StandardOperations.Update update = (StandardOperations.Update) operation; + final SQLCommand command = new SQLSaveCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.UpdateByQuery) { + final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; + final SQLCommand command = new SQLUpdateCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.Insert) { + final StandardOperations.Insert insert = (StandardOperations.Insert) operation; + final SQLCommand command = new SQLInsertCommand(this, setup, insert); + return command.execute(); + } else if (operation instanceof StandardOperations.Delete) { + final StandardOperations.Delete delete = (StandardOperations.Delete) operation; + final SQLCommand command = new SQLDeleteCommand(this, setup, delete); + return command.execute(); + } else if (operation instanceof StandardOperations.Watch) { + throw new UnsupportedOperationException("Watch"); + } else if (operation instanceof StandardOperations.DeleteByKey) { + throw new UnsupportedOperationException("DeleteByKey"); + } else if (operation instanceof StandardOperations.GetByKey) { + throw new UnsupportedOperationException("GetByKey"); + } - } else { - throw new UnsupportedOperationException(); - } - return DefaultResult.of(publisher); + return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", + operation, SQLBackend.class.getSimpleName()))); + } } - } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java new file mode 100644 index 000000000..b7c8b9ffe --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java @@ -0,0 +1,56 @@ +/* + * 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.commands; + +import org.immutables.criteria.sql.compiler.SQLConstantExpression; +import org.immutables.criteria.sql.conversion.TypeConverters; +import org.reactivestreams.Publisher; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public interface SQLCommand { + Publisher execute(); + + /** + * Convert from properties encoded in {@code }SQLConstantExpression} to the underlying database + * for use by {@code FluentStatment} + * + * @param properties the properties to convert + * @return A new map with keys and types mapped to the underlying database columns and types + */ + default Map toParameters(final Map properties) { + final Map parameters = new HashMap<>(); + for (final SQLConstantExpression property : properties.values()) { + // Conversion of lists to target type lists - used for IN() + if (property.value() instanceof List) { + final List v = (List) property.value(); + final List converted = v.stream() + .map(f -> TypeConverters.convert(f.getClass(), property.target().mapping().type(), f)) + .collect(Collectors.toList()); + parameters.put(property.sql(), converted); + } else { + parameters.put(property.sql(), + TypeConverters.convert(property.value().getClass(), + property.target().mapping().type(), + property.value())); + } + } + return parameters; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java new file mode 100644 index 000000000..6441040ce --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java @@ -0,0 +1,80 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Select; +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.compiler.SQLCompiler; +import org.immutables.criteria.sql.compiler.SQLCountStatement; +import org.immutables.criteria.sql.compiler.SQLFilterExpression; +import org.immutables.criteria.sql.conversion.RowMappers; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class SQLCountCommand implements SQLCommand { + + private final SQLSetup setup; + private final Select operation; + private final SQLBackend.SQLSession session; + + public SQLCountCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Select operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query().count() : "count() query expected"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SQLBackend.SQLSession session, final Select operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + + final SQLCountStatement count = SQLCompiler.count(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().count(count))) { + final Optional ret = statement + .set(toParameters(count + .filter() + .map(SQLFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .list((rs) -> RowMappers.get(Long.class).map(rs)) + .stream() + .findFirst(); + return ret.orElseThrow(() -> new SQLException("No results returned from count()")); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java new file mode 100644 index 000000000..6cd4badf7 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java @@ -0,0 +1,75 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Delete; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.compiler.SQLCompiler; +import org.immutables.criteria.sql.compiler.SQLDeleteStatement; +import org.immutables.criteria.sql.compiler.SQLFilterExpression; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.concurrent.Callable; + +public class SQLDeleteCommand implements SQLCommand { + + private final SQLSetup setup; + private final Delete operation; + private final SQLBackend.SQLSession session; + + public SQLDeleteCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Delete operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SQLBackend.SQLSession session, final Delete operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + + final SQLDeleteStatement delete = SQLCompiler.delete(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().delete(delete))) { + final int result = statement + .set(toParameters(delete + .filter() + .map(SQLFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .delete(); + return WriteResult.empty().withDeletedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java new file mode 100644 index 000000000..761f99158 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java @@ -0,0 +1,92 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Insert; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.compiler.SQLCompiler; +import org.immutables.criteria.sql.compiler.SQLConstantExpression; +import org.immutables.criteria.sql.compiler.SQLInsertStatement; +import org.immutables.criteria.sql.conversion.TypeConverters; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +public class SQLInsertCommand implements SQLCommand { + + private final SQLSetup setup; + private final Insert operation; + private final SQLBackend.SQLSession session; + + public SQLInsertCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Insert operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.values().size() > 0 : "insert requires at least 1 object"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + private static List> values(final SQLSetup setup, final SQLInsertStatement statement) { + final List> ret = new ArrayList<>(); + for (final Map row : statement.values()) { + final List l = new ArrayList<>(); + for (final String column : statement.columns()) { + final SQLConstantExpression c = row.get(column); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + } + ret.add(l); + } + return ret; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SQLBackend.SQLSession session, final Insert operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.values() != null : "Missing `operation.values()` parameter"; + + // Short circuit empty insert + if (operation.values().size() == 0) { + return WriteResult.empty().withInsertedCount(0); + } + + final SQLInsertStatement insert = SQLCompiler.insert(setup, operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().insert(insert))) { + final int result = statement.insert(values(setup, insert)); + return WriteResult.empty().withInsertedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java new file mode 100644 index 000000000..5ba463e42 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java @@ -0,0 +1,94 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Update; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.compiler.SQLCompiler; +import org.immutables.criteria.sql.compiler.SQLConstantExpression; +import org.immutables.criteria.sql.compiler.SQLSaveStatement; +import org.immutables.criteria.sql.conversion.TypeConverters; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.*; +import java.util.concurrent.Callable; + +public class SQLSaveCommand implements SQLCommand { + + private final SQLSetup setup; + private final Update operation; + private final SQLBackend.SQLSession session; + + public SQLSaveCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Update operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.values() != null : "update requires values"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + private static List> values(final SQLSetup setup, final SQLSaveStatement statement) { + final List> ret = new ArrayList<>(); + final Set properties = new TreeSet<>(statement.columns()); + properties.remove(statement.key()); + for (final Map entity : statement.properties()) { + final List l = new ArrayList<>(); + for (final String property : properties) { + final SQLConstantExpression c = entity.get(property); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + } + // Add key as the last parameters + final SQLConstantExpression c = entity.get(statement.key()); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + ret.add(l); + } + return ret; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SQLBackend.SQLSession session, final Update operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.values() != null : "Expected `operation.values()` on update"; + + // Short circuit empty update + if (operation.values().size() == 0) { + return WriteResult.empty().withInsertedCount(0); + } + final SQLSaveStatement save = SQLCompiler.save(setup, operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().save(save))) { + final int result = statement.update(values(setup, save)); + return WriteResult.empty().withUpdatedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java new file mode 100644 index 000000000..5f132bf11 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java @@ -0,0 +1,75 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Select; +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.compiler.SQLCompiler; +import org.immutables.criteria.sql.compiler.SQLFilterExpression; +import org.immutables.criteria.sql.compiler.SQLSelectStatement; +import org.immutables.criteria.sql.conversion.RowMappers; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.concurrent.Callable; + +public class SQLSelectCommand implements SQLCommand { + + private final SQLSetup setup; + private final Select operation; + private final SQLBackend.SQLSession session; + + public SQLSelectCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Select operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query().count() == false : "count() query unexpected"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable> callable = toCallable(session, operation); + return Flowable.fromCallable(callable).flatMapIterable(x -> x); + } + + private Callable> toCallable(final SQLBackend.SQLSession session, final Select operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + + final SQLSelectStatement select = SQLCompiler.select(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().select(select))) { + return statement + .set(toParameters(select + .filter() + .map(SQLFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .list((rs) -> RowMappers.get(session.metadata()).map(rs)); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java new file mode 100644 index 000000000..ba9a53dc8 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java @@ -0,0 +1,83 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.UpdateByQuery; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.compiler.SQLCompiler; +import org.immutables.criteria.sql.compiler.SQLFilterExpression; +import org.immutables.criteria.sql.compiler.SQLUpdateStatement; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SQLUpdateCommand implements SQLCommand { + + private final SQLSetup setup; + private final UpdateByQuery operation; + private final SQLBackend.SQLSession session; + + public SQLUpdateCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final UpdateByQuery operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query() != null : "update requires a query"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SQLBackend.SQLSession session, final UpdateByQuery operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + assert operation.values() != null : "Expected `operation.values()` on update"; + + final SQLUpdateStatement update = SQLCompiler.update(setup, operation.query(), operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().update(update))) { + final Map merged = toParameters(Stream.concat( + update.updates().entrySet().stream(), + update.filter() + .map(SQLFilterExpression::parameters) + .orElse(Collections.emptyMap()).entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + final int result = statement + .set(merged) + .update(); + return WriteResult.empty().withUpdatedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java new file mode 100644 index 000000000..9d636c22f --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java @@ -0,0 +1,156 @@ +package org.immutables.criteria.sql.compiler; + +import org.immutables.criteria.expression.*; +import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; + +import java.util.*; +import java.util.stream.Collectors; + +public class SQLCompiler { + public static SQLCountStatement count(final SQLSetup setup, final Query query) { + // TODO: Aggregations + return ImmutableSQLCountStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .distinct(query.distinct()) + .filter(compileWhereClause(setup, query.filter())) + .ordering(compileOrderBy(setup, query.collations())) + .qualifier(compileDistinctCount(setup, query).orElse("COUNT(*)")) + .offset(query.offset()) + .limit(query.limit()) + .type(Long.class) + .build(); + } + + public static SQLSelectStatement select(final SQLSetup setup, final Query query) { + // TODO: Projections, Aggregations + return ImmutableSQLSelectStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .distinct(query.distinct()) + .filter(compileWhereClause(setup, query.filter())) + .ordering(compileOrderBy(setup, query.collations())) + .offset(query.offset()) + .limit(query.limit()) + .type(List.class) + .build(); + } + + private static Optional compileOrderBy(final SQLSetup setup, final List collations) { + final String ordering = collations.stream() + .map(c -> String.format("`%s` %s", + setup.metadata().properties().get(c.path().toString()).mapping().name(), + c.direction().isAscending() ? "ASC" : "DESC")) + .collect(Collectors.joining(",")); + return ordering.length() > 0 ? Optional.of(ordering) : Optional.empty(); + } + + private static Optional compileDistinctCount(final SQLSetup setup, final Query query) { + if (query.distinct()) { + if (query.projections().size() != 1) { + throw new SQLException("Expected a single projection argument to count with distinct"); + } + return Optional.of(String.format("COUNT(DISTINCT `%s`)", + setup.metadata().properties().get(query.projections().get(0).toString()).mapping().name())); + } + return Optional.empty(); + } + + public static SQLDeleteStatement delete(final SQLSetup setup, final Query query) { + return ImmutableSQLDeleteStatement.builder() + .table(setup.metadata().table()) + .filter(compileWhereClause(setup, query.filter())) + .type(Long.class) + .build(); + } + + public static SQLInsertStatement insert(final SQLSetup setup, final List entities) { + return ImmutableSQLInsertStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .values(toPropertyMap(setup, entities)) + .type(Long.class) + .build(); + } + + public static SQLUpdateStatement update(final SQLSetup setup, final Query query, final Map values) { + final Map updates = new HashMap<>(); + for (final Map.Entry e : values.entrySet()) { + final Path path = (Path) e.getKey(); + final Object value = e.getValue(); + final SQLPropertyMetadata p = setup.metadata().properties().get(path.toString()); + updates.put(p.mapping().name(), ImmutableSQLConstantExpression.builder() + .sql(":" + p.mapping().name()) + .type(p.type()) + .value(value) + .target(p) + .build()); + } + return ImmutableSQLUpdateStatement.builder() + .table(setup.metadata().table()) + .filter(compileWhereClause(setup, query.filter())) + .updates(updates) + .type(Long.class) + .build(); + } + + public static SQLSaveStatement save(final SQLSetup setup, final List entities) { + final Class type = setup.metadata().type(); + if (!(setup.metadata().key().metadata().isKeyDefined() && setup.metadata().key().metadata().isExpression())) { + throw new SQLException("Update using objects requires a simple key to be defined"); + } + + final List> values = new ArrayList<>(); + for (final Object o : entities) { + if (!type.isAssignableFrom(o.getClass())) { + throw new SQLException(String.format("Incompatible save() type. Expected %s found %s", + type.getSimpleName(), o.getClass().getSimpleName())); + } + } + return ImmutableSQLSaveStatement.builder() + .table(setup.metadata().table()) + .key(setup.metadata().key().metadata().keys().get(0).toString()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .properties(toPropertyMap(setup, entities)) + .type(Long.class) + .build(); + } + + private static Optional compileWhereClause(final SQLSetup setup, final Optional filter) { + if (filter.isPresent()) { + if (!(filter.get() instanceof Call)) { + throw new SQLException("Filter expression must be a call"); + } + final SQLQueryVisitor visitor = new SQLQueryVisitor(setup); + return Optional.of(visitor.call((Call) filter.get())); + } + return Optional.empty(); + + } + + private static List> toPropertyMap(final SQLSetup setup, final List entities) { + final Class type = setup.metadata().type(); + final List> values = new ArrayList<>(); + for (final Object o : entities) { + // Sanity check that all the objects in the list match the metadata type + if (!type.isAssignableFrom(o.getClass())) { + throw new SQLException(String.format("Incompatible insert() type. Expected %s found %s", + type.getSimpleName(), o.getClass().getSimpleName())); + } + final Map row = new HashMap<>(); + for (final SQLPropertyMetadata p : setup.metadata().properties().values()) { + final Object value = p.extractor().extract(o); + row.put(p.mapping().name(), ImmutableSQLConstantExpression.builder() + .sql(p.mapping().name()) + .type(p.type()) + .value(value) + .target(p) + .build()); + } + values.add(row); + } + return values; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java new file mode 100644 index 000000000..058e3e22f --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java @@ -0,0 +1,27 @@ +/* + * 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.compiler; + +import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.value.Value; + +@Value.Immutable +public interface SQLConstantExpression extends SQLExpression { + + SQLPropertyMetadata target(); + + Object value(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java new file mode 100644 index 000000000..dec0ee5a7 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java @@ -0,0 +1,39 @@ +/** + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; + +@Value.Immutable +public interface SQLCountStatement extends SQLStatement { + Optional distinct(); + + String qualifier(); + + OptionalLong limit(); + + OptionalLong offset(); + + Set columns(); + + Optional ordering(); + + Optional filter(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java new file mode 100644 index 000000000..3805a19fc --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java @@ -0,0 +1,25 @@ +/** + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Optional; + +@Value.Immutable +public interface SQLDeleteStatement extends SQLStatement { + Optional filter(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java new file mode 100644 index 000000000..d03ca280d --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java @@ -0,0 +1,20 @@ +/* + * 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.compiler; + +public interface SQLExpression extends SQLNode { + String sql(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java new file mode 100644 index 000000000..2c2bb7974 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java @@ -0,0 +1,25 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Map; + +@Value.Immutable +public interface SQLFilterExpression extends SQLExpression { + Map parameters(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java new file mode 100644 index 000000000..179f0277f --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java @@ -0,0 +1,29 @@ +/** + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + +@Value.Immutable +public interface SQLInsertStatement extends SQLStatement { + SortedSet columns(); + + List> values(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java new file mode 100644 index 000000000..e05e03b95 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java @@ -0,0 +1,24 @@ +/* + * 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.compiler; + +import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.value.Value; + +@Value.Immutable +public interface SQLNameExpression extends SQLExpression { + SQLPropertyMetadata metadata(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java new file mode 100644 index 000000000..cafcb3b3c --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java @@ -0,0 +1,20 @@ +/** + * 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.compiler; + +public interface SQLNode { + Class type(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java new file mode 100644 index 000000000..d72ebb0ec --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java @@ -0,0 +1,12 @@ +package org.immutables.criteria.sql.compiler; + +import org.immutables.criteria.backend.JavaBeanNaming; +import org.immutables.criteria.expression.Path; + +public class SQLPathNaming extends JavaBeanNaming { + @Override + public String name(Path path) { + String name = super.name(path); + return "`" + name.replaceAll("\\.", "`.`") + "`"; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java new file mode 100644 index 000000000..3e7fbe7f1 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java @@ -0,0 +1,124 @@ +package org.immutables.criteria.sql.compiler; + +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.immutables.criteria.expression.*; +import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public class SQLQueryVisitor { + private final Map parameters = new HashMap<>(); + private final SQLSetup setup; + + public SQLQueryVisitor(final SQLSetup setup) { + this.setup = setup; + } + + private static SQLFilterExpression unary(final Call call) { + return null; + } + + private static SQLFilterExpression ternary(final Call call) { + return null; + } + + public SQLFilterExpression call(final Call call) { + if (call.operator().arity() == Operator.Arity.UNARY) { + return unary(call); + } + if (call.operator().arity() == Operator.Arity.BINARY) { + return binary(call); + } + if (call.operator().arity() == Operator.Arity.TERNARY) { + return ternary(call); + } + throw new SQLException("Invalid operator arity " + call.operator()); + } + + public SQLConstantExpression constant(final SQLNameExpression target, final Constant constant) { + final String params = constant.values().stream().map(e -> { + final String name = String.format(":param_%d", parameters.size()); + final SQLConstantExpression result = ImmutableSQLConstantExpression.builder() + .sql(name) + .target(target.metadata()) + .value(e) + .type(TypeFactory.rawClass(constant.returnType())) + .build(); + parameters.put(name, result); + return name; + }).collect(Collectors.joining(",")); + final SQLConstantExpression result = ImmutableSQLConstantExpression.builder() + .sql(params) + .target(target.metadata()) + .value(constant.value()) + .type(TypeFactory.rawClass(constant.returnType())) + .build(); + return result; + } + + public SQLNameExpression path(final Path path) { + final SQLPropertyMetadata meta = setup.metadata() + .properties() + .get(path.toString()); + return ImmutableSQLNameExpression.builder() + // TODO: Column aliases + .sql("`" + meta.mapping().name() + "`") + .metadata(meta) + .type(meta.mapping().type()) + .build(); + } + + private SQLFilterExpression logical(final Call call) { + final Operator op = call.operator(); + final List args = call.arguments(); + + if (!(args.get(0) instanceof Call)) { + throw new SQLException("left hand side of logical expression must be a call"); + } + if (!(args.get(1) instanceof Call)) { + throw new SQLException("right hand side of logical expression must be a call"); + } + final String sql = setup.dialect().logical((Operators) op, + call((Call) args.get(0)), + call((Call) args.get(1))); + return ImmutableSQLFilterExpression.builder() + .sql(sql) + .parameters(parameters) + .type(TypeFactory.rawClass(args.get(0).returnType())) + .build(); + + } + + private SQLFilterExpression binary(final Call call) { + final Operator op = call.operator(); + final List args = call.arguments(); + if (args.size() != 2) { + throw new SQLException("binary expression expects exactly 2 args"); + } + if (op == Operators.AND || op == Operators.OR) { + return logical(call); + } + if (!(args.get(0) instanceof Path)) { + throw new SQLException("left hand side of comparison should be a path"); + } + if (!(args.get(1) instanceof Constant)) { + throw new SQLException("right hand side of comparison should be a constant"); + } + final Path left = (Path) args.get(0); + final Constant right = (Constant) args.get(1); + final SQLNameExpression target = path(left); + final Optional operator = setup.dialect().binary(op, target, constant(target, right)); + return operator.map(s -> ImmutableSQLFilterExpression.builder() + .sql(s) + .parameters(parameters) + .type(TypeFactory.rawClass(left.returnType())) + .build()).orElseThrow(() -> + new SQLException("Unhandled binary operator " + op)); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java new file mode 100644 index 000000000..af40c73d3 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java @@ -0,0 +1,31 @@ +/** + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + +@Value.Immutable +public interface SQLSaveStatement extends SQLStatement { + String key(); + + SortedSet columns(); + + List> properties(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java new file mode 100644 index 000000000..31f8d5c68 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java @@ -0,0 +1,37 @@ +/** + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.SortedSet; + +@Value.Immutable +public interface SQLSelectStatement extends SQLStatement { + Optional distinct(); + + OptionalLong limit(); + + OptionalLong offset(); + + Optional filter(); + + Optional ordering(); + + SortedSet columns(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java new file mode 100644 index 000000000..5c63ea980 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java @@ -0,0 +1,7 @@ +package org.immutables.criteria.sql.compiler; + +import org.immutables.value.Value; + +@Value.Immutable +public interface SQLSortExpression extends SQLExpression { +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java new file mode 100644 index 000000000..cd078880a --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java @@ -0,0 +1,20 @@ +/** + * 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.compiler; + +public interface SQLStatement extends SQLNode { + String table(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java new file mode 100644 index 000000000..008536c4c --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java @@ -0,0 +1,28 @@ +/** + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Map; +import java.util.Optional; + +@Value.Immutable +public interface SQLUpdateStatement extends SQLStatement { + Map updates(); + + Optional filter(); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java new file mode 100644 index 000000000..f57baac5e --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java @@ -0,0 +1,8 @@ +package org.immutables.criteria.sql.conversion; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface ColumnFetcher { + T apply(ResultSet row, int index) throws SQLException; +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java new file mode 100644 index 000000000..983bc3717 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java @@ -0,0 +1,38 @@ +package org.immutables.criteria.sql.conversion; + +import org.immutables.criteria.sql.util.TypeKeyHashMap; + +import java.sql.ResultSet; +import java.util.UUID; + +public class ColumnFetchers { + private static final TypeKeyHashMap> FETCHERS; + + static { + FETCHERS = new TypeKeyHashMap<>(); + register(boolean.class, ResultSet::getBoolean); + register(byte.class, ResultSet::getByte); + register(short.class, ResultSet::getShort); + register(int.class, ResultSet::getInt); + register(long.class, ResultSet::getLong); + register(float.class, ResultSet::getFloat); + register(double.class, ResultSet::getDouble); + register(Boolean.class, ResultSet::getBoolean); + register(Byte.class, ResultSet::getByte); + register(Short.class, ResultSet::getShort); + register(Integer.class, ResultSet::getInt); + register(Long.class, ResultSet::getLong); + register(Float.class, ResultSet::getFloat); + register(Double.class, ResultSet::getDouble); + register(String.class, ResultSet::getString); + register(UUID.class, (r, i) -> UUID.fromString(r.getString(i))); + } + + public static void register(final Class type, final ColumnFetcher fetcher) { + FETCHERS.put(type, fetcher); + } + + public static ColumnFetcher get(final Class type) { + return FETCHERS.get(type); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java new file mode 100644 index 000000000..aaad3a73b --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java @@ -0,0 +1,12 @@ +package org.immutables.criteria.sql.conversion; + +import org.immutables.value.Value; + +@Value.Immutable +public interface ColumnMapping { + String name(); + + Class type(); + + ColumnFetcher fetcher(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java new file mode 100644 index 000000000..789ec82bd --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java @@ -0,0 +1,9 @@ +package org.immutables.criteria.sql.conversion; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@FunctionalInterface +public interface RowMapper { + T map(ResultSet row) throws SQLException; +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java new file mode 100644 index 000000000..e991c671c --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java @@ -0,0 +1,73 @@ +package org.immutables.criteria.sql.conversion; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.criteria.sql.util.TypeKeyHashMap; + +import java.sql.ResultSetMetaData; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class RowMappers { + private static final ObjectMapper MAPPER; + private static final TypeKeyHashMap> MAPPER_CACHE; + + static { + MAPPER = new ObjectMapper() + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .registerModule(new ParameterNamesModule()); + MAPPER_CACHE = new TypeKeyHashMap<>(); + register(String.class, r -> r.getString(1)); + register(byte.class, r -> r.getByte(1)); + register(Byte.class, r -> r.getByte(1)); + register(short.class, r -> r.getShort(1)); + register(Short.class, r -> r.getShort(1)); + register(int.class, r -> r.getInt(1)); + register(Integer.class, r -> r.getInt(1)); + register(long.class, r -> r.getLong(1)); + register(Long.class, r -> r.getLong(1)); + register(float.class, r -> r.getFloat(1)); + register(Float.class, r -> r.getFloat(1)); + register(double.class, r -> r.getDouble(1)); + register(Double.class, r -> r.getDouble(1)); + } + + public static void register(final Class type, final RowMapper mapper) { + MAPPER_CACHE.put(type, mapper); + } + + @SuppressWarnings("unchecked") + public static RowMapper get(final Class clazz) { + return (RowMapper) MAPPER_CACHE.get(clazz); + } + + @SuppressWarnings("unchecked") + public static RowMapper get(final SQLTypeMetadata metadata) { + return (RowMapper) MAPPER_CACHE.computeIfAbsent(metadata.type(), t -> newRowMapper(metadata)); + } + + @SuppressWarnings("unchecked") + private static RowMapper newRowMapper(final SQLTypeMetadata metadata) { + return row -> { + final Map data = new HashMap<>(); + final ResultSetMetaData rm = row.getMetaData(); + for (int i = 1; i <= rm.getColumnCount(); ++i) { + final String name = rm.getColumnLabel(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, i))); + } + } + return (T) MAPPER.convertValue(data, metadata.type()); + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java new file mode 100644 index 000000000..943fd6271 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java @@ -0,0 +1,6 @@ +package org.immutables.criteria.sql.conversion; + +@FunctionalInterface +public interface TypeConverter { + T apply(final F value); +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java new file mode 100644 index 000000000..ae480f587 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java @@ -0,0 +1,207 @@ +package org.immutables.criteria.sql.conversion; + +import org.immutables.criteria.sql.SQLException; + +import java.time.Instant; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.UUID; + +public final class TypeConverters { + private static final Map>> CONVERTERS; + + static { + CONVERTERS = new IdentityHashMap<>(); + register(boolean.class, boolean.class, v -> v); + register(boolean.class, Boolean.class, v -> v); + register(boolean.class, byte.class, v -> (byte) (v ? 1 : 0)); + register(boolean.class, Byte.class, v -> (byte) (v ? 1 : 0)); + register(boolean.class, short.class, v -> (short) (v ? 1 : 0)); + register(boolean.class, Short.class, v -> (short) (v ? 1 : 0)); + register(boolean.class, int.class, v -> (v ? 1 : 0)); + register(boolean.class, Integer.class, v -> (v ? 1 : 0)); + register(boolean.class, long.class, v -> (long) (v ? 1 : 0)); + register(boolean.class, Long.class, v -> (long) (v ? 1 : 0)); + register(boolean.class, String.class, v -> (v ? "true" : "false")); + + register(Boolean.class, boolean.class, v -> v); + register(Boolean.class, Boolean.class, v -> v); + register(Boolean.class, byte.class, v -> (byte) (v ? 1 : 0)); + register(Boolean.class, Byte.class, v -> (byte) (v ? 1 : 0)); + register(Boolean.class, short.class, v -> (short) (v ? 1 : 0)); + register(Boolean.class, Short.class, v -> (short) (v ? 1 : 0)); + register(Boolean.class, int.class, v -> (v ? 1 : 0)); + register(Boolean.class, Integer.class, v -> (v ? 1 : 0)); + register(Boolean.class, long.class, v -> (long) (v ? 1 : 0)); + register(Boolean.class, Long.class, v -> (long) (v ? 1 : 0)); + register(Boolean.class, String.class, v -> (v ? "true" : "false")); + + register(byte.class, boolean.class, v -> v == 0); + register(byte.class, Boolean.class, v -> v == 0); + register(byte.class, byte.class, v -> v); + register(byte.class, Byte.class, v -> v); + register(byte.class, short.class, Byte::shortValue); + register(byte.class, Short.class, Byte::shortValue); + register(byte.class, int.class, Byte::intValue); + register(byte.class, Integer.class, Byte::intValue); + register(byte.class, long.class, Byte::longValue); + register(byte.class, Long.class, Byte::longValue); + register(byte.class, float.class, Byte::floatValue); + register(byte.class, Float.class, Byte::floatValue); + register(byte.class, double.class, Byte::doubleValue); + register(byte.class, Double.class, Byte::doubleValue); + register(byte.class, String.class, String::valueOf); + + register(Byte.class, boolean.class, v -> v == 0); + register(Byte.class, Boolean.class, v -> v == 0); + register(Byte.class, byte.class, v -> v); + register(Byte.class, Byte.class, v -> v); + register(Byte.class, short.class, Byte::shortValue); + register(Byte.class, Short.class, Byte::shortValue); + register(Byte.class, int.class, Byte::intValue); + register(Byte.class, Integer.class, Byte::intValue); + register(Byte.class, long.class, Byte::longValue); + register(Byte.class, Long.class, Byte::longValue); + register(Byte.class, float.class, Byte::floatValue); + register(Byte.class, Float.class, Byte::floatValue); + register(Byte.class, double.class, Byte::doubleValue); + register(Byte.class, Double.class, Byte::doubleValue); + register(Byte.class, String.class, String::valueOf); + + register(short.class, boolean.class, v -> v == 0); + register(short.class, Boolean.class, v -> v == 0); + register(short.class, byte.class, Short::byteValue); + register(short.class, Byte.class, Short::byteValue); + register(short.class, short.class, v -> v); + register(short.class, Short.class, v -> v); + register(short.class, int.class, Short::intValue); + register(short.class, Integer.class, Short::intValue); + register(short.class, long.class, Short::longValue); + register(short.class, Long.class, Short::longValue); + register(short.class, float.class, Short::floatValue); + register(short.class, Float.class, Short::floatValue); + register(short.class, double.class, Short::doubleValue); + register(short.class, Double.class, Short::doubleValue); + register(short.class, String.class, String::valueOf); + + register(Short.class, boolean.class, v -> v == 0); + register(Short.class, Boolean.class, v -> v == 0); + register(Short.class, byte.class, Short::byteValue); + register(Short.class, Byte.class, Short::byteValue); + register(Short.class, short.class, v -> v); + register(Short.class, Short.class, v -> v); + register(Short.class, int.class, Short::intValue); + register(Short.class, Integer.class, Short::intValue); + register(Short.class, long.class, Short::longValue); + register(Short.class, Long.class, Short::longValue); + register(Short.class, float.class, Short::floatValue); + register(Short.class, Float.class, Short::floatValue); + register(Short.class, double.class, Short::doubleValue); + register(Short.class, Double.class, Short::doubleValue); + register(Short.class, String.class, String::valueOf); + + register(int.class, boolean.class, v -> v == 0); + register(int.class, Boolean.class, v -> v == 0); + register(int.class, byte.class, Integer::byteValue); + register(int.class, Byte.class, Integer::byteValue); + register(int.class, short.class, Integer::shortValue); + register(int.class, Short.class, Integer::shortValue); + register(int.class, int.class, v -> v); + register(int.class, Integer.class, v -> v); + register(int.class, long.class, Integer::longValue); + register(int.class, Long.class, Integer::longValue); + register(int.class, float.class, Integer::floatValue); + register(int.class, Float.class, Integer::floatValue); + register(int.class, double.class, Integer::doubleValue); + register(int.class, Double.class, Integer::doubleValue); + register(int.class, String.class, String::valueOf); + + register(Integer.class, boolean.class, v -> v == 0); + register(Integer.class, Boolean.class, v -> v == 0); + register(Integer.class, byte.class, Integer::byteValue); + register(Integer.class, Byte.class, Integer::byteValue); + register(Integer.class, short.class, Integer::shortValue); + register(Integer.class, Short.class, Integer::shortValue); + register(Integer.class, int.class, v -> v); + register(Integer.class, Integer.class, v -> v); + register(Integer.class, long.class, Integer::longValue); + register(Integer.class, Long.class, Integer::longValue); + register(Integer.class, float.class, Integer::floatValue); + register(Integer.class, Float.class, Integer::floatValue); + register(Integer.class, double.class, Integer::doubleValue); + register(Integer.class, Double.class, Integer::doubleValue); + register(Integer.class, String.class, String::valueOf); + + register(long.class, boolean.class, v -> v == 0); + register(long.class, Boolean.class, v -> v == 0); + register(long.class, byte.class, Long::byteValue); + register(long.class, Byte.class, Long::byteValue); + register(long.class, short.class, Long::shortValue); + register(long.class, Short.class, Long::shortValue); + register(long.class, int.class, Long::intValue); + register(long.class, Integer.class, Long::intValue); + register(long.class, long.class, v -> v); + register(long.class, Long.class, v -> v); + register(long.class, float.class, Long::floatValue); + register(long.class, Float.class, Long::floatValue); + register(long.class, double.class, Long::doubleValue); + register(long.class, Double.class, Long::doubleValue); + register(long.class, String.class, String::valueOf); + + register(Long.class, boolean.class, v -> v == 0); + register(Long.class, Boolean.class, v -> v == 0); + register(Long.class, byte.class, Long::byteValue); + register(Long.class, Byte.class, Long::byteValue); + register(Long.class, short.class, Long::shortValue); + register(Long.class, Short.class, Long::shortValue); + register(Long.class, int.class, Long::intValue); + register(Long.class, Integer.class, Long::intValue); + register(Long.class, long.class, v -> v); + register(Long.class, Long.class, v -> v); + register(Long.class, float.class, Long::floatValue); + register(Long.class, Float.class, Long::floatValue); + register(Long.class, double.class, Long::doubleValue); + register(Long.class, Double.class, Long::doubleValue); + register(Long.class, String.class, String::valueOf); + + register(String.class, boolean.class, v -> v != null && v.equalsIgnoreCase("true")); + register(String.class, Boolean.class, v -> v != null && v.equalsIgnoreCase("true")); + register(String.class, byte.class, Byte::valueOf); + register(String.class, Byte.class, Byte::valueOf); + register(String.class, short.class, Short::valueOf); + register(String.class, Short.class, Short::valueOf); + register(String.class, int.class, Integer::valueOf); + register(String.class, Integer.class, Integer::valueOf); + register(String.class, long.class, Long::valueOf); + register(String.class, Long.class, Long::valueOf); + register(String.class, float.class, Float::valueOf); + register(String.class, Float.class, Float::valueOf); + register(String.class, double.class, Double::valueOf); + register(String.class, Double.class, Double::valueOf); + register(String.class, String.class, v -> v); + + register(UUID.class, String.class, UUID::toString); + register(String.class, UUID.class, UUID::fromString); + + register(Instant.class, long.class, Instant::toEpochMilli); + register(long.class, Instant.class, Instant::ofEpochMilli); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static T convert(final Class from, final Class to, final F value) { + final Map> map = CONVERTERS.get(from); + if (map == null) { + throw new SQLException(String.format("No type converters found from %s", from)); + } + final TypeConverter converter = (TypeConverter) map.get(to); + if (converter == null) { + throw new SQLException(String.format("No type converters found from %s to %s", from, to)); + } + return converter.apply(value); + } + + public static void register(final Class from, final Class to, final TypeConverter converter) { + CONVERTERS.computeIfAbsent(from, key -> new IdentityHashMap<>()).put(to, converter); + } +} + diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java new file mode 100644 index 000000000..70744207f --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java @@ -0,0 +1,210 @@ +package org.immutables.criteria.sql.dialects; + +import org.immutables.criteria.backend.PathNaming; +import org.immutables.criteria.expression.ComparableOperators; +import org.immutables.criteria.expression.Operator; +import org.immutables.criteria.expression.Operators; +import org.immutables.criteria.expression.StringOperators; +import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.compiler.*; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class SQL92Dialect implements SQLDialect { + private final PathNaming naming = new SQLPathNaming(); + + @Override + public Optional limit(final OptionalLong limit, final OptionalLong offset) { + if (limit.isPresent() || offset.isPresent()) { + final String l = limit.isPresent() ? String.format("LIMIT %d", limit.getAsLong()) : ""; + final String o = offset.isPresent() ? String.format("OFFSET %d", offset.getAsLong()) : ""; + return Optional.of(String.format("%s %s", l, o).trim()); + } + return Optional.empty(); + } + + @Override + public String logical(final Operators op, final SQLExpression left, final SQLExpression right) { + switch ((Operators) op) { + case AND: + return String.format("(%s)AND(%s)", left.sql(), right.sql()); + case OR: + return String.format("(%s)OR(%s)", left.sql(), right.sql()); + } + throw new SQLException("Invalid logical operator " + op.name()); + } + + @Override + public Optional equality(final Operators op, final SQLExpression left, final SQLExpression right) { + if (!(left instanceof SQLNameExpression)) { + throw new SQLException("left hand side of comparison should be a path/name"); + } + if (!(right instanceof SQLConstantExpression)) { + throw new SQLException("right hand side of comparison should be a constant"); + } + switch (op) { + case EQUAL: + return Optional.of(String.format("(%s)=(%s)", left.sql(), right.sql())); + case NOT_EQUAL: + return Optional.of(String.format("(%s)!=(%s)", left.sql(), right.sql())); + case IN: + return Optional.of(String.format("(%s)IN(%s)", left.sql(), right.sql())); + case NOT_IN: + return Optional.of(String.format("(%s)NOT IN(%s)", left.sql(), right.sql())); + } + throw new SQLException("Invalid logical operator " + op.name()); + } + + @Override + public Optional comparison(final ComparableOperators op, final SQLExpression left, final SQLExpression right) { + switch (op) { + case LESS_THAN: + return Optional.of(String.format("(%s)<(%s)", left.sql(), right.sql())); + case LESS_THAN_OR_EQUAL: + return Optional.of(String.format("(%s)<=(%s)", left.sql(), right.sql())); + case GREATER_THAN: + return Optional.of(String.format("(%s)>(%s)", left.sql(), right.sql())); + case GREATER_THAN_OR_EQUAL: + return Optional.of(String.format("(%s)>=(%s)", left.sql(), right.sql())); + } + throw new SQLException("Invalid comparison operator " + op.name()); + } + + @Override + public Optional regex(final Operator op, final SQLExpression left, final Pattern right) { + // TODO: Verify that the regex does not contain other patterns + final String converted = right.pattern() + .replace(".*", "%") + .replace(".?", "%") + .replace("'", "''") + .replace('?', '%') + .replace('.', '_'); + switch ((StringOperators) op) { + case MATCHES: + return Optional.of(String.format("(%s)LIKE('%s')", left.sql(), converted)); + } + throw new SQLException("Invalid regex operator " + op.name()); + } + + @Override + public Optional string(final StringOperators op, final SQLExpression left, final SQLExpression right) { + switch (op) { + case HAS_LENGTH: + return Optional.of(String.format("LEN(%s)=(%s)", left.sql(), right.sql())); + case CONTAINS: + return Optional.of(String.format("(%s)LIKE(CONCAT(\"%%\",%s,\"%%\"))", left.sql(), right.sql())); + case ENDS_WITH: + return Optional.of(String.format("(%s)LIKE(CONCAT(\"%%\",%s))", left.sql(), right.sql())); + case STARTS_WITH: + return Optional.of(String.format("(%s)LIKE(CONCAT(%s,\"%%\"))", left.sql(), right.sql())); + case MATCHES: + final Object arg = ((SQLConstantExpression) right).value(); + assert arg instanceof Pattern : "Argument to regex() should be Pattern"; + return regex(op, left, (Pattern) arg); + } + throw new SQLException("Invalid string operator " + op.name()); + } + + @Override + public Optional binary(final Operator op, final SQLExpression left, final SQLExpression right) { + if (!(left instanceof SQLNameExpression)) { + throw new SQLException("left hand side of comparison should be a path/name"); + } + if (!(right instanceof SQLConstantExpression)) { + throw new SQLException("right hand side of comparison should be a constant"); + } + final SQLConstantExpression updated = ImmutableSQLConstantExpression + .builder() + .from((SQLConstantExpression) right) + .target(((SQLNameExpression) left).metadata()) + .build(); + if (op instanceof Operators) { + return equality((Operators) op, left, updated); + } + if (op instanceof StringOperators) { + return string((StringOperators) op, left, updated); + } + if (op instanceof ComparableOperators) { + return comparison((ComparableOperators) op, left, updated); + } + throw new SQLException("Unhandled operator: " + op.name()); + } + + @Override + public PathNaming naming() { + return naming; + } + + @Override + public String count(final SQLCountStatement statement) { + return String.format("SELECT %s FROM `%s`%s%s %s", + statement.qualifier(), + statement.table(), + statement.filter().map(s -> " WHERE " + s.sql()).orElse(""), + statement.ordering().map(s -> " ORDER BY " + s).orElse(""), + limit(statement.limit(), statement.offset()).orElse("")) + .trim(); + } + + @Override + public String select(final SQLSelectStatement statement) { + return String.format("SELECT %s%s FROM `%s`%s%s %s", + statement.distinct().map(e -> e ? "DISTINCT " : "").orElse(""), + statement.columns().stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")), + statement.table(), + statement.filter().map(s -> " WHERE " + s.sql()).orElse(""), + statement.ordering().map(s -> " ORDER BY " + s).orElse(""), + limit(statement.limit(), statement.offset()).orElse("")) + .trim(); + } + + @Override + public String delete(final SQLDeleteStatement statement) { + // TODO: Sorting + return String.format("DELETE FROM `%s`%s", + statement.table(), + statement.filter().map(s -> " WHERE " + s.sql()).orElse("")) + .trim(); + } + + @Override + public String insert(final SQLInsertStatement statement) { + return String.format("INSERT INTO %s (%s)\nVALUES\n%s", + statement.table(), + statement.columns().stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")), + "(" + statement.columns().stream().map(c -> "?").collect(Collectors.joining(",")) + ")") + .trim(); + } + + @Override + public String update(final SQLUpdateStatement statement) { + return String.format("UPDATE `%s` SET %s%s", + statement.table(), + statement.updates() + .entrySet() + .stream().map(e -> String.format("`%s`=:%s", e.getKey(), e.getKey())) + .collect(Collectors.joining(",")), + statement.filter().map(s -> " WHERE " + s.sql()).orElse("")) + .trim(); + } + + @Override + public String save(final SQLSaveStatement statement) { + final Set properties = new TreeSet<>(statement.columns()); + properties.remove(statement.key()); + return String.format("UPDATE `%s` SET %s WHERE %s=:%s", + statement.table(), + properties.stream() + .map(e -> String.format("`%s`=:%s", e, e)) + .collect(Collectors.joining(",")), + statement.key(), + statement.key() + ) + .trim(); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java new file mode 100644 index 000000000..d4c007be0 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java @@ -0,0 +1,42 @@ +package org.immutables.criteria.sql.dialects; + +import org.immutables.criteria.backend.PathNaming; +import org.immutables.criteria.expression.ComparableOperators; +import org.immutables.criteria.expression.Operator; +import org.immutables.criteria.expression.Operators; +import org.immutables.criteria.expression.StringOperators; +import org.immutables.criteria.sql.compiler.*; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.regex.Pattern; + +public interface SQLDialect { + PathNaming naming(); + + Optional limit(final OptionalLong limit, final OptionalLong offset); + + Optional string(StringOperators op, SQLExpression left, SQLExpression right); + + Optional comparison(ComparableOperators op, SQLExpression left, SQLExpression right); + + String logical(Operators op, SQLExpression left, SQLExpression right); + + Optional equality(Operators op, SQLExpression left, SQLExpression right); + + Optional binary(Operator op, SQLExpression left, SQLExpression right); + + Optional regex(Operator op, SQLExpression left, Pattern right); + + String count(SQLCountStatement statement); + + String select(SQLSelectStatement statement); + + String delete(SQLDeleteStatement statement); + + String insert(SQLInsertStatement statement); + + String update(SQLUpdateStatement statement); + + String save(SQLSaveStatement statement); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java b/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java new file mode 100644 index 000000000..5d3c807a0 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java @@ -0,0 +1,239 @@ +package org.immutables.criteria.sql.jdbc; + +import javax.sql.DataSource; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Date; +import java.sql.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.sql.Statement.RETURN_GENERATED_KEYS; + +public class FluentStatement implements AutoCloseable { + static final Pattern PATTERN = Pattern.compile("(? fields = new ArrayList<>(); + + public FluentStatement(final Connection connection, final String sql) throws SQLException { + this(connection, sql, true); + } + + public FluentStatement(final Connection connection, final String sql, final boolean keys) throws SQLException { + final Matcher matcher = PATTERN.matcher(sql); + while (matcher.find()) { + fields.add(matcher.group()); + } + final String transformed = sql.replaceAll(PATTERN.pattern(), "?"); + this.connection = connection; + statement = keys + ? connection.prepareStatement(transformed, RETURN_GENERATED_KEYS) + : connection.prepareStatement(transformed); + } + + public static FluentStatement of(final Connection connection, final String sql) throws SQLException { + return new FluentStatement(connection, sql); + } + + public static FluentStatement of(final DataSource ds, final String sql) throws SQLException { + return new FluentStatement(ds.getConnection(), sql); + } + + public static List convert(final ResultSet results, final Mapper mapper) throws SQLException { + final List ret = new ArrayList<>(); + while (results.next()) { + ret.add(mapper.handle(results)); + } + return ret; + } + + public PreparedStatement statement() { + return statement; + } + + public int insert(final List> values) throws SQLException { + return batch(values); + } + + public List list(final Mapper mapper) throws SQLException { + return convert(statement.executeQuery(), mapper); + } + + public int batch(final List> values) throws SQLException { + connection.setAutoCommit(false); + for (final List row : values) { + for (int i = 0; i < row.size(); i++) { + set(i + 1, row.get(i)); + } + statement.addBatch(); + } + final int[] counts = statement.executeBatch(); + connection.commit(); + return Arrays.stream(counts).sum(); + + } + + public int update(final List> values) throws SQLException { + return batch(values); + } + + public int update() throws SQLException { + return statement.executeUpdate(); + } + + public int delete() throws SQLException { + return statement.executeUpdate(); + } + + @Override + public void close() throws SQLException { + statement.close(); + } + + public FluentStatement set(final Map parameters) throws SQLException { + assert parameters != null : "parameters cannot be null"; + for (final Map.Entry e : parameters.entrySet()) { + set(e.getKey(), e.getValue()); + } + return this; + } + + public FluentStatement set(final int index, final byte value) throws SQLException { + statement.setByte(index, value); + return this; + } + + public FluentStatement set(final int index, final short value) throws SQLException { + statement.setShort(index, value); + return this; + } + + public FluentStatement set(final int index, final int value) throws SQLException { + statement.setInt(index, value); + return this; + } + + public FluentStatement set(final int index, final long value) throws SQLException { + statement.setLong(index, value); + return this; + } + + public FluentStatement set(final int index, final float value) throws SQLException { + statement.setFloat(index, value); + return this; + } + + public FluentStatement set(final int index, final double value) throws SQLException { + statement.setDouble(index, value); + return this; + } + + public FluentStatement set(final int index, final boolean value) throws SQLException { + statement.setBoolean(index, value); + return this; + } + + public FluentStatement set(final int index, final String value) throws SQLException { + statement.setString(index, value); + return this; + } + + public FluentStatement set(final int index, final BigDecimal value) throws SQLException { + statement.setBigDecimal(index, value); + return this; + } + + public FluentStatement set(final int index, final byte[] value) throws SQLException { + statement.setBytes(index, value); + return this; + } + + public FluentStatement set(final int index, final Date value) throws SQLException { + statement.setDate(index, value); + return this; + } + + public FluentStatement set(final int index, final Time value) throws SQLException { + statement.setTime(index, value); + return this; + } + + public FluentStatement set(final int index, final Time value, final Calendar calendar) throws SQLException { + statement.setTime(index, value, calendar); + return this; + } + + public FluentStatement set(final int index, final Timestamp value) throws SQLException { + statement.setTimestamp(index, value); + return this; + } + + public FluentStatement set(final int index, final Blob value) throws SQLException { + statement.setBlob(index, value); + return this; + } + + public FluentStatement set(final int index, final Clob value) throws SQLException { + statement.setClob(index, value); + return this; + } + + public FluentStatement set(final int index, final NClob value) throws SQLException { + statement.setNClob(index, value); + return this; + } + + public FluentStatement set(final int index, final Reader value) throws SQLException { + statement.setCharacterStream(index, value); + return this; + } + + public FluentStatement set(final int index, final InputStream value) throws SQLException { + statement.setBinaryStream(index, value); + return this; + } + + public FluentStatement set(final int index, final URL value) throws SQLException { + statement.setURL(index, value); + return this; + } + + public FluentStatement set(final String name, final Object value) throws SQLException { + return set(getIndex(name), value); + } + + public FluentStatement set(final int index, final Object value) throws SQLException { + if (value instanceof Byte) { + set(index, (byte) value); + } else if (value instanceof Short) { + set(index, (short) value); + } else if (value instanceof Integer) { + set(index, (int) value); + } else if (value instanceof Long) { + set(index, (long) value); + } else if (value instanceof Float) { + set(index, (float) value); + } else if (value instanceof Double) { + set(index, (double) value); + } else if (value instanceof String) { + set(index, (String) value); + } else { + statement.setObject(index, value); + } + return this; + } + + private int getIndex(final String name) { + return fields.indexOf(name) + 1; + } + + @FunctionalInterface + public interface Mapper { + T handle(ResultSet row) throws SQLException; + } +} \ No newline at end of file diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java new file mode 100644 index 000000000..8de361665 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java @@ -0,0 +1,6 @@ +package org.immutables.criteria.sql.reflection; + +@FunctionalInterface +public interface PropertyExtractor { + Object extract(Object entity); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java new file mode 100644 index 000000000..ca19dca89 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java @@ -0,0 +1,30 @@ +package org.immutables.criteria.sql.reflection; + +import org.immutables.criteria.backend.ContainerNaming; +import org.immutables.criteria.sql.SQL; + +import java.util.Objects; + +public interface SQLContainerNaming extends ContainerNaming { + ContainerNaming FROM_SQL_TABLE_ANNOTATION = clazz -> { + Objects.requireNonNull(clazz, "clazz"); + final SQL.Table annotation = clazz.getAnnotation(SQL.Table.class); + if (annotation == null || annotation.value().isEmpty()) { + throw new UnsupportedOperationException(String.format("%s.name annotation is not defined on %s", + SQL.Table.class.getSimpleName(), clazz.getName())); + } + return annotation.value(); + }; + + ContainerNaming SQL = clazz -> { + try { + return FROM_SQL_TABLE_ANNOTATION.name(clazz); + } catch (UnsupportedOperationException u) { + try { + return FROM_REPOSITORY_ANNOTATION.name(clazz); + } catch (UnsupportedOperationException e) { + return FROM_CLASSNAME.name(clazz); + } + } + }; +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java new file mode 100644 index 000000000..d2c567087 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java @@ -0,0 +1,15 @@ +package org.immutables.criteria.sql.reflection; + +import org.immutables.criteria.sql.conversion.ColumnMapping; +import org.immutables.value.Value; + +@Value.Immutable +public interface SQLPropertyMetadata { + String name(); + + Class type(); + + ColumnMapping mapping(); + + PropertyExtractor extractor(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java new file mode 100644 index 000000000..703683208 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java @@ -0,0 +1,156 @@ +package org.immutables.criteria.sql.reflection; + +import org.immutables.criteria.backend.KeyExtractor; +import org.immutables.criteria.reflect.ClassScanner; +import org.immutables.criteria.reflect.MemberExtractor; +import org.immutables.criteria.sql.SQL; +import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.conversion.ColumnFetchers; +import org.immutables.criteria.sql.conversion.ColumnMapping; +import org.immutables.criteria.sql.conversion.ImmutableColumnMapping; + +import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class SQLTypeMetadata { + private static final Predicate MAYBE_GETTER = method -> Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getReturnType() != Void.class + && method.getParameterCount() == 0; + static final Predicate MAYBE_PERSISTED = t -> { + if (t instanceof Method) { + return MAYBE_GETTER.test((Method) t); + } + return false; + }; + private static final Predicate BOOLEAN_GETTER = method -> MAYBE_GETTER.test(method) + && method.getName().startsWith("is") + && method.getName().length() > "is".length() + && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class); + private static final Predicate GENERIC_GETTER = method -> MAYBE_GETTER.test(method) + && method.getName().startsWith("get") + && method.getName().length() > "get".length(); + static final Predicate IS_GETTER = GENERIC_GETTER.or(BOOLEAN_GETTER); + private final String table; + private final Class type; + private final List members; + private final List metadata; + + private final KeyExtractor extractor; + private final Map columns = new HashMap<>(); + private final Map properties = new HashMap<>(); + + private SQLTypeMetadata(final Class clazz) { + type = clazz; + table = SQLContainerNaming.SQL.name(clazz); + extractor = KeyExtractor.defaultFactory().create(clazz); + members = computePersistedMembers(clazz); + metadata = computePropertyMetadata(members); + metadata.forEach(m -> { + properties.put(m.name(), m); + columns.put(m.mapping().name(), m); + }); + } + + public static SQLTypeMetadata of(final Class type) { + return new SQLTypeMetadata(type); + } + + private static List computePersistedMembers(@Nonnull final Class type) { + return ClassScanner.of(type) + .stream() + .filter(m -> MAYBE_PERSISTED.test(m)) + .collect(Collectors.toList()); + } + + private static List computePropertyMetadata(final List members) { + return members.stream() + .map(m -> { + return ImmutableSQLPropertyMetadata.builder() + .name(m instanceof Field ? computePropertyName((Field) m) : computePropertyName((Method) m)) + .type(m instanceof Field ? ((Field) m).getType() : ((Method) m).getReturnType()) + .mapping(computeColumnMapping(m)) + .extractor(v -> MemberExtractor.ofReflection().extract(m, v)) + .build(); + }) + .collect(Collectors.toList()); + } + + private static ColumnMapping computeColumnMapping(final Member m) { + if (m instanceof Field) { + final Field field = (Field) m; + final SQL.Column annotation = field.getAnnotation(SQL.Column.class); + final Class type = annotation != null && annotation.type() != null + ? annotation.type() + : field.getType(); + return ImmutableColumnMapping.builder() + .type(type) + .fetcher(ColumnFetchers.get(type)) + .name(annotation != null && annotation.name().length() > 0 + ? annotation.name() + : computeColumnName(field)) + .build(); + } else if (m instanceof Method) { + final Method method = (Method) m; + final SQL.Column annotation = method.getAnnotation(SQL.Column.class); + final Class type = annotation != null && annotation.type() != null + ? annotation.type() + : method.getReturnType(); + return ImmutableColumnMapping.builder() + .type(type) + .fetcher(ColumnFetchers.get(type)) + .name(annotation != null && annotation.name().length() > 0 + ? annotation.name() + : computeColumnName(method)) + .build(); + } + throw new SQLException("Unable to determine column mapping for member: " + m); + } + + private static String computePropertyName(final Field field) { + return field.getName(); + } + + private static String computePropertyName(final Method method) { + // TODO: String get/is prefix etc. + return method.getName(); + } + + private static String computeColumnName(final Field field) { + return field.getName().toLowerCase(Locale.ROOT); + } + + private static String computeColumnName(final Method method) { + // TODO: String get/is prefix etc. + return method.getName().toLowerCase(Locale.ROOT); + } + + public Class type() { + return type; + } + + public String table() { + return table; + } + + public KeyExtractor key() { + return extractor; + } + + public Map properties() { + return properties; + } + + public Map columns() { + return columns; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java b/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java new file mode 100644 index 000000000..e94e4774d --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java @@ -0,0 +1,39 @@ +/* + * 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.util; + +import java.util.HashMap; +import java.util.Optional; + +@SuppressWarnings("serial") +public class TypeKeyHashMap extends HashMap, V> { + @Override + public boolean containsKey(final Object key) { + return findEntry((Class) key).isPresent(); + } + + @Override + public V get(final Object key) { + final Optional, V>> entry = findEntry((Class) key); + return entry.map(Entry::getValue).orElse(null); + } + + private Optional, V>> findEntry(final Class key) { + return entrySet().stream() + .filter(e -> e.getKey().isAssignableFrom(key)) + .findFirst(); + } +} \ No newline at end of file diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java new file mode 100644 index 000000000..038777e0a --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java @@ -0,0 +1,13 @@ +package org.immutables.criteria.sql.compiler; + +import org.immutables.criteria.Criteria; +import org.immutables.value.Value; + +@Criteria +@Value.Immutable +public interface Dummy { + @Criteria.Id + int id(); + + String name(); +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java new file mode 100644 index 000000000..8a4350111 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java @@ -0,0 +1,282 @@ +package org.immutables.criteria.sql.compiler; + +import org.h2.jdbcx.JdbcDataSource; +import org.immutables.criteria.Criterias; +import org.immutables.criteria.expression.Query; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.junit.Test; + +import javax.sql.DataSource; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SQLCompilerTests { + private static DataSource datasource() { + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword("sa"); + return ds; + } + + private static SQLSetup setup(final DataSource datasource, final String table) { + return SQLSetup.of(datasource, SQLTypeMetadata.of(Dummy.class)); + } + + @Test + public void testEmptySelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup, Query.of(Dummy.class)); + assertEquals("SELECT `id`,`name` FROM `dummy`", + setup.dialect().select(result)); + } + + @Test + public void testEqualitySelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testInequalitySelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.isNot(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)!=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testLessThanSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.lessThan(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testLessThanEqualsSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.atMost(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testGreaterThanSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.greaterThan(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testGreaterThanEqualsSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.atLeast(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testBetweenSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.between(1, 2))); + // Note that this is how criteria maps this + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)>=(:param_0))AND((`id`)<=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + } + + @Test + public void testInSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.in(1, 2, 3))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + assertEquals(3, result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testNotInSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.notIn(1, 2, 3))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)NOT IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + assertEquals(3, result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testStringInSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.in("a", "b", "c"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + assertEquals("b", result.filter().get().parameters().get(":param_1").value()); + assertEquals("c", result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testStringNotInSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.notIn("a", "b", "c"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)NOT IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + assertEquals("b", result.filter().get().parameters().get(":param_1").value()); + assertEquals("c", result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testStringStartsWithSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.startsWith("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(:param_0,\"%\"))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringEndsWithSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.endsWith("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringContainsSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.contains("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0,\"%\"))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringMatchesSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.matches(Pattern.compile("a.*b'.b?")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE('a%b''_b%')", + setup.dialect().select(result)); + } + + @Test + public void testStringHasLengthSelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.hasLength(10))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE LEN(`name`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(10, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringIsEmptySelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.isEmpty())); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringIsNotEmptySelect() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.notEmpty())); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)!=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testAndExpression() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1).and(DummyCriteria.dummy.name.is("A")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))AND((`name`)=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals("A", result.filter().get().parameters().get(":param_1").value()); + } + + @Test + public void testOrExpression() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1).or(DummyCriteria.dummy.name.is("A")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))OR((`name`)=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals("A", result.filter().get().parameters().get(":param_1").value()); + } + + @Test + public void testCompoundExpression() { + final SQLSetup setup = setup(datasource(), "dummy"); + final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.is("A") + .or() + .name.is("B") + .id.is(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`name`)=(:param_0))OR(((`name`)=(:param_1))AND((`id`)=(:param_2)))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("A", result.filter().get().parameters().get(":param_0").value()); + assertEquals("B", result.filter().get().parameters().get(":param_1").value()); + assertEquals(1, result.filter().get().parameters().get(":param_2").value()); + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java new file mode 100644 index 000000000..dd4f38ac0 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java @@ -0,0 +1,72 @@ +package org.immutables.criteria.sql.note; + +import liquibase.Liquibase; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.LiquibaseException; +import liquibase.resource.ClassLoaderResourceAccessor; +import org.h2.jdbcx.JdbcDataSource; +import org.immutables.criteria.Criterion; +import org.immutables.criteria.expression.*; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public abstract class AbstractTestBase { + protected static DataSource getDataSource() { + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword("sa"); + return ds; + } + + protected static void migrate(final DataSource ds) throws LiquibaseException, SQLException { + final Connection connection = ds.getConnection(); + final Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); + final Liquibase liquibase = new Liquibase("test-migrations.xml", new ClassLoaderResourceAccessor(), database); + liquibase.dropAll(); + liquibase.update(""); + } + + protected static final List newEntities(final int count) { + final List ret = new ArrayList<>(); + for (int i = 0; i < count; i++) { + ret.add(newEntity()); + } + return ret; + } + + protected static final Note newEntity() { + return ImmutableNote.builder() + .id(UUID.randomUUID()) + .created(Instant.now()) + .message("Test note") + .build(); + } + + protected static final Query newQuery(final UUID id) { + try { + final Path path = Path.ofMember(Note.class.getMethod("id")); + final Constant value = Expressions.constant(id); + final Query query = ImmutableQuery.builder() + .entityClass(Note.class) + .filter(Expressions.call(Operators.EQUAL, Arrays.asList(path, value))) + .build(); + return query; + } catch (final Throwable t) { + throw new RuntimeException(t); + } + } + + protected static final Criterion newCriterion(final UUID id) { + return NoteCriteria.note.id.is(id); + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/Note.java b/criteria/sql/test/org/immutables/criteria/sql/note/Note.java new file mode 100644 index 000000000..cb909f537 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/Note.java @@ -0,0 +1,43 @@ +/* + * 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.note; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.immutables.criteria.Criteria; +import org.immutables.criteria.sql.SQL; +import org.immutables.value.Value; + +import java.time.Instant; +import java.util.UUID; + +@Criteria +@Criteria.Repository +@Value.Immutable +@SQL.Table("notes") +@JsonDeserialize(as = ImmutableNote.class) +@JsonSerialize(as = ImmutableNote.class) +public interface Note { + String message(); + + @SQL.Column(type = long.class, name = "created_on") + Instant created(); + + @Criteria.Id + @SQL.Column(type = String.class) + UUID id(); +} + diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java b/criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java new file mode 100644 index 000000000..414c21dbc --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java @@ -0,0 +1,325 @@ +/* + * 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.note; + +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SQLException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.sql.DataSource; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NoteRepositoryTests extends AbstractTestBase { + protected NoteRepository repository; + + @BeforeEach + public void onStart() throws Exception { + final DataSource datasource = getDataSource(); + migrate(datasource); + repository = new NoteRepository(NoteSetup.backend(datasource)); + } + + @Test + public void testFindAllCount() { + assertEquals(5, repository.findAll().count()); + } + + @Test + public void testFindWithDistinctCount() { + assertEquals(5, repository.findAll().select(NoteCriteria.note.id).distinct().count()); + } + + @Test + public void testFindWithLimitCount() { + // The limit applies to the result of the COUNT() + assertEquals(5, repository.findAll().limit(3L).count()); + } + + @Test + public void testFindWithLimitSetToZero() { + // The limit applies to the result of the COUNT() and returns no results, so throws an exception + assertThrows(SQLException.class, () -> repository.findAll().limit(0L).count()); + } + + @Test + public void testFindWithOffsetCount() { + // The offset applies to the result of the COUNT() which will result in no results being returned + // then an exception being thrown + assertThrows(SQLException.class, () -> repository.findAll().offset(3L).count()); + } + + @Test + public void testFindAllFetch() { + final List results = repository.findAll().fetch(); + assertEquals(5, results.size()); + assertTrue(results.stream().anyMatch(e -> + e.id().equals(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269")) && + e.created().toEpochMilli() == 1640995200000L && + e.message().equals("Message #1")), + "Missing item"); + } + + @Test + public void testFindAllFetchWithLimit() { + final List results = repository.findAll().limit(3).fetch(); + assertEquals(3, results.size()); + assertTrue(results.stream().anyMatch(e -> + e.id().equals(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269")) && + e.created().toEpochMilli() == 1640995200000L && + e.message().equals("Message #1")), + "Missing item"); + } + + @Test + public void testFindAllFetchWithLimitSetToZero() { + final List results = repository.findAll().limit(0).fetch(); + assertEquals(0, results.size()); + } + + @Test + public void testFindAllFetchWithOffset() { + final List results = repository.findAll().offset(3).fetch(); + assertEquals(2, results.size()); + } + + @Test + public void testFindAllFetchWithLargeOffset() { + final List results = repository.findAll().offset(300L).fetch(); + assertEquals(0, results.size()); + } + + @Test + public void testIdCriteriaBasedFetch() { + final Note results = repository.find(NoteCriteria.note + .id.is(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269"))).one(); + assertNotNull(results); + } + + + @Test + public void testCreatedOnCriteriaBasedFetch() { + final Note results = repository.find(NoteCriteria.note + .created.atLeast(Instant.ofEpochMilli(1640995200000L))).one(); + assertNotNull(results); + } + + + @Test + public void testCriteriaBasedMultiFetch() { + final List results = repository.find(NoteCriteria.note + .id.in(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269"), + UUID.fromString("75b90525-38be-41b9-b43d-df427a66893c"))) + .fetch(); + assertNotNull(results); + assertEquals(2, results.size()); + } + + @Test + public void testFindOneFetchWithSingleOrdering() { + final Note results = repository.findAll() + .orderBy(NoteCriteria.note.created.asc()) + .limit(1) + .one(); + assertEquals(UUID.fromString("c49371f3-8cda-4ddf-ad74-877ee6f67abe"), results.id()); + assertEquals(0L, results.created().toEpochMilli()); + } + + @Test + public void testFindOneFetchWithMultipleOrdering() { + final Note results = repository.findAll() + .orderBy(NoteCriteria.note.id.asc(), NoteCriteria.note.created.asc()) + .limit(1) + .one(); + assertEquals(UUID.fromString("578f932f-c972-4d3b-92b4-bf8fda9599f9"), results.id()); + assertEquals(1577836800000L, results.created().toEpochMilli()); + } + + @Test + public void testInsertSingleValue() { + final Note event = insertAndVerify(newEntity()); + assertEquals(1, repository.find(NoteCriteria.note.id.is(event.id())).count()); + } + + + @Test + public void testInsertMultipleValues() { + final List data = insertAndVerify(newEntities(5)); + data.forEach(e -> assertEquals(1, repository.find(NoteCriteria.note.id.is(e.id())).count())); + } + + @Test + public void testDeleteWhereNotEmpty() { + repository.delete(NoteCriteria.note.message.notEmpty()); + final long after = repository.findAll().count(); + assertEquals(0, after); + } + + @Test + public void testDeleteSingleUsingCriteria() { + final long before = repository.findAll().count(); + + // Insert new data and verify + final Note data = insertAndVerify(newEntity()); + + // Delete and verify state is correct + repository.delete(NoteCriteria.note.id.is(data.id())); + final long after = repository.findAll().count(); + assertEquals(before, after); + } + + @Test + public void testDeleteMultipleUsingCriteria() { + final long before = repository.findAll().count(); + + // Insert new data and verify + final List data = insertAndVerify(newEntities(3)); + + // Delete and verify state is correct + repository.delete(NoteCriteria.note.id.in(data.stream().map(e -> e.id()).collect(Collectors.toList()))); + final long after = repository.findAll().count(); + assertEquals(before, after); + } + + @Test + public void testUpdateSingleEntityUsingCriteria() { + final String message = "I'm a jolly little update"; + final NoteCriteria criteria = NoteCriteria.note; + final Note target = repository.findAll().fetch().get(0); + final WriteResult result = repository.update(criteria.id.is(target.id())) + .set(criteria.message, message) + .execute(); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(1, result.updatedCount().getAsLong()); + + final Note updated = repository.find(NoteCriteria.note.id.is(target.id())).one(); + assertNotNull(updated); + assertEquals(message, updated.message()); + } + + @Test + public void testUpdateMultipleEntitiesUsingCriteria() { + final String message = "I'm a jolly little update"; + final NoteCriteria criteria = NoteCriteria.note; + final Note target = repository.findAll().fetch().get(0); + final WriteResult result = repository.update(criteria.id.isNot(UUID.randomUUID())) + .set(criteria.message, message) + .execute(); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(5, result.updatedCount().getAsLong()); + + repository.findAll().fetch().forEach(e -> { + assertEquals(message, e.message()); + }); + } + + @Test + public void testUpdateSingleEntityUsingObject() { + final String message = "I'm a jolly little update"; + final Note target = repository.findAll().fetch().get(0); + final ImmutableNote update = ImmutableNote.builder() + .from(target) + .message(message) + .build(); + final WriteResult result = repository.update(update); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(1, result.updatedCount().getAsLong()); + + final Note updated = repository.find(NoteCriteria.note.id.is(target.id())).one(); + assertTrue(repository.find(NoteCriteria.note.id.is(target.id())).exists(), "Unable to find note"); + assertNotNull(updated); + assertEquals(message, updated.message()); + } + + @Test + public void testUpdateMultipleEntitiesUsingObject() { + final String message = "I'm a jolly little update"; + final List targets = repository.findAll().limit(3).fetch(); + final List updates = targets.stream() + .map(e -> ImmutableNote.builder() + .from(e) + .message(message) + .build()).collect(Collectors.toList()); + final WriteResult result = repository.updateAll(updates); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(3, result.updatedCount().getAsLong()); + + targets.forEach(t -> { + final Note updated = repository.find(NoteCriteria.note.id.is(t.id())).one(); + assertTrue(repository.find(NoteCriteria.note.id.is(t.id())).exists(), "Unable to find note"); + assertNotNull(updated); + assertEquals(message, updated.message()); + }); + } + + protected final List insertAndVerify(final List entities) { + final long before = repository.findAll().count(); + + final WriteResult result = repository.insertAll(entities); + assertNotNull(result); + assertTrue(result.insertedCount().isPresent()); + assertTrue(result.deletedCount().isPresent()); + assertTrue(result.updatedCount().isPresent()); + assertEquals(entities.size(), result.insertedCount().getAsLong()); + assertEquals(0, result.deletedCount().getAsLong()); + assertEquals(0, result.updatedCount().getAsLong()); + + // Ensure post-insert state is correct + final long after = repository.findAll().count(); + assertEquals(before + entities.size(), after); + + // Ensure that we can find the entities + entities.forEach(e -> assertEquals(1, repository.find(NoteCriteria.note.id.is(e.id())).count())); + + return entities; + } + + protected final Note insertAndVerify(final Note event) { + // Ensure database state is correct + final long before = repository.findAll().count(); + + // Ensure that the insert worked + final WriteResult result = repository.insert(event); + assertNotNull(result); + assertTrue(result.insertedCount().isPresent()); + assertTrue(result.deletedCount().isPresent()); + assertTrue(result.updatedCount().isPresent()); + assertEquals(1, result.insertedCount().getAsLong()); + assertEquals(0, result.deletedCount().getAsLong()); + assertEquals(0, result.updatedCount().getAsLong()); + + // Ensure post-insert state is correct + final long after = repository.findAll().count(); + assertEquals(before + 1, after); + + // Ensure that we can find the entity + assertEquals(1, repository.find(NoteCriteria.note.id.is(event.id())).count()); + + return event; + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java b/criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java new file mode 100644 index 000000000..03dcec90f --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java @@ -0,0 +1,33 @@ +/* + * 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.note; + +import org.immutables.criteria.sql.SQLBackend; +import org.immutables.criteria.sql.SQLSetup; +import org.immutables.criteria.sql.reflection.SQLTypeMetadata; + +import javax.sql.DataSource; + +public class NoteSetup { + public static SQLBackend backend(final DataSource datasource) { + return SQLBackend.of(setup(datasource)); + } + + public static SQLSetup setup(final DataSource datasource) { + final SQLSetup setup = SQLSetup.of(datasource, SQLTypeMetadata.of(Note.class)); + return setup; + } +} diff --git a/criteria/sql/test/test-migrations.xml b/criteria/sql/test/test-migrations.xml new file mode 100644 index 000000000..be2f47170 --- /dev/null +++ b/criteria/sql/test/test-migrations.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fe60fadd8facf4bdb6a4e4fc1419edc48c388b37 Mon Sep 17 00:00:00 2001 From: Gavin Nicol Date: Wed, 24 Aug 2022 22:11:48 -0400 Subject: [PATCH 2/6] Added copyright headers, fixed a naming/case issue --- .../src/org/immutables/criteria/sql/SQL.java | 15 +++ .../immutables/criteria/sql/SQLException.java | 15 +++ .../immutables/criteria/sql/SqlBackend.java | 111 ------------------ .../criteria/sql/compiler/SQLCompiler.java | 15 +++ .../criteria/sql/compiler/SQLPathNaming.java | 15 +++ .../sql/compiler/SQLQueryVisitor.java | 15 +++ .../sql/compiler/SQLSortExpression.java | 15 +++ .../sql/conversion/ColumnFetcher.java | 15 +++ .../sql/conversion/ColumnFetchers.java | 15 +++ .../sql/conversion/ColumnMapping.java | 15 +++ .../criteria/sql/conversion/RowMapper.java | 15 +++ .../criteria/sql/conversion/RowMappers.java | 15 +++ .../sql/conversion/TypeConverter.java | 15 +++ .../sql/conversion/TypeConverters.java | 15 +++ .../criteria/sql/dialects/SQL92Dialect.java | 15 +++ .../criteria/sql/dialects/SQLDialect.java | 15 +++ .../criteria/sql/jdbc/FluentStatement.java | 15 +++ .../sql/reflection/PropertyExtractor.java | 15 +++ .../sql/reflection/SQLContainerNaming.java | 15 +++ .../sql/reflection/SQLPropertyMetadata.java | 15 +++ .../sql/reflection/SQLTypeMetadata.java | 15 +++ .../criteria/sql/compiler/Dummy.java | 15 +++ .../sql/compiler/SQLCompilerTests.java | 15 +++ .../criteria/sql/note/AbstractTestBase.java | 15 +++ 24 files changed, 345 insertions(+), 111 deletions(-) delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQL.java b/criteria/sql/src/org/immutables/criteria/sql/SQL.java index 38fcb6630..451f24aac 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SQL.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SQL.java @@ -1,3 +1,18 @@ +/* + * 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; diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java index cf7bb5ea2..a7c30c5b8 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java @@ -1,3 +1,18 @@ +/* + * 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; public class SQLException extends RuntimeException { diff --git a/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java b/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java deleted file mode 100644 index 6ee92c082..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 io.reactivex.Flowable; -import org.immutables.criteria.backend.Backend; -import org.immutables.criteria.backend.DefaultResult; -import org.immutables.criteria.backend.StandardOperations; -import org.immutables.criteria.sql.commands.*; -import org.immutables.criteria.sql.reflection.SQLTypeMetadata; -import org.reactivestreams.Publisher; - -/** - * Implementation of {@code Backend} which delegates to the underlying SQL specific commands. - */ -public class SQLBackend implements Backend { - private final SQLSetup setup; - - public SQLBackend(final SQLSetup setup) { - this.setup = setup; - } - - public static SQLBackend of(final SQLSetup setup) { - return new SQLBackend(setup); - } - - @Override - public Session open(final Class type) { - return new SQLSession(type, setup); - } - - public static class SQLSession implements Backend.Session { - private final Class type; - private final SQLSetup setup; - - private final SQLTypeMetadata metadata; - - SQLSession(final Class type, final SQLSetup setup) { - this.type = type; - this.setup = setup; - - metadata = SQLTypeMetadata.of(type); - } - - public SQLSetup setup() { - return setup; - } - - public SQLTypeMetadata metadata() { - return metadata; - } - - @Override - public Class entityType() { - return type; - } - - @Override - public Result execute(final Operation operation) { - return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); - } - - private Publisher executeInternal(final Operation operation) { - if (operation instanceof StandardOperations.Select) { - final StandardOperations.Select select = (StandardOperations.Select) operation; - final SQLCommand command = select.query().count() - ? new SQLCountCommand(this, setup, select) - : new SQLSelectCommand(this, setup, select); - return command.execute(); - } else if (operation instanceof StandardOperations.Update) { - final StandardOperations.Update update = (StandardOperations.Update) operation; - final SQLCommand command = new SQLSaveCommand(this, setup, update); - return command.execute(); - } else if (operation instanceof StandardOperations.UpdateByQuery) { - final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; - final SQLCommand command = new SQLUpdateCommand(this, setup, update); - return command.execute(); - } else if (operation instanceof StandardOperations.Insert) { - final StandardOperations.Insert insert = (StandardOperations.Insert) operation; - final SQLCommand command = new SQLInsertCommand(this, setup, insert); - return command.execute(); - } else if (operation instanceof StandardOperations.Delete) { - final StandardOperations.Delete delete = (StandardOperations.Delete) operation; - final SQLCommand command = new SQLDeleteCommand(this, setup, delete); - return command.execute(); - } else if (operation instanceof StandardOperations.Watch) { - throw new UnsupportedOperationException("Watch"); - } else if (operation instanceof StandardOperations.DeleteByKey) { - throw new UnsupportedOperationException("DeleteByKey"); - } else if (operation instanceof StandardOperations.GetByKey) { - throw new UnsupportedOperationException("GetByKey"); - } - - return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", - operation, SQLBackend.class.getSimpleName()))); - } - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java index 9d636c22f..7e4fa2afe 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java @@ -1,3 +1,18 @@ +/* + * 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.compiler; import org.immutables.criteria.expression.*; diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java index d72ebb0ec..ef4cc7d01 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java @@ -1,3 +1,18 @@ +/* + * 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.compiler; import org.immutables.criteria.backend.JavaBeanNaming; diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java index 3e7fbe7f1..3e811d3fa 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java @@ -1,3 +1,18 @@ +/* + * 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.compiler; import com.fasterxml.jackson.databind.type.TypeFactory; diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java index 5c63ea980..e2a5e8520 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java @@ -1,3 +1,18 @@ +/* + * 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.compiler; import org.immutables.value.Value; diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java index f57baac5e..27a7defc0 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; import java.sql.ResultSet; diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java index 983bc3717..a1562195a 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; import org.immutables.criteria.sql.util.TypeKeyHashMap; diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java index aaad3a73b..cc75dce78 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; import org.immutables.value.Value; diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java index 789ec82bd..a9f42e6f2 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; import java.sql.ResultSet; diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java index e991c671c..0922e5854 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java index 943fd6271..9180c4cb9 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; @FunctionalInterface diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java index ae480f587..9c6dbe398 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java @@ -1,3 +1,18 @@ +/* + * 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.conversion; import org.immutables.criteria.sql.SQLException; diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java index 70744207f..3abb51ec1 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java @@ -1,3 +1,18 @@ +/* + * 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.dialects; import org.immutables.criteria.backend.PathNaming; diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java index d4c007be0..e2290fbab 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java @@ -1,3 +1,18 @@ +/* + * 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.dialects; import org.immutables.criteria.backend.PathNaming; diff --git a/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java b/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java index 5d3c807a0..a3ecb7a41 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java @@ -1,3 +1,18 @@ +/* + * 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.jdbc; import javax.sql.DataSource; diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java index 8de361665..e1305cd1a 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java @@ -1,3 +1,18 @@ +/* + * 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.reflection; @FunctionalInterface diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java index ca19dca89..173dd6ce2 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java @@ -1,3 +1,18 @@ +/* + * 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.reflection; import org.immutables.criteria.backend.ContainerNaming; diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java index d2c567087..9176f87c0 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java @@ -1,3 +1,18 @@ +/* + * 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.reflection; import org.immutables.criteria.sql.conversion.ColumnMapping; diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java index 703683208..792fc1b99 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java @@ -1,3 +1,18 @@ +/* + * 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.reflection; import org.immutables.criteria.backend.KeyExtractor; diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java index 038777e0a..36078c8b1 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java @@ -1,3 +1,18 @@ +/* + * 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.compiler; import org.immutables.criteria.Criteria; diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java index 8a4350111..11436416b 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java @@ -1,3 +1,18 @@ +/* + * 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.compiler; import org.h2.jdbcx.JdbcDataSource; diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java index dd4f38ac0..d0982b9ca 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java +++ b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java @@ -1,3 +1,18 @@ +/* + * 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.note; import liquibase.Liquibase; From 00c3f337327f62d40c36c0c3dd9a69c8bd9fb814 Mon Sep 17 00:00:00 2001 From: Gavin Nicol Date: Fri, 25 Nov 2022 17:19:07 -0500 Subject: [PATCH 3/6] Major changes to remove Jackson dependencies etc. --- .java-version | 1 + criteria/sql/.editorconfig | 9 + criteria/sql/README.md | 44 +- criteria/sql/pom.xml | 64 +-- .../src/org/immutables/criteria/sql/SQL.java | 38 +- .../immutables/criteria/sql/SQLBackend.java | 138 ++--- .../immutables/criteria/sql/SQLException.java | 8 +- .../org/immutables/criteria/sql/SQLSetup.java | 32 +- .../immutables/criteria/sql/SqlBackend.java | 111 ++++ .../immutables/criteria/sql/SqlException.java | 22 + .../org/immutables/criteria/sql/SqlSetup.java | 45 ++ .../criteria/sql/commands/SQLCommand.java | 56 +- .../sql/commands/SQLCountCommand.java | 90 ++-- .../sql/commands/SQLDeleteCommand.java | 82 +-- .../sql/commands/SQLInsertCommand.java | 104 ++-- .../criteria/sql/commands/SQLSaveCommand.java | 114 ++-- .../sql/commands/SQLSelectCommand.java | 82 +-- .../sql/commands/SQLUpdateCommand.java | 92 ++-- .../criteria/sql/commands/SqlCommand.java | 56 ++ .../sql/commands/SqlCountCommand.java | 80 +++ .../sql/commands/SqlDeleteCommand.java | 75 +++ .../sql/commands/SqlInsertCommand.java | 92 ++++ .../criteria/sql/commands/SqlSaveCommand.java | 94 ++++ .../sql/commands/SqlSelectCommand.java | 75 +++ .../sql/commands/SqlUpdateCommand.java | 83 +++ .../criteria/sql/compiler/SQLCompiler.java | 266 +++++----- .../sql/compiler/SQLConstantExpression.java | 10 +- .../sql/compiler/SQLCountStatement.java | 28 +- .../sql/compiler/SQLDeleteStatement.java | 16 +- .../criteria/sql/compiler/SQLExpression.java | 6 +- .../sql/compiler/SQLFilterExpression.java | 6 +- .../sql/compiler/SQLInsertStatement.java | 18 +- .../sql/compiler/SQLNameExpression.java | 8 +- .../criteria/sql/compiler/SQLNode.java | 16 +- .../criteria/sql/compiler/SQLPathNaming.java | 12 +- .../sql/compiler/SQLQueryVisitor.java | 200 +++---- .../sql/compiler/SQLSaveStatement.java | 20 +- .../sql/compiler/SQLSelectStatement.java | 26 +- .../sql/compiler/SQLSortExpression.java | 2 +- .../criteria/sql/compiler/SQLStatement.java | 16 +- .../sql/compiler/SQLUpdateStatement.java | 18 +- .../criteria/sql/compiler/SqlCompiler.java | 171 ++++++ .../sql/compiler/SqlConstantExpression.java} | 20 +- .../sql/compiler/SqlCountStatement.java | 39 ++ .../sql/compiler/SqlDeleteStatement.java | 25 + .../criteria/sql/compiler/SqlExpression.java | 20 + .../sql/compiler/SqlFilterExpression.java | 25 + .../sql/compiler/SqlInsertStatement.java | 29 ++ .../sql/compiler/SqlNameExpression.java | 24 + .../criteria/sql/compiler/SqlNode.java | 20 + .../criteria/sql/compiler/SqlPathNaming.java | 27 + .../sql/compiler/SqlQueryVisitor.java | 139 +++++ .../sql/compiler/SqlSaveStatement.java | 31 ++ .../sql/compiler/SqlSelectStatement.java | 37 ++ .../sql/compiler/SqlSortExpression.java | 22 + .../criteria/sql/compiler/SqlStatement.java | 20 + .../sql/compiler/SqlUpdateStatement.java | 28 + .../sql/conversion/ColumnFetcher.java | 2 +- .../sql/conversion/ColumnFetchers.java | 52 +- .../sql/conversion/ColumnMapping.java | 6 +- .../criteria/sql/conversion/RowMapper.java | 2 +- .../criteria/sql/conversion/RowMappers.java | 99 ++-- .../sql/conversion/TypeConverter.java | 4 +- .../sql/conversion/TypeConverters.java | 386 +++++++------- .../criteria/sql/dialects/SQL92Dialect.java | 353 +++++++------ .../criteria/sql/dialects/SQLDialect.java | 30 +- .../criteria/sql/dialects/SqlDialect.java | 57 ++ .../criteria/sql/generator/Sql.generator | 79 +++ .../criteria/sql/generator/Sql.java | 136 +++++ .../criteria/sql/generator/SqlProcessor.java | 34 ++ .../criteria/sql/jdbc/FluentStatement.java | 446 ++++++++-------- .../sql/reflection/PropertyExtractor.java | 2 +- .../sql/reflection/SQLContainerNaming.java | 42 +- .../sql/reflection/SQLPropertyMetadata.java | 10 +- .../sql/reflection/SQLTypeMetadata.java | 262 +++++----- .../sql/reflection/SqlContainerNaming.java | 45 ++ .../sql/reflection/SqlPropertyMetadata.java | 30 ++ .../sql/reflection/SqlTypeMetadata.java | 171 ++++++ .../criteria/sql/util/TypeKeyHashMap.java | 30 +- .../criteria/sql/compiler/Dummy.java | 6 +- .../sql/compiler/SQLCompilerTests.java | 490 +++++++++--------- .../sql/compiler/SqlCompilerTests.java | 297 +++++++++++ .../sql/note/AbstractNoteRepositoryTests.java | 316 +++++++++++ .../criteria/sql/note/AbstractTestBase.java | 89 ++-- .../criteria/sql/note/CustomNoteSetup.java | 27 + .../criteria/sql/note/CustomSetupTests.java | 14 + .../sql/note/GeneratedSetupTests.java | 15 + .../immutables/criteria/sql/note/Note.java | 16 +- .../sql/note/NoteRepositoryTests.java | 325 ------------ .../immutables/criteria/sql/note/notes.sql | 12 + criteria/sql/test/test-migrations.xml | 41 -- 91 files changed, 4593 insertions(+), 2365 deletions(-) create mode 100644 .java-version create mode 100644 criteria/sql/.editorconfig create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SqlException.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/SqlSetup.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlCountCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlDeleteCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlInsertCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlSaveCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlSelectCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SqlUpdateCommand.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCompiler.java rename criteria/sql/{test/org/immutables/criteria/sql/note/NoteSetup.java => src/org/immutables/criteria/sql/compiler/SqlConstantExpression.java} (52%) create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCountStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlDeleteStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlFilterExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlInsertStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNameExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNode.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlPathNaming.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlQueryVisitor.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSaveStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSelectStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSortExpression.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SqlUpdateStatement.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/dialects/SqlDialect.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator create mode 100644 criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SqlContainerNaming.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SqlPropertyMetadata.java create mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SqlTypeMetadata.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/compiler/SqlCompilerTests.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/AbstractNoteRepositoryTests.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/CustomNoteSetup.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/CustomSetupTests.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/GeneratedSetupTests.java delete mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/notes.sql delete mode 100644 criteria/sql/test/test-migrations.xml diff --git a/.java-version b/.java-version new file mode 100644 index 000000000..a88255ac8 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +17.0.4.1 diff --git a/criteria/sql/.editorconfig b/criteria/sql/.editorconfig new file mode 100644 index 000000000..7f741b307 --- /dev/null +++ b/criteria/sql/.editorconfig @@ -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 + diff --git a/criteria/sql/README.md b/criteria/sql/README.md index 93dc7b25a..e9faae96e 100644 --- a/criteria/sql/README.md +++ b/criteria/sql/README.md @@ -1,9 +1,13 @@ # 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 persistend in single tables. It is intended to be a lightweight -alternative to other ORMs but does not compete directly or support all the features of SQL databases. -There are no dependencies in this implementation beyond basic JDBC support and Jackson. +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 @@ -25,14 +29,44 @@ underlying tables. ### Type conversion Object and properties are converted to underlying SQL data types using classes -in the `conversion` package. The default `RowMapper` uses `jackson` to convert -from a row to objects, with the jackson annotations having an impact on the mapping. +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 RowMapper newRowMapper(final SqlTypeMetadata metadata) { + return row -> { + final Map 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._ diff --git a/criteria/sql/pom.xml b/criteria/sql/pom.xml index f1ef4a044..074239100 100644 --- a/criteria/sql/pom.xml +++ b/criteria/sql/pom.xml @@ -14,12 +14,14 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - criteria org.immutables + immutables 2.9.2-SNAPSHOT + 4.0.0 criteria-sql ${project.groupId}.${project.artifactId} @@ -38,55 +40,37 @@ jar ${project.version} - - com.google.guava - guava - ${guava.version} - - - com.google.code.findbugs - jsr305 - ${jsr305.version} - provided - org.immutables value ${project.version} true - - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson-databind.version} - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson.version} + org.immutables + generator-processor + ${project.version} - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - ${jackson.version} + org.immutables + metainf + ${project.version} + provided - com.fasterxml.jackson.module - jackson-module-parameter-names - ${jackson.version} + org.immutables + testing + ${project.version} + test + - com.fasterxml.jackson.datatype - jackson-datatype-guava - ${jackson.version} + com.google.code.findbugs + jsr305 + ${jsr305.version} + provided + io.reactivex.rxjava2 rxjava @@ -111,11 +95,5 @@ ${h2.version} test - - org.liquibase - liquibase-core - ${liquibase.version} - test - diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQL.java b/criteria/sql/src/org/immutables/criteria/sql/SQL.java index 451f24aac..c0f6349af 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SQL.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SQL.java @@ -24,25 +24,25 @@ * 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 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.TYPE, ElementType.METHOD}) - @Retention(RetentionPolicy.RUNTIME) - @interface Column { - String name() 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(); - } + Class type(); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java b/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java index 6ee92c082..d46d9445b 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java @@ -20,92 +20,92 @@ import org.immutables.criteria.backend.DefaultResult; import org.immutables.criteria.backend.StandardOperations; import org.immutables.criteria.sql.commands.*; -import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; import org.reactivestreams.Publisher; /** * Implementation of {@code Backend} which delegates to the underlying SQL specific commands. */ -public class SQLBackend implements Backend { - private final SQLSetup setup; +public class SqlBackend implements Backend { + private final SqlSetup setup; - public SQLBackend(final SQLSetup setup) { - this.setup = setup; - } + public SqlBackend(final SqlSetup setup) { + this.setup = setup; + } - public static SQLBackend of(final SQLSetup setup) { - return new SQLBackend(setup); - } + public static SqlBackend of(final SqlSetup setup) { + return new SqlBackend(setup); + } - @Override - public Session open(final Class type) { - return new SQLSession(type, setup); - } + @Override + public Session open(final Class type) { + return new SQLSession(type, setup); + } - public static class SQLSession implements Backend.Session { - private final Class type; - private final SQLSetup setup; + public static class SQLSession implements Backend.Session { + private final Class type; + private final SqlSetup setup; - private final SQLTypeMetadata metadata; + private final SqlTypeMetadata metadata; - SQLSession(final Class type, final SQLSetup setup) { - this.type = type; - this.setup = setup; + SQLSession(final Class type, final SqlSetup setup) { + this.type = type; + this.setup = setup; - metadata = SQLTypeMetadata.of(type); - } + metadata = SqlTypeMetadata.of(type); + } - public SQLSetup setup() { - return setup; - } + public SqlSetup setup() { + return setup; + } - public SQLTypeMetadata metadata() { - return metadata; - } + public SqlTypeMetadata metadata() { + return metadata; + } - @Override - public Class entityType() { - return type; - } + @Override + public Class entityType() { + return type; + } - @Override - public Result execute(final Operation operation) { - return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); - } + @Override + public Result execute(final Operation operation) { + return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); + } - private Publisher executeInternal(final Operation operation) { - if (operation instanceof StandardOperations.Select) { - final StandardOperations.Select select = (StandardOperations.Select) operation; - final SQLCommand command = select.query().count() - ? new SQLCountCommand(this, setup, select) - : new SQLSelectCommand(this, setup, select); - return command.execute(); - } else if (operation instanceof StandardOperations.Update) { - final StandardOperations.Update update = (StandardOperations.Update) operation; - final SQLCommand command = new SQLSaveCommand(this, setup, update); - return command.execute(); - } else if (operation instanceof StandardOperations.UpdateByQuery) { - final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; - final SQLCommand command = new SQLUpdateCommand(this, setup, update); - return command.execute(); - } else if (operation instanceof StandardOperations.Insert) { - final StandardOperations.Insert insert = (StandardOperations.Insert) operation; - final SQLCommand command = new SQLInsertCommand(this, setup, insert); - return command.execute(); - } else if (operation instanceof StandardOperations.Delete) { - final StandardOperations.Delete delete = (StandardOperations.Delete) operation; - final SQLCommand command = new SQLDeleteCommand(this, setup, delete); - return command.execute(); - } else if (operation instanceof StandardOperations.Watch) { - throw new UnsupportedOperationException("Watch"); - } else if (operation instanceof StandardOperations.DeleteByKey) { - throw new UnsupportedOperationException("DeleteByKey"); - } else if (operation instanceof StandardOperations.GetByKey) { - throw new UnsupportedOperationException("GetByKey"); - } + private Publisher executeInternal(final Operation operation) { + if (operation instanceof StandardOperations.Select) { + final StandardOperations.Select select = (StandardOperations.Select) operation; + final SqlCommand command = select.query().count() + ? new SqlCountCommand(this, setup, select) + : new SqlSelectCommand(this, setup, select); + return command.execute(); + } else if (operation instanceof StandardOperations.Update) { + final StandardOperations.Update update = (StandardOperations.Update) operation; + final SqlCommand command = new SqlSaveCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.UpdateByQuery) { + final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; + final SqlCommand command = new SqlUpdateCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.Insert) { + final StandardOperations.Insert insert = (StandardOperations.Insert) operation; + final SqlCommand command = new SqlInsertCommand(this, setup, insert); + return command.execute(); + } else if (operation instanceof StandardOperations.Delete) { + final StandardOperations.Delete delete = (StandardOperations.Delete) operation; + final SqlCommand command = new SqlDeleteCommand(this, setup, delete); + return command.execute(); + } else if (operation instanceof StandardOperations.Watch) { + throw new UnsupportedOperationException("Watch"); + } else if (operation instanceof StandardOperations.DeleteByKey) { + throw new UnsupportedOperationException("DeleteByKey"); + } else if (operation instanceof StandardOperations.GetByKey) { + throw new UnsupportedOperationException("GetByKey"); + } - return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", - operation, SQLBackend.class.getSimpleName()))); - } + return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", + operation, SqlBackend.class.getSimpleName()))); } + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java index a7c30c5b8..21b8f9073 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java @@ -15,8 +15,8 @@ */ package org.immutables.criteria.sql; -public class SQLException extends RuntimeException { - public SQLException(String message) { - super(message); - } +public class SqlException extends RuntimeException { + public SqlException(String message) { + super(message); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java b/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java index 3ce34e402..a2b6fcb26 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java +++ b/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java @@ -16,30 +16,30 @@ package org.immutables.criteria.sql; import org.immutables.criteria.sql.dialects.SQL92Dialect; -import org.immutables.criteria.sql.dialects.SQLDialect; -import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.criteria.sql.dialects.SqlDialect; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; import org.immutables.value.Value; import javax.sql.DataSource; @Value.Immutable -public interface SQLSetup { - static SQLSetup of(final DataSource datasource, final SQLTypeMetadata metadata) { +public interface SqlSetup { + static SqlSetup of(final DataSource datasource, final SqlTypeMetadata metadata) { - return of(datasource, new SQL92Dialect(), metadata); - } + return of(datasource, new SQL92Dialect(), metadata); + } - static SQLSetup of(final DataSource datasource, final SQLDialect dialect, final SQLTypeMetadata metadata) { - return ImmutableSQLSetup.builder() - .datasource(datasource) - .dialect(dialect) - .metadata(metadata) - .build(); - } + static SqlSetup of(final DataSource datasource, final SqlDialect dialect, final SqlTypeMetadata metadata) { + return ImmutableSqlSetup.builder() + .datasource(datasource) + .dialect(dialect) + .metadata(metadata) + .build(); + } - SQLTypeMetadata metadata(); + SqlTypeMetadata metadata(); - DataSource datasource(); + DataSource datasource(); - SQLDialect dialect(); + SqlDialect dialect(); } diff --git a/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java b/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java new file mode 100644 index 000000000..d46d9445b --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SqlBackend.java @@ -0,0 +1,111 @@ +/* + * 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 io.reactivex.Flowable; +import org.immutables.criteria.backend.Backend; +import org.immutables.criteria.backend.DefaultResult; +import org.immutables.criteria.backend.StandardOperations; +import org.immutables.criteria.sql.commands.*; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; +import org.reactivestreams.Publisher; + +/** + * Implementation of {@code Backend} which delegates to the underlying SQL specific commands. + */ +public class SqlBackend implements Backend { + private final SqlSetup setup; + + public SqlBackend(final SqlSetup setup) { + this.setup = setup; + } + + public static SqlBackend of(final SqlSetup setup) { + return new SqlBackend(setup); + } + + @Override + public Session open(final Class type) { + return new SQLSession(type, setup); + } + + public static class SQLSession implements Backend.Session { + private final Class type; + private final SqlSetup setup; + + private final SqlTypeMetadata metadata; + + SQLSession(final Class type, final SqlSetup setup) { + this.type = type; + this.setup = setup; + + metadata = SqlTypeMetadata.of(type); + } + + public SqlSetup setup() { + return setup; + } + + public SqlTypeMetadata metadata() { + return metadata; + } + + @Override + public Class entityType() { + return type; + } + + @Override + public Result execute(final Operation operation) { + return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); + } + + private Publisher executeInternal(final Operation operation) { + if (operation instanceof StandardOperations.Select) { + final StandardOperations.Select select = (StandardOperations.Select) operation; + final SqlCommand command = select.query().count() + ? new SqlCountCommand(this, setup, select) + : new SqlSelectCommand(this, setup, select); + return command.execute(); + } else if (operation instanceof StandardOperations.Update) { + final StandardOperations.Update update = (StandardOperations.Update) operation; + final SqlCommand command = new SqlSaveCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.UpdateByQuery) { + final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; + final SqlCommand command = new SqlUpdateCommand(this, setup, update); + return command.execute(); + } else if (operation instanceof StandardOperations.Insert) { + final StandardOperations.Insert insert = (StandardOperations.Insert) operation; + final SqlCommand command = new SqlInsertCommand(this, setup, insert); + return command.execute(); + } else if (operation instanceof StandardOperations.Delete) { + final StandardOperations.Delete delete = (StandardOperations.Delete) operation; + final SqlCommand command = new SqlDeleteCommand(this, setup, delete); + return command.execute(); + } else if (operation instanceof StandardOperations.Watch) { + throw new UnsupportedOperationException("Watch"); + } else if (operation instanceof StandardOperations.DeleteByKey) { + throw new UnsupportedOperationException("DeleteByKey"); + } else if (operation instanceof StandardOperations.GetByKey) { + throw new UnsupportedOperationException("GetByKey"); + } + + return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", + operation, SqlBackend.class.getSimpleName()))); + } + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SqlException.java b/criteria/sql/src/org/immutables/criteria/sql/SqlException.java new file mode 100644 index 000000000..21b8f9073 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SqlException.java @@ -0,0 +1,22 @@ +/* + * 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; + +public class SqlException extends RuntimeException { + public SqlException(String message) { + super(message); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SqlSetup.java b/criteria/sql/src/org/immutables/criteria/sql/SqlSetup.java new file mode 100644 index 000000000..a2b6fcb26 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/SqlSetup.java @@ -0,0 +1,45 @@ +/* + * 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 org.immutables.criteria.sql.dialects.SQL92Dialect; +import org.immutables.criteria.sql.dialects.SqlDialect; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; +import org.immutables.value.Value; + +import javax.sql.DataSource; + +@Value.Immutable +public interface SqlSetup { + static SqlSetup of(final DataSource datasource, final SqlTypeMetadata metadata) { + + return of(datasource, new SQL92Dialect(), metadata); + } + + static SqlSetup of(final DataSource datasource, final SqlDialect dialect, final SqlTypeMetadata metadata) { + return ImmutableSqlSetup.builder() + .datasource(datasource) + .dialect(dialect) + .metadata(metadata) + .build(); + } + + SqlTypeMetadata metadata(); + + DataSource datasource(); + + SqlDialect dialect(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java index b7c8b9ffe..b4c71fff6 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java @@ -15,7 +15,7 @@ */ package org.immutables.criteria.sql.commands; -import org.immutables.criteria.sql.compiler.SQLConstantExpression; +import org.immutables.criteria.sql.compiler.SqlConstantExpression; import org.immutables.criteria.sql.conversion.TypeConverters; import org.reactivestreams.Publisher; @@ -24,33 +24,33 @@ import java.util.Map; import java.util.stream.Collectors; -public interface SQLCommand { - Publisher execute(); +public interface SqlCommand { + Publisher execute(); - /** - * Convert from properties encoded in {@code }SQLConstantExpression} to the underlying database - * for use by {@code FluentStatment} - * - * @param properties the properties to convert - * @return A new map with keys and types mapped to the underlying database columns and types - */ - default Map toParameters(final Map properties) { - final Map parameters = new HashMap<>(); - for (final SQLConstantExpression property : properties.values()) { - // Conversion of lists to target type lists - used for IN() - if (property.value() instanceof List) { - final List v = (List) property.value(); - final List converted = v.stream() - .map(f -> TypeConverters.convert(f.getClass(), property.target().mapping().type(), f)) - .collect(Collectors.toList()); - parameters.put(property.sql(), converted); - } else { - parameters.put(property.sql(), - TypeConverters.convert(property.value().getClass(), - property.target().mapping().type(), - property.value())); - } - } - return parameters; + /** + * Convert from properties encoded in {@code }SqlConstantExpression} to the underlying database + * for use by {@code FluentStatment} + * + * @param properties the properties to convert + * @return A new map with keys and types mapped to the underlying database columns and types + */ + default Map toParameters(final Map properties) { + final Map parameters = new HashMap<>(); + for (final SqlConstantExpression property : properties.values()) { + // Conversion of lists to target type lists - used for IN() + if (property.value() instanceof List) { + final List v = (List) property.value(); + final List converted = v.stream() + .map(f -> TypeConverters.convert(f.getClass(), property.target().mapping().type(), f)) + .collect(Collectors.toList()); + parameters.put(property.sql(), converted); + } else { + parameters.put(property.sql(), + TypeConverters.convert(property.value().getClass(), + property.target().mapping().type(), + property.value())); + } } + return parameters; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java index 6441040ce..774c10641 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java @@ -18,12 +18,12 @@ import com.google.common.base.Throwables; import io.reactivex.Flowable; import org.immutables.criteria.backend.StandardOperations.Select; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLException; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.compiler.SQLCompiler; -import org.immutables.criteria.sql.compiler.SQLCountStatement; -import org.immutables.criteria.sql.compiler.SQLFilterExpression; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlCountStatement; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; import org.immutables.criteria.sql.conversion.RowMappers; import org.immutables.criteria.sql.jdbc.FluentStatement; import org.reactivestreams.Publisher; @@ -32,49 +32,49 @@ import java.util.Optional; import java.util.concurrent.Callable; -public class SQLCountCommand implements SQLCommand { +public class SqlCountCommand implements SqlCommand { - private final SQLSetup setup; - private final Select operation; - private final SQLBackend.SQLSession session; + private final SqlSetup setup; + private final Select operation; + private final SqlBackend.SQLSession session; - public SQLCountCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Select operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.query().count() : "count() query expected"; + public SqlCountCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Select operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query().count() : "count() query expected"; - this.session = session; - this.operation = operation; - this.setup = setup; - } + this.session = session; + this.operation = operation; + this.setup = setup; + } - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } - private Callable toCallable(final SQLBackend.SQLSession session, final Select operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; + private Callable toCallable(final SqlBackend.SQLSession session, final Select operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; - final SQLCountStatement count = SQLCompiler.count(setup, operation.query()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().count(count))) { - final Optional ret = statement - .set(toParameters(count - .filter() - .map(SQLFilterExpression::parameters) - .orElse(Collections.emptyMap()))) - .list((rs) -> RowMappers.get(Long.class).map(rs)) - .stream() - .findFirst(); - return ret.orElseThrow(() -> new SQLException("No results returned from count()")); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } + final SqlCountStatement count = SqlCompiler.count(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().count(count))) { + final Optional ret = statement + .set(toParameters(count + .filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .list((rs) -> RowMappers.get(Long.class).map(rs)) + .stream() + .findFirst(); + return ret.orElseThrow(() -> new SqlException("No results returned from count()")); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java index 6cd4badf7..fa4fd0557 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java @@ -19,57 +19,57 @@ import io.reactivex.Flowable; import org.immutables.criteria.backend.StandardOperations.Delete; import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.compiler.SQLCompiler; -import org.immutables.criteria.sql.compiler.SQLDeleteStatement; -import org.immutables.criteria.sql.compiler.SQLFilterExpression; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlDeleteStatement; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; import org.immutables.criteria.sql.jdbc.FluentStatement; import org.reactivestreams.Publisher; import java.util.Collections; import java.util.concurrent.Callable; -public class SQLDeleteCommand implements SQLCommand { +public class SqlDeleteCommand implements SqlCommand { - private final SQLSetup setup; - private final Delete operation; - private final SQLBackend.SQLSession session; + private final SqlSetup setup; + private final Delete operation; + private final SqlBackend.SQLSession session; - public SQLDeleteCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Delete operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; + public SqlDeleteCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Delete operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; - this.session = session; - this.operation = operation; - this.setup = setup; - } + this.session = session; + this.operation = operation; + this.setup = setup; + } - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } - private Callable toCallable(final SQLBackend.SQLSession session, final Delete operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; + private Callable toCallable(final SqlBackend.SQLSession session, final Delete operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; - final SQLDeleteStatement delete = SQLCompiler.delete(setup, operation.query()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().delete(delete))) { - final int result = statement - .set(toParameters(delete - .filter() - .map(SQLFilterExpression::parameters) - .orElse(Collections.emptyMap()))) - .delete(); - return WriteResult.empty().withDeletedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } + final SqlDeleteStatement delete = SqlCompiler.delete(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().delete(delete))) { + final int result = statement + .set(toParameters(delete + .filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .delete(); + return WriteResult.empty().withDeletedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java index 761f99158..fc0816cda 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java @@ -19,11 +19,11 @@ import io.reactivex.Flowable; import org.immutables.criteria.backend.StandardOperations.Insert; import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.compiler.SQLCompiler; -import org.immutables.criteria.sql.compiler.SQLConstantExpression; -import org.immutables.criteria.sql.compiler.SQLInsertStatement; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlConstantExpression; +import org.immutables.criteria.sql.compiler.SqlInsertStatement; import org.immutables.criteria.sql.conversion.TypeConverters; import org.immutables.criteria.sql.jdbc.FluentStatement; import org.reactivestreams.Publisher; @@ -33,60 +33,60 @@ import java.util.Map; import java.util.concurrent.Callable; -public class SQLInsertCommand implements SQLCommand { +public class SqlInsertCommand implements SqlCommand { - private final SQLSetup setup; - private final Insert operation; - private final SQLBackend.SQLSession session; + private final SqlSetup setup; + private final Insert operation; + private final SqlBackend.SQLSession session; - public SQLInsertCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Insert operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.values().size() > 0 : "insert requires at least 1 object"; + public SqlInsertCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Insert operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.values().size() > 0 : "insert requires at least 1 object"; - this.session = session; - this.operation = operation; - this.setup = setup; - } + this.session = session; + this.operation = operation; + this.setup = setup; + } - private static List> values(final SQLSetup setup, final SQLInsertStatement statement) { - final List> ret = new ArrayList<>(); - for (final Map row : statement.values()) { - final List l = new ArrayList<>(); - for (final String column : statement.columns()) { - final SQLConstantExpression c = row.get(column); - l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); - } - ret.add(l); - } - return ret; + private static List> values(final SqlSetup setup, final SqlInsertStatement statement) { + final List> ret = new ArrayList<>(); + for (final Map row : statement.values()) { + final List l = new ArrayList<>(); + for (final String column : statement.columns()) { + final SqlConstantExpression c = row.get(column); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + } + ret.add(l); } + return ret; + } - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } - private Callable toCallable(final SQLBackend.SQLSession session, final Insert operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.values() != null : "Missing `operation.values()` parameter"; + private Callable toCallable(final SqlBackend.SQLSession session, final Insert operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.values() != null : "Missing `operation.values()` parameter"; - // Short circuit empty insert - if (operation.values().size() == 0) { - return WriteResult.empty().withInsertedCount(0); - } + // Short circuit empty insert + if (operation.values().size() == 0) { + return WriteResult.empty().withInsertedCount(0); + } - final SQLInsertStatement insert = SQLCompiler.insert(setup, operation.values()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().insert(insert))) { - final int result = statement.insert(values(setup, insert)); - return WriteResult.empty().withInsertedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } + final SqlInsertStatement insert = SqlCompiler.insert(setup, operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().insert(insert))) { + final int result = statement.insert(values(setup, insert)); + return WriteResult.empty().withInsertedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java index 5ba463e42..3e3e918a4 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java @@ -19,11 +19,11 @@ import io.reactivex.Flowable; import org.immutables.criteria.backend.StandardOperations.Update; import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.compiler.SQLCompiler; -import org.immutables.criteria.sql.compiler.SQLConstantExpression; -import org.immutables.criteria.sql.compiler.SQLSaveStatement; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlConstantExpression; +import org.immutables.criteria.sql.compiler.SqlSaveStatement; import org.immutables.criteria.sql.conversion.TypeConverters; import org.immutables.criteria.sql.jdbc.FluentStatement; import org.reactivestreams.Publisher; @@ -31,64 +31,64 @@ import java.util.*; import java.util.concurrent.Callable; -public class SQLSaveCommand implements SQLCommand { +public class SqlSaveCommand implements SqlCommand { - private final SQLSetup setup; - private final Update operation; - private final SQLBackend.SQLSession session; + private final SqlSetup setup; + private final Update operation; + private final SqlBackend.SQLSession session; - public SQLSaveCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Update operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.values() != null : "update requires values"; + public SqlSaveCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Update operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.values() != null : "update requires values"; - this.session = session; - this.operation = operation; - this.setup = setup; - } + this.session = session; + this.operation = operation; + this.setup = setup; + } - private static List> values(final SQLSetup setup, final SQLSaveStatement statement) { - final List> ret = new ArrayList<>(); - final Set properties = new TreeSet<>(statement.columns()); - properties.remove(statement.key()); - for (final Map entity : statement.properties()) { - final List l = new ArrayList<>(); - for (final String property : properties) { - final SQLConstantExpression c = entity.get(property); - l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); - } - // Add key as the last parameters - final SQLConstantExpression c = entity.get(statement.key()); - l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); - ret.add(l); - } - return ret; + private static List> values(final SqlSetup setup, final SqlSaveStatement statement) { + final List> ret = new ArrayList<>(); + final Set properties = new TreeSet<>(statement.columns()); + properties.remove(statement.key()); + for (final Map entity : statement.properties()) { + final List l = new ArrayList<>(); + for (final String property : properties) { + final SqlConstantExpression c = entity.get(property); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + } + // Add key as the last parameters + final SqlConstantExpression c = entity.get(statement.key()); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + ret.add(l); } + return ret; + } - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } - private Callable toCallable(final SQLBackend.SQLSession session, final Update operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.values() != null : "Expected `operation.values()` on update"; + private Callable toCallable(final SqlBackend.SQLSession session, final Update operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.values() != null : "Expected `operation.values()` on update"; - // Short circuit empty update - if (operation.values().size() == 0) { - return WriteResult.empty().withInsertedCount(0); - } - final SQLSaveStatement save = SQLCompiler.save(setup, operation.values()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().save(save))) { - final int result = statement.update(values(setup, save)); - return WriteResult.empty().withUpdatedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } + // Short circuit empty update + if (operation.values().size() == 0) { + return WriteResult.empty().withInsertedCount(0); + } + final SqlSaveStatement save = SqlCompiler.save(setup, operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().save(save))) { + final int result = statement.update(values(setup, save)); + return WriteResult.empty().withUpdatedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java index 5f132bf11..bc9f2ce5f 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java @@ -18,11 +18,11 @@ import com.google.common.base.Throwables; import io.reactivex.Flowable; import org.immutables.criteria.backend.StandardOperations.Select; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.compiler.SQLCompiler; -import org.immutables.criteria.sql.compiler.SQLFilterExpression; -import org.immutables.criteria.sql.compiler.SQLSelectStatement; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; +import org.immutables.criteria.sql.compiler.SqlSelectStatement; import org.immutables.criteria.sql.conversion.RowMappers; import org.immutables.criteria.sql.jdbc.FluentStatement; import org.reactivestreams.Publisher; @@ -30,46 +30,46 @@ import java.util.Collections; import java.util.concurrent.Callable; -public class SQLSelectCommand implements SQLCommand { +public class SqlSelectCommand implements SqlCommand { - private final SQLSetup setup; - private final Select operation; - private final SQLBackend.SQLSession session; + private final SqlSetup setup; + private final Select operation; + private final SqlBackend.SQLSession session; - public SQLSelectCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final Select operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.query().count() == false : "count() query unexpected"; + public SqlSelectCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Select operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert !operation.query().count() : "count() query unexpected"; - this.session = session; - this.operation = operation; - this.setup = setup; - } + this.session = session; + this.operation = operation; + this.setup = setup; + } - @Override - public Publisher execute() { - final Callable> callable = toCallable(session, operation); - return Flowable.fromCallable(callable).flatMapIterable(x -> x); - } + @Override + public Publisher execute() { + final Callable> callable = toCallable(session, operation); + return Flowable.fromCallable(callable).flatMapIterable(x -> x); + } - private Callable> toCallable(final SQLBackend.SQLSession session, final Select operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; + private Callable> toCallable(final SqlBackend.SQLSession session, final Select operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; - final SQLSelectStatement select = SQLCompiler.select(setup, operation.query()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().select(select))) { - return statement - .set(toParameters(select - .filter() - .map(SQLFilterExpression::parameters) - .orElse(Collections.emptyMap()))) - .list((rs) -> RowMappers.get(session.metadata()).map(rs)); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } + final SqlSelectStatement select = SqlCompiler.select(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().select(select))) { + return statement + .set(toParameters(select + .filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .list((rs) -> RowMappers.get(session.metadata()).map(rs)); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java index ba9a53dc8..f5df1ee66 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java @@ -19,11 +19,11 @@ import io.reactivex.Flowable; import org.immutables.criteria.backend.StandardOperations.UpdateByQuery; import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.compiler.SQLCompiler; -import org.immutables.criteria.sql.compiler.SQLFilterExpression; -import org.immutables.criteria.sql.compiler.SQLUpdateStatement; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; +import org.immutables.criteria.sql.compiler.SqlUpdateStatement; import org.immutables.criteria.sql.jdbc.FluentStatement; import org.reactivestreams.Publisher; @@ -33,51 +33,51 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class SQLUpdateCommand implements SQLCommand { +public class SqlUpdateCommand implements SqlCommand { - private final SQLSetup setup; - private final UpdateByQuery operation; - private final SQLBackend.SQLSession session; + private final SqlSetup setup; + private final UpdateByQuery operation; + private final SqlBackend.SQLSession session; - public SQLUpdateCommand(final SQLBackend.SQLSession session, final SQLSetup setup, final UpdateByQuery operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.query() != null : "update requires a query"; + public SqlUpdateCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final UpdateByQuery operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query() != null : "update requires a query"; - this.session = session; - this.operation = operation; - this.setup = setup; - } + this.session = session; + this.operation = operation; + this.setup = setup; + } - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } - private Callable toCallable(final SQLBackend.SQLSession session, final UpdateByQuery operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; - assert operation.values() != null : "Expected `operation.values()` on update"; + private Callable toCallable(final SqlBackend.SQLSession session, final UpdateByQuery operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + assert operation.values() != null : "Expected `operation.values()` on update"; - final SQLUpdateStatement update = SQLCompiler.update(setup, operation.query(), operation.values()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().update(update))) { - final Map merged = toParameters(Stream.concat( - update.updates().entrySet().stream(), - update.filter() - .map(SQLFilterExpression::parameters) - .orElse(Collections.emptyMap()).entrySet().stream()) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - final int result = statement - .set(merged) - .update(); - return WriteResult.empty().withUpdatedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } + final SqlUpdateStatement update = SqlCompiler.update(setup, operation.query(), operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().update(update))) { + final Map merged = toParameters(Stream.concat( + update.updates().entrySet().stream(), + update.filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()).entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + final int result = statement + .set(merged) + .update(); + return WriteResult.empty().withUpdatedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlCommand.java new file mode 100644 index 000000000..b4c71fff6 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlCommand.java @@ -0,0 +1,56 @@ +/* + * 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.commands; + +import org.immutables.criteria.sql.compiler.SqlConstantExpression; +import org.immutables.criteria.sql.conversion.TypeConverters; +import org.reactivestreams.Publisher; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public interface SqlCommand { + Publisher execute(); + + /** + * Convert from properties encoded in {@code }SqlConstantExpression} to the underlying database + * for use by {@code FluentStatment} + * + * @param properties the properties to convert + * @return A new map with keys and types mapped to the underlying database columns and types + */ + default Map toParameters(final Map properties) { + final Map parameters = new HashMap<>(); + for (final SqlConstantExpression property : properties.values()) { + // Conversion of lists to target type lists - used for IN() + if (property.value() instanceof List) { + final List v = (List) property.value(); + final List converted = v.stream() + .map(f -> TypeConverters.convert(f.getClass(), property.target().mapping().type(), f)) + .collect(Collectors.toList()); + parameters.put(property.sql(), converted); + } else { + parameters.put(property.sql(), + TypeConverters.convert(property.value().getClass(), + property.target().mapping().type(), + property.value())); + } + } + return parameters; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlCountCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlCountCommand.java new file mode 100644 index 000000000..774c10641 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlCountCommand.java @@ -0,0 +1,80 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Select; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlCountStatement; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; +import org.immutables.criteria.sql.conversion.RowMappers; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class SqlCountCommand implements SqlCommand { + + private final SqlSetup setup; + private final Select operation; + private final SqlBackend.SQLSession session; + + public SqlCountCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Select operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query().count() : "count() query expected"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SqlBackend.SQLSession session, final Select operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + + final SqlCountStatement count = SqlCompiler.count(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().count(count))) { + final Optional ret = statement + .set(toParameters(count + .filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .list((rs) -> RowMappers.get(Long.class).map(rs)) + .stream() + .findFirst(); + return ret.orElseThrow(() -> new SqlException("No results returned from count()")); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlDeleteCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlDeleteCommand.java new file mode 100644 index 000000000..fa4fd0557 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlDeleteCommand.java @@ -0,0 +1,75 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Delete; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlDeleteStatement; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.concurrent.Callable; + +public class SqlDeleteCommand implements SqlCommand { + + private final SqlSetup setup; + private final Delete operation; + private final SqlBackend.SQLSession session; + + public SqlDeleteCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Delete operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SqlBackend.SQLSession session, final Delete operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + + final SqlDeleteStatement delete = SqlCompiler.delete(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().delete(delete))) { + final int result = statement + .set(toParameters(delete + .filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .delete(); + return WriteResult.empty().withDeletedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlInsertCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlInsertCommand.java new file mode 100644 index 000000000..fc0816cda --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlInsertCommand.java @@ -0,0 +1,92 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Insert; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlConstantExpression; +import org.immutables.criteria.sql.compiler.SqlInsertStatement; +import org.immutables.criteria.sql.conversion.TypeConverters; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +public class SqlInsertCommand implements SqlCommand { + + private final SqlSetup setup; + private final Insert operation; + private final SqlBackend.SQLSession session; + + public SqlInsertCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Insert operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.values().size() > 0 : "insert requires at least 1 object"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + private static List> values(final SqlSetup setup, final SqlInsertStatement statement) { + final List> ret = new ArrayList<>(); + for (final Map row : statement.values()) { + final List l = new ArrayList<>(); + for (final String column : statement.columns()) { + final SqlConstantExpression c = row.get(column); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + } + ret.add(l); + } + return ret; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SqlBackend.SQLSession session, final Insert operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.values() != null : "Missing `operation.values()` parameter"; + + // Short circuit empty insert + if (operation.values().size() == 0) { + return WriteResult.empty().withInsertedCount(0); + } + + final SqlInsertStatement insert = SqlCompiler.insert(setup, operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().insert(insert))) { + final int result = statement.insert(values(setup, insert)); + return WriteResult.empty().withInsertedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlSaveCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlSaveCommand.java new file mode 100644 index 000000000..3e3e918a4 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlSaveCommand.java @@ -0,0 +1,94 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Update; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlConstantExpression; +import org.immutables.criteria.sql.compiler.SqlSaveStatement; +import org.immutables.criteria.sql.conversion.TypeConverters; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.*; +import java.util.concurrent.Callable; + +public class SqlSaveCommand implements SqlCommand { + + private final SqlSetup setup; + private final Update operation; + private final SqlBackend.SQLSession session; + + public SqlSaveCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Update operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.values() != null : "update requires values"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + private static List> values(final SqlSetup setup, final SqlSaveStatement statement) { + final List> ret = new ArrayList<>(); + final Set properties = new TreeSet<>(statement.columns()); + properties.remove(statement.key()); + for (final Map entity : statement.properties()) { + final List l = new ArrayList<>(); + for (final String property : properties) { + final SqlConstantExpression c = entity.get(property); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + } + // Add key as the last parameters + final SqlConstantExpression c = entity.get(statement.key()); + l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); + ret.add(l); + } + return ret; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SqlBackend.SQLSession session, final Update operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.values() != null : "Expected `operation.values()` on update"; + + // Short circuit empty update + if (operation.values().size() == 0) { + return WriteResult.empty().withInsertedCount(0); + } + final SqlSaveStatement save = SqlCompiler.save(setup, operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().save(save))) { + final int result = statement.update(values(setup, save)); + return WriteResult.empty().withUpdatedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlSelectCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlSelectCommand.java new file mode 100644 index 000000000..bc9f2ce5f --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlSelectCommand.java @@ -0,0 +1,75 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.Select; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; +import org.immutables.criteria.sql.compiler.SqlSelectStatement; +import org.immutables.criteria.sql.conversion.RowMappers; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.concurrent.Callable; + +public class SqlSelectCommand implements SqlCommand { + + private final SqlSetup setup; + private final Select operation; + private final SqlBackend.SQLSession session; + + public SqlSelectCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Select operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert !operation.query().count() : "count() query unexpected"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable> callable = toCallable(session, operation); + return Flowable.fromCallable(callable).flatMapIterable(x -> x); + } + + private Callable> toCallable(final SqlBackend.SQLSession session, final Select operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + + final SqlSelectStatement select = SqlCompiler.select(setup, operation.query()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().select(select))) { + return statement + .set(toParameters(select + .filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()))) + .list((rs) -> RowMappers.get(session.metadata()).map(rs)); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SqlUpdateCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlUpdateCommand.java new file mode 100644 index 000000000..f5df1ee66 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/commands/SqlUpdateCommand.java @@ -0,0 +1,83 @@ +/* + * 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.commands; + +import com.google.common.base.Throwables; +import io.reactivex.Flowable; +import org.immutables.criteria.backend.StandardOperations.UpdateByQuery; +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.compiler.SqlCompiler; +import org.immutables.criteria.sql.compiler.SqlFilterExpression; +import org.immutables.criteria.sql.compiler.SqlUpdateStatement; +import org.immutables.criteria.sql.jdbc.FluentStatement; +import org.reactivestreams.Publisher; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SqlUpdateCommand implements SqlCommand { + + private final SqlSetup setup; + private final UpdateByQuery operation; + private final SqlBackend.SQLSession session; + + public SqlUpdateCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final UpdateByQuery operation) { + assert session != null : "session cannot be null"; + assert setup != null : "setup cannot be null"; + assert operation != null : "operation cannot be null"; + assert operation.query() != null : "update requires a query"; + + this.session = session; + this.operation = operation; + this.setup = setup; + } + + @Override + public Publisher execute() { + final Callable callable = toCallable(session, operation); + return Flowable.fromCallable(callable); + } + + private Callable toCallable(final SqlBackend.SQLSession session, final UpdateByQuery operation) { + return () -> { + assert operation != null : "Missing `operation` parameter"; + assert operation.query() != null : "Missing `operation.query()` parameter"; + assert operation.values() != null : "Expected `operation.values()` on update"; + + final SqlUpdateStatement update = SqlCompiler.update(setup, operation.query(), operation.values()); + try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), + session.setup().dialect().update(update))) { + final Map merged = toParameters(Stream.concat( + update.updates().entrySet().stream(), + update.filter() + .map(SqlFilterExpression::parameters) + .orElse(Collections.emptyMap()).entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + final int result = statement + .set(merged) + .update(); + return WriteResult.empty().withUpdatedCount(result); + } catch (final Throwable t) { + throw Throwables.propagate(t); + } + }; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java index 7e4fa2afe..6ae065263 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java @@ -16,156 +16,156 @@ package org.immutables.criteria.sql.compiler; import org.immutables.criteria.expression.*; -import org.immutables.criteria.sql.SQLException; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; import java.util.*; import java.util.stream.Collectors; -public class SQLCompiler { - public static SQLCountStatement count(final SQLSetup setup, final Query query) { - // TODO: Aggregations - return ImmutableSQLCountStatement.builder() - .table(setup.metadata().table()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .distinct(query.distinct()) - .filter(compileWhereClause(setup, query.filter())) - .ordering(compileOrderBy(setup, query.collations())) - .qualifier(compileDistinctCount(setup, query).orElse("COUNT(*)")) - .offset(query.offset()) - .limit(query.limit()) - .type(Long.class) - .build(); - } +public class SqlCompiler { + public static SqlCountStatement count(final SqlSetup setup, final Query query) { + // TODO: Aggregations + return ImmutableSqlCountStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .distinct(query.distinct()) + .filter(compileWhereClause(setup, query.filter())) + .ordering(compileOrderBy(setup, query.collations())) + .qualifier(compileDistinctCount(setup, query).orElse("COUNT(*)")) + .offset(query.offset()) + .limit(query.limit()) + .type(Long.class) + .build(); + } - public static SQLSelectStatement select(final SQLSetup setup, final Query query) { - // TODO: Projections, Aggregations - return ImmutableSQLSelectStatement.builder() - .table(setup.metadata().table()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .distinct(query.distinct()) - .filter(compileWhereClause(setup, query.filter())) - .ordering(compileOrderBy(setup, query.collations())) - .offset(query.offset()) - .limit(query.limit()) - .type(List.class) - .build(); - } + public static SqlSelectStatement select(final SqlSetup setup, final Query query) { + // TODO: Projections, Aggregations + return ImmutableSqlSelectStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .distinct(query.distinct()) + .filter(compileWhereClause(setup, query.filter())) + .ordering(compileOrderBy(setup, query.collations())) + .offset(query.offset()) + .limit(query.limit()) + .type(List.class) + .build(); + } - private static Optional compileOrderBy(final SQLSetup setup, final List collations) { - final String ordering = collations.stream() - .map(c -> String.format("`%s` %s", - setup.metadata().properties().get(c.path().toString()).mapping().name(), - c.direction().isAscending() ? "ASC" : "DESC")) - .collect(Collectors.joining(",")); - return ordering.length() > 0 ? Optional.of(ordering) : Optional.empty(); - } + private static Optional compileOrderBy(final SqlSetup setup, final List collations) { + final String ordering = collations.stream() + .map(c -> String.format("`%s` %s", + setup.metadata().properties().get(c.path().toString()).mapping().name(), + c.direction().isAscending() ? "ASC" : "DESC")) + .collect(Collectors.joining(",")); + return ordering.length() > 0 ? Optional.of(ordering) : Optional.empty(); + } - private static Optional compileDistinctCount(final SQLSetup setup, final Query query) { - if (query.distinct()) { - if (query.projections().size() != 1) { - throw new SQLException("Expected a single projection argument to count with distinct"); - } - return Optional.of(String.format("COUNT(DISTINCT `%s`)", - setup.metadata().properties().get(query.projections().get(0).toString()).mapping().name())); - } - return Optional.empty(); + private static Optional compileDistinctCount(final SqlSetup setup, final Query query) { + if (query.distinct()) { + if (query.projections().size() != 1) { + throw new SqlException("Expected a single projection argument to count with distinct"); + } + return Optional.of(String.format("COUNT(DISTINCT `%s`)", + setup.metadata().properties().get(query.projections().get(0).toString()).mapping().name())); } + return Optional.empty(); + } - public static SQLDeleteStatement delete(final SQLSetup setup, final Query query) { - return ImmutableSQLDeleteStatement.builder() - .table(setup.metadata().table()) - .filter(compileWhereClause(setup, query.filter())) - .type(Long.class) - .build(); - } + public static SqlDeleteStatement delete(final SqlSetup setup, final Query query) { + return ImmutableSqlDeleteStatement.builder() + .table(setup.metadata().table()) + .filter(compileWhereClause(setup, query.filter())) + .type(Long.class) + .build(); + } - public static SQLInsertStatement insert(final SQLSetup setup, final List entities) { - return ImmutableSQLInsertStatement.builder() - .table(setup.metadata().table()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .values(toPropertyMap(setup, entities)) - .type(Long.class) - .build(); - } + public static SqlInsertStatement insert(final SqlSetup setup, final List entities) { + return ImmutableSqlInsertStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .values(toPropertyMap(setup, entities)) + .type(Long.class) + .build(); + } - public static SQLUpdateStatement update(final SQLSetup setup, final Query query, final Map values) { - final Map updates = new HashMap<>(); - for (final Map.Entry e : values.entrySet()) { - final Path path = (Path) e.getKey(); - final Object value = e.getValue(); - final SQLPropertyMetadata p = setup.metadata().properties().get(path.toString()); - updates.put(p.mapping().name(), ImmutableSQLConstantExpression.builder() - .sql(":" + p.mapping().name()) - .type(p.type()) - .value(value) - .target(p) - .build()); - } - return ImmutableSQLUpdateStatement.builder() - .table(setup.metadata().table()) - .filter(compileWhereClause(setup, query.filter())) - .updates(updates) - .type(Long.class) - .build(); + public static SqlUpdateStatement update(final SqlSetup setup, final Query query, final Map values) { + final Map updates = new HashMap<>(); + for (final Map.Entry e : values.entrySet()) { + final Path path = (Path) e.getKey(); + final Object value = e.getValue(); + final SqlPropertyMetadata p = setup.metadata().properties().get(path.toString()); + updates.put(p.mapping().name(), ImmutableSqlConstantExpression.builder() + .sql(":" + p.mapping().name()) + .type(p.type()) + .value(value) + .target(p) + .build()); } + return ImmutableSqlUpdateStatement.builder() + .table(setup.metadata().table()) + .filter(compileWhereClause(setup, query.filter())) + .updates(updates) + .type(Long.class) + .build(); + } - public static SQLSaveStatement save(final SQLSetup setup, final List entities) { - final Class type = setup.metadata().type(); - if (!(setup.metadata().key().metadata().isKeyDefined() && setup.metadata().key().metadata().isExpression())) { - throw new SQLException("Update using objects requires a simple key to be defined"); - } - - final List> values = new ArrayList<>(); - for (final Object o : entities) { - if (!type.isAssignableFrom(o.getClass())) { - throw new SQLException(String.format("Incompatible save() type. Expected %s found %s", - type.getSimpleName(), o.getClass().getSimpleName())); - } - } - return ImmutableSQLSaveStatement.builder() - .table(setup.metadata().table()) - .key(setup.metadata().key().metadata().keys().get(0).toString()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .properties(toPropertyMap(setup, entities)) - .type(Long.class) - .build(); + public static SqlSaveStatement save(final SqlSetup setup, final List entities) { + final Class type = setup.metadata().type(); + if (!(setup.metadata().key().metadata().isKeyDefined() && setup.metadata().key().metadata().isExpression())) { + throw new SqlException("Update using objects requires a simple key to be defined"); } - private static Optional compileWhereClause(final SQLSetup setup, final Optional filter) { - if (filter.isPresent()) { - if (!(filter.get() instanceof Call)) { - throw new SQLException("Filter expression must be a call"); - } - final SQLQueryVisitor visitor = new SQLQueryVisitor(setup); - return Optional.of(visitor.call((Call) filter.get())); - } - return Optional.empty(); + final List> values = new ArrayList<>(); + for (final Object o : entities) { + if (!type.isAssignableFrom(o.getClass())) { + throw new SqlException(String.format("Incompatible save() type. Expected %s found %s", + type.getSimpleName(), o.getClass().getSimpleName())); + } + } + return ImmutableSqlSaveStatement.builder() + .table(setup.metadata().table()) + .key(setup.metadata().key().metadata().keys().get(0).toString()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .properties(toPropertyMap(setup, entities)) + .type(Long.class) + .build(); + } + private static Optional compileWhereClause(final SqlSetup setup, final Optional filter) { + if (filter.isPresent()) { + if (!(filter.get() instanceof Call)) { + throw new SqlException("Filter expression must be a call"); + } + final SqlQueryVisitor visitor = new SqlQueryVisitor(setup); + return Optional.of(visitor.call((Call) filter.get())); } + return Optional.empty(); + + } - private static List> toPropertyMap(final SQLSetup setup, final List entities) { - final Class type = setup.metadata().type(); - final List> values = new ArrayList<>(); - for (final Object o : entities) { - // Sanity check that all the objects in the list match the metadata type - if (!type.isAssignableFrom(o.getClass())) { - throw new SQLException(String.format("Incompatible insert() type. Expected %s found %s", - type.getSimpleName(), o.getClass().getSimpleName())); - } - final Map row = new HashMap<>(); - for (final SQLPropertyMetadata p : setup.metadata().properties().values()) { - final Object value = p.extractor().extract(o); - row.put(p.mapping().name(), ImmutableSQLConstantExpression.builder() - .sql(p.mapping().name()) - .type(p.type()) - .value(value) - .target(p) - .build()); - } - values.add(row); - } - return values; + private static List> toPropertyMap(final SqlSetup setup, final List entities) { + final Class type = setup.metadata().type(); + final List> values = new ArrayList<>(); + for (final Object o : entities) { + // Sanity check that all the objects in the list match the metadata type + if (!type.isAssignableFrom(o.getClass())) { + throw new SqlException(String.format("Incompatible insert() type. Expected %s found %s", + type.getSimpleName(), o.getClass().getSimpleName())); + } + final Map row = new HashMap<>(); + for (final SqlPropertyMetadata p : setup.metadata().properties().values()) { + final Object value = p.extractor().extract(o); + row.put(p.mapping().name(), ImmutableSqlConstantExpression.builder() + .sql(p.mapping().name()) + .type(p.type()) + .value(value) + .target(p) + .build()); + } + values.add(row); } + return values; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java index 058e3e22f..9d80be815 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java @@ -15,13 +15,13 @@ */ package org.immutables.criteria.sql.compiler; -import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; import org.immutables.value.Value; @Value.Immutable -public interface SQLConstantExpression extends SQLExpression { +public interface SqlConstantExpression extends SqlExpression { - SQLPropertyMetadata target(); + SqlPropertyMetadata target(); - Object value(); -} \ No newline at end of file + Object value(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java index dec0ee5a7..06ca42a12 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -22,18 +22,18 @@ import java.util.Set; @Value.Immutable -public interface SQLCountStatement extends SQLStatement { - Optional distinct(); +public interface SqlCountStatement extends SqlStatement { + Optional distinct(); - String qualifier(); + String qualifier(); - OptionalLong limit(); + OptionalLong limit(); - OptionalLong offset(); + OptionalLong offset(); - Set columns(); + Set columns(); - Optional ordering(); + Optional ordering(); - Optional filter(); -} \ No newline at end of file + Optional filter(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java index 3805a19fc..9f618c1d9 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -20,6 +20,6 @@ import java.util.Optional; @Value.Immutable -public interface SQLDeleteStatement extends SQLStatement { - Optional filter(); -} \ No newline at end of file +public interface SqlDeleteStatement extends SqlStatement { + Optional filter(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java index d03ca280d..aa34e1540 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java @@ -15,6 +15,6 @@ */ package org.immutables.criteria.sql.compiler; -public interface SQLExpression extends SQLNode { - String sql(); -} \ No newline at end of file +public interface SqlExpression extends SqlNode { + String sql(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java index 2c2bb7974..90d37df71 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java @@ -20,6 +20,6 @@ import java.util.Map; @Value.Immutable -public interface SQLFilterExpression extends SQLExpression { - Map parameters(); -} \ No newline at end of file +public interface SqlFilterExpression extends SqlExpression { + Map parameters(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java index 179f0277f..2815a3ca2 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -22,8 +22,8 @@ import java.util.SortedSet; @Value.Immutable -public interface SQLInsertStatement extends SQLStatement { - SortedSet columns(); +public interface SqlInsertStatement extends SqlStatement { + SortedSet columns(); - List> values(); -} \ No newline at end of file + List> values(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java index e05e03b95..34e8209b7 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java @@ -15,10 +15,10 @@ */ package org.immutables.criteria.sql.compiler; -import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; import org.immutables.value.Value; @Value.Immutable -public interface SQLNameExpression extends SQLExpression { - SQLPropertyMetadata metadata(); -} \ No newline at end of file +public interface SqlNameExpression extends SqlExpression { + SqlPropertyMetadata metadata(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java index cafcb3b3c..c8fdb1b29 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -15,6 +15,6 @@ */ package org.immutables.criteria.sql.compiler; -public interface SQLNode { - Class type(); -} \ No newline at end of file +public interface SqlNode { + Class type(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java index ef4cc7d01..61c120ff9 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java @@ -18,10 +18,10 @@ import org.immutables.criteria.backend.JavaBeanNaming; import org.immutables.criteria.expression.Path; -public class SQLPathNaming extends JavaBeanNaming { - @Override - public String name(Path path) { - String name = super.name(path); - return "`" + name.replaceAll("\\.", "`.`") + "`"; - } +public class SqlPathNaming extends JavaBeanNaming { + @Override + public String name(Path path) { + String name = super.name(path); + return "`" + name.replaceAll("\\.", "`.`") + "`"; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java index 3e811d3fa..bf5049840 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java @@ -15,11 +15,11 @@ */ package org.immutables.criteria.sql.compiler; -import com.fasterxml.jackson.databind.type.TypeFactory; +import com.google.common.reflect.TypeToken; import org.immutables.criteria.expression.*; -import org.immutables.criteria.sql.SQLException; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; import java.util.HashMap; import java.util.List; @@ -27,113 +27,113 @@ import java.util.Optional; import java.util.stream.Collectors; -public class SQLQueryVisitor { - private final Map parameters = new HashMap<>(); - private final SQLSetup setup; +public class SqlQueryVisitor { + private final Map parameters = new HashMap<>(); + private final SqlSetup setup; - public SQLQueryVisitor(final SQLSetup setup) { - this.setup = setup; - } + public SqlQueryVisitor(final SqlSetup setup) { + this.setup = setup; + } - private static SQLFilterExpression unary(final Call call) { - return null; - } + private static SqlFilterExpression unary(final Call call) { + return null; + } - private static SQLFilterExpression ternary(final Call call) { - return null; - } + private static SqlFilterExpression ternary(final Call call) { + return null; + } - public SQLFilterExpression call(final Call call) { - if (call.operator().arity() == Operator.Arity.UNARY) { - return unary(call); - } - if (call.operator().arity() == Operator.Arity.BINARY) { - return binary(call); - } - if (call.operator().arity() == Operator.Arity.TERNARY) { - return ternary(call); - } - throw new SQLException("Invalid operator arity " + call.operator()); + public SqlFilterExpression call(final Call call) { + if (call.operator().arity() == Operator.Arity.UNARY) { + return unary(call); } - - public SQLConstantExpression constant(final SQLNameExpression target, final Constant constant) { - final String params = constant.values().stream().map(e -> { - final String name = String.format(":param_%d", parameters.size()); - final SQLConstantExpression result = ImmutableSQLConstantExpression.builder() - .sql(name) - .target(target.metadata()) - .value(e) - .type(TypeFactory.rawClass(constant.returnType())) - .build(); - parameters.put(name, result); - return name; - }).collect(Collectors.joining(",")); - final SQLConstantExpression result = ImmutableSQLConstantExpression.builder() - .sql(params) - .target(target.metadata()) - .value(constant.value()) - .type(TypeFactory.rawClass(constant.returnType())) - .build(); - return result; + if (call.operator().arity() == Operator.Arity.BINARY) { + return binary(call); } - - public SQLNameExpression path(final Path path) { - final SQLPropertyMetadata meta = setup.metadata() - .properties() - .get(path.toString()); - return ImmutableSQLNameExpression.builder() - // TODO: Column aliases - .sql("`" + meta.mapping().name() + "`") - .metadata(meta) - .type(meta.mapping().type()) - .build(); + if (call.operator().arity() == Operator.Arity.TERNARY) { + return ternary(call); } + throw new SqlException("Invalid operator arity " + call.operator()); + } + + public SqlConstantExpression constant(final SqlNameExpression target, final Constant constant) { + final String params = constant.values().stream().map(e -> { + final String name = String.format(":param_%d", parameters.size()); + final SqlConstantExpression result = ImmutableSqlConstantExpression.builder() + .sql(name) + .target(target.metadata()) + .value(e) + .type(TypeToken.of(constant.returnType()).getRawType()) + .build(); + parameters.put(name, result); + return name; + }).collect(Collectors.joining(",")); + final SqlConstantExpression result = ImmutableSqlConstantExpression.builder() + .sql(params) + .target(target.metadata()) + .value(constant.value()) + .type(TypeToken.of(constant.returnType()).getRawType()) + .build(); + return result; + } - private SQLFilterExpression logical(final Call call) { - final Operator op = call.operator(); - final List args = call.arguments(); + public SqlNameExpression path(final Path path) { + final SqlPropertyMetadata meta = setup.metadata() + .properties() + .get(path.toString()); + return ImmutableSqlNameExpression.builder() + // TODO: Column aliases + .sql("`" + meta.mapping().name() + "`") + .metadata(meta) + .type(meta.mapping().type()) + .build(); + } - if (!(args.get(0) instanceof Call)) { - throw new SQLException("left hand side of logical expression must be a call"); - } - if (!(args.get(1) instanceof Call)) { - throw new SQLException("right hand side of logical expression must be a call"); - } - final String sql = setup.dialect().logical((Operators) op, - call((Call) args.get(0)), - call((Call) args.get(1))); - return ImmutableSQLFilterExpression.builder() - .sql(sql) - .parameters(parameters) - .type(TypeFactory.rawClass(args.get(0).returnType())) - .build(); + private SqlFilterExpression logical(final Call call) { + final Operator op = call.operator(); + final List args = call.arguments(); + if (!(args.get(0) instanceof Call)) { + throw new SqlException("left hand side of logical expression must be a call"); } + if (!(args.get(1) instanceof Call)) { + throw new SqlException("right hand side of logical expression must be a call"); + } + final String sql = setup.dialect().logical((Operators) op, + call((Call) args.get(0)), + call((Call) args.get(1))); + return ImmutableSqlFilterExpression.builder() + .sql(sql) + .parameters(parameters) + .type(TypeToken.of(args.get(0).returnType()).getRawType()) + .build(); + + } - private SQLFilterExpression binary(final Call call) { - final Operator op = call.operator(); - final List args = call.arguments(); - if (args.size() != 2) { - throw new SQLException("binary expression expects exactly 2 args"); - } - if (op == Operators.AND || op == Operators.OR) { - return logical(call); - } - if (!(args.get(0) instanceof Path)) { - throw new SQLException("left hand side of comparison should be a path"); - } - if (!(args.get(1) instanceof Constant)) { - throw new SQLException("right hand side of comparison should be a constant"); - } - final Path left = (Path) args.get(0); - final Constant right = (Constant) args.get(1); - final SQLNameExpression target = path(left); - final Optional operator = setup.dialect().binary(op, target, constant(target, right)); - return operator.map(s -> ImmutableSQLFilterExpression.builder() - .sql(s) - .parameters(parameters) - .type(TypeFactory.rawClass(left.returnType())) - .build()).orElseThrow(() -> - new SQLException("Unhandled binary operator " + op)); + private SqlFilterExpression binary(final Call call) { + final Operator op = call.operator(); + final List args = call.arguments(); + if (args.size() != 2) { + throw new SqlException("binary expression expects exactly 2 args"); + } + if (op == Operators.AND || op == Operators.OR) { + return logical(call); + } + if (!(args.get(0) instanceof Path)) { + throw new SqlException("left hand side of comparison should be a path"); + } + if (!(args.get(1) instanceof Constant)) { + throw new SqlException("right hand side of comparison should be a constant"); } + final Path left = (Path) args.get(0); + final Constant right = (Constant) args.get(1); + final SqlNameExpression target = path(left); + final Optional operator = setup.dialect().binary(op, target, constant(target, right)); + return operator.map(s -> ImmutableSqlFilterExpression.builder() + .sql(s) + .parameters(parameters) + .type(TypeToken.of(left.returnType()).getRawType()) + .build()).orElseThrow(() -> + new SqlException("Unhandled binary operator " + op)); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java index af40c73d3..7b19a2225 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -22,10 +22,10 @@ import java.util.SortedSet; @Value.Immutable -public interface SQLSaveStatement extends SQLStatement { - String key(); +public interface SqlSaveStatement extends SqlStatement { + String key(); - SortedSet columns(); + SortedSet columns(); - List> properties(); -} \ No newline at end of file + List> properties(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java index 31f8d5c68..32539dde1 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -22,16 +22,16 @@ import java.util.SortedSet; @Value.Immutable -public interface SQLSelectStatement extends SQLStatement { - Optional distinct(); +public interface SqlSelectStatement extends SqlStatement { + Optional distinct(); - OptionalLong limit(); + OptionalLong limit(); - OptionalLong offset(); + OptionalLong offset(); - Optional filter(); + Optional filter(); - Optional ordering(); + Optional ordering(); - SortedSet columns(); -} \ No newline at end of file + SortedSet columns(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java index e2a5e8520..e1fbafef6 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java @@ -18,5 +18,5 @@ import org.immutables.value.Value; @Value.Immutable -public interface SQLSortExpression extends SQLExpression { +public interface SqlSortExpression extends SqlExpression { } diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java index cd078880a..c10b3a089 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -15,6 +15,6 @@ */ package org.immutables.criteria.sql.compiler; -public interface SQLStatement extends SQLNode { - String table(); -} \ No newline at end of file +public interface SqlStatement extends SqlNode { + String table(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java index 008536c4c..e05ebd6de 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java @@ -1,12 +1,12 @@ -/** +/* * 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 - *

+ * + * 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. @@ -21,8 +21,8 @@ import java.util.Optional; @Value.Immutable -public interface SQLUpdateStatement extends SQLStatement { - Map updates(); +public interface SqlUpdateStatement extends SqlStatement { + Map updates(); - Optional filter(); -} \ No newline at end of file + Optional filter(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCompiler.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCompiler.java new file mode 100644 index 000000000..6ae065263 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCompiler.java @@ -0,0 +1,171 @@ +/* + * 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.compiler; + +import org.immutables.criteria.expression.*; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; + +import java.util.*; +import java.util.stream.Collectors; + +public class SqlCompiler { + public static SqlCountStatement count(final SqlSetup setup, final Query query) { + // TODO: Aggregations + return ImmutableSqlCountStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .distinct(query.distinct()) + .filter(compileWhereClause(setup, query.filter())) + .ordering(compileOrderBy(setup, query.collations())) + .qualifier(compileDistinctCount(setup, query).orElse("COUNT(*)")) + .offset(query.offset()) + .limit(query.limit()) + .type(Long.class) + .build(); + } + + public static SqlSelectStatement select(final SqlSetup setup, final Query query) { + // TODO: Projections, Aggregations + return ImmutableSqlSelectStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .distinct(query.distinct()) + .filter(compileWhereClause(setup, query.filter())) + .ordering(compileOrderBy(setup, query.collations())) + .offset(query.offset()) + .limit(query.limit()) + .type(List.class) + .build(); + } + + private static Optional compileOrderBy(final SqlSetup setup, final List collations) { + final String ordering = collations.stream() + .map(c -> String.format("`%s` %s", + setup.metadata().properties().get(c.path().toString()).mapping().name(), + c.direction().isAscending() ? "ASC" : "DESC")) + .collect(Collectors.joining(",")); + return ordering.length() > 0 ? Optional.of(ordering) : Optional.empty(); + } + + private static Optional compileDistinctCount(final SqlSetup setup, final Query query) { + if (query.distinct()) { + if (query.projections().size() != 1) { + throw new SqlException("Expected a single projection argument to count with distinct"); + } + return Optional.of(String.format("COUNT(DISTINCT `%s`)", + setup.metadata().properties().get(query.projections().get(0).toString()).mapping().name())); + } + return Optional.empty(); + } + + public static SqlDeleteStatement delete(final SqlSetup setup, final Query query) { + return ImmutableSqlDeleteStatement.builder() + .table(setup.metadata().table()) + .filter(compileWhereClause(setup, query.filter())) + .type(Long.class) + .build(); + } + + public static SqlInsertStatement insert(final SqlSetup setup, final List entities) { + return ImmutableSqlInsertStatement.builder() + .table(setup.metadata().table()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .values(toPropertyMap(setup, entities)) + .type(Long.class) + .build(); + } + + public static SqlUpdateStatement update(final SqlSetup setup, final Query query, final Map values) { + final Map updates = new HashMap<>(); + for (final Map.Entry e : values.entrySet()) { + final Path path = (Path) e.getKey(); + final Object value = e.getValue(); + final SqlPropertyMetadata p = setup.metadata().properties().get(path.toString()); + updates.put(p.mapping().name(), ImmutableSqlConstantExpression.builder() + .sql(":" + p.mapping().name()) + .type(p.type()) + .value(value) + .target(p) + .build()); + } + return ImmutableSqlUpdateStatement.builder() + .table(setup.metadata().table()) + .filter(compileWhereClause(setup, query.filter())) + .updates(updates) + .type(Long.class) + .build(); + } + + public static SqlSaveStatement save(final SqlSetup setup, final List entities) { + final Class type = setup.metadata().type(); + if (!(setup.metadata().key().metadata().isKeyDefined() && setup.metadata().key().metadata().isExpression())) { + throw new SqlException("Update using objects requires a simple key to be defined"); + } + + final List> values = new ArrayList<>(); + for (final Object o : entities) { + if (!type.isAssignableFrom(o.getClass())) { + throw new SqlException(String.format("Incompatible save() type. Expected %s found %s", + type.getSimpleName(), o.getClass().getSimpleName())); + } + } + return ImmutableSqlSaveStatement.builder() + .table(setup.metadata().table()) + .key(setup.metadata().key().metadata().keys().get(0).toString()) + .columns(new TreeSet<>(setup.metadata().columns().keySet())) + .properties(toPropertyMap(setup, entities)) + .type(Long.class) + .build(); + } + + private static Optional compileWhereClause(final SqlSetup setup, final Optional filter) { + if (filter.isPresent()) { + if (!(filter.get() instanceof Call)) { + throw new SqlException("Filter expression must be a call"); + } + final SqlQueryVisitor visitor = new SqlQueryVisitor(setup); + return Optional.of(visitor.call((Call) filter.get())); + } + return Optional.empty(); + + } + + private static List> toPropertyMap(final SqlSetup setup, final List entities) { + final Class type = setup.metadata().type(); + final List> values = new ArrayList<>(); + for (final Object o : entities) { + // Sanity check that all the objects in the list match the metadata type + if (!type.isAssignableFrom(o.getClass())) { + throw new SqlException(String.format("Incompatible insert() type. Expected %s found %s", + type.getSimpleName(), o.getClass().getSimpleName())); + } + final Map row = new HashMap<>(); + for (final SqlPropertyMetadata p : setup.metadata().properties().values()) { + final Object value = p.extractor().extract(o); + row.put(p.mapping().name(), ImmutableSqlConstantExpression.builder() + .sql(p.mapping().name()) + .type(p.type()) + .value(value) + .target(p) + .build()); + } + values.add(row); + } + return values; + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlConstantExpression.java similarity index 52% rename from criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java rename to criteria/sql/src/org/immutables/criteria/sql/compiler/SqlConstantExpression.java index 03dcec90f..9d80be815 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/note/NoteSetup.java +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlConstantExpression.java @@ -13,21 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.immutables.criteria.sql.note; +package org.immutables.criteria.sql.compiler; -import org.immutables.criteria.sql.SQLBackend; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; +import org.immutables.value.Value; -import javax.sql.DataSource; +@Value.Immutable +public interface SqlConstantExpression extends SqlExpression { -public class NoteSetup { - public static SQLBackend backend(final DataSource datasource) { - return SQLBackend.of(setup(datasource)); - } + SqlPropertyMetadata target(); - public static SQLSetup setup(final DataSource datasource) { - final SQLSetup setup = SQLSetup.of(datasource, SQLTypeMetadata.of(Note.class)); - return setup; - } + Object value(); } diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCountStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCountStatement.java new file mode 100644 index 000000000..06ca42a12 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlCountStatement.java @@ -0,0 +1,39 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; + +@Value.Immutable +public interface SqlCountStatement extends SqlStatement { + Optional distinct(); + + String qualifier(); + + OptionalLong limit(); + + OptionalLong offset(); + + Set columns(); + + Optional ordering(); + + Optional filter(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlDeleteStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlDeleteStatement.java new file mode 100644 index 000000000..9f618c1d9 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlDeleteStatement.java @@ -0,0 +1,25 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Optional; + +@Value.Immutable +public interface SqlDeleteStatement extends SqlStatement { + Optional filter(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlExpression.java new file mode 100644 index 000000000..aa34e1540 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlExpression.java @@ -0,0 +1,20 @@ +/* + * 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.compiler; + +public interface SqlExpression extends SqlNode { + String sql(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlFilterExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlFilterExpression.java new file mode 100644 index 000000000..90d37df71 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlFilterExpression.java @@ -0,0 +1,25 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Map; + +@Value.Immutable +public interface SqlFilterExpression extends SqlExpression { + Map parameters(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlInsertStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlInsertStatement.java new file mode 100644 index 000000000..2815a3ca2 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlInsertStatement.java @@ -0,0 +1,29 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + +@Value.Immutable +public interface SqlInsertStatement extends SqlStatement { + SortedSet columns(); + + List> values(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNameExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNameExpression.java new file mode 100644 index 000000000..34e8209b7 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNameExpression.java @@ -0,0 +1,24 @@ +/* + * 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.compiler; + +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; +import org.immutables.value.Value; + +@Value.Immutable +public interface SqlNameExpression extends SqlExpression { + SqlPropertyMetadata metadata(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNode.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNode.java new file mode 100644 index 000000000..c8fdb1b29 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlNode.java @@ -0,0 +1,20 @@ +/* + * 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.compiler; + +public interface SqlNode { + Class type(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlPathNaming.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlPathNaming.java new file mode 100644 index 000000000..61c120ff9 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlPathNaming.java @@ -0,0 +1,27 @@ +/* + * 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.compiler; + +import org.immutables.criteria.backend.JavaBeanNaming; +import org.immutables.criteria.expression.Path; + +public class SqlPathNaming extends JavaBeanNaming { + @Override + public String name(Path path) { + String name = super.name(path); + return "`" + name.replaceAll("\\.", "`.`") + "`"; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlQueryVisitor.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlQueryVisitor.java new file mode 100644 index 000000000..bf5049840 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlQueryVisitor.java @@ -0,0 +1,139 @@ +/* + * 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.compiler; + +import com.google.common.reflect.TypeToken; +import org.immutables.criteria.expression.*; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public class SqlQueryVisitor { + private final Map parameters = new HashMap<>(); + private final SqlSetup setup; + + public SqlQueryVisitor(final SqlSetup setup) { + this.setup = setup; + } + + private static SqlFilterExpression unary(final Call call) { + return null; + } + + private static SqlFilterExpression ternary(final Call call) { + return null; + } + + public SqlFilterExpression call(final Call call) { + if (call.operator().arity() == Operator.Arity.UNARY) { + return unary(call); + } + if (call.operator().arity() == Operator.Arity.BINARY) { + return binary(call); + } + if (call.operator().arity() == Operator.Arity.TERNARY) { + return ternary(call); + } + throw new SqlException("Invalid operator arity " + call.operator()); + } + + public SqlConstantExpression constant(final SqlNameExpression target, final Constant constant) { + final String params = constant.values().stream().map(e -> { + final String name = String.format(":param_%d", parameters.size()); + final SqlConstantExpression result = ImmutableSqlConstantExpression.builder() + .sql(name) + .target(target.metadata()) + .value(e) + .type(TypeToken.of(constant.returnType()).getRawType()) + .build(); + parameters.put(name, result); + return name; + }).collect(Collectors.joining(",")); + final SqlConstantExpression result = ImmutableSqlConstantExpression.builder() + .sql(params) + .target(target.metadata()) + .value(constant.value()) + .type(TypeToken.of(constant.returnType()).getRawType()) + .build(); + return result; + } + + public SqlNameExpression path(final Path path) { + final SqlPropertyMetadata meta = setup.metadata() + .properties() + .get(path.toString()); + return ImmutableSqlNameExpression.builder() + // TODO: Column aliases + .sql("`" + meta.mapping().name() + "`") + .metadata(meta) + .type(meta.mapping().type()) + .build(); + } + + private SqlFilterExpression logical(final Call call) { + final Operator op = call.operator(); + final List args = call.arguments(); + + if (!(args.get(0) instanceof Call)) { + throw new SqlException("left hand side of logical expression must be a call"); + } + if (!(args.get(1) instanceof Call)) { + throw new SqlException("right hand side of logical expression must be a call"); + } + final String sql = setup.dialect().logical((Operators) op, + call((Call) args.get(0)), + call((Call) args.get(1))); + return ImmutableSqlFilterExpression.builder() + .sql(sql) + .parameters(parameters) + .type(TypeToken.of(args.get(0).returnType()).getRawType()) + .build(); + + } + + private SqlFilterExpression binary(final Call call) { + final Operator op = call.operator(); + final List args = call.arguments(); + if (args.size() != 2) { + throw new SqlException("binary expression expects exactly 2 args"); + } + if (op == Operators.AND || op == Operators.OR) { + return logical(call); + } + if (!(args.get(0) instanceof Path)) { + throw new SqlException("left hand side of comparison should be a path"); + } + if (!(args.get(1) instanceof Constant)) { + throw new SqlException("right hand side of comparison should be a constant"); + } + final Path left = (Path) args.get(0); + final Constant right = (Constant) args.get(1); + final SqlNameExpression target = path(left); + final Optional operator = setup.dialect().binary(op, target, constant(target, right)); + return operator.map(s -> ImmutableSqlFilterExpression.builder() + .sql(s) + .parameters(parameters) + .type(TypeToken.of(left.returnType()).getRawType()) + .build()).orElseThrow(() -> + new SqlException("Unhandled binary operator " + op)); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSaveStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSaveStatement.java new file mode 100644 index 000000000..7b19a2225 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSaveStatement.java @@ -0,0 +1,31 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + +@Value.Immutable +public interface SqlSaveStatement extends SqlStatement { + String key(); + + SortedSet columns(); + + List> properties(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSelectStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSelectStatement.java new file mode 100644 index 000000000..32539dde1 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSelectStatement.java @@ -0,0 +1,37 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.SortedSet; + +@Value.Immutable +public interface SqlSelectStatement extends SqlStatement { + Optional distinct(); + + OptionalLong limit(); + + OptionalLong offset(); + + Optional filter(); + + Optional ordering(); + + SortedSet columns(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSortExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSortExpression.java new file mode 100644 index 000000000..e1fbafef6 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlSortExpression.java @@ -0,0 +1,22 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +@Value.Immutable +public interface SqlSortExpression extends SqlExpression { +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlStatement.java new file mode 100644 index 000000000..c10b3a089 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlStatement.java @@ -0,0 +1,20 @@ +/* + * 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.compiler; + +public interface SqlStatement extends SqlNode { + String table(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlUpdateStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlUpdateStatement.java new file mode 100644 index 000000000..e05ebd6de --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/compiler/SqlUpdateStatement.java @@ -0,0 +1,28 @@ +/* + * 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.compiler; + +import org.immutables.value.Value; + +import java.util.Map; +import java.util.Optional; + +@Value.Immutable +public interface SqlUpdateStatement extends SqlStatement { + Map updates(); + + Optional filter(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java index 27a7defc0..aa38209f7 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetcher.java @@ -19,5 +19,5 @@ import java.sql.SQLException; public interface ColumnFetcher { - T apply(ResultSet row, int index) throws SQLException; + T apply(ResultSet row, String name) throws SQLException; } diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java index a1562195a..6ca171e40 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnFetchers.java @@ -21,33 +21,33 @@ import java.util.UUID; public class ColumnFetchers { - private static final TypeKeyHashMap> FETCHERS; + private static final TypeKeyHashMap> FETCHERS; - static { - FETCHERS = new TypeKeyHashMap<>(); - register(boolean.class, ResultSet::getBoolean); - register(byte.class, ResultSet::getByte); - register(short.class, ResultSet::getShort); - register(int.class, ResultSet::getInt); - register(long.class, ResultSet::getLong); - register(float.class, ResultSet::getFloat); - register(double.class, ResultSet::getDouble); - register(Boolean.class, ResultSet::getBoolean); - register(Byte.class, ResultSet::getByte); - register(Short.class, ResultSet::getShort); - register(Integer.class, ResultSet::getInt); - register(Long.class, ResultSet::getLong); - register(Float.class, ResultSet::getFloat); - register(Double.class, ResultSet::getDouble); - register(String.class, ResultSet::getString); - register(UUID.class, (r, i) -> UUID.fromString(r.getString(i))); - } + static { + FETCHERS = new TypeKeyHashMap<>(); + register(boolean.class, ResultSet::getBoolean); + register(byte.class, ResultSet::getByte); + register(short.class, ResultSet::getShort); + register(int.class, ResultSet::getInt); + register(long.class, ResultSet::getLong); + register(float.class, ResultSet::getFloat); + register(double.class, ResultSet::getDouble); + register(Boolean.class, ResultSet::getBoolean); + register(Byte.class, ResultSet::getByte); + register(Short.class, ResultSet::getShort); + register(Integer.class, ResultSet::getInt); + register(Long.class, ResultSet::getLong); + register(Float.class, ResultSet::getFloat); + register(Double.class, ResultSet::getDouble); + register(String.class, ResultSet::getString); + register(UUID.class, (r, i) -> UUID.fromString(r.getString(i))); + } - public static void register(final Class type, final ColumnFetcher fetcher) { - FETCHERS.put(type, fetcher); - } + public static void register(final Class type, final ColumnFetcher fetcher) { + FETCHERS.put(type, fetcher); + } - public static ColumnFetcher get(final Class type) { - return FETCHERS.get(type); - } + public static ColumnFetcher get(final Class type) { + return FETCHERS.get(type); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java index cc75dce78..80f40c1de 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/ColumnMapping.java @@ -19,9 +19,9 @@ @Value.Immutable public interface ColumnMapping { - String name(); + String name(); - Class type(); + Class type(); - ColumnFetcher fetcher(); + ColumnFetcher fetcher(); } diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java index a9f42e6f2..7f494c6ef 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMapper.java @@ -20,5 +20,5 @@ @FunctionalInterface public interface RowMapper { - T map(ResultSet row) throws SQLException; + T map(ResultSet row) throws SQLException; } diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java index 0922e5854..cd57e62f8 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/RowMappers.java @@ -15,74 +15,41 @@ */ package org.immutables.criteria.sql.conversion; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; -import org.immutables.criteria.sql.reflection.SQLPropertyMetadata; -import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; import org.immutables.criteria.sql.util.TypeKeyHashMap; -import java.sql.ResultSetMetaData; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - public class RowMappers { - private static final ObjectMapper MAPPER; - private static final TypeKeyHashMap> MAPPER_CACHE; - - static { - MAPPER = new ObjectMapper() - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()) - .registerModule(new ParameterNamesModule()); - MAPPER_CACHE = new TypeKeyHashMap<>(); - register(String.class, r -> r.getString(1)); - register(byte.class, r -> r.getByte(1)); - register(Byte.class, r -> r.getByte(1)); - register(short.class, r -> r.getShort(1)); - register(Short.class, r -> r.getShort(1)); - register(int.class, r -> r.getInt(1)); - register(Integer.class, r -> r.getInt(1)); - register(long.class, r -> r.getLong(1)); - register(Long.class, r -> r.getLong(1)); - register(float.class, r -> r.getFloat(1)); - register(Float.class, r -> r.getFloat(1)); - register(double.class, r -> r.getDouble(1)); - register(Double.class, r -> r.getDouble(1)); - } - - public static void register(final Class type, final RowMapper mapper) { - MAPPER_CACHE.put(type, mapper); - } - - @SuppressWarnings("unchecked") - public static RowMapper get(final Class clazz) { - return (RowMapper) MAPPER_CACHE.get(clazz); - } - - @SuppressWarnings("unchecked") - public static RowMapper get(final SQLTypeMetadata metadata) { - return (RowMapper) MAPPER_CACHE.computeIfAbsent(metadata.type(), t -> newRowMapper(metadata)); - } - @SuppressWarnings("unchecked") - private static RowMapper newRowMapper(final SQLTypeMetadata metadata) { - return row -> { - final Map data = new HashMap<>(); - final ResultSetMetaData rm = row.getMetaData(); - for (int i = 1; i <= rm.getColumnCount(); ++i) { - final String name = rm.getColumnLabel(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, i))); - } - } - return (T) MAPPER.convertValue(data, metadata.type()); - }; - } + private static final TypeKeyHashMap> MAPPER_CACHE; + + static { + MAPPER_CACHE = new TypeKeyHashMap<>(); + register(String.class, r -> r.getString(1)); + register(byte.class, r -> r.getByte(1)); + register(Byte.class, r -> r.getByte(1)); + register(short.class, r -> r.getShort(1)); + register(Short.class, r -> r.getShort(1)); + register(int.class, r -> r.getInt(1)); + register(Integer.class, r -> r.getInt(1)); + register(long.class, r -> r.getLong(1)); + register(Long.class, r -> r.getLong(1)); + register(float.class, r -> r.getFloat(1)); + register(Float.class, r -> r.getFloat(1)); + register(double.class, r -> r.getDouble(1)); + register(Double.class, r -> r.getDouble(1)); + } + + public static void register(final Class type, final RowMapper mapper) { + MAPPER_CACHE.put(type, mapper); + } + + @SuppressWarnings("unchecked") + public static RowMapper get(final Class clazz) { + return (RowMapper) MAPPER_CACHE.get(clazz); + } + + @SuppressWarnings("unchecked") + public static RowMapper get(final SqlTypeMetadata metadata) { + return (RowMapper) MAPPER_CACHE.get(metadata.type()); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java index 9180c4cb9..ddf5ea189 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverter.java @@ -17,5 +17,5 @@ @FunctionalInterface public interface TypeConverter { - T apply(final F value); -} \ No newline at end of file + T apply(final F value); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java index 9c6dbe398..1951f1d89 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java +++ b/criteria/sql/src/org/immutables/criteria/sql/conversion/TypeConverters.java @@ -15,7 +15,7 @@ */ package org.immutables.criteria.sql.conversion; -import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.SqlException; import java.time.Instant; import java.util.IdentityHashMap; @@ -23,200 +23,200 @@ import java.util.UUID; public final class TypeConverters { - private static final Map>> CONVERTERS; - - static { - CONVERTERS = new IdentityHashMap<>(); - register(boolean.class, boolean.class, v -> v); - register(boolean.class, Boolean.class, v -> v); - register(boolean.class, byte.class, v -> (byte) (v ? 1 : 0)); - register(boolean.class, Byte.class, v -> (byte) (v ? 1 : 0)); - register(boolean.class, short.class, v -> (short) (v ? 1 : 0)); - register(boolean.class, Short.class, v -> (short) (v ? 1 : 0)); - register(boolean.class, int.class, v -> (v ? 1 : 0)); - register(boolean.class, Integer.class, v -> (v ? 1 : 0)); - register(boolean.class, long.class, v -> (long) (v ? 1 : 0)); - register(boolean.class, Long.class, v -> (long) (v ? 1 : 0)); - register(boolean.class, String.class, v -> (v ? "true" : "false")); - - register(Boolean.class, boolean.class, v -> v); - register(Boolean.class, Boolean.class, v -> v); - register(Boolean.class, byte.class, v -> (byte) (v ? 1 : 0)); - register(Boolean.class, Byte.class, v -> (byte) (v ? 1 : 0)); - register(Boolean.class, short.class, v -> (short) (v ? 1 : 0)); - register(Boolean.class, Short.class, v -> (short) (v ? 1 : 0)); - register(Boolean.class, int.class, v -> (v ? 1 : 0)); - register(Boolean.class, Integer.class, v -> (v ? 1 : 0)); - register(Boolean.class, long.class, v -> (long) (v ? 1 : 0)); - register(Boolean.class, Long.class, v -> (long) (v ? 1 : 0)); - register(Boolean.class, String.class, v -> (v ? "true" : "false")); - - register(byte.class, boolean.class, v -> v == 0); - register(byte.class, Boolean.class, v -> v == 0); - register(byte.class, byte.class, v -> v); - register(byte.class, Byte.class, v -> v); - register(byte.class, short.class, Byte::shortValue); - register(byte.class, Short.class, Byte::shortValue); - register(byte.class, int.class, Byte::intValue); - register(byte.class, Integer.class, Byte::intValue); - register(byte.class, long.class, Byte::longValue); - register(byte.class, Long.class, Byte::longValue); - register(byte.class, float.class, Byte::floatValue); - register(byte.class, Float.class, Byte::floatValue); - register(byte.class, double.class, Byte::doubleValue); - register(byte.class, Double.class, Byte::doubleValue); - register(byte.class, String.class, String::valueOf); - - register(Byte.class, boolean.class, v -> v == 0); - register(Byte.class, Boolean.class, v -> v == 0); - register(Byte.class, byte.class, v -> v); - register(Byte.class, Byte.class, v -> v); - register(Byte.class, short.class, Byte::shortValue); - register(Byte.class, Short.class, Byte::shortValue); - register(Byte.class, int.class, Byte::intValue); - register(Byte.class, Integer.class, Byte::intValue); - register(Byte.class, long.class, Byte::longValue); - register(Byte.class, Long.class, Byte::longValue); - register(Byte.class, float.class, Byte::floatValue); - register(Byte.class, Float.class, Byte::floatValue); - register(Byte.class, double.class, Byte::doubleValue); - register(Byte.class, Double.class, Byte::doubleValue); - register(Byte.class, String.class, String::valueOf); - - register(short.class, boolean.class, v -> v == 0); - register(short.class, Boolean.class, v -> v == 0); - register(short.class, byte.class, Short::byteValue); - register(short.class, Byte.class, Short::byteValue); - register(short.class, short.class, v -> v); - register(short.class, Short.class, v -> v); - register(short.class, int.class, Short::intValue); - register(short.class, Integer.class, Short::intValue); - register(short.class, long.class, Short::longValue); - register(short.class, Long.class, Short::longValue); - register(short.class, float.class, Short::floatValue); - register(short.class, Float.class, Short::floatValue); - register(short.class, double.class, Short::doubleValue); - register(short.class, Double.class, Short::doubleValue); - register(short.class, String.class, String::valueOf); - - register(Short.class, boolean.class, v -> v == 0); - register(Short.class, Boolean.class, v -> v == 0); - register(Short.class, byte.class, Short::byteValue); - register(Short.class, Byte.class, Short::byteValue); - register(Short.class, short.class, v -> v); - register(Short.class, Short.class, v -> v); - register(Short.class, int.class, Short::intValue); - register(Short.class, Integer.class, Short::intValue); - register(Short.class, long.class, Short::longValue); - register(Short.class, Long.class, Short::longValue); - register(Short.class, float.class, Short::floatValue); - register(Short.class, Float.class, Short::floatValue); - register(Short.class, double.class, Short::doubleValue); - register(Short.class, Double.class, Short::doubleValue); - register(Short.class, String.class, String::valueOf); - - register(int.class, boolean.class, v -> v == 0); - register(int.class, Boolean.class, v -> v == 0); - register(int.class, byte.class, Integer::byteValue); - register(int.class, Byte.class, Integer::byteValue); - register(int.class, short.class, Integer::shortValue); - register(int.class, Short.class, Integer::shortValue); - register(int.class, int.class, v -> v); - register(int.class, Integer.class, v -> v); - register(int.class, long.class, Integer::longValue); - register(int.class, Long.class, Integer::longValue); - register(int.class, float.class, Integer::floatValue); - register(int.class, Float.class, Integer::floatValue); - register(int.class, double.class, Integer::doubleValue); - register(int.class, Double.class, Integer::doubleValue); - register(int.class, String.class, String::valueOf); - - register(Integer.class, boolean.class, v -> v == 0); - register(Integer.class, Boolean.class, v -> v == 0); - register(Integer.class, byte.class, Integer::byteValue); - register(Integer.class, Byte.class, Integer::byteValue); - register(Integer.class, short.class, Integer::shortValue); - register(Integer.class, Short.class, Integer::shortValue); - register(Integer.class, int.class, v -> v); - register(Integer.class, Integer.class, v -> v); - register(Integer.class, long.class, Integer::longValue); - register(Integer.class, Long.class, Integer::longValue); - register(Integer.class, float.class, Integer::floatValue); - register(Integer.class, Float.class, Integer::floatValue); - register(Integer.class, double.class, Integer::doubleValue); - register(Integer.class, Double.class, Integer::doubleValue); - register(Integer.class, String.class, String::valueOf); - - register(long.class, boolean.class, v -> v == 0); - register(long.class, Boolean.class, v -> v == 0); - register(long.class, byte.class, Long::byteValue); - register(long.class, Byte.class, Long::byteValue); - register(long.class, short.class, Long::shortValue); - register(long.class, Short.class, Long::shortValue); - register(long.class, int.class, Long::intValue); - register(long.class, Integer.class, Long::intValue); - register(long.class, long.class, v -> v); - register(long.class, Long.class, v -> v); - register(long.class, float.class, Long::floatValue); - register(long.class, Float.class, Long::floatValue); - register(long.class, double.class, Long::doubleValue); - register(long.class, Double.class, Long::doubleValue); - register(long.class, String.class, String::valueOf); - - register(Long.class, boolean.class, v -> v == 0); - register(Long.class, Boolean.class, v -> v == 0); - register(Long.class, byte.class, Long::byteValue); - register(Long.class, Byte.class, Long::byteValue); - register(Long.class, short.class, Long::shortValue); - register(Long.class, Short.class, Long::shortValue); - register(Long.class, int.class, Long::intValue); - register(Long.class, Integer.class, Long::intValue); - register(Long.class, long.class, v -> v); - register(Long.class, Long.class, v -> v); - register(Long.class, float.class, Long::floatValue); - register(Long.class, Float.class, Long::floatValue); - register(Long.class, double.class, Long::doubleValue); - register(Long.class, Double.class, Long::doubleValue); - register(Long.class, String.class, String::valueOf); - - register(String.class, boolean.class, v -> v != null && v.equalsIgnoreCase("true")); - register(String.class, Boolean.class, v -> v != null && v.equalsIgnoreCase("true")); - register(String.class, byte.class, Byte::valueOf); - register(String.class, Byte.class, Byte::valueOf); - register(String.class, short.class, Short::valueOf); - register(String.class, Short.class, Short::valueOf); - register(String.class, int.class, Integer::valueOf); - register(String.class, Integer.class, Integer::valueOf); - register(String.class, long.class, Long::valueOf); - register(String.class, Long.class, Long::valueOf); - register(String.class, float.class, Float::valueOf); - register(String.class, Float.class, Float::valueOf); - register(String.class, double.class, Double::valueOf); - register(String.class, Double.class, Double::valueOf); - register(String.class, String.class, v -> v); - - register(UUID.class, String.class, UUID::toString); - register(String.class, UUID.class, UUID::fromString); - - register(Instant.class, long.class, Instant::toEpochMilli); - register(long.class, Instant.class, Instant::ofEpochMilli); + private static final Map>> CONVERTERS; + + static { + CONVERTERS = new IdentityHashMap<>(); + register(boolean.class, boolean.class, v -> v); + register(boolean.class, Boolean.class, v -> v); + register(boolean.class, byte.class, v -> (byte) (v ? 1 : 0)); + register(boolean.class, Byte.class, v -> (byte) (v ? 1 : 0)); + register(boolean.class, short.class, v -> (short) (v ? 1 : 0)); + register(boolean.class, Short.class, v -> (short) (v ? 1 : 0)); + register(boolean.class, int.class, v -> (v ? 1 : 0)); + register(boolean.class, Integer.class, v -> (v ? 1 : 0)); + register(boolean.class, long.class, v -> (long) (v ? 1 : 0)); + register(boolean.class, Long.class, v -> (long) (v ? 1 : 0)); + register(boolean.class, String.class, v -> (v ? "true" : "false")); + + register(Boolean.class, boolean.class, v -> v); + register(Boolean.class, Boolean.class, v -> v); + register(Boolean.class, byte.class, v -> (byte) (v ? 1 : 0)); + register(Boolean.class, Byte.class, v -> (byte) (v ? 1 : 0)); + register(Boolean.class, short.class, v -> (short) (v ? 1 : 0)); + register(Boolean.class, Short.class, v -> (short) (v ? 1 : 0)); + register(Boolean.class, int.class, v -> (v ? 1 : 0)); + register(Boolean.class, Integer.class, v -> (v ? 1 : 0)); + register(Boolean.class, long.class, v -> (long) (v ? 1 : 0)); + register(Boolean.class, Long.class, v -> (long) (v ? 1 : 0)); + register(Boolean.class, String.class, v -> (v ? "true" : "false")); + + register(byte.class, boolean.class, v -> v == 0); + register(byte.class, Boolean.class, v -> v == 0); + register(byte.class, byte.class, v -> v); + register(byte.class, Byte.class, v -> v); + register(byte.class, short.class, Byte::shortValue); + register(byte.class, Short.class, Byte::shortValue); + register(byte.class, int.class, Byte::intValue); + register(byte.class, Integer.class, Byte::intValue); + register(byte.class, long.class, Byte::longValue); + register(byte.class, Long.class, Byte::longValue); + register(byte.class, float.class, Byte::floatValue); + register(byte.class, Float.class, Byte::floatValue); + register(byte.class, double.class, Byte::doubleValue); + register(byte.class, Double.class, Byte::doubleValue); + register(byte.class, String.class, String::valueOf); + + register(Byte.class, boolean.class, v -> v == 0); + register(Byte.class, Boolean.class, v -> v == 0); + register(Byte.class, byte.class, v -> v); + register(Byte.class, Byte.class, v -> v); + register(Byte.class, short.class, Byte::shortValue); + register(Byte.class, Short.class, Byte::shortValue); + register(Byte.class, int.class, Byte::intValue); + register(Byte.class, Integer.class, Byte::intValue); + register(Byte.class, long.class, Byte::longValue); + register(Byte.class, Long.class, Byte::longValue); + register(Byte.class, float.class, Byte::floatValue); + register(Byte.class, Float.class, Byte::floatValue); + register(Byte.class, double.class, Byte::doubleValue); + register(Byte.class, Double.class, Byte::doubleValue); + register(Byte.class, String.class, String::valueOf); + + register(short.class, boolean.class, v -> v == 0); + register(short.class, Boolean.class, v -> v == 0); + register(short.class, byte.class, Short::byteValue); + register(short.class, Byte.class, Short::byteValue); + register(short.class, short.class, v -> v); + register(short.class, Short.class, v -> v); + register(short.class, int.class, Short::intValue); + register(short.class, Integer.class, Short::intValue); + register(short.class, long.class, Short::longValue); + register(short.class, Long.class, Short::longValue); + register(short.class, float.class, Short::floatValue); + register(short.class, Float.class, Short::floatValue); + register(short.class, double.class, Short::doubleValue); + register(short.class, Double.class, Short::doubleValue); + register(short.class, String.class, String::valueOf); + + register(Short.class, boolean.class, v -> v == 0); + register(Short.class, Boolean.class, v -> v == 0); + register(Short.class, byte.class, Short::byteValue); + register(Short.class, Byte.class, Short::byteValue); + register(Short.class, short.class, v -> v); + register(Short.class, Short.class, v -> v); + register(Short.class, int.class, Short::intValue); + register(Short.class, Integer.class, Short::intValue); + register(Short.class, long.class, Short::longValue); + register(Short.class, Long.class, Short::longValue); + register(Short.class, float.class, Short::floatValue); + register(Short.class, Float.class, Short::floatValue); + register(Short.class, double.class, Short::doubleValue); + register(Short.class, Double.class, Short::doubleValue); + register(Short.class, String.class, String::valueOf); + + register(int.class, boolean.class, v -> v == 0); + register(int.class, Boolean.class, v -> v == 0); + register(int.class, byte.class, Integer::byteValue); + register(int.class, Byte.class, Integer::byteValue); + register(int.class, short.class, Integer::shortValue); + register(int.class, Short.class, Integer::shortValue); + register(int.class, int.class, v -> v); + register(int.class, Integer.class, v -> v); + register(int.class, long.class, Integer::longValue); + register(int.class, Long.class, Integer::longValue); + register(int.class, float.class, Integer::floatValue); + register(int.class, Float.class, Integer::floatValue); + register(int.class, double.class, Integer::doubleValue); + register(int.class, Double.class, Integer::doubleValue); + register(int.class, String.class, String::valueOf); + + register(Integer.class, boolean.class, v -> v == 0); + register(Integer.class, Boolean.class, v -> v == 0); + register(Integer.class, byte.class, Integer::byteValue); + register(Integer.class, Byte.class, Integer::byteValue); + register(Integer.class, short.class, Integer::shortValue); + register(Integer.class, Short.class, Integer::shortValue); + register(Integer.class, int.class, v -> v); + register(Integer.class, Integer.class, v -> v); + register(Integer.class, long.class, Integer::longValue); + register(Integer.class, Long.class, Integer::longValue); + register(Integer.class, float.class, Integer::floatValue); + register(Integer.class, Float.class, Integer::floatValue); + register(Integer.class, double.class, Integer::doubleValue); + register(Integer.class, Double.class, Integer::doubleValue); + register(Integer.class, String.class, String::valueOf); + + register(long.class, boolean.class, v -> v == 0); + register(long.class, Boolean.class, v -> v == 0); + register(long.class, byte.class, Long::byteValue); + register(long.class, Byte.class, Long::byteValue); + register(long.class, short.class, Long::shortValue); + register(long.class, Short.class, Long::shortValue); + register(long.class, int.class, Long::intValue); + register(long.class, Integer.class, Long::intValue); + register(long.class, long.class, v -> v); + register(long.class, Long.class, v -> v); + register(long.class, float.class, Long::floatValue); + register(long.class, Float.class, Long::floatValue); + register(long.class, double.class, Long::doubleValue); + register(long.class, Double.class, Long::doubleValue); + register(long.class, String.class, String::valueOf); + + register(Long.class, boolean.class, v -> v == 0); + register(Long.class, Boolean.class, v -> v == 0); + register(Long.class, byte.class, Long::byteValue); + register(Long.class, Byte.class, Long::byteValue); + register(Long.class, short.class, Long::shortValue); + register(Long.class, Short.class, Long::shortValue); + register(Long.class, int.class, Long::intValue); + register(Long.class, Integer.class, Long::intValue); + register(Long.class, long.class, v -> v); + register(Long.class, Long.class, v -> v); + register(Long.class, float.class, Long::floatValue); + register(Long.class, Float.class, Long::floatValue); + register(Long.class, double.class, Long::doubleValue); + register(Long.class, Double.class, Long::doubleValue); + register(Long.class, String.class, String::valueOf); + + register(String.class, boolean.class, v -> v != null && v.equalsIgnoreCase("true")); + register(String.class, Boolean.class, v -> v != null && v.equalsIgnoreCase("true")); + register(String.class, byte.class, Byte::valueOf); + register(String.class, Byte.class, Byte::valueOf); + register(String.class, short.class, Short::valueOf); + register(String.class, Short.class, Short::valueOf); + register(String.class, int.class, Integer::valueOf); + register(String.class, Integer.class, Integer::valueOf); + register(String.class, long.class, Long::valueOf); + register(String.class, Long.class, Long::valueOf); + register(String.class, float.class, Float::valueOf); + register(String.class, Float.class, Float::valueOf); + register(String.class, double.class, Double::valueOf); + register(String.class, Double.class, Double::valueOf); + register(String.class, String.class, v -> v); + + register(UUID.class, String.class, UUID::toString); + register(String.class, UUID.class, UUID::fromString); + + register(Instant.class, long.class, Instant::toEpochMilli); + register(long.class, Instant.class, Instant::ofEpochMilli); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static T convert(final Class from, final Class to, final F value) { + final Map> map = CONVERTERS.get(from); + if (map == null) { + throw new SqlException(String.format("No type converters found from %s", from)); } - - @SuppressWarnings({"rawtypes", "unchecked"}) - public static T convert(final Class from, final Class to, final F value) { - final Map> map = CONVERTERS.get(from); - if (map == null) { - throw new SQLException(String.format("No type converters found from %s", from)); - } - final TypeConverter converter = (TypeConverter) map.get(to); - if (converter == null) { - throw new SQLException(String.format("No type converters found from %s to %s", from, to)); - } - return converter.apply(value); + final TypeConverter converter = (TypeConverter) map.get(to); + if (converter == null) { + throw new SqlException(String.format("No type converters found from %s to %s", from, to)); } + return converter.apply(value); + } - public static void register(final Class from, final Class to, final TypeConverter converter) { - CONVERTERS.computeIfAbsent(from, key -> new IdentityHashMap<>()).put(to, converter); - } + public static void register(final Class from, final Class to, final TypeConverter converter) { + CONVERTERS.computeIfAbsent(from, key -> new IdentityHashMap<>()).put(to, converter); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java index 3abb51ec1..bc5260478 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQL92Dialect.java @@ -20,7 +20,7 @@ import org.immutables.criteria.expression.Operator; import org.immutables.criteria.expression.Operators; import org.immutables.criteria.expression.StringOperators; -import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.SqlException; import org.immutables.criteria.sql.compiler.*; import java.util.Optional; @@ -30,196 +30,195 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -public class SQL92Dialect implements SQLDialect { - private final PathNaming naming = new SQLPathNaming(); - - @Override - public Optional limit(final OptionalLong limit, final OptionalLong offset) { - if (limit.isPresent() || offset.isPresent()) { - final String l = limit.isPresent() ? String.format("LIMIT %d", limit.getAsLong()) : ""; - final String o = offset.isPresent() ? String.format("OFFSET %d", offset.getAsLong()) : ""; - return Optional.of(String.format("%s %s", l, o).trim()); - } - return Optional.empty(); - } +public class SQL92Dialect implements SqlDialect { + private final PathNaming naming = new SqlPathNaming(); - @Override - public String logical(final Operators op, final SQLExpression left, final SQLExpression right) { - switch ((Operators) op) { - case AND: - return String.format("(%s)AND(%s)", left.sql(), right.sql()); - case OR: - return String.format("(%s)OR(%s)", left.sql(), right.sql()); - } - throw new SQLException("Invalid logical operator " + op.name()); + @Override + public Optional limit(final OptionalLong limit, final OptionalLong offset) { + if (limit.isPresent() || offset.isPresent()) { + final String l = limit.isPresent() ? String.format("LIMIT %d", limit.getAsLong()) : ""; + final String o = offset.isPresent() ? String.format("OFFSET %d", offset.getAsLong()) : ""; + return Optional.of(String.format("%s %s", l, o).trim()); } - - @Override - public Optional equality(final Operators op, final SQLExpression left, final SQLExpression right) { - if (!(left instanceof SQLNameExpression)) { - throw new SQLException("left hand side of comparison should be a path/name"); - } - if (!(right instanceof SQLConstantExpression)) { - throw new SQLException("right hand side of comparison should be a constant"); - } - switch (op) { - case EQUAL: - return Optional.of(String.format("(%s)=(%s)", left.sql(), right.sql())); - case NOT_EQUAL: - return Optional.of(String.format("(%s)!=(%s)", left.sql(), right.sql())); - case IN: - return Optional.of(String.format("(%s)IN(%s)", left.sql(), right.sql())); - case NOT_IN: - return Optional.of(String.format("(%s)NOT IN(%s)", left.sql(), right.sql())); - } - throw new SQLException("Invalid logical operator " + op.name()); + return Optional.empty(); + } + + @Override + public String logical(final Operators op, final SqlExpression left, final SqlExpression right) { + switch (op) { + case AND: + return String.format("(%s)AND(%s)", left.sql(), right.sql()); + case OR: + return String.format("(%s)OR(%s)", left.sql(), right.sql()); } + throw new SqlException("Invalid logical operator " + op.name()); + } - @Override - public Optional comparison(final ComparableOperators op, final SQLExpression left, final SQLExpression right) { - switch (op) { - case LESS_THAN: - return Optional.of(String.format("(%s)<(%s)", left.sql(), right.sql())); - case LESS_THAN_OR_EQUAL: - return Optional.of(String.format("(%s)<=(%s)", left.sql(), right.sql())); - case GREATER_THAN: - return Optional.of(String.format("(%s)>(%s)", left.sql(), right.sql())); - case GREATER_THAN_OR_EQUAL: - return Optional.of(String.format("(%s)>=(%s)", left.sql(), right.sql())); - } - throw new SQLException("Invalid comparison operator " + op.name()); + @Override + public Optional equality(final Operators op, final SqlExpression left, final SqlExpression right) { + if (!(left instanceof SqlNameExpression)) { + throw new SqlException("left hand side of comparison should be a path/name"); } - - @Override - public Optional regex(final Operator op, final SQLExpression left, final Pattern right) { - // TODO: Verify that the regex does not contain other patterns - final String converted = right.pattern() - .replace(".*", "%") - .replace(".?", "%") - .replace("'", "''") - .replace('?', '%') - .replace('.', '_'); - switch ((StringOperators) op) { - case MATCHES: - return Optional.of(String.format("(%s)LIKE('%s')", left.sql(), converted)); - } - throw new SQLException("Invalid regex operator " + op.name()); + if (!(right instanceof SqlConstantExpression)) { + throw new SqlException("right hand side of comparison should be a constant"); } - - @Override - public Optional string(final StringOperators op, final SQLExpression left, final SQLExpression right) { - switch (op) { - case HAS_LENGTH: - return Optional.of(String.format("LEN(%s)=(%s)", left.sql(), right.sql())); - case CONTAINS: - return Optional.of(String.format("(%s)LIKE(CONCAT(\"%%\",%s,\"%%\"))", left.sql(), right.sql())); - case ENDS_WITH: - return Optional.of(String.format("(%s)LIKE(CONCAT(\"%%\",%s))", left.sql(), right.sql())); - case STARTS_WITH: - return Optional.of(String.format("(%s)LIKE(CONCAT(%s,\"%%\"))", left.sql(), right.sql())); - case MATCHES: - final Object arg = ((SQLConstantExpression) right).value(); - assert arg instanceof Pattern : "Argument to regex() should be Pattern"; - return regex(op, left, (Pattern) arg); - } - throw new SQLException("Invalid string operator " + op.name()); + switch (op) { + case EQUAL: + return Optional.of(String.format("(%s)=(%s)", left.sql(), right.sql())); + case NOT_EQUAL: + return Optional.of(String.format("(%s)!=(%s)", left.sql(), right.sql())); + case IN: + return Optional.of(String.format("(%s)IN(%s)", left.sql(), right.sql())); + case NOT_IN: + return Optional.of(String.format("(%s)NOT IN(%s)", left.sql(), right.sql())); } - - @Override - public Optional binary(final Operator op, final SQLExpression left, final SQLExpression right) { - if (!(left instanceof SQLNameExpression)) { - throw new SQLException("left hand side of comparison should be a path/name"); - } - if (!(right instanceof SQLConstantExpression)) { - throw new SQLException("right hand side of comparison should be a constant"); - } - final SQLConstantExpression updated = ImmutableSQLConstantExpression - .builder() - .from((SQLConstantExpression) right) - .target(((SQLNameExpression) left).metadata()) - .build(); - if (op instanceof Operators) { - return equality((Operators) op, left, updated); - } - if (op instanceof StringOperators) { - return string((StringOperators) op, left, updated); - } - if (op instanceof ComparableOperators) { - return comparison((ComparableOperators) op, left, updated); - } - throw new SQLException("Unhandled operator: " + op.name()); + throw new SqlException("Invalid logical operator " + op.name()); + } + + @Override + public Optional comparison(final ComparableOperators op, final SqlExpression left, final SqlExpression right) { + switch (op) { + case LESS_THAN: + return Optional.of(String.format("(%s)<(%s)", left.sql(), right.sql())); + case LESS_THAN_OR_EQUAL: + return Optional.of(String.format("(%s)<=(%s)", left.sql(), right.sql())); + case GREATER_THAN: + return Optional.of(String.format("(%s)>(%s)", left.sql(), right.sql())); + case GREATER_THAN_OR_EQUAL: + return Optional.of(String.format("(%s)>=(%s)", left.sql(), right.sql())); } - - @Override - public PathNaming naming() { - return naming; + throw new SqlException("Invalid comparison operator " + op.name()); + } + + @Override + public Optional regex(final Operator op, final SqlExpression left, final Pattern right) { + // TODO: Verify that the regex does not contain other patterns + final String converted = right.pattern() + .replace(".*", "%") + .replace(".?", "%") + .replace("'", "''") + .replace('?', '%') + .replace('.', '_'); + if ((StringOperators) op == StringOperators.MATCHES) { + return Optional.of(String.format("(%s)LIKE('%s')", left.sql(), converted)); } - - @Override - public String count(final SQLCountStatement statement) { - return String.format("SELECT %s FROM `%s`%s%s %s", - statement.qualifier(), - statement.table(), - statement.filter().map(s -> " WHERE " + s.sql()).orElse(""), - statement.ordering().map(s -> " ORDER BY " + s).orElse(""), - limit(statement.limit(), statement.offset()).orElse("")) - .trim(); + throw new SqlException("Invalid regex operator " + op.name()); + } + + @Override + public Optional string(final StringOperators op, final SqlExpression left, final SqlExpression right) { + switch (op) { + case HAS_LENGTH: + return Optional.of(String.format("LEN(%s)=(%s)", left.sql(), right.sql())); + case CONTAINS: + return Optional.of(String.format("(%s)LIKE(CONCAT(\"%%\",%s,\"%%\"))", left.sql(), right.sql())); + case ENDS_WITH: + return Optional.of(String.format("(%s)LIKE(CONCAT(\"%%\",%s))", left.sql(), right.sql())); + case STARTS_WITH: + return Optional.of(String.format("(%s)LIKE(CONCAT(%s,\"%%\"))", left.sql(), right.sql())); + case MATCHES: + final Object arg = ((SqlConstantExpression) right).value(); + assert arg instanceof Pattern : "Argument to regex() should be Pattern"; + return regex(op, left, (Pattern) arg); } + throw new SqlException("Invalid string operator " + op.name()); + } - @Override - public String select(final SQLSelectStatement statement) { - return String.format("SELECT %s%s FROM `%s`%s%s %s", - statement.distinct().map(e -> e ? "DISTINCT " : "").orElse(""), - statement.columns().stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")), - statement.table(), - statement.filter().map(s -> " WHERE " + s.sql()).orElse(""), - statement.ordering().map(s -> " ORDER BY " + s).orElse(""), - limit(statement.limit(), statement.offset()).orElse("")) - .trim(); + @Override + public Optional binary(final Operator op, final SqlExpression left, final SqlExpression right) { + if (!(left instanceof SqlNameExpression)) { + throw new SqlException("left hand side of comparison should be a path/name"); } - - @Override - public String delete(final SQLDeleteStatement statement) { - // TODO: Sorting - return String.format("DELETE FROM `%s`%s", - statement.table(), - statement.filter().map(s -> " WHERE " + s.sql()).orElse("")) - .trim(); + if (!(right instanceof SqlConstantExpression)) { + throw new SqlException("right hand side of comparison should be a constant"); } - - @Override - public String insert(final SQLInsertStatement statement) { - return String.format("INSERT INTO %s (%s)\nVALUES\n%s", - statement.table(), - statement.columns().stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")), - "(" + statement.columns().stream().map(c -> "?").collect(Collectors.joining(",")) + ")") - .trim(); + final SqlConstantExpression updated = ImmutableSqlConstantExpression + .builder() + .from((SqlConstantExpression) right) + .target(((SqlNameExpression) left).metadata()) + .build(); + if (op instanceof Operators) { + return equality((Operators) op, left, updated); } - - @Override - public String update(final SQLUpdateStatement statement) { - return String.format("UPDATE `%s` SET %s%s", - statement.table(), - statement.updates() - .entrySet() - .stream().map(e -> String.format("`%s`=:%s", e.getKey(), e.getKey())) - .collect(Collectors.joining(",")), - statement.filter().map(s -> " WHERE " + s.sql()).orElse("")) - .trim(); + if (op instanceof StringOperators) { + return string((StringOperators) op, left, updated); } - - @Override - public String save(final SQLSaveStatement statement) { - final Set properties = new TreeSet<>(statement.columns()); - properties.remove(statement.key()); - return String.format("UPDATE `%s` SET %s WHERE %s=:%s", - statement.table(), - properties.stream() - .map(e -> String.format("`%s`=:%s", e, e)) - .collect(Collectors.joining(",")), - statement.key(), - statement.key() - ) - .trim(); + if (op instanceof ComparableOperators) { + return comparison((ComparableOperators) op, left, updated); } + throw new SqlException("Unhandled operator: " + op.name()); + } + + @Override + public PathNaming naming() { + return naming; + } + + @Override + public String count(final SqlCountStatement statement) { + return String.format("SELECT %s FROM `%s`%s%s %s", + statement.qualifier(), + statement.table(), + statement.filter().map(s -> " WHERE " + s.sql()).orElse(""), + statement.ordering().map(s -> " ORDER BY " + s).orElse(""), + limit(statement.limit(), statement.offset()).orElse("")) + .trim(); + } + + @Override + public String select(final SqlSelectStatement statement) { + return String.format("SELECT %s%s FROM `%s`%s%s %s", + statement.distinct().map(e -> e ? "DISTINCT " : "").orElse(""), + statement.columns().stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")), + statement.table(), + statement.filter().map(s -> " WHERE " + s.sql()).orElse(""), + statement.ordering().map(s -> " ORDER BY " + s).orElse(""), + limit(statement.limit(), statement.offset()).orElse("")) + .trim(); + } + + @Override + public String delete(final SqlDeleteStatement statement) { + // TODO: Sorting + return String.format("DELETE FROM `%s`%s", + statement.table(), + statement.filter().map(s -> " WHERE " + s.sql()).orElse("")) + .trim(); + } + + @Override + public String insert(final SqlInsertStatement statement) { + return String.format("INSERT INTO %s (%s)\nVALUES\n%s", + statement.table(), + statement.columns().stream().map(c -> String.format("`%s`", c)).collect(Collectors.joining(",")), + "(" + statement.columns().stream().map(c -> "?").collect(Collectors.joining(",")) + ")") + .trim(); + } + + @Override + public String update(final SqlUpdateStatement statement) { + return String.format("UPDATE `%s` SET %s%s", + statement.table(), + statement.updates() + .entrySet() + .stream().map(e -> String.format("`%s`=:%s", e.getKey(), e.getKey())) + .collect(Collectors.joining(",")), + statement.filter().map(s -> " WHERE " + s.sql()).orElse("")) + .trim(); + } + + @Override + public String save(final SqlSaveStatement statement) { + final Set properties = new TreeSet<>(statement.columns()); + properties.remove(statement.key()); + return String.format("UPDATE `%s` SET %s WHERE %s=:%s", + statement.table(), + properties.stream() + .map(e -> String.format("`%s`=:%s", e, e)) + .collect(Collectors.joining(",")), + statement.key(), + statement.key() + ) + .trim(); + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java index e2290fbab..d4ea0e36a 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java @@ -26,32 +26,32 @@ import java.util.OptionalLong; import java.util.regex.Pattern; -public interface SQLDialect { - PathNaming naming(); +public interface SqlDialect { + PathNaming naming(); - Optional limit(final OptionalLong limit, final OptionalLong offset); + Optional limit(final OptionalLong limit, final OptionalLong offset); - Optional string(StringOperators op, SQLExpression left, SQLExpression right); + Optional string(StringOperators op, SqlExpression left, SqlExpression right); - Optional comparison(ComparableOperators op, SQLExpression left, SQLExpression right); + Optional comparison(ComparableOperators op, SqlExpression left, SqlExpression right); - String logical(Operators op, SQLExpression left, SQLExpression right); + String logical(Operators op, SqlExpression left, SqlExpression right); - Optional equality(Operators op, SQLExpression left, SQLExpression right); + Optional equality(Operators op, SqlExpression left, SqlExpression right); - Optional binary(Operator op, SQLExpression left, SQLExpression right); + Optional binary(Operator op, SqlExpression left, SqlExpression right); - Optional regex(Operator op, SQLExpression left, Pattern right); + Optional regex(Operator op, SqlExpression left, Pattern right); - String count(SQLCountStatement statement); + String count(SqlCountStatement statement); - String select(SQLSelectStatement statement); + String select(SqlSelectStatement statement); - String delete(SQLDeleteStatement statement); + String delete(SqlDeleteStatement statement); - String insert(SQLInsertStatement statement); + String insert(SqlInsertStatement statement); - String update(SQLUpdateStatement statement); + String update(SqlUpdateStatement statement); - String save(SQLSaveStatement statement); + String save(SqlSaveStatement statement); } diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SqlDialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SqlDialect.java new file mode 100644 index 000000000..d4ea0e36a --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/dialects/SqlDialect.java @@ -0,0 +1,57 @@ +/* + * 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.dialects; + +import org.immutables.criteria.backend.PathNaming; +import org.immutables.criteria.expression.ComparableOperators; +import org.immutables.criteria.expression.Operator; +import org.immutables.criteria.expression.Operators; +import org.immutables.criteria.expression.StringOperators; +import org.immutables.criteria.sql.compiler.*; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.regex.Pattern; + +public interface SqlDialect { + PathNaming naming(); + + Optional limit(final OptionalLong limit, final OptionalLong offset); + + Optional string(StringOperators op, SqlExpression left, SqlExpression right); + + Optional comparison(ComparableOperators op, SqlExpression left, SqlExpression right); + + String logical(Operators op, SqlExpression left, SqlExpression right); + + Optional equality(Operators op, SqlExpression left, SqlExpression right); + + Optional binary(Operator op, SqlExpression left, SqlExpression right); + + Optional regex(Operator op, SqlExpression left, Pattern right); + + String count(SqlCountStatement statement); + + String select(SqlSelectStatement statement); + + String delete(SqlDeleteStatement statement); + + String insert(SqlInsertStatement statement); + + String update(SqlUpdateStatement statement); + + String save(SqlSaveStatement statement); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator new file mode 100644 index 000000000..ecb14d17b --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator @@ -0,0 +1,79 @@ +[-- + 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. +--] +[template public generate] +[for _mapping_ in mappings, PackageElement _package_ = _mapping_.element.enclosingElement] +[let _setup_class_name_]Sql[_mapping_.mappingClassName]Setup[/let] +[let _row_mapper_class_name_]Sql[_mapping_.mappingClassName]RowMapper[/let] +[let _immutable_]Immutable[_mapping_.mappingClassName][/let] +[let _builder_][_immutable_].Builder[/let] + +[output.java _package_.qualifiedName _setup_class_name_ _mapping_.element] +package [_package_.qualifiedName]; + +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; + +import javax.sql.DataSource; + +import org.immutables.criteria.sql.conversion.RowMappers; + +public class [_setup_class_name_] { + public static SqlBackend backend(final DataSource datasource) { + return SqlBackend.of(setup(datasource)); + } + public static SqlSetup setup(final DataSource datasource) { + final SqlTypeMetadata metadata = SqlTypeMetadata.of([_mapping_.mappingClassName].class); + final SqlSetup setup = SqlSetup.of(datasource, metadata); + RowMappers.register([_mapping_.mappingClassName].class, new [_row_mapper_class_name_](metadata)); + return setup; + } +} +[/output.java] + +[output.java _package_.qualifiedName _row_mapper_class_name_ _mapping_.element] +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.immutables.criteria.sql.conversion.RowMapper; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; +import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; +import org.immutables.criteria.sql.conversion.TypeConverters; + +package [_package_.qualifiedName]; +public class [_row_mapper_class_name_] implements RowMapper<[_mapping_.mappingClassName]> { + private final SqlTypeMetadata metadata; + + public [_row_mapper_class_name_](SqlTypeMetadata metadata) { + this.metadata = metadata; + } + public [_mapping_.mappingClassName] map(ResultSet row) throws SQLException + { + [_builder_] builder = [_immutable_].builder(); + SqlPropertyMetadata property = null;; + [for _column_ in _mapping_.columns] + // [_column_] + property = metadata.columns().get("[_column_.column]"); + builder.[_column_.method](TypeConverters.convert(property.mapping().type(),property.type(),property.mapping().fetcher().apply(row, "[_column_.column]"))); + [/for] + return builder.build(); + } +} +[/output.java] +[/for] +[/template] + + diff --git a/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java new file mode 100644 index 000000000..e61cfa08c --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java @@ -0,0 +1,136 @@ +/* + * 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.generator; + +import org.immutables.criteria.sql.SQL; +import org.immutables.generator.AbstractTemplate; +import org.immutables.generator.Generator; +import org.immutables.value.Value; + +import javax.lang.model.element.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Generator.Template +@Generator.Import({ + TypeElement.class, + PackageElement.class +}) +abstract class Sql extends AbstractTemplate { + static String tableName(TypeElement e) { + final SQL.Table annotation = e.getAnnotation(SQL.Table.class); + if (annotation == null || annotation.value().isEmpty()) { + throw new UnsupportedOperationException(String.format("%s.name annotation is not defined on %s", + SQL.Table.class.getSimpleName(), e.getSimpleName())); + } + return annotation.value(); + + } + + static Set columns(TypeElement e) { + return e.getEnclosedElements().stream() + .filter(x -> x instanceof ExecutableElement) + .map(x -> (ExecutableElement) x) + .filter(x -> !(x.getReturnType().toString().equals("Void") || x.getParameters().size() > 0)) + .map(Sql::column) + .collect(Collectors.toSet()); + } + + static ColumnMapping column(ExecutableElement e) { + String name = e.getSimpleName().toString(); + String property_type = e.getReturnType().toString(); + String column_type = e.getReturnType().toString(); + AnnotationMirror column = getAnnotation(e, SQL.Column.class); + if (column != null) { + AnnotationValue alias = value(column, "name"); + AnnotationValue cast = value(column, "type"); + name = alias != null ? String.valueOf(alias.getValue()) : name; + column_type = cast != null ? String.valueOf(cast.getValue()) : e.getReturnType().toString(); + } + return ImmutableColumnMapping.builder() + .element(e) + .method(e.getSimpleName().toString()) + .column(name) + .property_type(property_type) + .column_type(column_type) + .build(); + } + + static AnnotationValue value(AnnotationMirror a, String name) { + for (ExecutableElement e : a.getElementValues().keySet()) { + if (e.getSimpleName().toString().equals(name)) { + return a.getElementValues().get(e); + } + } + return null; + } + + static AnnotationMirror getAnnotation(Element e, Class type) { + for (AnnotationMirror m : e.getAnnotationMirrors()) { + if (m.getAnnotationType().asElement().getSimpleName().toString().equals(type.getSimpleName())) + return m; + } + return null; + } + + public List mappings() { + List ret = new ArrayList<>(); + for (TypeElement e : elements()) { + ret.add(ImmutableClassMapping.builder() + .table(tableName(e)) + .element(e) + .columns(columns(e)) + .build()); + } + return ret; + } + + public Set elements() { + return round().getElementsAnnotatedWith(SQL.Table.class) + .stream() + .filter(e -> e instanceof TypeElement) + .map(e -> (TypeElement) e) + .collect(Collectors.toSet()); + } + + @Value.Immutable + public interface ClassMapping { + String table(); + + default String mappingClassName() { + return element().getSimpleName().toString(); + } + + TypeElement element(); + + Set columns(); + } + + @Value.Immutable + public interface ColumnMapping { + ExecutableElement element(); + + String column(); + + String method(); + + String property_type(); + + String column_type(); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java b/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java new file mode 100644 index 000000000..a7b0b3d0b --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java @@ -0,0 +1,34 @@ +/* + * 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.generator; + +import org.immutables.criteria.sql.SQL; +import org.immutables.generator.AbstractGenerator; +import org.immutables.generator.Generator; +import org.immutables.metainf.Metainf; + +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; + +@Metainf.Service +@Generator.SupportedAnnotations({SQL.Table.class}) +@SupportedSourceVersion(SourceVersion.RELEASE_7) +public class SqlProcessor extends AbstractGenerator { + @Override + protected void process() { + invoke(new Generator_Sql().generate()); + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java b/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java index a3ecb7a41..592251c4c 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java +++ b/criteria/sql/src/org/immutables/criteria/sql/jdbc/FluentStatement.java @@ -29,226 +29,226 @@ import static java.sql.Statement.RETURN_GENERATED_KEYS; public class FluentStatement implements AutoCloseable { - static final Pattern PATTERN = Pattern.compile("(? fields = new ArrayList<>(); - - public FluentStatement(final Connection connection, final String sql) throws SQLException { - this(connection, sql, true); - } - - public FluentStatement(final Connection connection, final String sql, final boolean keys) throws SQLException { - final Matcher matcher = PATTERN.matcher(sql); - while (matcher.find()) { - fields.add(matcher.group()); - } - final String transformed = sql.replaceAll(PATTERN.pattern(), "?"); - this.connection = connection; - statement = keys - ? connection.prepareStatement(transformed, RETURN_GENERATED_KEYS) - : connection.prepareStatement(transformed); - } - - public static FluentStatement of(final Connection connection, final String sql) throws SQLException { - return new FluentStatement(connection, sql); - } - - public static FluentStatement of(final DataSource ds, final String sql) throws SQLException { - return new FluentStatement(ds.getConnection(), sql); - } - - public static List convert(final ResultSet results, final Mapper mapper) throws SQLException { - final List ret = new ArrayList<>(); - while (results.next()) { - ret.add(mapper.handle(results)); - } - return ret; - } - - public PreparedStatement statement() { - return statement; - } - - public int insert(final List> values) throws SQLException { - return batch(values); - } - - public List list(final Mapper mapper) throws SQLException { - return convert(statement.executeQuery(), mapper); - } - - public int batch(final List> values) throws SQLException { - connection.setAutoCommit(false); - for (final List row : values) { - for (int i = 0; i < row.size(); i++) { - set(i + 1, row.get(i)); - } - statement.addBatch(); - } - final int[] counts = statement.executeBatch(); - connection.commit(); - return Arrays.stream(counts).sum(); - - } - - public int update(final List> values) throws SQLException { - return batch(values); - } - - public int update() throws SQLException { - return statement.executeUpdate(); - } - - public int delete() throws SQLException { - return statement.executeUpdate(); - } - - @Override - public void close() throws SQLException { - statement.close(); - } - - public FluentStatement set(final Map parameters) throws SQLException { - assert parameters != null : "parameters cannot be null"; - for (final Map.Entry e : parameters.entrySet()) { - set(e.getKey(), e.getValue()); - } - return this; - } - - public FluentStatement set(final int index, final byte value) throws SQLException { - statement.setByte(index, value); - return this; - } - - public FluentStatement set(final int index, final short value) throws SQLException { - statement.setShort(index, value); - return this; - } - - public FluentStatement set(final int index, final int value) throws SQLException { - statement.setInt(index, value); - return this; - } - - public FluentStatement set(final int index, final long value) throws SQLException { - statement.setLong(index, value); - return this; - } - - public FluentStatement set(final int index, final float value) throws SQLException { - statement.setFloat(index, value); - return this; - } - - public FluentStatement set(final int index, final double value) throws SQLException { - statement.setDouble(index, value); - return this; - } - - public FluentStatement set(final int index, final boolean value) throws SQLException { - statement.setBoolean(index, value); - return this; - } - - public FluentStatement set(final int index, final String value) throws SQLException { - statement.setString(index, value); - return this; - } - - public FluentStatement set(final int index, final BigDecimal value) throws SQLException { - statement.setBigDecimal(index, value); - return this; - } - - public FluentStatement set(final int index, final byte[] value) throws SQLException { - statement.setBytes(index, value); - return this; - } - - public FluentStatement set(final int index, final Date value) throws SQLException { - statement.setDate(index, value); - return this; - } - - public FluentStatement set(final int index, final Time value) throws SQLException { - statement.setTime(index, value); - return this; - } - - public FluentStatement set(final int index, final Time value, final Calendar calendar) throws SQLException { - statement.setTime(index, value, calendar); - return this; - } - - public FluentStatement set(final int index, final Timestamp value) throws SQLException { - statement.setTimestamp(index, value); - return this; - } - - public FluentStatement set(final int index, final Blob value) throws SQLException { - statement.setBlob(index, value); - return this; - } - - public FluentStatement set(final int index, final Clob value) throws SQLException { - statement.setClob(index, value); - return this; - } - - public FluentStatement set(final int index, final NClob value) throws SQLException { - statement.setNClob(index, value); - return this; - } - - public FluentStatement set(final int index, final Reader value) throws SQLException { - statement.setCharacterStream(index, value); - return this; - } - - public FluentStatement set(final int index, final InputStream value) throws SQLException { - statement.setBinaryStream(index, value); - return this; - } - - public FluentStatement set(final int index, final URL value) throws SQLException { - statement.setURL(index, value); - return this; - } - - public FluentStatement set(final String name, final Object value) throws SQLException { - return set(getIndex(name), value); - } - - public FluentStatement set(final int index, final Object value) throws SQLException { - if (value instanceof Byte) { - set(index, (byte) value); - } else if (value instanceof Short) { - set(index, (short) value); - } else if (value instanceof Integer) { - set(index, (int) value); - } else if (value instanceof Long) { - set(index, (long) value); - } else if (value instanceof Float) { - set(index, (float) value); - } else if (value instanceof Double) { - set(index, (double) value); - } else if (value instanceof String) { - set(index, (String) value); - } else { - statement.setObject(index, value); - } - return this; - } - - private int getIndex(final String name) { - return fields.indexOf(name) + 1; - } - - @FunctionalInterface - public interface Mapper { - T handle(ResultSet row) throws SQLException; - } -} \ No newline at end of file + static final Pattern PATTERN = Pattern.compile("(? fields = new ArrayList<>(); + + public FluentStatement(final Connection connection, final String sql) throws SQLException { + this(connection, sql, true); + } + + public FluentStatement(final Connection connection, final String sql, final boolean keys) throws SQLException { + final Matcher matcher = PATTERN.matcher(sql); + while (matcher.find()) { + fields.add(matcher.group()); + } + final String transformed = sql.replaceAll(PATTERN.pattern(), "?"); + this.connection = connection; + statement = keys + ? connection.prepareStatement(transformed, RETURN_GENERATED_KEYS) + : connection.prepareStatement(transformed); + } + + public static FluentStatement of(final Connection connection, final String sql) throws SQLException { + return new FluentStatement(connection, sql); + } + + public static FluentStatement of(final DataSource ds, final String sql) throws SQLException { + return new FluentStatement(ds.getConnection(), sql); + } + + public static List convert(final ResultSet results, final Mapper mapper) throws SQLException { + final List ret = new ArrayList<>(); + while (results.next()) { + ret.add(mapper.handle(results)); + } + return ret; + } + + public PreparedStatement statement() { + return statement; + } + + public int insert(final List> values) throws SQLException { + return batch(values); + } + + public List list(final Mapper mapper) throws SQLException { + return convert(statement.executeQuery(), mapper); + } + + public int batch(final List> values) throws SQLException { + connection.setAutoCommit(false); + for (final List row : values) { + for (int i = 0; i < row.size(); i++) { + set(i + 1, row.get(i)); + } + statement.addBatch(); + } + final int[] counts = statement.executeBatch(); + connection.commit(); + return Arrays.stream(counts).sum(); + + } + + public int update(final List> values) throws SQLException { + return batch(values); + } + + public int update() throws SQLException { + return statement.executeUpdate(); + } + + public int delete() throws SQLException { + return statement.executeUpdate(); + } + + @Override + public void close() throws SQLException { + statement.close(); + } + + public FluentStatement set(final Map parameters) throws SQLException { + assert parameters != null : "parameters cannot be null"; + for (final Map.Entry e : parameters.entrySet()) { + set(e.getKey(), e.getValue()); + } + return this; + } + + public FluentStatement set(final int index, final byte value) throws SQLException { + statement.setByte(index, value); + return this; + } + + public FluentStatement set(final int index, final short value) throws SQLException { + statement.setShort(index, value); + return this; + } + + public FluentStatement set(final int index, final int value) throws SQLException { + statement.setInt(index, value); + return this; + } + + public FluentStatement set(final int index, final long value) throws SQLException { + statement.setLong(index, value); + return this; + } + + public FluentStatement set(final int index, final float value) throws SQLException { + statement.setFloat(index, value); + return this; + } + + public FluentStatement set(final int index, final double value) throws SQLException { + statement.setDouble(index, value); + return this; + } + + public FluentStatement set(final int index, final boolean value) throws SQLException { + statement.setBoolean(index, value); + return this; + } + + public FluentStatement set(final int index, final String value) throws SQLException { + statement.setString(index, value); + return this; + } + + public FluentStatement set(final int index, final BigDecimal value) throws SQLException { + statement.setBigDecimal(index, value); + return this; + } + + public FluentStatement set(final int index, final byte[] value) throws SQLException { + statement.setBytes(index, value); + return this; + } + + public FluentStatement set(final int index, final Date value) throws SQLException { + statement.setDate(index, value); + return this; + } + + public FluentStatement set(final int index, final Time value) throws SQLException { + statement.setTime(index, value); + return this; + } + + public FluentStatement set(final int index, final Time value, final Calendar calendar) throws SQLException { + statement.setTime(index, value, calendar); + return this; + } + + public FluentStatement set(final int index, final Timestamp value) throws SQLException { + statement.setTimestamp(index, value); + return this; + } + + public FluentStatement set(final int index, final Blob value) throws SQLException { + statement.setBlob(index, value); + return this; + } + + public FluentStatement set(final int index, final Clob value) throws SQLException { + statement.setClob(index, value); + return this; + } + + public FluentStatement set(final int index, final NClob value) throws SQLException { + statement.setNClob(index, value); + return this; + } + + public FluentStatement set(final int index, final Reader value) throws SQLException { + statement.setCharacterStream(index, value); + return this; + } + + public FluentStatement set(final int index, final InputStream value) throws SQLException { + statement.setBinaryStream(index, value); + return this; + } + + public FluentStatement set(final int index, final URL value) throws SQLException { + statement.setURL(index, value); + return this; + } + + public FluentStatement set(final String name, final Object value) throws SQLException { + return set(getIndex(name), value); + } + + public FluentStatement set(final int index, final Object value) throws SQLException { + if (value instanceof Byte) { + set(index, (byte) value); + } else if (value instanceof Short) { + set(index, (short) value); + } else if (value instanceof Integer) { + set(index, (int) value); + } else if (value instanceof Long) { + set(index, (long) value); + } else if (value instanceof Float) { + set(index, (float) value); + } else if (value instanceof Double) { + set(index, (double) value); + } else if (value instanceof String) { + set(index, (String) value); + } else { + statement.setObject(index, value); + } + return this; + } + + private int getIndex(final String name) { + return fields.indexOf(name) + 1; + } + + @FunctionalInterface + public interface Mapper { + T handle(ResultSet row) throws SQLException; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java index e1305cd1a..eb6482a90 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/PropertyExtractor.java @@ -17,5 +17,5 @@ @FunctionalInterface public interface PropertyExtractor { - Object extract(Object entity); + Object extract(Object entity); } diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java index 173dd6ce2..4bea79eb9 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java @@ -20,26 +20,26 @@ import java.util.Objects; -public interface SQLContainerNaming extends ContainerNaming { - ContainerNaming FROM_SQL_TABLE_ANNOTATION = clazz -> { - Objects.requireNonNull(clazz, "clazz"); - final SQL.Table annotation = clazz.getAnnotation(SQL.Table.class); - if (annotation == null || annotation.value().isEmpty()) { - throw new UnsupportedOperationException(String.format("%s.name annotation is not defined on %s", - SQL.Table.class.getSimpleName(), clazz.getName())); - } - return annotation.value(); - }; +public interface SqlContainerNaming extends ContainerNaming { + ContainerNaming FROM_SQL_TABLE_ANNOTATION = clazz -> { + Objects.requireNonNull(clazz, "clazz"); + final SQL.Table annotation = clazz.getAnnotation(SQL.Table.class); + if (annotation == null || annotation.value().isEmpty()) { + throw new UnsupportedOperationException(String.format("%s.name annotation is not defined on %s", + SQL.Table.class.getSimpleName(), clazz.getName())); + } + return annotation.value(); + }; - ContainerNaming SQL = clazz -> { - try { - return FROM_SQL_TABLE_ANNOTATION.name(clazz); - } catch (UnsupportedOperationException u) { - try { - return FROM_REPOSITORY_ANNOTATION.name(clazz); - } catch (UnsupportedOperationException e) { - return FROM_CLASSNAME.name(clazz); - } - } - }; + ContainerNaming SQL = clazz -> { + try { + return FROM_SQL_TABLE_ANNOTATION.name(clazz); + } catch (UnsupportedOperationException u) { + try { + return FROM_REPOSITORY_ANNOTATION.name(clazz); + } catch (UnsupportedOperationException e) { + return FROM_CLASSNAME.name(clazz); + } + } + }; } diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java index 9176f87c0..afb2e643b 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java @@ -19,12 +19,12 @@ import org.immutables.value.Value; @Value.Immutable -public interface SQLPropertyMetadata { - String name(); +public interface SqlPropertyMetadata { + String name(); - Class type(); + Class type(); - ColumnMapping mapping(); + ColumnMapping mapping(); - PropertyExtractor extractor(); + PropertyExtractor extractor(); } diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java index 792fc1b99..ca2248cb1 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java @@ -19,7 +19,7 @@ import org.immutables.criteria.reflect.ClassScanner; import org.immutables.criteria.reflect.MemberExtractor; import org.immutables.criteria.sql.SQL; -import org.immutables.criteria.sql.SQLException; +import org.immutables.criteria.sql.SqlException; import org.immutables.criteria.sql.conversion.ColumnFetchers; import org.immutables.criteria.sql.conversion.ColumnMapping; import org.immutables.criteria.sql.conversion.ImmutableColumnMapping; @@ -36,136 +36,136 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -public class SQLTypeMetadata { - private static final Predicate MAYBE_GETTER = method -> Modifier.isPublic(method.getModifiers()) - && !Modifier.isStatic(method.getModifiers()) - && method.getReturnType() != Void.class - && method.getParameterCount() == 0; - static final Predicate MAYBE_PERSISTED = t -> { - if (t instanceof Method) { - return MAYBE_GETTER.test((Method) t); - } - return false; - }; - private static final Predicate BOOLEAN_GETTER = method -> MAYBE_GETTER.test(method) - && method.getName().startsWith("is") - && method.getName().length() > "is".length() - && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class); - private static final Predicate GENERIC_GETTER = method -> MAYBE_GETTER.test(method) - && method.getName().startsWith("get") - && method.getName().length() > "get".length(); - static final Predicate IS_GETTER = GENERIC_GETTER.or(BOOLEAN_GETTER); - private final String table; - private final Class type; - private final List members; - private final List metadata; - - private final KeyExtractor extractor; - private final Map columns = new HashMap<>(); - private final Map properties = new HashMap<>(); - - private SQLTypeMetadata(final Class clazz) { - type = clazz; - table = SQLContainerNaming.SQL.name(clazz); - extractor = KeyExtractor.defaultFactory().create(clazz); - members = computePersistedMembers(clazz); - metadata = computePropertyMetadata(members); - metadata.forEach(m -> { - properties.put(m.name(), m); - columns.put(m.mapping().name(), m); - }); +public class SqlTypeMetadata { + private static final Predicate MAYBE_GETTER = method -> Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getReturnType() != Void.class + && method.getParameterCount() == 0; + static final Predicate MAYBE_PERSISTED = t -> { + if (t instanceof Method) { + return MAYBE_GETTER.test((Method) t); } - - public static SQLTypeMetadata of(final Class type) { - return new SQLTypeMetadata(type); - } - - private static List computePersistedMembers(@Nonnull final Class type) { - return ClassScanner.of(type) - .stream() - .filter(m -> MAYBE_PERSISTED.test(m)) - .collect(Collectors.toList()); - } - - private static List computePropertyMetadata(final List members) { - return members.stream() - .map(m -> { - return ImmutableSQLPropertyMetadata.builder() - .name(m instanceof Field ? computePropertyName((Field) m) : computePropertyName((Method) m)) - .type(m instanceof Field ? ((Field) m).getType() : ((Method) m).getReturnType()) - .mapping(computeColumnMapping(m)) - .extractor(v -> MemberExtractor.ofReflection().extract(m, v)) - .build(); - }) - .collect(Collectors.toList()); - } - - private static ColumnMapping computeColumnMapping(final Member m) { - if (m instanceof Field) { - final Field field = (Field) m; - final SQL.Column annotation = field.getAnnotation(SQL.Column.class); - final Class type = annotation != null && annotation.type() != null - ? annotation.type() - : field.getType(); - return ImmutableColumnMapping.builder() - .type(type) - .fetcher(ColumnFetchers.get(type)) - .name(annotation != null && annotation.name().length() > 0 - ? annotation.name() - : computeColumnName(field)) - .build(); - } else if (m instanceof Method) { - final Method method = (Method) m; - final SQL.Column annotation = method.getAnnotation(SQL.Column.class); - final Class type = annotation != null && annotation.type() != null - ? annotation.type() - : method.getReturnType(); - return ImmutableColumnMapping.builder() - .type(type) - .fetcher(ColumnFetchers.get(type)) - .name(annotation != null && annotation.name().length() > 0 - ? annotation.name() - : computeColumnName(method)) - .build(); - } - throw new SQLException("Unable to determine column mapping for member: " + m); - } - - private static String computePropertyName(final Field field) { - return field.getName(); - } - - private static String computePropertyName(final Method method) { - // TODO: String get/is prefix etc. - return method.getName(); - } - - private static String computeColumnName(final Field field) { - return field.getName().toLowerCase(Locale.ROOT); - } - - private static String computeColumnName(final Method method) { - // TODO: String get/is prefix etc. - return method.getName().toLowerCase(Locale.ROOT); - } - - public Class type() { - return type; - } - - public String table() { - return table; - } - - public KeyExtractor key() { - return extractor; - } - - public Map properties() { - return properties; - } - - public Map columns() { - return columns; + return false; + }; + private static final Predicate BOOLEAN_GETTER = method -> MAYBE_GETTER.test(method) + && method.getName().startsWith("is") + && method.getName().length() > "is".length() + && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class); + private static final Predicate GENERIC_GETTER = method -> MAYBE_GETTER.test(method) + && method.getName().startsWith("get") + && method.getName().length() > "get".length(); + static final Predicate IS_GETTER = GENERIC_GETTER.or(BOOLEAN_GETTER); + private final String table; + private final Class type; + private final List members; + private final List metadata; + + private final KeyExtractor extractor; + private final Map columns = new HashMap<>(); + private final Map properties = new HashMap<>(); + + private SqlTypeMetadata(final Class clazz) { + type = clazz; + table = SqlContainerNaming.SQL.name(clazz); + extractor = KeyExtractor.defaultFactory().create(clazz); + members = computePersistedMembers(clazz); + metadata = computePropertyMetadata(members); + metadata.forEach(m -> { + properties.put(m.name(), m); + columns.put(m.mapping().name(), m); + }); + } + + public static SqlTypeMetadata of(final Class type) { + return new SqlTypeMetadata(type); + } + + private static List computePersistedMembers(@Nonnull final Class type) { + return ClassScanner.of(type) + .stream() + .filter(m -> MAYBE_PERSISTED.test(m)) + .collect(Collectors.toList()); + } + + private static List computePropertyMetadata(final List members) { + return members.stream() + .map(m -> { + return ImmutableSqlPropertyMetadata.builder() + .name(m instanceof Field ? computePropertyName((Field) m) : computePropertyName((Method) m)) + .type(m instanceof Field ? ((Field) m).getType() : ((Method) m).getReturnType()) + .mapping(computeColumnMapping(m)) + .extractor(v -> MemberExtractor.ofReflection().extract(m, v)) + .build(); + }) + .collect(Collectors.toList()); + } + + private static ColumnMapping computeColumnMapping(final Member m) { + if (m instanceof Field) { + final Field field = (Field) m; + final SQL.Column annotation = field.getAnnotation(SQL.Column.class); + final Class type = annotation != null && annotation.type() != null + ? annotation.type() + : field.getType(); + return ImmutableColumnMapping.builder() + .type(type) + .fetcher(ColumnFetchers.get(type)) + .name(annotation != null && annotation.name().length() > 0 + ? annotation.name() + : computeColumnName(field)) + .build(); + } else if (m instanceof Method) { + final Method method = (Method) m; + final SQL.Column annotation = method.getAnnotation(SQL.Column.class); + final Class type = annotation != null && annotation.type() != null + ? annotation.type() + : method.getReturnType(); + return ImmutableColumnMapping.builder() + .type(type) + .fetcher(ColumnFetchers.get(type)) + .name(annotation != null && annotation.name().length() > 0 + ? annotation.name() + : computeColumnName(method)) + .build(); } + throw new SqlException("Unable to determine column mapping for member: " + m); + } + + private static String computePropertyName(final Field field) { + return field.getName(); + } + + private static String computePropertyName(final Method method) { + // TODO: String get/is prefix etc. + return method.getName(); + } + + private static String computeColumnName(final Field field) { + return field.getName().toLowerCase(Locale.ROOT); + } + + private static String computeColumnName(final Method method) { + // TODO: String get/is prefix etc. + return method.getName().toLowerCase(Locale.ROOT); + } + + public Class type() { + return type; + } + + public String table() { + return table; + } + + public KeyExtractor key() { + return extractor; + } + + public Map properties() { + return properties; + } + + public Map columns() { + return columns; + } } diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlContainerNaming.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlContainerNaming.java new file mode 100644 index 000000000..4bea79eb9 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlContainerNaming.java @@ -0,0 +1,45 @@ +/* + * 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.reflection; + +import org.immutables.criteria.backend.ContainerNaming; +import org.immutables.criteria.sql.SQL; + +import java.util.Objects; + +public interface SqlContainerNaming extends ContainerNaming { + ContainerNaming FROM_SQL_TABLE_ANNOTATION = clazz -> { + Objects.requireNonNull(clazz, "clazz"); + final SQL.Table annotation = clazz.getAnnotation(SQL.Table.class); + if (annotation == null || annotation.value().isEmpty()) { + throw new UnsupportedOperationException(String.format("%s.name annotation is not defined on %s", + SQL.Table.class.getSimpleName(), clazz.getName())); + } + return annotation.value(); + }; + + ContainerNaming SQL = clazz -> { + try { + return FROM_SQL_TABLE_ANNOTATION.name(clazz); + } catch (UnsupportedOperationException u) { + try { + return FROM_REPOSITORY_ANNOTATION.name(clazz); + } catch (UnsupportedOperationException e) { + return FROM_CLASSNAME.name(clazz); + } + } + }; +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlPropertyMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlPropertyMetadata.java new file mode 100644 index 000000000..afb2e643b --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlPropertyMetadata.java @@ -0,0 +1,30 @@ +/* + * 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.reflection; + +import org.immutables.criteria.sql.conversion.ColumnMapping; +import org.immutables.value.Value; + +@Value.Immutable +public interface SqlPropertyMetadata { + String name(); + + Class type(); + + ColumnMapping mapping(); + + PropertyExtractor extractor(); +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlTypeMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlTypeMetadata.java new file mode 100644 index 000000000..ca2248cb1 --- /dev/null +++ b/criteria/sql/src/org/immutables/criteria/sql/reflection/SqlTypeMetadata.java @@ -0,0 +1,171 @@ +/* + * 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.reflection; + +import org.immutables.criteria.backend.KeyExtractor; +import org.immutables.criteria.reflect.ClassScanner; +import org.immutables.criteria.reflect.MemberExtractor; +import org.immutables.criteria.sql.SQL; +import org.immutables.criteria.sql.SqlException; +import org.immutables.criteria.sql.conversion.ColumnFetchers; +import org.immutables.criteria.sql.conversion.ColumnMapping; +import org.immutables.criteria.sql.conversion.ImmutableColumnMapping; + +import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class SqlTypeMetadata { + private static final Predicate MAYBE_GETTER = method -> Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getReturnType() != Void.class + && method.getParameterCount() == 0; + static final Predicate MAYBE_PERSISTED = t -> { + if (t instanceof Method) { + return MAYBE_GETTER.test((Method) t); + } + return false; + }; + private static final Predicate BOOLEAN_GETTER = method -> MAYBE_GETTER.test(method) + && method.getName().startsWith("is") + && method.getName().length() > "is".length() + && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class); + private static final Predicate GENERIC_GETTER = method -> MAYBE_GETTER.test(method) + && method.getName().startsWith("get") + && method.getName().length() > "get".length(); + static final Predicate IS_GETTER = GENERIC_GETTER.or(BOOLEAN_GETTER); + private final String table; + private final Class type; + private final List members; + private final List metadata; + + private final KeyExtractor extractor; + private final Map columns = new HashMap<>(); + private final Map properties = new HashMap<>(); + + private SqlTypeMetadata(final Class clazz) { + type = clazz; + table = SqlContainerNaming.SQL.name(clazz); + extractor = KeyExtractor.defaultFactory().create(clazz); + members = computePersistedMembers(clazz); + metadata = computePropertyMetadata(members); + metadata.forEach(m -> { + properties.put(m.name(), m); + columns.put(m.mapping().name(), m); + }); + } + + public static SqlTypeMetadata of(final Class type) { + return new SqlTypeMetadata(type); + } + + private static List computePersistedMembers(@Nonnull final Class type) { + return ClassScanner.of(type) + .stream() + .filter(m -> MAYBE_PERSISTED.test(m)) + .collect(Collectors.toList()); + } + + private static List computePropertyMetadata(final List members) { + return members.stream() + .map(m -> { + return ImmutableSqlPropertyMetadata.builder() + .name(m instanceof Field ? computePropertyName((Field) m) : computePropertyName((Method) m)) + .type(m instanceof Field ? ((Field) m).getType() : ((Method) m).getReturnType()) + .mapping(computeColumnMapping(m)) + .extractor(v -> MemberExtractor.ofReflection().extract(m, v)) + .build(); + }) + .collect(Collectors.toList()); + } + + private static ColumnMapping computeColumnMapping(final Member m) { + if (m instanceof Field) { + final Field field = (Field) m; + final SQL.Column annotation = field.getAnnotation(SQL.Column.class); + final Class type = annotation != null && annotation.type() != null + ? annotation.type() + : field.getType(); + return ImmutableColumnMapping.builder() + .type(type) + .fetcher(ColumnFetchers.get(type)) + .name(annotation != null && annotation.name().length() > 0 + ? annotation.name() + : computeColumnName(field)) + .build(); + } else if (m instanceof Method) { + final Method method = (Method) m; + final SQL.Column annotation = method.getAnnotation(SQL.Column.class); + final Class type = annotation != null && annotation.type() != null + ? annotation.type() + : method.getReturnType(); + return ImmutableColumnMapping.builder() + .type(type) + .fetcher(ColumnFetchers.get(type)) + .name(annotation != null && annotation.name().length() > 0 + ? annotation.name() + : computeColumnName(method)) + .build(); + } + throw new SqlException("Unable to determine column mapping for member: " + m); + } + + private static String computePropertyName(final Field field) { + return field.getName(); + } + + private static String computePropertyName(final Method method) { + // TODO: String get/is prefix etc. + return method.getName(); + } + + private static String computeColumnName(final Field field) { + return field.getName().toLowerCase(Locale.ROOT); + } + + private static String computeColumnName(final Method method) { + // TODO: String get/is prefix etc. + return method.getName().toLowerCase(Locale.ROOT); + } + + public Class type() { + return type; + } + + public String table() { + return table; + } + + public KeyExtractor key() { + return extractor; + } + + public Map properties() { + return properties; + } + + public Map columns() { + return columns; + } +} diff --git a/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java b/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java index e94e4774d..652281308 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java +++ b/criteria/sql/src/org/immutables/criteria/sql/util/TypeKeyHashMap.java @@ -20,20 +20,20 @@ @SuppressWarnings("serial") public class TypeKeyHashMap extends HashMap, V> { - @Override - public boolean containsKey(final Object key) { - return findEntry((Class) key).isPresent(); - } + @Override + public boolean containsKey(final Object key) { + return findEntry((Class) key).isPresent(); + } - @Override - public V get(final Object key) { - final Optional, V>> entry = findEntry((Class) key); - return entry.map(Entry::getValue).orElse(null); - } + @Override + public V get(final Object key) { + final Optional, V>> entry = findEntry((Class) key); + return entry.map(Entry::getValue).orElse(null); + } - private Optional, V>> findEntry(final Class key) { - return entrySet().stream() - .filter(e -> e.getKey().isAssignableFrom(key)) - .findFirst(); - } -} \ No newline at end of file + private Optional, V>> findEntry(final Class key) { + return entrySet().stream() + .filter(e -> e.getKey().isAssignableFrom(key)) + .findFirst(); + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java index 36078c8b1..34d97c145 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/Dummy.java @@ -21,8 +21,8 @@ @Criteria @Value.Immutable public interface Dummy { - @Criteria.Id - int id(); + @Criteria.Id + int id(); - String name(); + String name(); } diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java index 11436416b..310fe925a 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java @@ -18,8 +18,8 @@ import org.h2.jdbcx.JdbcDataSource; import org.immutables.criteria.Criterias; import org.immutables.criteria.expression.Query; -import org.immutables.criteria.sql.SQLSetup; -import org.immutables.criteria.sql.reflection.SQLTypeMetadata; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; import org.junit.Test; import javax.sql.DataSource; @@ -28,270 +28,270 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class SQLCompilerTests { - private static DataSource datasource() { - final JdbcDataSource ds = new JdbcDataSource(); - ds.setURL("jdbc:h2:mem:test"); - ds.setUser("sa"); - ds.setPassword("sa"); - return ds; - } +public class SqlCompilerTests { + private static DataSource datasource() { + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword("sa"); + return ds; + } - private static SQLSetup setup(final DataSource datasource, final String table) { - return SQLSetup.of(datasource, SQLTypeMetadata.of(Dummy.class)); - } + private static SqlSetup setup(final DataSource datasource, final String table) { + return SqlSetup.of(datasource, SqlTypeMetadata.of(Dummy.class)); + } - @Test - public void testEmptySelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup, Query.of(Dummy.class)); - assertEquals("SELECT `id`,`name` FROM `dummy`", - setup.dialect().select(result)); - } + @Test + public void testEmptySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup, Query.of(Dummy.class)); + assertEquals("SELECT `id`,`name` FROM `dummy`", + setup.dialect().select(result)); + } - @Test - public void testEqualitySelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.is(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testEqualitySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testInequalitySelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.isNot(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)!=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testInequalitySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.isNot(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)!=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testLessThanSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.lessThan(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testLessThanSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.lessThan(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testLessThanEqualsSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.atMost(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testLessThanEqualsSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.atMost(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testGreaterThanSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.greaterThan(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testGreaterThanSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.greaterThan(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testGreaterThanEqualsSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.atLeast(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testGreaterThanEqualsSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.atLeast(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testBetweenSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.between(1, 2))); - // Note that this is how criteria maps this - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)>=(:param_0))AND((`id`)<=(:param_1))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals(2, result.filter().get().parameters().get(":param_1").value()); - } + @Test + public void testBetweenSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.between(1, 2))); + // Note that this is how criteria maps this + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)>=(:param_0))AND((`id`)<=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + } - @Test - public void testInSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.in(1, 2, 3))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals(2, result.filter().get().parameters().get(":param_1").value()); - assertEquals(3, result.filter().get().parameters().get(":param_2").value()); - } + @Test + public void testInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.in(1, 2, 3))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + assertEquals(3, result.filter().get().parameters().get(":param_2").value()); + } - @Test - public void testNotInSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.notIn(1, 2, 3))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)NOT IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals(2, result.filter().get().parameters().get(":param_1").value()); - assertEquals(3, result.filter().get().parameters().get(":param_2").value()); - } + @Test + public void testNotInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.notIn(1, 2, 3))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)NOT IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + assertEquals(3, result.filter().get().parameters().get(":param_2").value()); + } - @Test - public void testStringInSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.in("a", "b", "c"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - assertEquals("b", result.filter().get().parameters().get(":param_1").value()); - assertEquals("c", result.filter().get().parameters().get(":param_2").value()); - } + @Test + public void testStringInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.in("a", "b", "c"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + assertEquals("b", result.filter().get().parameters().get(":param_1").value()); + assertEquals("c", result.filter().get().parameters().get(":param_2").value()); + } - @Test - public void testStringNotInSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.notIn("a", "b", "c"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)NOT IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - assertEquals("b", result.filter().get().parameters().get(":param_1").value()); - assertEquals("c", result.filter().get().parameters().get(":param_2").value()); - } + @Test + public void testStringNotInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.notIn("a", "b", "c"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)NOT IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + assertEquals("b", result.filter().get().parameters().get(":param_1").value()); + assertEquals("c", result.filter().get().parameters().get(":param_2").value()); + } - @Test - public void testStringStartsWithSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.startsWith("a"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(:param_0,\"%\"))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testStringStartsWithSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.startsWith("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(:param_0,\"%\"))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testStringEndsWithSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.endsWith("a"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testStringEndsWithSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.endsWith("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testStringContainsSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.contains("a"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0,\"%\"))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testStringContainsSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.contains("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0,\"%\"))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testStringMatchesSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.matches(Pattern.compile("a.*b'.b?")))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE('a%b''_b%')", - setup.dialect().select(result)); - } + @Test + public void testStringMatchesSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.matches(Pattern.compile("a.*b'.b?")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE('a%b''_b%')", + setup.dialect().select(result)); + } - @Test - public void testStringHasLengthSelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.hasLength(10))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE LEN(`name`)=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(10, result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testStringHasLengthSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.hasLength(10))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE LEN(`name`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(10, result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testStringIsEmptySelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.isEmpty())); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("", result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testStringIsEmptySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.isEmpty())); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("", result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testStringIsNotEmptySelect() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.notEmpty())); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)!=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("", result.filter().get().parameters().get(":param_0").value()); - } + @Test + public void testStringIsNotEmptySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.notEmpty())); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)!=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("", result.filter().get().parameters().get(":param_0").value()); + } - @Test - public void testAndExpression() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.is(1).and(DummyCriteria.dummy.name.is("A")))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))AND((`name`)=(:param_1))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals("A", result.filter().get().parameters().get(":param_1").value()); - } + @Test + public void testAndExpression() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1).and(DummyCriteria.dummy.name.is("A")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))AND((`name`)=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals("A", result.filter().get().parameters().get(":param_1").value()); + } - @Test - public void testOrExpression() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.is(1).or(DummyCriteria.dummy.name.is("A")))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))OR((`name`)=(:param_1))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals("A", result.filter().get().parameters().get(":param_1").value()); - } + @Test + public void testOrExpression() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1).or(DummyCriteria.dummy.name.is("A")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))OR((`name`)=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals("A", result.filter().get().parameters().get(":param_1").value()); + } - @Test - public void testCompoundExpression() { - final SQLSetup setup = setup(datasource(), "dummy"); - final SQLSelectStatement result = SQLCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.is("A") - .or() - .name.is("B") - .id.is(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`name`)=(:param_0))OR(((`name`)=(:param_1))AND((`id`)=(:param_2)))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("A", result.filter().get().parameters().get(":param_0").value()); - assertEquals("B", result.filter().get().parameters().get(":param_1").value()); - assertEquals(1, result.filter().get().parameters().get(":param_2").value()); - } + @Test + public void testCompoundExpression() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.is("A") + .or() + .name.is("B") + .id.is(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`name`)=(:param_0))OR(((`name`)=(:param_1))AND((`id`)=(:param_2)))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("A", result.filter().get().parameters().get(":param_0").value()); + assertEquals("B", result.filter().get().parameters().get(":param_1").value()); + assertEquals(1, result.filter().get().parameters().get(":param_2").value()); + } } diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/SqlCompilerTests.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/SqlCompilerTests.java new file mode 100644 index 000000000..310fe925a --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/compiler/SqlCompilerTests.java @@ -0,0 +1,297 @@ +/* + * 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.compiler; + +import org.h2.jdbcx.JdbcDataSource; +import org.immutables.criteria.Criterias; +import org.immutables.criteria.expression.Query; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; +import org.junit.Test; + +import javax.sql.DataSource; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SqlCompilerTests { + private static DataSource datasource() { + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword("sa"); + return ds; + } + + private static SqlSetup setup(final DataSource datasource, final String table) { + return SqlSetup.of(datasource, SqlTypeMetadata.of(Dummy.class)); + } + + @Test + public void testEmptySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup, Query.of(Dummy.class)); + assertEquals("SELECT `id`,`name` FROM `dummy`", + setup.dialect().select(result)); + } + + @Test + public void testEqualitySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testInequalitySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.isNot(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)!=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testLessThanSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.lessThan(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testLessThanEqualsSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.atMost(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testGreaterThanSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.greaterThan(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testGreaterThanEqualsSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.atLeast(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testBetweenSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.between(1, 2))); + // Note that this is how criteria maps this + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)>=(:param_0))AND((`id`)<=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + } + + @Test + public void testInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.in(1, 2, 3))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + assertEquals(3, result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testNotInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.notIn(1, 2, 3))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)NOT IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals(2, result.filter().get().parameters().get(":param_1").value()); + assertEquals(3, result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testStringInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.in("a", "b", "c"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + assertEquals("b", result.filter().get().parameters().get(":param_1").value()); + assertEquals("c", result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testStringNotInSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.notIn("a", "b", "c"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)NOT IN(:param_0,:param_1,:param_2)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + assertEquals("b", result.filter().get().parameters().get(":param_1").value()); + assertEquals("c", result.filter().get().parameters().get(":param_2").value()); + } + + @Test + public void testStringStartsWithSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.startsWith("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(:param_0,\"%\"))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringEndsWithSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.endsWith("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringContainsSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.contains("a"))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0,\"%\"))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("a", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringMatchesSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.matches(Pattern.compile("a.*b'.b?")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE('a%b''_b%')", + setup.dialect().select(result)); + } + + @Test + public void testStringHasLengthSelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.hasLength(10))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE LEN(`name`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(10, result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringIsEmptySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.isEmpty())); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testStringIsNotEmptySelect() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.notEmpty())); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)!=(:param_0)", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("", result.filter().get().parameters().get(":param_0").value()); + } + + @Test + public void testAndExpression() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1).and(DummyCriteria.dummy.name.is("A")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))AND((`name`)=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals("A", result.filter().get().parameters().get(":param_1").value()); + } + + @Test + public void testOrExpression() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.id.is(1).or(DummyCriteria.dummy.name.is("A")))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))OR((`name`)=(:param_1))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals(1, result.filter().get().parameters().get(":param_0").value()); + assertEquals("A", result.filter().get().parameters().get(":param_1").value()); + } + + @Test + public void testCompoundExpression() { + final SqlSetup setup = setup(datasource(), "dummy"); + final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), + Criterias.toQuery(DummyCriteria.dummy.name.is("A") + .or() + .name.is("B") + .id.is(1))); + assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`name`)=(:param_0))OR(((`name`)=(:param_1))AND((`id`)=(:param_2)))", + setup.dialect().select(result)); + assertTrue("Missing filter", result.filter().isPresent()); + assertEquals("A", result.filter().get().parameters().get(":param_0").value()); + assertEquals("B", result.filter().get().parameters().get(":param_1").value()); + assertEquals(1, result.filter().get().parameters().get(":param_2").value()); + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/AbstractNoteRepositoryTests.java b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractNoteRepositoryTests.java new file mode 100644 index 000000000..6a6da8e69 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractNoteRepositoryTests.java @@ -0,0 +1,316 @@ +/* + * 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.note; + +import org.immutables.criteria.backend.WriteResult; +import org.immutables.criteria.sql.SqlException; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public abstract class AbstractNoteRepositoryTests extends AbstractTestBase { + protected NoteRepository repository; + + @Test + public void testFindAllCount() { + assertEquals(5, repository.findAll().count()); + } + + @Test + public void testFindWithDistinctCount() { + assertEquals(5, repository.findAll().select(NoteCriteria.note.id).distinct().count()); + } + + @Test + public void testFindWithLimitCount() { + // The limit applies to the result of the COUNT() + assertEquals(5, repository.findAll().limit(3L).count()); + } + + @Test + public void testFindWithLimitSetToZero() { + // The limit applies to the result of the COUNT() and returns no results, so throws an exception + assertThrows(SqlException.class, () -> repository.findAll().limit(0L).count()); + } + + @Test + public void testFindWithOffsetCount() { + // The offset applies to the result of the COUNT() which will result in no results being returned + // then an exception being thrown + assertThrows(SqlException.class, () -> repository.findAll().offset(3L).count()); + } + + @Test + public void testFindAllFetch() { + final List results = repository.findAll().fetch(); + assertEquals(5, results.size()); + assertTrue(results.stream().anyMatch(e -> + e.id().equals(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269")) && + e.created().toEpochMilli() == 1640995200000L && + e.message().equals("Message #1")), + "Missing item"); + } + + @Test + public void testFindAllFetchWithLimit() { + final List results = repository.findAll().limit(3).fetch(); + assertEquals(3, results.size()); + assertTrue(results.stream().anyMatch(e -> + e.id().equals(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269")) && + e.created().toEpochMilli() == 1640995200000L && + e.message().equals("Message #1")), + "Missing item"); + } + + @Test + public void testFindAllFetchWithLimitSetToZero() { + final List results = repository.findAll().limit(0).fetch(); + assertEquals(0, results.size()); + } + + @Test + public void testFindAllFetchWithOffset() { + final List results = repository.findAll().offset(3).fetch(); + assertEquals(2, results.size()); + } + + @Test + public void testFindAllFetchWithLargeOffset() { + final List results = repository.findAll().offset(300L).fetch(); + assertEquals(0, results.size()); + } + + @Test + public void testIdCriteriaBasedFetch() { + final Note results = repository.find(NoteCriteria.note + .id.is(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269"))).one(); + assertNotNull(results); + } + + + @Test + public void testCreatedOnCriteriaBasedFetch() { + final Note results = repository.find(NoteCriteria.note + .created.atLeast(Instant.ofEpochMilli(1640995200000L))).one(); + assertNotNull(results); + } + + + @Test + public void testCriteriaBasedMultiFetch() { + final List results = repository.find(NoteCriteria.note + .id.in(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269"), + UUID.fromString("75b90525-38be-41b9-b43d-df427a66893c"))) + .fetch(); + assertNotNull(results); + assertEquals(2, results.size()); + } + + @Test + public void testFindOneFetchWithSingleOrdering() { + final Note results = repository.findAll() + .orderBy(NoteCriteria.note.created.asc()) + .limit(1) + .one(); + assertEquals(UUID.fromString("c49371f3-8cda-4ddf-ad74-877ee6f67abe"), results.id()); + assertEquals(0L, results.created().toEpochMilli()); + } + + @Test + public void testFindOneFetchWithMultipleOrdering() { + final Note results = repository.findAll() + .orderBy(NoteCriteria.note.id.asc(), NoteCriteria.note.created.asc()) + .limit(1) + .one(); + assertEquals(UUID.fromString("578f932f-c972-4d3b-92b4-bf8fda9599f9"), results.id()); + assertEquals(1577836800000L, results.created().toEpochMilli()); + } + + @Test + public void testInsertSingleValue() { + final Note event = insertAndVerify(newEntity()); + assertEquals(1, repository.find(NoteCriteria.note.id.is(event.id())).count()); + } + + + @Test + public void testInsertMultipleValues() { + final List data = insertAndVerify(newEntities(5)); + data.forEach(e -> assertEquals(1, repository.find(NoteCriteria.note.id.is(e.id())).count())); + } + + @Test + public void testDeleteWhereNotEmpty() { + repository.delete(NoteCriteria.note.message.notEmpty()); + final long after = repository.findAll().count(); + assertEquals(0, after); + } + + @Test + public void testDeleteSingleUsingCriteria() { + final long before = repository.findAll().count(); + + // Insert new data and verify + final Note data = insertAndVerify(newEntity()); + + // Delete and verify state is correct + repository.delete(NoteCriteria.note.id.is(data.id())); + final long after = repository.findAll().count(); + assertEquals(before, after); + } + + @Test + public void testDeleteMultipleUsingCriteria() { + final long before = repository.findAll().count(); + + // Insert new data and verify + final List data = insertAndVerify(newEntities(3)); + + // Delete and verify state is correct + repository.delete(NoteCriteria.note.id.in(data.stream().map(e -> e.id()).collect(Collectors.toList()))); + final long after = repository.findAll().count(); + assertEquals(before, after); + } + + @Test + public void testUpdateSingleEntityUsingCriteria() { + final String message = "I'm a jolly little update"; + final NoteCriteria criteria = NoteCriteria.note; + final Note target = repository.findAll().fetch().get(0); + final WriteResult result = repository.update(criteria.id.is(target.id())) + .set(criteria.message, message) + .execute(); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(1, result.updatedCount().getAsLong()); + + final Note updated = repository.find(NoteCriteria.note.id.is(target.id())).one(); + assertNotNull(updated); + assertEquals(message, updated.message()); + } + + @Test + public void testUpdateMultipleEntitiesUsingCriteria() { + final String message = "I'm a jolly little update"; + final NoteCriteria criteria = NoteCriteria.note; + final Note target = repository.findAll().fetch().get(0); + final WriteResult result = repository.update(criteria.id.isNot(UUID.randomUUID())) + .set(criteria.message, message) + .execute(); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(5, result.updatedCount().getAsLong()); + + repository.findAll().fetch().forEach(e -> { + assertEquals(message, e.message()); + }); + } + + @Test + public void testUpdateSingleEntityUsingObject() { + final String message = "I'm a jolly little update"; + final Note target = repository.findAll().fetch().get(0); + final ImmutableNote update = ImmutableNote.builder() + .from(target) + .message(message) + .build(); + final WriteResult result = repository.update(update); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(1, result.updatedCount().getAsLong()); + + final Note updated = repository.find(NoteCriteria.note.id.is(target.id())).one(); + assertTrue(repository.find(NoteCriteria.note.id.is(target.id())).exists(), "Unable to find note"); + assertNotNull(updated); + assertEquals(message, updated.message()); + } + + @Test + public void testUpdateMultipleEntitiesUsingObject() { + final String message = "I'm a jolly little update"; + final List targets = repository.findAll().limit(3).fetch(); + final List updates = targets.stream() + .map(e -> ImmutableNote.builder() + .from(e) + .message(message) + .build()).collect(Collectors.toList()); + final WriteResult result = repository.updateAll(updates); + assertNotNull(result); + assertTrue(result.updatedCount().isPresent()); + assertEquals(3, result.updatedCount().getAsLong()); + + targets.forEach(t -> { + final Note updated = repository.find(NoteCriteria.note.id.is(t.id())).one(); + assertTrue(repository.find(NoteCriteria.note.id.is(t.id())).exists(), "Unable to find note"); + assertNotNull(updated); + assertEquals(message, updated.message()); + }); + } + + protected final List insertAndVerify(final List entities) { + final long before = repository.findAll().count(); + + final WriteResult result = repository.insertAll(entities); + assertNotNull(result); + assertTrue(result.insertedCount().isPresent()); + assertTrue(result.deletedCount().isPresent()); + assertTrue(result.updatedCount().isPresent()); + assertEquals(entities.size(), result.insertedCount().getAsLong()); + assertEquals(0, result.deletedCount().getAsLong()); + assertEquals(0, result.updatedCount().getAsLong()); + + // Ensure post-insert state is correct + final long after = repository.findAll().count(); + assertEquals(before + entities.size(), after); + + // Ensure that we can find the entities + entities.forEach(e -> assertEquals(1, repository.find(NoteCriteria.note.id.is(e.id())).count())); + + return entities; + } + + protected final Note insertAndVerify(final Note event) { + // Ensure database state is correct + final long before = repository.findAll().count(); + + // Ensure that the insert worked + final WriteResult result = repository.insert(event); + assertNotNull(result); + assertTrue(result.insertedCount().isPresent()); + assertTrue(result.deletedCount().isPresent()); + assertTrue(result.updatedCount().isPresent()); + assertEquals(1, result.insertedCount().getAsLong()); + assertEquals(0, result.deletedCount().getAsLong()); + assertEquals(0, result.updatedCount().getAsLong()); + + // Ensure post-insert state is correct + final long after = repository.findAll().count(); + assertEquals(before + 1, after); + + // Ensure that we can find the entity + assertEquals(1, repository.find(NoteCriteria.note.id.is(event.id())).count()); + + return event; + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java index d0982b9ca..74e96d8a4 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java +++ b/criteria/sql/test/org/immutables/criteria/sql/note/AbstractTestBase.java @@ -15,17 +15,13 @@ */ package org.immutables.criteria.sql.note; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.database.jvm.JdbcConnection; -import liquibase.exception.LiquibaseException; -import liquibase.resource.ClassLoaderResourceAccessor; import org.h2.jdbcx.JdbcDataSource; +import org.h2.tools.RunScript; import org.immutables.criteria.Criterion; import org.immutables.criteria.expression.*; import javax.sql.DataSource; +import java.io.InputStreamReader; import java.sql.Connection; import java.sql.SQLException; import java.time.Instant; @@ -35,53 +31,50 @@ import java.util.UUID; public abstract class AbstractTestBase { - protected static DataSource getDataSource() { - final JdbcDataSource ds = new JdbcDataSource(); - ds.setURL("jdbc:h2:mem:test"); - ds.setUser("sa"); - ds.setPassword("sa"); - return ds; - } + protected static DataSource getDataSource() { + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword("sa"); + return ds; + } - protected static void migrate(final DataSource ds) throws LiquibaseException, SQLException { - final Connection connection = ds.getConnection(); - final Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - final Liquibase liquibase = new Liquibase("test-migrations.xml", new ClassLoaderResourceAccessor(), database); - liquibase.dropAll(); - liquibase.update(""); - } + protected static void migrate(final DataSource ds) throws SQLException { + final Connection connection = ds.getConnection(); + RunScript.execute(connection, new InputStreamReader(AbstractTestBase.class.getResourceAsStream("notes.sql"))); + } - protected static final List newEntities(final int count) { - final List ret = new ArrayList<>(); - for (int i = 0; i < count; i++) { - ret.add(newEntity()); - } - return ret; + protected static final List newEntities(final int count) { + final List ret = new ArrayList<>(); + for (int i = 0; i < count; i++) { + ret.add(newEntity()); } + return ret; + } - protected static final Note newEntity() { - return ImmutableNote.builder() - .id(UUID.randomUUID()) - .created(Instant.now()) - .message("Test note") - .build(); - } + protected static final Note newEntity() { + return ImmutableNote.builder() + .id(UUID.randomUUID()) + .created(Instant.now()) + .message("Test note") + .build(); + } - protected static final Query newQuery(final UUID id) { - try { - final Path path = Path.ofMember(Note.class.getMethod("id")); - final Constant value = Expressions.constant(id); - final Query query = ImmutableQuery.builder() - .entityClass(Note.class) - .filter(Expressions.call(Operators.EQUAL, Arrays.asList(path, value))) - .build(); - return query; - } catch (final Throwable t) { - throw new RuntimeException(t); - } + protected static final Query newQuery(final UUID id) { + try { + final Path path = Path.ofMember(Note.class.getMethod("id")); + final Constant value = Expressions.constant(id); + final Query query = ImmutableQuery.builder() + .entityClass(Note.class) + .filter(Expressions.call(Operators.EQUAL, Arrays.asList(path, value))) + .build(); + return query; + } catch (final Throwable t) { + throw new RuntimeException(t); } + } - protected static final Criterion newCriterion(final UUID id) { - return NoteCriteria.note.id.is(id); - } + protected static final Criterion newCriterion(final UUID id) { + return NoteCriteria.note.id.is(id); + } } diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/CustomNoteSetup.java b/criteria/sql/test/org/immutables/criteria/sql/note/CustomNoteSetup.java new file mode 100644 index 000000000..a273a71c4 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/CustomNoteSetup.java @@ -0,0 +1,27 @@ +package org.immutables.criteria.sql.note; + +import org.immutables.criteria.sql.SqlBackend; +import org.immutables.criteria.sql.SqlSetup; +import org.immutables.criteria.sql.conversion.RowMappers; +import org.immutables.criteria.sql.reflection.SqlTypeMetadata; + +import javax.sql.DataSource; +import java.time.Instant; +import java.util.UUID; + +public class CustomNoteSetup { + public static SqlBackend backend(final DataSource datasource) { + return SqlBackend.of(setup(datasource)); + } + public static SqlSetup setup(final DataSource datasource) { + final SqlTypeMetadata metadata = SqlTypeMetadata.of(Note.class); + final SqlSetup setup = SqlSetup.of(datasource, metadata); + RowMappers.register(Note.class, (row) -> + ImmutableNote.builder() + .id(UUID.fromString(row.getString("id"))) + .created(Instant.ofEpochMilli(row.getLong("created_on"))) + .message(row.getString("message")) + .build()); + return setup; + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/CustomSetupTests.java b/criteria/sql/test/org/immutables/criteria/sql/note/CustomSetupTests.java new file mode 100644 index 000000000..a7d43c5f2 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/CustomSetupTests.java @@ -0,0 +1,14 @@ +package org.immutables.criteria.sql.note; + +import org.junit.jupiter.api.BeforeEach; + +import javax.sql.DataSource; + +public class CustomSetupTests extends AbstractNoteRepositoryTests { + @BeforeEach + public void onStart() throws Exception { + final DataSource datasource = getDataSource(); + migrate(datasource); + repository = new NoteRepository(CustomNoteSetup.backend(datasource)); + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/GeneratedSetupTests.java b/criteria/sql/test/org/immutables/criteria/sql/note/GeneratedSetupTests.java new file mode 100644 index 000000000..499128f43 --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/GeneratedSetupTests.java @@ -0,0 +1,15 @@ +package org.immutables.criteria.sql.note; + +import org.junit.jupiter.api.BeforeEach; + +import javax.sql.DataSource; + +public class GeneratedSetupTests extends AbstractNoteRepositoryTests { + + @BeforeEach + public void onStart() throws Exception { + final DataSource datasource = getDataSource(); + migrate(datasource); + repository = new NoteRepository(SqlNoteSetup.backend(datasource)); + } +} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/Note.java b/criteria/sql/test/org/immutables/criteria/sql/note/Note.java index cb909f537..1eb44385b 100644 --- a/criteria/sql/test/org/immutables/criteria/sql/note/Note.java +++ b/criteria/sql/test/org/immutables/criteria/sql/note/Note.java @@ -15,8 +15,6 @@ */ package org.immutables.criteria.sql.note; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.criteria.Criteria; import org.immutables.criteria.sql.SQL; import org.immutables.value.Value; @@ -28,16 +26,14 @@ @Criteria.Repository @Value.Immutable @SQL.Table("notes") -@JsonDeserialize(as = ImmutableNote.class) -@JsonSerialize(as = ImmutableNote.class) public interface Note { - String message(); + String message(); - @SQL.Column(type = long.class, name = "created_on") - Instant created(); + @SQL.Column(type = long.class, name = "created_on") + Instant created(); - @Criteria.Id - @SQL.Column(type = String.class) - UUID id(); + @Criteria.Id + @SQL.Column(type = String.class) + UUID id(); } diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java b/criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java deleted file mode 100644 index 414c21dbc..000000000 --- a/criteria/sql/test/org/immutables/criteria/sql/note/NoteRepositoryTests.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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.note; - -import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SQLException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.sql.DataSource; -import java.time.Instant; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class NoteRepositoryTests extends AbstractTestBase { - protected NoteRepository repository; - - @BeforeEach - public void onStart() throws Exception { - final DataSource datasource = getDataSource(); - migrate(datasource); - repository = new NoteRepository(NoteSetup.backend(datasource)); - } - - @Test - public void testFindAllCount() { - assertEquals(5, repository.findAll().count()); - } - - @Test - public void testFindWithDistinctCount() { - assertEquals(5, repository.findAll().select(NoteCriteria.note.id).distinct().count()); - } - - @Test - public void testFindWithLimitCount() { - // The limit applies to the result of the COUNT() - assertEquals(5, repository.findAll().limit(3L).count()); - } - - @Test - public void testFindWithLimitSetToZero() { - // The limit applies to the result of the COUNT() and returns no results, so throws an exception - assertThrows(SQLException.class, () -> repository.findAll().limit(0L).count()); - } - - @Test - public void testFindWithOffsetCount() { - // The offset applies to the result of the COUNT() which will result in no results being returned - // then an exception being thrown - assertThrows(SQLException.class, () -> repository.findAll().offset(3L).count()); - } - - @Test - public void testFindAllFetch() { - final List results = repository.findAll().fetch(); - assertEquals(5, results.size()); - assertTrue(results.stream().anyMatch(e -> - e.id().equals(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269")) && - e.created().toEpochMilli() == 1640995200000L && - e.message().equals("Message #1")), - "Missing item"); - } - - @Test - public void testFindAllFetchWithLimit() { - final List results = repository.findAll().limit(3).fetch(); - assertEquals(3, results.size()); - assertTrue(results.stream().anyMatch(e -> - e.id().equals(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269")) && - e.created().toEpochMilli() == 1640995200000L && - e.message().equals("Message #1")), - "Missing item"); - } - - @Test - public void testFindAllFetchWithLimitSetToZero() { - final List results = repository.findAll().limit(0).fetch(); - assertEquals(0, results.size()); - } - - @Test - public void testFindAllFetchWithOffset() { - final List results = repository.findAll().offset(3).fetch(); - assertEquals(2, results.size()); - } - - @Test - public void testFindAllFetchWithLargeOffset() { - final List results = repository.findAll().offset(300L).fetch(); - assertEquals(0, results.size()); - } - - @Test - public void testIdCriteriaBasedFetch() { - final Note results = repository.find(NoteCriteria.note - .id.is(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269"))).one(); - assertNotNull(results); - } - - - @Test - public void testCreatedOnCriteriaBasedFetch() { - final Note results = repository.find(NoteCriteria.note - .created.atLeast(Instant.ofEpochMilli(1640995200000L))).one(); - assertNotNull(results); - } - - - @Test - public void testCriteriaBasedMultiFetch() { - final List results = repository.find(NoteCriteria.note - .id.in(UUID.fromString("7aae20e3-9144-40ba-86f0-c9de8f143269"), - UUID.fromString("75b90525-38be-41b9-b43d-df427a66893c"))) - .fetch(); - assertNotNull(results); - assertEquals(2, results.size()); - } - - @Test - public void testFindOneFetchWithSingleOrdering() { - final Note results = repository.findAll() - .orderBy(NoteCriteria.note.created.asc()) - .limit(1) - .one(); - assertEquals(UUID.fromString("c49371f3-8cda-4ddf-ad74-877ee6f67abe"), results.id()); - assertEquals(0L, results.created().toEpochMilli()); - } - - @Test - public void testFindOneFetchWithMultipleOrdering() { - final Note results = repository.findAll() - .orderBy(NoteCriteria.note.id.asc(), NoteCriteria.note.created.asc()) - .limit(1) - .one(); - assertEquals(UUID.fromString("578f932f-c972-4d3b-92b4-bf8fda9599f9"), results.id()); - assertEquals(1577836800000L, results.created().toEpochMilli()); - } - - @Test - public void testInsertSingleValue() { - final Note event = insertAndVerify(newEntity()); - assertEquals(1, repository.find(NoteCriteria.note.id.is(event.id())).count()); - } - - - @Test - public void testInsertMultipleValues() { - final List data = insertAndVerify(newEntities(5)); - data.forEach(e -> assertEquals(1, repository.find(NoteCriteria.note.id.is(e.id())).count())); - } - - @Test - public void testDeleteWhereNotEmpty() { - repository.delete(NoteCriteria.note.message.notEmpty()); - final long after = repository.findAll().count(); - assertEquals(0, after); - } - - @Test - public void testDeleteSingleUsingCriteria() { - final long before = repository.findAll().count(); - - // Insert new data and verify - final Note data = insertAndVerify(newEntity()); - - // Delete and verify state is correct - repository.delete(NoteCriteria.note.id.is(data.id())); - final long after = repository.findAll().count(); - assertEquals(before, after); - } - - @Test - public void testDeleteMultipleUsingCriteria() { - final long before = repository.findAll().count(); - - // Insert new data and verify - final List data = insertAndVerify(newEntities(3)); - - // Delete and verify state is correct - repository.delete(NoteCriteria.note.id.in(data.stream().map(e -> e.id()).collect(Collectors.toList()))); - final long after = repository.findAll().count(); - assertEquals(before, after); - } - - @Test - public void testUpdateSingleEntityUsingCriteria() { - final String message = "I'm a jolly little update"; - final NoteCriteria criteria = NoteCriteria.note; - final Note target = repository.findAll().fetch().get(0); - final WriteResult result = repository.update(criteria.id.is(target.id())) - .set(criteria.message, message) - .execute(); - assertNotNull(result); - assertTrue(result.updatedCount().isPresent()); - assertEquals(1, result.updatedCount().getAsLong()); - - final Note updated = repository.find(NoteCriteria.note.id.is(target.id())).one(); - assertNotNull(updated); - assertEquals(message, updated.message()); - } - - @Test - public void testUpdateMultipleEntitiesUsingCriteria() { - final String message = "I'm a jolly little update"; - final NoteCriteria criteria = NoteCriteria.note; - final Note target = repository.findAll().fetch().get(0); - final WriteResult result = repository.update(criteria.id.isNot(UUID.randomUUID())) - .set(criteria.message, message) - .execute(); - assertNotNull(result); - assertTrue(result.updatedCount().isPresent()); - assertEquals(5, result.updatedCount().getAsLong()); - - repository.findAll().fetch().forEach(e -> { - assertEquals(message, e.message()); - }); - } - - @Test - public void testUpdateSingleEntityUsingObject() { - final String message = "I'm a jolly little update"; - final Note target = repository.findAll().fetch().get(0); - final ImmutableNote update = ImmutableNote.builder() - .from(target) - .message(message) - .build(); - final WriteResult result = repository.update(update); - assertNotNull(result); - assertTrue(result.updatedCount().isPresent()); - assertEquals(1, result.updatedCount().getAsLong()); - - final Note updated = repository.find(NoteCriteria.note.id.is(target.id())).one(); - assertTrue(repository.find(NoteCriteria.note.id.is(target.id())).exists(), "Unable to find note"); - assertNotNull(updated); - assertEquals(message, updated.message()); - } - - @Test - public void testUpdateMultipleEntitiesUsingObject() { - final String message = "I'm a jolly little update"; - final List targets = repository.findAll().limit(3).fetch(); - final List updates = targets.stream() - .map(e -> ImmutableNote.builder() - .from(e) - .message(message) - .build()).collect(Collectors.toList()); - final WriteResult result = repository.updateAll(updates); - assertNotNull(result); - assertTrue(result.updatedCount().isPresent()); - assertEquals(3, result.updatedCount().getAsLong()); - - targets.forEach(t -> { - final Note updated = repository.find(NoteCriteria.note.id.is(t.id())).one(); - assertTrue(repository.find(NoteCriteria.note.id.is(t.id())).exists(), "Unable to find note"); - assertNotNull(updated); - assertEquals(message, updated.message()); - }); - } - - protected final List insertAndVerify(final List entities) { - final long before = repository.findAll().count(); - - final WriteResult result = repository.insertAll(entities); - assertNotNull(result); - assertTrue(result.insertedCount().isPresent()); - assertTrue(result.deletedCount().isPresent()); - assertTrue(result.updatedCount().isPresent()); - assertEquals(entities.size(), result.insertedCount().getAsLong()); - assertEquals(0, result.deletedCount().getAsLong()); - assertEquals(0, result.updatedCount().getAsLong()); - - // Ensure post-insert state is correct - final long after = repository.findAll().count(); - assertEquals(before + entities.size(), after); - - // Ensure that we can find the entities - entities.forEach(e -> assertEquals(1, repository.find(NoteCriteria.note.id.is(e.id())).count())); - - return entities; - } - - protected final Note insertAndVerify(final Note event) { - // Ensure database state is correct - final long before = repository.findAll().count(); - - // Ensure that the insert worked - final WriteResult result = repository.insert(event); - assertNotNull(result); - assertTrue(result.insertedCount().isPresent()); - assertTrue(result.deletedCount().isPresent()); - assertTrue(result.updatedCount().isPresent()); - assertEquals(1, result.insertedCount().getAsLong()); - assertEquals(0, result.deletedCount().getAsLong()); - assertEquals(0, result.updatedCount().getAsLong()); - - // Ensure post-insert state is correct - final long after = repository.findAll().count(); - assertEquals(before + 1, after); - - // Ensure that we can find the entity - assertEquals(1, repository.find(NoteCriteria.note.id.is(event.id())).count()); - - return event; - } -} diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/notes.sql b/criteria/sql/test/org/immutables/criteria/sql/note/notes.sql new file mode 100644 index 000000000..5d182d8ce --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/notes.sql @@ -0,0 +1,12 @@ +DROP TABLE IF EXISTS notes; +CREATE TABLE notes ( + id VARCHAR NOT NULL PRIMARY KEY, + created_on BIGINT NOT NULL, + message VARCHAR(255) NOT NULL +); +INSERT INTO notes (id, created_on, message) +VALUES('7aae20e3-9144-40ba-86f0-c9de8f143269',1640995200000,'Message #1'), + ('75b90525-38be-41b9-b43d-df427a66893c',1609459200000,'Message #2'), + ('578f932f-c972-4d3b-92b4-bf8fda9599f9',1577836800000,'Message #3'), + ('cf508c17-8217-4f30-a99f-70fdfbcdc643',1546300800000,'Message #4'), + ('c49371f3-8cda-4ddf-ad74-877ee6f67abe',0,'Message #5'); diff --git a/criteria/sql/test/test-migrations.xml b/criteria/sql/test/test-migrations.xml deleted file mode 100644 index be2f47170..000000000 --- a/criteria/sql/test/test-migrations.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 2255b9ae3276411bc3a1744ce3f12610c92e71bb Mon Sep 17 00:00:00 2001 From: Gavin Nicol Date: Thu, 15 Dec 2022 09:07:22 -0500 Subject: [PATCH 4/6] Update pom.xml Move to 2.9.3 release version. --- criteria/sql/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/criteria/sql/pom.xml b/criteria/sql/pom.xml index 6d3762912..72b9bede4 100644 --- a/criteria/sql/pom.xml +++ b/criteria/sql/pom.xml @@ -18,7 +18,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> org.immutables - 2.9.3-SNAPSHOT + 2.9.3 4.0.0 From 614d3bc093fdc430daa94976300b0c96c09f98c3 Mon Sep 17 00:00:00 2001 From: Gavin Nicol Date: Sun, 25 Dec 2022 15:29:27 -0500 Subject: [PATCH 5/6] Cleaning up generator dependencies --- criteria/sql/pom.xml | 95 +++++++++++++++---- .../criteria/sql/generator/Sql.generator | 1 + .../criteria/sql/generator/Sql.java | 2 +- .../criteria/sql/generator/SqlProcessor.java | 4 +- .../immutables/criteria/sql/note/Message.java | 39 ++++++++ 5 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 criteria/sql/test/org/immutables/criteria/sql/note/Message.java diff --git a/criteria/sql/pom.xml b/criteria/sql/pom.xml index 7acd9a052..6271a702e 100644 --- a/criteria/sql/pom.xml +++ b/criteria/sql/pom.xml @@ -17,6 +17,7 @@ + criteria org.immutables 2.9.4-SNAPSHOT @@ -45,36 +46,19 @@ ${project.version} true + org.immutables - generator-processor + generator ${project.version} org.immutables metainf ${project.version} - provided - - - org.immutables - testing - ${project.version} - test - - com.google.code.findbugs - jsr305 - ${jsr305.version} - provided - - - io.reactivex.rxjava2 - rxjava - ${rxjava2.version} - org.immutables testing @@ -95,4 +79,77 @@ test + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + default-compile + compile + + + -XDcompilePolicy=simple + -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 + -Xlint:unchecked + + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + org.immutables + value + ${project.version} + + + org.immutables + metainf + ${project.version} + + + org.immutables + generator-processor + ${project.version} + + + + + + default-testCompile + test-compile + + + -XDcompilePolicy=simple + -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 + -Xlint:unchecked + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + org.immutables + value + ${project.version} + + + org.immutables + criteria-sql + ${project.version} + + + + + + + + diff --git a/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator index ecb14d17b..47198ec51 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator +++ b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.generator @@ -60,6 +60,7 @@ public class [_row_mapper_class_name_] implements RowMapper<[_mapping_.mappingCl public [_row_mapper_class_name_](SqlTypeMetadata metadata) { this.metadata = metadata; } + @Override public [_mapping_.mappingClassName] map(ResultSet row) throws SQLException { [_builder_] builder = [_immutable_].builder(); diff --git a/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java index e61cfa08c..d79271baf 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java +++ b/criteria/sql/src/org/immutables/criteria/sql/generator/Sql.java @@ -31,7 +31,7 @@ TypeElement.class, PackageElement.class }) -abstract class Sql extends AbstractTemplate { +public class Sql extends AbstractTemplate { static String tableName(TypeElement e) { final SQL.Table annotation = e.getAnnotation(SQL.Table.class); if (annotation == null || annotation.value().isEmpty()) { diff --git a/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java b/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java index a7b0b3d0b..6dc92eef6 100644 --- a/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java +++ b/criteria/sql/src/org/immutables/criteria/sql/generator/SqlProcessor.java @@ -25,10 +25,10 @@ @Metainf.Service @Generator.SupportedAnnotations({SQL.Table.class}) -@SupportedSourceVersion(SourceVersion.RELEASE_7) +@SupportedSourceVersion(SourceVersion.RELEASE_8) public class SqlProcessor extends AbstractGenerator { @Override protected void process() { - invoke(new Generator_Sql().generate()); + System.out.println("----------------------------------------");invoke(new Generator_Sql().generate()); } } diff --git a/criteria/sql/test/org/immutables/criteria/sql/note/Message.java b/criteria/sql/test/org/immutables/criteria/sql/note/Message.java new file mode 100644 index 000000000..4a7aa140e --- /dev/null +++ b/criteria/sql/test/org/immutables/criteria/sql/note/Message.java @@ -0,0 +1,39 @@ +/* + * 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.note; + +import org.immutables.criteria.Criteria; +import org.immutables.criteria.sql.SQL; +import org.immutables.value.Value; + +import java.time.Instant; +import java.util.UUID; + +@Criteria +@Criteria.Repository +@Value.Immutable +@SQL.Table("messages") +public interface Message { + String message(); + + @SQL.Column(type = long.class, name = "created_on") + Instant created(); + + @Criteria.Id + @SQL.Column(type = String.class) + UUID id(); +} + From 9f7ffa059457a5b1665dab1d4066daaf5de7f4a1 Mon Sep 17 00:00:00 2001 From: Gavin Nicol Date: Sat, 7 Jan 2023 23:40:18 +0000 Subject: [PATCH 6/6] Removing duplicates caused by name case issues on macos --- .../immutables/criteria/sql/SQLBackend.java | 111 ------- .../immutables/criteria/sql/SQLException.java | 22 -- .../org/immutables/criteria/sql/SQLSetup.java | 45 --- .../criteria/sql/commands/SQLCommand.java | 56 ---- .../sql/commands/SQLCountCommand.java | 80 ----- .../sql/commands/SQLDeleteCommand.java | 75 ----- .../sql/commands/SQLInsertCommand.java | 92 ------ .../criteria/sql/commands/SQLSaveCommand.java | 94 ------ .../sql/commands/SQLSelectCommand.java | 75 ----- .../sql/commands/SQLUpdateCommand.java | 83 ----- .../criteria/sql/compiler/SQLCompiler.java | 171 ---------- .../sql/compiler/SQLConstantExpression.java | 27 -- .../sql/compiler/SQLCountStatement.java | 39 --- .../sql/compiler/SQLDeleteStatement.java | 25 -- .../criteria/sql/compiler/SQLExpression.java | 20 -- .../sql/compiler/SQLFilterExpression.java | 25 -- .../sql/compiler/SQLInsertStatement.java | 29 -- .../sql/compiler/SQLNameExpression.java | 24 -- .../criteria/sql/compiler/SQLNode.java | 20 -- .../criteria/sql/compiler/SQLPathNaming.java | 27 -- .../sql/compiler/SQLQueryVisitor.java | 139 -------- .../sql/compiler/SQLSaveStatement.java | 31 -- .../sql/compiler/SQLSelectStatement.java | 37 --- .../sql/compiler/SQLSortExpression.java | 22 -- .../criteria/sql/compiler/SQLStatement.java | 20 -- .../sql/compiler/SQLUpdateStatement.java | 28 -- .../criteria/sql/dialects/SQLDialect.java | 57 ---- .../sql/reflection/SQLContainerNaming.java | 45 --- .../sql/reflection/SQLPropertyMetadata.java | 30 -- .../sql/reflection/SQLTypeMetadata.java | 171 ---------- .../sql/compiler/SQLCompilerTests.java | 297 ------------------ 31 files changed, 2017 deletions(-) delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQLException.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java delete mode 100644 criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java delete mode 100644 criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java b/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java deleted file mode 100644 index d46d9445b..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLBackend.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 io.reactivex.Flowable; -import org.immutables.criteria.backend.Backend; -import org.immutables.criteria.backend.DefaultResult; -import org.immutables.criteria.backend.StandardOperations; -import org.immutables.criteria.sql.commands.*; -import org.immutables.criteria.sql.reflection.SqlTypeMetadata; -import org.reactivestreams.Publisher; - -/** - * Implementation of {@code Backend} which delegates to the underlying SQL specific commands. - */ -public class SqlBackend implements Backend { - private final SqlSetup setup; - - public SqlBackend(final SqlSetup setup) { - this.setup = setup; - } - - public static SqlBackend of(final SqlSetup setup) { - return new SqlBackend(setup); - } - - @Override - public Session open(final Class type) { - return new SQLSession(type, setup); - } - - public static class SQLSession implements Backend.Session { - private final Class type; - private final SqlSetup setup; - - private final SqlTypeMetadata metadata; - - SQLSession(final Class type, final SqlSetup setup) { - this.type = type; - this.setup = setup; - - metadata = SqlTypeMetadata.of(type); - } - - public SqlSetup setup() { - return setup; - } - - public SqlTypeMetadata metadata() { - return metadata; - } - - @Override - public Class entityType() { - return type; - } - - @Override - public Result execute(final Operation operation) { - return DefaultResult.of(Flowable.defer(() -> executeInternal(operation))); - } - - private Publisher executeInternal(final Operation operation) { - if (operation instanceof StandardOperations.Select) { - final StandardOperations.Select select = (StandardOperations.Select) operation; - final SqlCommand command = select.query().count() - ? new SqlCountCommand(this, setup, select) - : new SqlSelectCommand(this, setup, select); - return command.execute(); - } else if (operation instanceof StandardOperations.Update) { - final StandardOperations.Update update = (StandardOperations.Update) operation; - final SqlCommand command = new SqlSaveCommand(this, setup, update); - return command.execute(); - } else if (operation instanceof StandardOperations.UpdateByQuery) { - final StandardOperations.UpdateByQuery update = (StandardOperations.UpdateByQuery) operation; - final SqlCommand command = new SqlUpdateCommand(this, setup, update); - return command.execute(); - } else if (operation instanceof StandardOperations.Insert) { - final StandardOperations.Insert insert = (StandardOperations.Insert) operation; - final SqlCommand command = new SqlInsertCommand(this, setup, insert); - return command.execute(); - } else if (operation instanceof StandardOperations.Delete) { - final StandardOperations.Delete delete = (StandardOperations.Delete) operation; - final SqlCommand command = new SqlDeleteCommand(this, setup, delete); - return command.execute(); - } else if (operation instanceof StandardOperations.Watch) { - throw new UnsupportedOperationException("Watch"); - } else if (operation instanceof StandardOperations.DeleteByKey) { - throw new UnsupportedOperationException("DeleteByKey"); - } else if (operation instanceof StandardOperations.GetByKey) { - throw new UnsupportedOperationException("GetByKey"); - } - - return Flowable.error(new UnsupportedOperationException(String.format("Operation %s not supported by %s", - operation, SqlBackend.class.getSimpleName()))); - } - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java b/criteria/sql/src/org/immutables/criteria/sql/SQLException.java deleted file mode 100644 index 21b8f9073..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLException.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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; - -public class SqlException extends RuntimeException { - public SqlException(String message) { - super(message); - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java b/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java deleted file mode 100644 index a2b6fcb26..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/SQLSetup.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 org.immutables.criteria.sql.dialects.SQL92Dialect; -import org.immutables.criteria.sql.dialects.SqlDialect; -import org.immutables.criteria.sql.reflection.SqlTypeMetadata; -import org.immutables.value.Value; - -import javax.sql.DataSource; - -@Value.Immutable -public interface SqlSetup { - static SqlSetup of(final DataSource datasource, final SqlTypeMetadata metadata) { - - return of(datasource, new SQL92Dialect(), metadata); - } - - static SqlSetup of(final DataSource datasource, final SqlDialect dialect, final SqlTypeMetadata metadata) { - return ImmutableSqlSetup.builder() - .datasource(datasource) - .dialect(dialect) - .metadata(metadata) - .build(); - } - - SqlTypeMetadata metadata(); - - DataSource datasource(); - - SqlDialect dialect(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java deleted file mode 100644 index b4c71fff6..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCommand.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.commands; - -import org.immutables.criteria.sql.compiler.SqlConstantExpression; -import org.immutables.criteria.sql.conversion.TypeConverters; -import org.reactivestreams.Publisher; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public interface SqlCommand { - Publisher execute(); - - /** - * Convert from properties encoded in {@code }SqlConstantExpression} to the underlying database - * for use by {@code FluentStatment} - * - * @param properties the properties to convert - * @return A new map with keys and types mapped to the underlying database columns and types - */ - default Map toParameters(final Map properties) { - final Map parameters = new HashMap<>(); - for (final SqlConstantExpression property : properties.values()) { - // Conversion of lists to target type lists - used for IN() - if (property.value() instanceof List) { - final List v = (List) property.value(); - final List converted = v.stream() - .map(f -> TypeConverters.convert(f.getClass(), property.target().mapping().type(), f)) - .collect(Collectors.toList()); - parameters.put(property.sql(), converted); - } else { - parameters.put(property.sql(), - TypeConverters.convert(property.value().getClass(), - property.target().mapping().type(), - property.value())); - } - } - return parameters; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java deleted file mode 100644 index 774c10641..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLCountCommand.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.commands; - -import com.google.common.base.Throwables; -import io.reactivex.Flowable; -import org.immutables.criteria.backend.StandardOperations.Select; -import org.immutables.criteria.sql.SqlBackend; -import org.immutables.criteria.sql.SqlException; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.compiler.SqlCompiler; -import org.immutables.criteria.sql.compiler.SqlCountStatement; -import org.immutables.criteria.sql.compiler.SqlFilterExpression; -import org.immutables.criteria.sql.conversion.RowMappers; -import org.immutables.criteria.sql.jdbc.FluentStatement; -import org.reactivestreams.Publisher; - -import java.util.Collections; -import java.util.Optional; -import java.util.concurrent.Callable; - -public class SqlCountCommand implements SqlCommand { - - private final SqlSetup setup; - private final Select operation; - private final SqlBackend.SQLSession session; - - public SqlCountCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Select operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.query().count() : "count() query expected"; - - this.session = session; - this.operation = operation; - this.setup = setup; - } - - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } - - private Callable toCallable(final SqlBackend.SQLSession session, final Select operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; - - final SqlCountStatement count = SqlCompiler.count(setup, operation.query()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().count(count))) { - final Optional ret = statement - .set(toParameters(count - .filter() - .map(SqlFilterExpression::parameters) - .orElse(Collections.emptyMap()))) - .list((rs) -> RowMappers.get(Long.class).map(rs)) - .stream() - .findFirst(); - return ret.orElseThrow(() -> new SqlException("No results returned from count()")); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java deleted file mode 100644 index fa4fd0557..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLDeleteCommand.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.commands; - -import com.google.common.base.Throwables; -import io.reactivex.Flowable; -import org.immutables.criteria.backend.StandardOperations.Delete; -import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SqlBackend; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.compiler.SqlCompiler; -import org.immutables.criteria.sql.compiler.SqlDeleteStatement; -import org.immutables.criteria.sql.compiler.SqlFilterExpression; -import org.immutables.criteria.sql.jdbc.FluentStatement; -import org.reactivestreams.Publisher; - -import java.util.Collections; -import java.util.concurrent.Callable; - -public class SqlDeleteCommand implements SqlCommand { - - private final SqlSetup setup; - private final Delete operation; - private final SqlBackend.SQLSession session; - - public SqlDeleteCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Delete operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - - this.session = session; - this.operation = operation; - this.setup = setup; - } - - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } - - private Callable toCallable(final SqlBackend.SQLSession session, final Delete operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; - - final SqlDeleteStatement delete = SqlCompiler.delete(setup, operation.query()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().delete(delete))) { - final int result = statement - .set(toParameters(delete - .filter() - .map(SqlFilterExpression::parameters) - .orElse(Collections.emptyMap()))) - .delete(); - return WriteResult.empty().withDeletedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java deleted file mode 100644 index fc0816cda..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLInsertCommand.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.commands; - -import com.google.common.base.Throwables; -import io.reactivex.Flowable; -import org.immutables.criteria.backend.StandardOperations.Insert; -import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SqlBackend; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.compiler.SqlCompiler; -import org.immutables.criteria.sql.compiler.SqlConstantExpression; -import org.immutables.criteria.sql.compiler.SqlInsertStatement; -import org.immutables.criteria.sql.conversion.TypeConverters; -import org.immutables.criteria.sql.jdbc.FluentStatement; -import org.reactivestreams.Publisher; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; - -public class SqlInsertCommand implements SqlCommand { - - private final SqlSetup setup; - private final Insert operation; - private final SqlBackend.SQLSession session; - - public SqlInsertCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Insert operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.values().size() > 0 : "insert requires at least 1 object"; - - this.session = session; - this.operation = operation; - this.setup = setup; - } - - private static List> values(final SqlSetup setup, final SqlInsertStatement statement) { - final List> ret = new ArrayList<>(); - for (final Map row : statement.values()) { - final List l = new ArrayList<>(); - for (final String column : statement.columns()) { - final SqlConstantExpression c = row.get(column); - l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); - } - ret.add(l); - } - return ret; - } - - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } - - private Callable toCallable(final SqlBackend.SQLSession session, final Insert operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.values() != null : "Missing `operation.values()` parameter"; - - // Short circuit empty insert - if (operation.values().size() == 0) { - return WriteResult.empty().withInsertedCount(0); - } - - final SqlInsertStatement insert = SqlCompiler.insert(setup, operation.values()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().insert(insert))) { - final int result = statement.insert(values(setup, insert)); - return WriteResult.empty().withInsertedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java deleted file mode 100644 index 3e3e918a4..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSaveCommand.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.commands; - -import com.google.common.base.Throwables; -import io.reactivex.Flowable; -import org.immutables.criteria.backend.StandardOperations.Update; -import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SqlBackend; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.compiler.SqlCompiler; -import org.immutables.criteria.sql.compiler.SqlConstantExpression; -import org.immutables.criteria.sql.compiler.SqlSaveStatement; -import org.immutables.criteria.sql.conversion.TypeConverters; -import org.immutables.criteria.sql.jdbc.FluentStatement; -import org.reactivestreams.Publisher; - -import java.util.*; -import java.util.concurrent.Callable; - -public class SqlSaveCommand implements SqlCommand { - - private final SqlSetup setup; - private final Update operation; - private final SqlBackend.SQLSession session; - - public SqlSaveCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Update operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.values() != null : "update requires values"; - - this.session = session; - this.operation = operation; - this.setup = setup; - } - - private static List> values(final SqlSetup setup, final SqlSaveStatement statement) { - final List> ret = new ArrayList<>(); - final Set properties = new TreeSet<>(statement.columns()); - properties.remove(statement.key()); - for (final Map entity : statement.properties()) { - final List l = new ArrayList<>(); - for (final String property : properties) { - final SqlConstantExpression c = entity.get(property); - l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); - } - // Add key as the last parameters - final SqlConstantExpression c = entity.get(statement.key()); - l.add(TypeConverters.convert(c.type(), c.target().mapping().type(), c.value())); - ret.add(l); - } - return ret; - } - - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } - - private Callable toCallable(final SqlBackend.SQLSession session, final Update operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.values() != null : "Expected `operation.values()` on update"; - - // Short circuit empty update - if (operation.values().size() == 0) { - return WriteResult.empty().withInsertedCount(0); - } - final SqlSaveStatement save = SqlCompiler.save(setup, operation.values()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().save(save))) { - final int result = statement.update(values(setup, save)); - return WriteResult.empty().withUpdatedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java deleted file mode 100644 index bc9f2ce5f..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLSelectCommand.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.commands; - -import com.google.common.base.Throwables; -import io.reactivex.Flowable; -import org.immutables.criteria.backend.StandardOperations.Select; -import org.immutables.criteria.sql.SqlBackend; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.compiler.SqlCompiler; -import org.immutables.criteria.sql.compiler.SqlFilterExpression; -import org.immutables.criteria.sql.compiler.SqlSelectStatement; -import org.immutables.criteria.sql.conversion.RowMappers; -import org.immutables.criteria.sql.jdbc.FluentStatement; -import org.reactivestreams.Publisher; - -import java.util.Collections; -import java.util.concurrent.Callable; - -public class SqlSelectCommand implements SqlCommand { - - private final SqlSetup setup; - private final Select operation; - private final SqlBackend.SQLSession session; - - public SqlSelectCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final Select operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert !operation.query().count() : "count() query unexpected"; - - this.session = session; - this.operation = operation; - this.setup = setup; - } - - @Override - public Publisher execute() { - final Callable> callable = toCallable(session, operation); - return Flowable.fromCallable(callable).flatMapIterable(x -> x); - } - - private Callable> toCallable(final SqlBackend.SQLSession session, final Select operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; - - final SqlSelectStatement select = SqlCompiler.select(setup, operation.query()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().select(select))) { - return statement - .set(toParameters(select - .filter() - .map(SqlFilterExpression::parameters) - .orElse(Collections.emptyMap()))) - .list((rs) -> RowMappers.get(session.metadata()).map(rs)); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java b/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java deleted file mode 100644 index f5df1ee66..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/commands/SQLUpdateCommand.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.commands; - -import com.google.common.base.Throwables; -import io.reactivex.Flowable; -import org.immutables.criteria.backend.StandardOperations.UpdateByQuery; -import org.immutables.criteria.backend.WriteResult; -import org.immutables.criteria.sql.SqlBackend; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.compiler.SqlCompiler; -import org.immutables.criteria.sql.compiler.SqlFilterExpression; -import org.immutables.criteria.sql.compiler.SqlUpdateStatement; -import org.immutables.criteria.sql.jdbc.FluentStatement; -import org.reactivestreams.Publisher; - -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class SqlUpdateCommand implements SqlCommand { - - private final SqlSetup setup; - private final UpdateByQuery operation; - private final SqlBackend.SQLSession session; - - public SqlUpdateCommand(final SqlBackend.SQLSession session, final SqlSetup setup, final UpdateByQuery operation) { - assert session != null : "session cannot be null"; - assert setup != null : "setup cannot be null"; - assert operation != null : "operation cannot be null"; - assert operation.query() != null : "update requires a query"; - - this.session = session; - this.operation = operation; - this.setup = setup; - } - - @Override - public Publisher execute() { - final Callable callable = toCallable(session, operation); - return Flowable.fromCallable(callable); - } - - private Callable toCallable(final SqlBackend.SQLSession session, final UpdateByQuery operation) { - return () -> { - assert operation != null : "Missing `operation` parameter"; - assert operation.query() != null : "Missing `operation.query()` parameter"; - assert operation.values() != null : "Expected `operation.values()` on update"; - - final SqlUpdateStatement update = SqlCompiler.update(setup, operation.query(), operation.values()); - try (final FluentStatement statement = FluentStatement.of(session.setup().datasource(), - session.setup().dialect().update(update))) { - final Map merged = toParameters(Stream.concat( - update.updates().entrySet().stream(), - update.filter() - .map(SqlFilterExpression::parameters) - .orElse(Collections.emptyMap()).entrySet().stream()) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - final int result = statement - .set(merged) - .update(); - return WriteResult.empty().withUpdatedCount(result); - } catch (final Throwable t) { - throw Throwables.propagate(t); - } - }; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java deleted file mode 100644 index 6ae065263..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCompiler.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.criteria.expression.*; -import org.immutables.criteria.sql.SqlException; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; - -import java.util.*; -import java.util.stream.Collectors; - -public class SqlCompiler { - public static SqlCountStatement count(final SqlSetup setup, final Query query) { - // TODO: Aggregations - return ImmutableSqlCountStatement.builder() - .table(setup.metadata().table()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .distinct(query.distinct()) - .filter(compileWhereClause(setup, query.filter())) - .ordering(compileOrderBy(setup, query.collations())) - .qualifier(compileDistinctCount(setup, query).orElse("COUNT(*)")) - .offset(query.offset()) - .limit(query.limit()) - .type(Long.class) - .build(); - } - - public static SqlSelectStatement select(final SqlSetup setup, final Query query) { - // TODO: Projections, Aggregations - return ImmutableSqlSelectStatement.builder() - .table(setup.metadata().table()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .distinct(query.distinct()) - .filter(compileWhereClause(setup, query.filter())) - .ordering(compileOrderBy(setup, query.collations())) - .offset(query.offset()) - .limit(query.limit()) - .type(List.class) - .build(); - } - - private static Optional compileOrderBy(final SqlSetup setup, final List collations) { - final String ordering = collations.stream() - .map(c -> String.format("`%s` %s", - setup.metadata().properties().get(c.path().toString()).mapping().name(), - c.direction().isAscending() ? "ASC" : "DESC")) - .collect(Collectors.joining(",")); - return ordering.length() > 0 ? Optional.of(ordering) : Optional.empty(); - } - - private static Optional compileDistinctCount(final SqlSetup setup, final Query query) { - if (query.distinct()) { - if (query.projections().size() != 1) { - throw new SqlException("Expected a single projection argument to count with distinct"); - } - return Optional.of(String.format("COUNT(DISTINCT `%s`)", - setup.metadata().properties().get(query.projections().get(0).toString()).mapping().name())); - } - return Optional.empty(); - } - - public static SqlDeleteStatement delete(final SqlSetup setup, final Query query) { - return ImmutableSqlDeleteStatement.builder() - .table(setup.metadata().table()) - .filter(compileWhereClause(setup, query.filter())) - .type(Long.class) - .build(); - } - - public static SqlInsertStatement insert(final SqlSetup setup, final List entities) { - return ImmutableSqlInsertStatement.builder() - .table(setup.metadata().table()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .values(toPropertyMap(setup, entities)) - .type(Long.class) - .build(); - } - - public static SqlUpdateStatement update(final SqlSetup setup, final Query query, final Map values) { - final Map updates = new HashMap<>(); - for (final Map.Entry e : values.entrySet()) { - final Path path = (Path) e.getKey(); - final Object value = e.getValue(); - final SqlPropertyMetadata p = setup.metadata().properties().get(path.toString()); - updates.put(p.mapping().name(), ImmutableSqlConstantExpression.builder() - .sql(":" + p.mapping().name()) - .type(p.type()) - .value(value) - .target(p) - .build()); - } - return ImmutableSqlUpdateStatement.builder() - .table(setup.metadata().table()) - .filter(compileWhereClause(setup, query.filter())) - .updates(updates) - .type(Long.class) - .build(); - } - - public static SqlSaveStatement save(final SqlSetup setup, final List entities) { - final Class type = setup.metadata().type(); - if (!(setup.metadata().key().metadata().isKeyDefined() && setup.metadata().key().metadata().isExpression())) { - throw new SqlException("Update using objects requires a simple key to be defined"); - } - - final List> values = new ArrayList<>(); - for (final Object o : entities) { - if (!type.isAssignableFrom(o.getClass())) { - throw new SqlException(String.format("Incompatible save() type. Expected %s found %s", - type.getSimpleName(), o.getClass().getSimpleName())); - } - } - return ImmutableSqlSaveStatement.builder() - .table(setup.metadata().table()) - .key(setup.metadata().key().metadata().keys().get(0).toString()) - .columns(new TreeSet<>(setup.metadata().columns().keySet())) - .properties(toPropertyMap(setup, entities)) - .type(Long.class) - .build(); - } - - private static Optional compileWhereClause(final SqlSetup setup, final Optional filter) { - if (filter.isPresent()) { - if (!(filter.get() instanceof Call)) { - throw new SqlException("Filter expression must be a call"); - } - final SqlQueryVisitor visitor = new SqlQueryVisitor(setup); - return Optional.of(visitor.call((Call) filter.get())); - } - return Optional.empty(); - - } - - private static List> toPropertyMap(final SqlSetup setup, final List entities) { - final Class type = setup.metadata().type(); - final List> values = new ArrayList<>(); - for (final Object o : entities) { - // Sanity check that all the objects in the list match the metadata type - if (!type.isAssignableFrom(o.getClass())) { - throw new SqlException(String.format("Incompatible insert() type. Expected %s found %s", - type.getSimpleName(), o.getClass().getSimpleName())); - } - final Map row = new HashMap<>(); - for (final SqlPropertyMetadata p : setup.metadata().properties().values()) { - final Object value = p.extractor().extract(o); - row.put(p.mapping().name(), ImmutableSqlConstantExpression.builder() - .sql(p.mapping().name()) - .type(p.type()) - .value(value) - .target(p) - .build()); - } - values.add(row); - } - return values; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java deleted file mode 100644 index 9d80be815..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLConstantExpression.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; -import org.immutables.value.Value; - -@Value.Immutable -public interface SqlConstantExpression extends SqlExpression { - - SqlPropertyMetadata target(); - - Object value(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java deleted file mode 100644 index 06ca42a12..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLCountStatement.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.Optional; -import java.util.OptionalLong; -import java.util.Set; - -@Value.Immutable -public interface SqlCountStatement extends SqlStatement { - Optional distinct(); - - String qualifier(); - - OptionalLong limit(); - - OptionalLong offset(); - - Set columns(); - - Optional ordering(); - - Optional filter(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java deleted file mode 100644 index 9f618c1d9..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLDeleteStatement.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.Optional; - -@Value.Immutable -public interface SqlDeleteStatement extends SqlStatement { - Optional filter(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java deleted file mode 100644 index aa34e1540..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLExpression.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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.compiler; - -public interface SqlExpression extends SqlNode { - String sql(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java deleted file mode 100644 index 90d37df71..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLFilterExpression.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.Map; - -@Value.Immutable -public interface SqlFilterExpression extends SqlExpression { - Map parameters(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java deleted file mode 100644 index 2815a3ca2..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLInsertStatement.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.List; -import java.util.Map; -import java.util.SortedSet; - -@Value.Immutable -public interface SqlInsertStatement extends SqlStatement { - SortedSet columns(); - - List> values(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java deleted file mode 100644 index 34e8209b7..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNameExpression.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; -import org.immutables.value.Value; - -@Value.Immutable -public interface SqlNameExpression extends SqlExpression { - SqlPropertyMetadata metadata(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java deleted file mode 100644 index c8fdb1b29..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLNode.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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.compiler; - -public interface SqlNode { - Class type(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java deleted file mode 100644 index 61c120ff9..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLPathNaming.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.criteria.backend.JavaBeanNaming; -import org.immutables.criteria.expression.Path; - -public class SqlPathNaming extends JavaBeanNaming { - @Override - public String name(Path path) { - String name = super.name(path); - return "`" + name.replaceAll("\\.", "`.`") + "`"; - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java deleted file mode 100644 index bf5049840..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLQueryVisitor.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.compiler; - -import com.google.common.reflect.TypeToken; -import org.immutables.criteria.expression.*; -import org.immutables.criteria.sql.SqlException; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.reflection.SqlPropertyMetadata; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -public class SqlQueryVisitor { - private final Map parameters = new HashMap<>(); - private final SqlSetup setup; - - public SqlQueryVisitor(final SqlSetup setup) { - this.setup = setup; - } - - private static SqlFilterExpression unary(final Call call) { - return null; - } - - private static SqlFilterExpression ternary(final Call call) { - return null; - } - - public SqlFilterExpression call(final Call call) { - if (call.operator().arity() == Operator.Arity.UNARY) { - return unary(call); - } - if (call.operator().arity() == Operator.Arity.BINARY) { - return binary(call); - } - if (call.operator().arity() == Operator.Arity.TERNARY) { - return ternary(call); - } - throw new SqlException("Invalid operator arity " + call.operator()); - } - - public SqlConstantExpression constant(final SqlNameExpression target, final Constant constant) { - final String params = constant.values().stream().map(e -> { - final String name = String.format(":param_%d", parameters.size()); - final SqlConstantExpression result = ImmutableSqlConstantExpression.builder() - .sql(name) - .target(target.metadata()) - .value(e) - .type(TypeToken.of(constant.returnType()).getRawType()) - .build(); - parameters.put(name, result); - return name; - }).collect(Collectors.joining(",")); - final SqlConstantExpression result = ImmutableSqlConstantExpression.builder() - .sql(params) - .target(target.metadata()) - .value(constant.value()) - .type(TypeToken.of(constant.returnType()).getRawType()) - .build(); - return result; - } - - public SqlNameExpression path(final Path path) { - final SqlPropertyMetadata meta = setup.metadata() - .properties() - .get(path.toString()); - return ImmutableSqlNameExpression.builder() - // TODO: Column aliases - .sql("`" + meta.mapping().name() + "`") - .metadata(meta) - .type(meta.mapping().type()) - .build(); - } - - private SqlFilterExpression logical(final Call call) { - final Operator op = call.operator(); - final List args = call.arguments(); - - if (!(args.get(0) instanceof Call)) { - throw new SqlException("left hand side of logical expression must be a call"); - } - if (!(args.get(1) instanceof Call)) { - throw new SqlException("right hand side of logical expression must be a call"); - } - final String sql = setup.dialect().logical((Operators) op, - call((Call) args.get(0)), - call((Call) args.get(1))); - return ImmutableSqlFilterExpression.builder() - .sql(sql) - .parameters(parameters) - .type(TypeToken.of(args.get(0).returnType()).getRawType()) - .build(); - - } - - private SqlFilterExpression binary(final Call call) { - final Operator op = call.operator(); - final List args = call.arguments(); - if (args.size() != 2) { - throw new SqlException("binary expression expects exactly 2 args"); - } - if (op == Operators.AND || op == Operators.OR) { - return logical(call); - } - if (!(args.get(0) instanceof Path)) { - throw new SqlException("left hand side of comparison should be a path"); - } - if (!(args.get(1) instanceof Constant)) { - throw new SqlException("right hand side of comparison should be a constant"); - } - final Path left = (Path) args.get(0); - final Constant right = (Constant) args.get(1); - final SqlNameExpression target = path(left); - final Optional operator = setup.dialect().binary(op, target, constant(target, right)); - return operator.map(s -> ImmutableSqlFilterExpression.builder() - .sql(s) - .parameters(parameters) - .type(TypeToken.of(left.returnType()).getRawType()) - .build()).orElseThrow(() -> - new SqlException("Unhandled binary operator " + op)); - } -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java deleted file mode 100644 index 7b19a2225..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSaveStatement.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.List; -import java.util.Map; -import java.util.SortedSet; - -@Value.Immutable -public interface SqlSaveStatement extends SqlStatement { - String key(); - - SortedSet columns(); - - List> properties(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java deleted file mode 100644 index 32539dde1..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSelectStatement.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.Optional; -import java.util.OptionalLong; -import java.util.SortedSet; - -@Value.Immutable -public interface SqlSelectStatement extends SqlStatement { - Optional distinct(); - - OptionalLong limit(); - - OptionalLong offset(); - - Optional filter(); - - Optional ordering(); - - SortedSet columns(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java deleted file mode 100644 index e1fbafef6..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLSortExpression.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -@Value.Immutable -public interface SqlSortExpression extends SqlExpression { -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java deleted file mode 100644 index c10b3a089..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLStatement.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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.compiler; - -public interface SqlStatement extends SqlNode { - String table(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java b/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java deleted file mode 100644 index e05ebd6de..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/compiler/SQLUpdateStatement.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.compiler; - -import org.immutables.value.Value; - -import java.util.Map; -import java.util.Optional; - -@Value.Immutable -public interface SqlUpdateStatement extends SqlStatement { - Map updates(); - - Optional filter(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java b/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java deleted file mode 100644 index d4ea0e36a..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/dialects/SQLDialect.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.dialects; - -import org.immutables.criteria.backend.PathNaming; -import org.immutables.criteria.expression.ComparableOperators; -import org.immutables.criteria.expression.Operator; -import org.immutables.criteria.expression.Operators; -import org.immutables.criteria.expression.StringOperators; -import org.immutables.criteria.sql.compiler.*; - -import java.util.Optional; -import java.util.OptionalLong; -import java.util.regex.Pattern; - -public interface SqlDialect { - PathNaming naming(); - - Optional limit(final OptionalLong limit, final OptionalLong offset); - - Optional string(StringOperators op, SqlExpression left, SqlExpression right); - - Optional comparison(ComparableOperators op, SqlExpression left, SqlExpression right); - - String logical(Operators op, SqlExpression left, SqlExpression right); - - Optional equality(Operators op, SqlExpression left, SqlExpression right); - - Optional binary(Operator op, SqlExpression left, SqlExpression right); - - Optional regex(Operator op, SqlExpression left, Pattern right); - - String count(SqlCountStatement statement); - - String select(SqlSelectStatement statement); - - String delete(SqlDeleteStatement statement); - - String insert(SqlInsertStatement statement); - - String update(SqlUpdateStatement statement); - - String save(SqlSaveStatement statement); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java deleted file mode 100644 index 4bea79eb9..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLContainerNaming.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.reflection; - -import org.immutables.criteria.backend.ContainerNaming; -import org.immutables.criteria.sql.SQL; - -import java.util.Objects; - -public interface SqlContainerNaming extends ContainerNaming { - ContainerNaming FROM_SQL_TABLE_ANNOTATION = clazz -> { - Objects.requireNonNull(clazz, "clazz"); - final SQL.Table annotation = clazz.getAnnotation(SQL.Table.class); - if (annotation == null || annotation.value().isEmpty()) { - throw new UnsupportedOperationException(String.format("%s.name annotation is not defined on %s", - SQL.Table.class.getSimpleName(), clazz.getName())); - } - return annotation.value(); - }; - - ContainerNaming SQL = clazz -> { - try { - return FROM_SQL_TABLE_ANNOTATION.name(clazz); - } catch (UnsupportedOperationException u) { - try { - return FROM_REPOSITORY_ANNOTATION.name(clazz); - } catch (UnsupportedOperationException e) { - return FROM_CLASSNAME.name(clazz); - } - } - }; -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java deleted file mode 100644 index afb2e643b..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLPropertyMetadata.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.reflection; - -import org.immutables.criteria.sql.conversion.ColumnMapping; -import org.immutables.value.Value; - -@Value.Immutable -public interface SqlPropertyMetadata { - String name(); - - Class type(); - - ColumnMapping mapping(); - - PropertyExtractor extractor(); -} diff --git a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java b/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java deleted file mode 100644 index ca2248cb1..000000000 --- a/criteria/sql/src/org/immutables/criteria/sql/reflection/SQLTypeMetadata.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.reflection; - -import org.immutables.criteria.backend.KeyExtractor; -import org.immutables.criteria.reflect.ClassScanner; -import org.immutables.criteria.reflect.MemberExtractor; -import org.immutables.criteria.sql.SQL; -import org.immutables.criteria.sql.SqlException; -import org.immutables.criteria.sql.conversion.ColumnFetchers; -import org.immutables.criteria.sql.conversion.ColumnMapping; -import org.immutables.criteria.sql.conversion.ImmutableColumnMapping; - -import javax.annotation.Nonnull; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class SqlTypeMetadata { - private static final Predicate MAYBE_GETTER = method -> Modifier.isPublic(method.getModifiers()) - && !Modifier.isStatic(method.getModifiers()) - && method.getReturnType() != Void.class - && method.getParameterCount() == 0; - static final Predicate MAYBE_PERSISTED = t -> { - if (t instanceof Method) { - return MAYBE_GETTER.test((Method) t); - } - return false; - }; - private static final Predicate BOOLEAN_GETTER = method -> MAYBE_GETTER.test(method) - && method.getName().startsWith("is") - && method.getName().length() > "is".length() - && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class); - private static final Predicate GENERIC_GETTER = method -> MAYBE_GETTER.test(method) - && method.getName().startsWith("get") - && method.getName().length() > "get".length(); - static final Predicate IS_GETTER = GENERIC_GETTER.or(BOOLEAN_GETTER); - private final String table; - private final Class type; - private final List members; - private final List metadata; - - private final KeyExtractor extractor; - private final Map columns = new HashMap<>(); - private final Map properties = new HashMap<>(); - - private SqlTypeMetadata(final Class clazz) { - type = clazz; - table = SqlContainerNaming.SQL.name(clazz); - extractor = KeyExtractor.defaultFactory().create(clazz); - members = computePersistedMembers(clazz); - metadata = computePropertyMetadata(members); - metadata.forEach(m -> { - properties.put(m.name(), m); - columns.put(m.mapping().name(), m); - }); - } - - public static SqlTypeMetadata of(final Class type) { - return new SqlTypeMetadata(type); - } - - private static List computePersistedMembers(@Nonnull final Class type) { - return ClassScanner.of(type) - .stream() - .filter(m -> MAYBE_PERSISTED.test(m)) - .collect(Collectors.toList()); - } - - private static List computePropertyMetadata(final List members) { - return members.stream() - .map(m -> { - return ImmutableSqlPropertyMetadata.builder() - .name(m instanceof Field ? computePropertyName((Field) m) : computePropertyName((Method) m)) - .type(m instanceof Field ? ((Field) m).getType() : ((Method) m).getReturnType()) - .mapping(computeColumnMapping(m)) - .extractor(v -> MemberExtractor.ofReflection().extract(m, v)) - .build(); - }) - .collect(Collectors.toList()); - } - - private static ColumnMapping computeColumnMapping(final Member m) { - if (m instanceof Field) { - final Field field = (Field) m; - final SQL.Column annotation = field.getAnnotation(SQL.Column.class); - final Class type = annotation != null && annotation.type() != null - ? annotation.type() - : field.getType(); - return ImmutableColumnMapping.builder() - .type(type) - .fetcher(ColumnFetchers.get(type)) - .name(annotation != null && annotation.name().length() > 0 - ? annotation.name() - : computeColumnName(field)) - .build(); - } else if (m instanceof Method) { - final Method method = (Method) m; - final SQL.Column annotation = method.getAnnotation(SQL.Column.class); - final Class type = annotation != null && annotation.type() != null - ? annotation.type() - : method.getReturnType(); - return ImmutableColumnMapping.builder() - .type(type) - .fetcher(ColumnFetchers.get(type)) - .name(annotation != null && annotation.name().length() > 0 - ? annotation.name() - : computeColumnName(method)) - .build(); - } - throw new SqlException("Unable to determine column mapping for member: " + m); - } - - private static String computePropertyName(final Field field) { - return field.getName(); - } - - private static String computePropertyName(final Method method) { - // TODO: String get/is prefix etc. - return method.getName(); - } - - private static String computeColumnName(final Field field) { - return field.getName().toLowerCase(Locale.ROOT); - } - - private static String computeColumnName(final Method method) { - // TODO: String get/is prefix etc. - return method.getName().toLowerCase(Locale.ROOT); - } - - public Class type() { - return type; - } - - public String table() { - return table; - } - - public KeyExtractor key() { - return extractor; - } - - public Map properties() { - return properties; - } - - public Map columns() { - return columns; - } -} diff --git a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java b/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java deleted file mode 100644 index 310fe925a..000000000 --- a/criteria/sql/test/org/immutables/criteria/sql/compiler/SQLCompilerTests.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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.compiler; - -import org.h2.jdbcx.JdbcDataSource; -import org.immutables.criteria.Criterias; -import org.immutables.criteria.expression.Query; -import org.immutables.criteria.sql.SqlSetup; -import org.immutables.criteria.sql.reflection.SqlTypeMetadata; -import org.junit.Test; - -import javax.sql.DataSource; -import java.util.regex.Pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class SqlCompilerTests { - private static DataSource datasource() { - final JdbcDataSource ds = new JdbcDataSource(); - ds.setURL("jdbc:h2:mem:test"); - ds.setUser("sa"); - ds.setPassword("sa"); - return ds; - } - - private static SqlSetup setup(final DataSource datasource, final String table) { - return SqlSetup.of(datasource, SqlTypeMetadata.of(Dummy.class)); - } - - @Test - public void testEmptySelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup, Query.of(Dummy.class)); - assertEquals("SELECT `id`,`name` FROM `dummy`", - setup.dialect().select(result)); - } - - @Test - public void testEqualitySelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.is(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testInequalitySelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.isNot(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)!=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testLessThanSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.lessThan(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testLessThanEqualsSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.atMost(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)<=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testGreaterThanSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.greaterThan(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testGreaterThanEqualsSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.atLeast(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)>=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testBetweenSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.between(1, 2))); - // Note that this is how criteria maps this - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)>=(:param_0))AND((`id`)<=(:param_1))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals(2, result.filter().get().parameters().get(":param_1").value()); - } - - @Test - public void testInSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.in(1, 2, 3))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals(2, result.filter().get().parameters().get(":param_1").value()); - assertEquals(3, result.filter().get().parameters().get(":param_2").value()); - } - - @Test - public void testNotInSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.notIn(1, 2, 3))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`id`)NOT IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals(2, result.filter().get().parameters().get(":param_1").value()); - assertEquals(3, result.filter().get().parameters().get(":param_2").value()); - } - - @Test - public void testStringInSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.in("a", "b", "c"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - assertEquals("b", result.filter().get().parameters().get(":param_1").value()); - assertEquals("c", result.filter().get().parameters().get(":param_2").value()); - } - - @Test - public void testStringNotInSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.notIn("a", "b", "c"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)NOT IN(:param_0,:param_1,:param_2)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - assertEquals("b", result.filter().get().parameters().get(":param_1").value()); - assertEquals("c", result.filter().get().parameters().get(":param_2").value()); - } - - @Test - public void testStringStartsWithSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.startsWith("a"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(:param_0,\"%\"))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testStringEndsWithSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.endsWith("a"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testStringContainsSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.contains("a"))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE(CONCAT(\"%\",:param_0,\"%\"))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("a", result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testStringMatchesSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.matches(Pattern.compile("a.*b'.b?")))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)LIKE('a%b''_b%')", - setup.dialect().select(result)); - } - - @Test - public void testStringHasLengthSelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.hasLength(10))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE LEN(`name`)=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(10, result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testStringIsEmptySelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.isEmpty())); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("", result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testStringIsNotEmptySelect() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.notEmpty())); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE (`name`)!=(:param_0)", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("", result.filter().get().parameters().get(":param_0").value()); - } - - @Test - public void testAndExpression() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.is(1).and(DummyCriteria.dummy.name.is("A")))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))AND((`name`)=(:param_1))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals("A", result.filter().get().parameters().get(":param_1").value()); - } - - @Test - public void testOrExpression() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.id.is(1).or(DummyCriteria.dummy.name.is("A")))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`id`)=(:param_0))OR((`name`)=(:param_1))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals(1, result.filter().get().parameters().get(":param_0").value()); - assertEquals("A", result.filter().get().parameters().get(":param_1").value()); - } - - @Test - public void testCompoundExpression() { - final SqlSetup setup = setup(datasource(), "dummy"); - final SqlSelectStatement result = SqlCompiler.select(setup(datasource(), "dummy"), - Criterias.toQuery(DummyCriteria.dummy.name.is("A") - .or() - .name.is("B") - .id.is(1))); - assertEquals("SELECT `id`,`name` FROM `dummy` WHERE ((`name`)=(:param_0))OR(((`name`)=(:param_1))AND((`id`)=(:param_2)))", - setup.dialect().select(result)); - assertTrue("Missing filter", result.filter().isPresent()); - assertEquals("A", result.filter().get().parameters().get(":param_0").value()); - assertEquals("B", result.filter().get().parameters().get(":param_1").value()); - assertEquals(1, result.filter().get().parameters().get(":param_2").value()); - } -}