From 721dc163216f8786b68dcad0c487ed3d4fc66bca Mon Sep 17 00:00:00 2001 From: EotT123 Date: Sun, 28 Apr 2024 00:24:05 +0200 Subject: [PATCH 1/3] Create all combinations of element refs and element ids for object creators --- .../sql/schema/type/SchemaParentType.java | 124 +++--------------- .../manifold/sql/util/CombinationUtil.java | 55 ++++++++ 2 files changed, 76 insertions(+), 103 deletions(-) create mode 100644 manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java diff --git a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java index a82eb6f5a..8b525a383 100644 --- a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java +++ b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java @@ -33,6 +33,7 @@ import manifold.sql.rt.util.DriverInfo; import manifold.sql.schema.api.*; import manifold.sql.schema.jdbc.JdbcSchemaForeignKey; +import manifold.sql.util.CombinationUtil; import manifold.util.concurrent.LocklessLazyVar; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -46,6 +47,8 @@ import java.sql.SQLException; import java.sql.Types; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import static java.nio.charset.StandardCharsets.UTF_8; import static manifold.api.gen.AbstractSrcClass.Kind.Class; @@ -395,43 +398,24 @@ private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table ) { return; } - - addBuilderMethod( srcClass, table, true ); - addBuilderMethod( srcClass, table, false ); + SchemaColumn[] requiredForeignKeys = table.getColumns().values().stream().filter( col -> isRequired( col ) && col.getForeignKey() != null ).toArray(SchemaColumn[]::new); + CombinationUtil.createAllCombinations( requiredForeignKeys ).forEach( columnsAsReference -> addBuilderMethod( srcClass, table, columnsAsReference ) ); } - private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table, boolean fkRefs ) - { - if( fkRefs && !hasRequiredForeignKeys( table ) ) - { - return; - } + private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table, List columnsAsReference ) + { SrcMethod method = new SrcMethod( srcClass ) .modifiers( Modifier.STATIC ) .name( "builder" ) .returns( new SrcType( "Builder" ) ); - if( fkRefs ) - { - addRequiredParametersUsingFkRefs( table, method ); - } - else - { - addRequiredParameters( table, method ); - } + addRequiredParameters( table, method, columnsAsReference ); srcClass.addMethod( method ); StringBuilder sb = new StringBuilder(); sb.append( "return new Builder() {\n" ); sb.append( " ${Bindings.class.getName()} _bindings = new DataBindings();\n" ); sb.append( " {\n" ); - if( fkRefs ) - { - initFromParametersUsingFkRefs( table, sb, "_bindings" ); - } - else - { - initFromParameters( table, sb, "_bindings" ); - } + initFromParameters( table, sb, "_bindings", columnsAsReference ); sb.append( " }\n" ); sb.append( " @Override public ${Bindings.class.getName()} getBindings() { return _bindings; }\n" ); @@ -441,34 +425,23 @@ private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table, boole private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table ) { - addCreateMethods( srcClass, table, true ); - addCreateMethods( srcClass, table, false ); + SchemaColumn[] requiredForeignKeys = table.getColumns().values().stream().filter( col -> isRequired( col ) && col.getForeignKey() != null ).toArray(SchemaColumn[]::new); + CombinationUtil.createAllCombinations( requiredForeignKeys ).forEach( columnsAsReference -> addCreateMethods( srcClass, table, columnsAsReference ) ); } - private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table, boolean fkRefs ) + + private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table, List columnsAsReference ) { if( table.getKind() != SchemaTable.Kind.Table ) { return; } - if( fkRefs && !hasRequiredForeignKeys( table ) ) - { - return; - } - String tableName = getTableFqn( table ); SrcMethod method = new SrcMethod( srcClass ) .modifiers( Modifier.STATIC ) .name( "create" ) .returns( new SrcType( tableName ) ); - if( fkRefs ) - { - addRequiredParametersUsingFkRefs( table, method ); - } - else - { - addRequiredParameters( table, method ); - } + addRequiredParameters(table, method, columnsAsReference); StringBuilder sb = new StringBuilder(); sb.append( "return create(defaultScope()" ); sb.append( method.getParameters().isEmpty() ? "" : ", " ); @@ -484,26 +457,12 @@ private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table, boole .returns( new SrcType( tableName ) ) .addParam( new SrcParameter( "txScope", new SrcType( TxScope.class.getSimpleName() ) ) .addAnnotation( NotNull.class.getSimpleName() ) ); - if( fkRefs ) - { - addRequiredParametersUsingFkRefs( table, method ); - } - else - { - addRequiredParameters( table, method ); - } + addRequiredParameters(table, method, columnsAsReference); srcClass.addMethod( method ); sb = new StringBuilder(); sb.append( "DataBindings args = new DataBindings();\n" ); - if( fkRefs ) - { - initFromParametersUsingFkRefs( table, sb, "args" ); - } - else - { - initFromParameters( table, sb, "args" ); - } + initFromParameters( table, sb, "args", columnsAsReference ); sb.append( " TxBindings bindings = new BasicTxBindings(txScope, TxKind.Insert, args);\n" ); sb.append( " $tableName customRow = ${Dependencies.class.getName()}.instance().getCustomEntityFactory().newInstance(bindings, $tableName.class);\n" ); sb.append( " $tableName entity = customRow != null ? customRow : new ${ManClassUtil.getShortClassName(tableName)}Entity(bindings);\n" ); @@ -513,25 +472,15 @@ private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table, boole method.body( sb.toString() ); } - private boolean hasRequiredForeignKeys( SchemaTable table ) - { - // at least one non-null foreign key - return table.getForeignKeys().values().stream() - .anyMatch( sfks -> sfks.stream() - .anyMatch( sfk -> sfk.getColumns().stream() - .anyMatch( c -> isRequired( c ) ) ) ); - } - - private void initFromParametersUsingFkRefs( SchemaTable table, StringBuilder sb, @SuppressWarnings( "unused" ) String bindingsVar ) + private void initFromParameters( SchemaTable table, StringBuilder sb, @SuppressWarnings( "unused" ) String bindingsVar, List columnsAsReference ) { Set fkCovered = new HashSet<>(); - for( Map.Entry> entry : table.getForeignKeys().entrySet() ) + for( List fk : table.getForeignKeys().values() ) { - List fk = entry.getValue(); for( SchemaForeignKey sfk : fk ) { List fkCols = sfk.getColumns(); - if( fkCols.stream().anyMatch( c -> isRequired( c ) ) ) + if( fkCols.stream().anyMatch( c -> isRequired( c ) && columnsAsReference.contains(c)) ) { //noinspection unused String fkParamName = makePascalCaseIdentifier( sfk.getName(), false ); @@ -560,22 +509,7 @@ private void initFromParametersUsingFkRefs( SchemaTable table, StringBuilder sb, } } - private void initFromParameters( SchemaTable table, StringBuilder sb, @SuppressWarnings( "unused" ) String bindingsVar ) - { - for( SchemaColumn col: table.getColumns().values() ) - { - if( isRequired( col ) ) - { - //noinspection unused - String colName = col.getName(); - //noinspection unused - String paramName = makePascalCaseIdentifier( col.getName(), false ); - sb.append( "$bindingsVar.put(\"$colName\", $paramName);\n" ); - } - } - } - - private void addRequiredParametersUsingFkRefs( SchemaTable table, AbstractSrcMethod method ) + private void addRequiredParameters( SchemaTable table, AbstractSrcMethod method, List columnsAsReference ) { // Note, parameters are added in order of appearance as they are with just columns, fks consolidate params @@ -584,7 +518,7 @@ private void addRequiredParametersUsingFkRefs( SchemaTable table, AbstractSrcMet { if( isRequired( col ) ) { - if( col.getForeignKey() != null ) + if( col.getForeignKey() != null && columnsAsReference.contains(col) ) { // Add fk ref param @@ -645,22 +579,6 @@ private Set getfk( SchemaTable table, SchemaColumn col ) return sfkSet; } - private void addRequiredParameters( SchemaTable table, AbstractSrcMethod method ) - { - for( SchemaColumn col: table.getColumns().values() ) - { - if( isRequired( col ) ) - { - SrcParameter param = new SrcParameter( makePascalCaseIdentifier( col.getName(), false ), col.getType() ); - if( !col.getType().isPrimitive() ) - { - param.addAnnotation( NotNull.class.getSimpleName() ); - } - method.addParam( param ); - } - } - } - private void addBuilderType( SrcLinkedClass enclosingType, SchemaTable table ) { if( table.getKind() != SchemaTable.Kind.Table ) diff --git a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java new file mode 100644 index 000000000..29ed2699e --- /dev/null +++ b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java @@ -0,0 +1,55 @@ +package manifold.sql.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +// See https://github.com/hmkcode/Java/tree/master/java-combinations +public class CombinationUtil { + + public static List> createAllCombinations(T[] elements) { + List> results = new ArrayList<>(); + results.add(Collections.emptyList()); + createAllCombinations(elements, results); + return results; + } + + private static void createAllCombinations(T[] elements, List> results) { + int n = elements.length; + for (int k = 1; k <= elements.length; k++) { + // init combination index array + int[] combination = new int[k]; + int r = 0; // index for combination array + int i = 0; // index for elements array + while (r >= 0) { + // forward step if i < (N + (r-K)) + if (i <= (n + (r - k))) { + combination[r] = i; + // if combination array is full print and increment i; + if (r == k - 1) { + results.add(combinationAsList(combination, elements)); + i++; + } else { + // if combination is not full yet, select next element + i = combination[r] + 1; + r++; + } + } + // backward step + else { + r--; + if (r >= 0) { + i = combination[r] + 1; + } + } + } + } + } + + private static List combinationAsList(int[] combination, T[] elements) { + return Arrays.stream(combination).mapToObj(i -> elements[i]).collect(Collectors.toList()); + } + +} From 0c1126340104e9dfd59f8341d7600f38228dccf0 Mon Sep 17 00:00:00 2001 From: EotT123 Date: Sun, 28 Apr 2024 23:56:33 +0200 Subject: [PATCH 2/3] add documentation --- .../sql/schema/type/SchemaParentType.java | 5 ++++ .../manifold/sql/util/CombinationUtil.java | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java index 8b525a383..c9fd6b6b6 100644 --- a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java +++ b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/schema/type/SchemaParentType.java @@ -399,6 +399,7 @@ private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table ) return; } SchemaColumn[] requiredForeignKeys = table.getColumns().values().stream().filter( col -> isRequired( col ) && col.getForeignKey() != null ).toArray(SchemaColumn[]::new); + // Create all possible combinations of the required foreign keys. For each combination, present schema columns are added by reference, others by id CombinationUtil.createAllCombinations( requiredForeignKeys ).forEach( columnsAsReference -> addBuilderMethod( srcClass, table, columnsAsReference ) ); } @@ -426,6 +427,7 @@ private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table, List< private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table ) { SchemaColumn[] requiredForeignKeys = table.getColumns().values().stream().filter( col -> isRequired( col ) && col.getForeignKey() != null ).toArray(SchemaColumn[]::new); + // Create all possible combinations of the required foreign keys. For each combination, present schema columns are added by reference, others by id CombinationUtil.createAllCombinations( requiredForeignKeys ).forEach( columnsAsReference -> addCreateMethods( srcClass, table, columnsAsReference ) ); } @@ -509,6 +511,9 @@ private void initFromParameters( SchemaTable table, StringBuilder sb, @SuppressW } } + /** + * Add the required parameters. All parameters present in the provided list _columnsAsReference_ are added by reference, others by id. + */ private void addRequiredParameters( SchemaTable table, AbstractSrcMethod method, List columnsAsReference ) { // Note, parameters are added in order of appearance as they are with just columns, fks consolidate params diff --git a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java index 29ed2699e..2b4cc4e62 100644 --- a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java +++ b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java @@ -6,9 +6,24 @@ import java.util.List; import java.util.stream.Collectors; -// See https://github.com/hmkcode/Java/tree/master/java-combinations public class CombinationUtil { + private CombinationUtil(){ + // hide utility class constructor + } + + /** + * Create all possible combinations with the provided elements. A combination doesn't need to use all elements. + *

+ * Example: + *

    + *
  • input: {1, 2, 3}
  • + *
  • output: [], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]
  • + *
