From ea7aa716f4031e1c21e73b5c28065e9b0b39092d Mon Sep 17 00:00:00 2001 From: x7airworker Date: Thu, 30 Mar 2023 18:02:36 +0200 Subject: [PATCH 1/6] Implemented support for Indices --- .../java/org/javawebstack/orm/TableInfo.java | 12 ++++++ .../javawebstack/orm/annotation/Index.java | 12 ++++++ .../javawebstack/orm/annotation/Indices.java | 12 ++++++ .../orm/migration/AutoMigrator.java | 40 +++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 src/main/java/org/javawebstack/orm/annotation/Index.java create mode 100644 src/main/java/org/javawebstack/orm/annotation/Indices.java diff --git a/src/main/java/org/javawebstack/orm/TableInfo.java b/src/main/java/org/javawebstack/orm/TableInfo.java index e144417..0ec7e55 100644 --- a/src/main/java/org/javawebstack/orm/TableInfo.java +++ b/src/main/java/org/javawebstack/orm/TableInfo.java @@ -32,6 +32,7 @@ public class TableInfo { private String relationField; private final Map filterable = new HashMap<>(); private final List searchable = new ArrayList<>(); + private final List indices = new ArrayList<>(); private static final Class[] appliesDefaultSize = { String.class, @@ -105,6 +106,14 @@ private void analyzeTable(Class model) throws ORMConfigurationE if (!fields.containsKey(dates.update())) throw new ORMConfigurationException("Missing dates field '" + dates.update() + "'"); } + if (model.isAnnotationPresent(Index.class)) { + Index[] unfilteredIndices = model.getDeclaredAnnotationsByType(Index.class); + for (Index index : unfilteredIndices) { + if (index.value().length == 0) + continue; + indices.add(index); + } + } } private void analyzeColumns(Class model) throws ORMConfigurationException { @@ -292,4 +301,7 @@ public String getRelationField() { return relationField; } + public List getIndices() { + return indices; + } } diff --git a/src/main/java/org/javawebstack/orm/annotation/Index.java b/src/main/java/org/javawebstack/orm/annotation/Index.java new file mode 100644 index 0000000..10fe4f8 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/annotation/Index.java @@ -0,0 +1,12 @@ +package org.javawebstack.orm.annotation; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Repeatable(Indices.class) +public @interface Index { + boolean unique() default false; + String id() default ""; + String[] value() default {}; +} diff --git a/src/main/java/org/javawebstack/orm/annotation/Indices.java b/src/main/java/org/javawebstack/orm/annotation/Indices.java new file mode 100644 index 0000000..eb894ba --- /dev/null +++ b/src/main/java/org/javawebstack/orm/annotation/Indices.java @@ -0,0 +1,12 @@ +package org.javawebstack.orm.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Indices { + Index[] value(); +} diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 0d4044c..5ef4520 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -1,7 +1,9 @@ package org.javawebstack.orm.migration; +import jdk.internal.joptsimple.internal.Strings; import org.javawebstack.orm.Repo; import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.annotation.Index; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.wrapper.SQL; @@ -12,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; public class AutoMigrator { @@ -122,6 +125,30 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { } } } + + List existingIndices = getIndices(sql, info.getTableName()); + for (Index index : info.getIndices()) { + String columns = Stream.of(index.value()).map(info::getColumnName).collect(Collectors.joining(",")); + String id = index.id().length() > 0 ? index.id() : "idx_" + Strings.join(index.value(), "_"); + if (existingIndices.contains(id)) + continue; + + StringBuilder sb = new StringBuilder("CREATE "); + if (index.unique()) + sb.append("UNIQUE "); + sb.append("INDEX `") + .append(id) + .append("` ON `") + .append(info.getTableName()) + .append("` (") + .append(columns) + .append(");"); + try { + sql.write(sb.toString()); + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } + } } private static Map getColumnKeys(SQL sql, String tableName) { @@ -137,6 +164,19 @@ private static Map getColumnKeys(SQL sql, String tableName) { } } + private static List getIndices(SQL sql, String tableName) { + try { + List indices = new ArrayList<>(); + ResultSet rs = sql.read("SHOW INDEX FROM `" + tableName + "`;"); + while (rs.next()) { + indices.add(rs.getString(1)); + } + return indices; + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } + } + private static List getTables(SQL sql) { try { List tables = new ArrayList<>(); From 29cac9e45b8d120e3cc2e5101404b92bf4e0f127 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Thu, 30 Mar 2023 20:29:33 +0200 Subject: [PATCH 2/6] Added option to specify BTREE or HASH --- src/main/java/org/javawebstack/orm/annotation/Index.java | 7 +++++++ .../java/org/javawebstack/orm/migration/AutoMigrator.java | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/annotation/Index.java b/src/main/java/org/javawebstack/orm/annotation/Index.java index 10fe4f8..9e4d9e9 100644 --- a/src/main/java/org/javawebstack/orm/annotation/Index.java +++ b/src/main/java/org/javawebstack/orm/annotation/Index.java @@ -6,7 +6,14 @@ @Target(ElementType.TYPE) @Repeatable(Indices.class) public @interface Index { + Type type() default Type.AUTO; boolean unique() default false; String id() default ""; String[] value() default {}; + + enum Type { + AUTO, + BTREE, + HASH + } } diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 5ef4520..888ff40 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -136,6 +136,8 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { StringBuilder sb = new StringBuilder("CREATE "); if (index.unique()) sb.append("UNIQUE "); + if (index.type() != Index.Type.AUTO) + sb.append("USING ").append(index.type().name()).append(" "); sb.append("INDEX `") .append(id) .append("` ON `") From c276e881cac62ac2bac395bc580b43f1055db522 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Thu, 30 Mar 2023 20:32:29 +0200 Subject: [PATCH 3/6] Added option to specify BTREE or HASH --- .../java/org/javawebstack/orm/migration/AutoMigrator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 888ff40..4117da5 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -136,11 +136,10 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { StringBuilder sb = new StringBuilder("CREATE "); if (index.unique()) sb.append("UNIQUE "); + sb.append("INDEX `").append(id).append("` "); if (index.type() != Index.Type.AUTO) sb.append("USING ").append(index.type().name()).append(" "); - sb.append("INDEX `") - .append(id) - .append("` ON `") + sb.append("ON `") .append(info.getTableName()) .append("` (") .append(columns) From a025318f1780b31013cb902389731dec807d081d Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 31 Mar 2023 10:51:37 +0200 Subject: [PATCH 4/6] Replace Strings class with String class --- src/main/java/org/javawebstack/orm/migration/AutoMigrator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 4117da5..e88041a 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -1,6 +1,5 @@ package org.javawebstack.orm.migration; -import jdk.internal.joptsimple.internal.Strings; import org.javawebstack.orm.Repo; import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.annotation.Index; @@ -129,7 +128,7 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { List existingIndices = getIndices(sql, info.getTableName()); for (Index index : info.getIndices()) { String columns = Stream.of(index.value()).map(info::getColumnName).collect(Collectors.joining(",")); - String id = index.id().length() > 0 ? index.id() : "idx_" + Strings.join(index.value(), "_"); + String id = index.id().length() > 0 ? index.id() : "idx_" + String.join("_", index.value()); if (existingIndices.contains(id)) continue; From 6e4dec247341c21f2047b847554b0fbeb9f1aeb6 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 31 Mar 2023 18:09:49 +0200 Subject: [PATCH 5/6] Moved Indices class to subclass of Index annotation --- .../java/org/javawebstack/orm/annotation/Index.java | 8 +++++++- .../org/javawebstack/orm/annotation/Indices.java | 12 ------------ 2 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 src/main/java/org/javawebstack/orm/annotation/Indices.java diff --git a/src/main/java/org/javawebstack/orm/annotation/Index.java b/src/main/java/org/javawebstack/orm/annotation/Index.java index 9e4d9e9..0e77653 100644 --- a/src/main/java/org/javawebstack/orm/annotation/Index.java +++ b/src/main/java/org/javawebstack/orm/annotation/Index.java @@ -4,7 +4,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -@Repeatable(Indices.class) +@Repeatable(Index.Indices.class) public @interface Index { Type type() default Type.AUTO; boolean unique() default false; @@ -16,4 +16,10 @@ enum Type { BTREE, HASH } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @interface Indices { + Index[] value(); + } } diff --git a/src/main/java/org/javawebstack/orm/annotation/Indices.java b/src/main/java/org/javawebstack/orm/annotation/Indices.java deleted file mode 100644 index eb894ba..0000000 --- a/src/main/java/org/javawebstack/orm/annotation/Indices.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.javawebstack.orm.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Indices { - Index[] value(); -} From 01d81f3cfe3d5467f73f6f0844c4d603cf462b6a Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 21 Apr 2023 11:47:16 +0200 Subject: [PATCH 6/6] Added tests, fixed syntax errors and verified implementation --- .../java/org/javawebstack/orm/TableInfo.java | 13 ++--- .../orm/migration/AutoMigrator.java | 2 +- .../orm/test/automigrate/IndexTest.java | 36 ++++++++++++ .../orm/test/shared/models/IndexType.java | 19 +++++++ .../orm/test/shared/verification/Indexes.java | 55 +++++++++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java create mode 100644 src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java create mode 100644 src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java diff --git a/src/main/java/org/javawebstack/orm/TableInfo.java b/src/main/java/org/javawebstack/orm/TableInfo.java index 0ec7e55..b9c1c59 100644 --- a/src/main/java/org/javawebstack/orm/TableInfo.java +++ b/src/main/java/org/javawebstack/orm/TableInfo.java @@ -106,13 +106,12 @@ private void analyzeTable(Class model) throws ORMConfigurationE if (!fields.containsKey(dates.update())) throw new ORMConfigurationException("Missing dates field '" + dates.update() + "'"); } - if (model.isAnnotationPresent(Index.class)) { - Index[] unfilteredIndices = model.getDeclaredAnnotationsByType(Index.class); - for (Index index : unfilteredIndices) { - if (index.value().length == 0) - continue; - indices.add(index); - } + + Index[] unfilteredIndices = model.getAnnotationsByType(Index.class); + for (Index index : unfilteredIndices) { + if (index.value().length == 0) + continue; + indices.add(index); } } diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index e88041a..a1b32b6 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -127,7 +127,7 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { List existingIndices = getIndices(sql, info.getTableName()); for (Index index : info.getIndices()) { - String columns = Stream.of(index.value()).map(info::getColumnName).collect(Collectors.joining(",")); + String columns = Stream.of(index.value()).map(info::getColumnName).map(s -> "`" + s + "`").collect(Collectors.joining(",")); String id = index.id().length() > 0 ? index.id() : "idx_" + String.join("_", index.value()); if (existingIndices.contains(id)) continue; diff --git a/src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java b/src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java new file mode 100644 index 0000000..b86956e --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java @@ -0,0 +1,36 @@ +package org.javawebstack.orm.test.automigrate; + +import org.javawebstack.orm.ORM; +import org.javawebstack.orm.ORMConfig; +import org.javawebstack.orm.exception.ORMConfigurationException; +import org.javawebstack.orm.test.ORMTestCase; +import org.javawebstack.orm.test.shared.models.IndexType; +import org.javawebstack.orm.test.shared.verification.Indexes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; + +public class IndexTest extends ORMTestCase { + + @BeforeEach + public void setUp() throws ORMConfigurationException { + ORMConfig config = new ORMConfig() + .setDefaultSize(255); + ORM.register(IndexType.class, sql(), config); + ORM.autoMigrate(true); + } + + @Test + public void hasIndexes() throws SQLException { + Indexes indexes = new Indexes("index_types"); + indexes.assertHasIndex("idx_key_value"); // auto generated + indexes.assertHasIndex("idx_custom"); // explicit set + } + + @Test + public void testUnique() throws SQLException { + Indexes indexes = new Indexes("index_types"); + indexes.assertIsUnique("idx_key"); + } +} diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java b/src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java new file mode 100644 index 0000000..e9a5085 --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java @@ -0,0 +1,19 @@ +package org.javawebstack.orm.test.shared.models; + +import org.javawebstack.orm.Model; +import org.javawebstack.orm.annotation.Column; +import org.javawebstack.orm.annotation.Index; + +import java.util.UUID; + +@Index({"key", "value"}) +@Index(value = {"id", "key"}, id = "idx_custom") +@Index(value = {"key"}, unique = true) +public class IndexType extends Model { + @Column + UUID id; + @Column + String key; + @Column + String value; +} diff --git a/src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java b/src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java new file mode 100644 index 0000000..3ca806e --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java @@ -0,0 +1,55 @@ +package org.javawebstack.orm.test.shared.verification; + +import lombok.Builder; +import lombok.ToString; +import org.javawebstack.orm.test.shared.settings.MySQLConnectionContainer; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class Indexes extends MySQLConnectionContainer { + String tableName; + List indices = new ArrayList<>(); + + public Indexes(String tableName) throws SQLException { + this.tableName = tableName; + String query = String.format("SHOW INDEXES FROM %s WHERE `key_name` != 'PRIMARY'", tableName); + ResultSet resultSet = sql().read(query); + while (resultSet.next()) { + indices.add(IndexInfo.builder() + .name(resultSet.getString("key_name")) + .type(resultSet.getString("index_type")) + .unique(resultSet.getInt("non_unique") != 1) + .build() + ); + } + } + + public void assertHasIndex(String indexName) { + assertTrue( + indices.stream().anyMatch(i -> i.name.equals(indexName)), + String.format("Index %s.%s doesn't exist on table.", tableName, indexName) + ); + } + + public void assertIsUnique(String indexName) { + IndexInfo indexInfo = indices.stream().filter(i -> i.name.equals(indexName)).findFirst().orElse(null); + assertNotNull(indexInfo); + assertTrue( + indexInfo.unique, + String.format("Index %s.%s is not unique.", tableName, indexInfo) + ); + } + + @Builder + @ToString + public static class IndexInfo { + String name; + String type; + boolean unique; + } +}