+ * @param elements the input elements + * @return a list of all possible combinations + * @param the type of the elements + */ public static List> createAllCombinations(T[] elements) { List> results = new ArrayList<>(); results.add(Collections.emptyList()); @@ -16,6 +31,12 @@ public static List> createAllCombinations(T[] elements) { return results; } + /** + * Helper class to create the combinations. See here + * @param elements the elements + * @param results the result list. + * @param the type of the elements + */ private static void createAllCombinations(T[] elements, List> results) { int n = elements.length; for (int k = 1; k <= elements.length; k++) { From 936e010370b7f3a469cc022511aa0712ad12741f Mon Sep 17 00:00:00 2001 From: EotT123 Date: Mon, 29 Apr 2024 00:00:01 +0200 Subject: [PATCH 3/3] add documentation --- .../src/main/java/manifold/sql/util/CombinationUtil.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java index 2b4cc4e62..1b32c051d 100644 --- a/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java +++ b/manifold-deps-parent/manifold-sql/src/main/java/manifold/sql/util/CombinationUtil.java @@ -13,7 +13,13 @@ private CombinationUtil(){ } /** - * Create all possible combinations with the provided elements. A combination doesn't need to use all elements. + * Create all possible combinations with the provided elements. + * A combinations follows the following rules: + *
    + *
  • It doesn't need to use all provided elements. Even an empty list is a valid case.
  • + *
  • Only its elements are important, not the order of the elements. [1, 2] is the same as [2, 1] and will only be included once.
  • + *
+ * *

* Example: *