Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SQL] Add all combinations of element refs and ids for 'create' methods #576

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -395,43 +398,25 @@ 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);
// 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 ) );
}
private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table, boolean fkRefs )
{
if( fkRefs && !hasRequiredForeignKeys( table ) )
{
return;
}

private void addBuilderMethod( SrcLinkedClass srcClass, SchemaTable table, List<SchemaColumn> 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" );
Expand All @@ -441,34 +426,24 @@ 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);
// 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 ) );
}
private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table, boolean fkRefs )

private void addCreateMethods( SrcLinkedClass srcClass, SchemaTable table, List<SchemaColumn> 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() ? "" : ", " );
Expand All @@ -484,26 +459,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" );
Expand All @@ -513,25 +474,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<SchemaColumn> columnsAsReference )
{
Set<SchemaColumn> fkCovered = new HashSet<>();
for( Map.Entry<SchemaTable, List<SchemaForeignKey>> entry : table.getForeignKeys().entrySet() )
for( List<SchemaForeignKey> fk : table.getForeignKeys().values() )
{
List<SchemaForeignKey> fk = entry.getValue();
for( SchemaForeignKey sfk : fk )
{
List<SchemaColumn> 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 );
Expand Down Expand Up @@ -560,22 +511,10 @@ 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 )
/**
* 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<SchemaColumn> columnsAsReference )
{
// Note, parameters are added in order of appearance as they are with just columns, fks consolidate params

Expand All @@ -584,7 +523,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

Expand Down Expand Up @@ -645,22 +584,6 @@ private Set<SchemaForeignKey> 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 )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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;

public class CombinationUtil {

private CombinationUtil(){
// hide utility class constructor
}

/**
* Create all possible combinations with the provided elements.
* A combinations follows the following rules:
* <ul>
* <li>It doesn't need to use all provided elements. Even an empty list is a valid case.</li>
* <li>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.</li>
* </ul>
*
* <p>
* Example:
* <ul>
* <li>input: {1, 2, 3}</li>
* <li>output: [], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]</li>
* </ul>
* @param elements the input elements
* @return a list of all possible combinations
* @param <T> the type of the elements
*/
public static <T> List<List<T>> createAllCombinations(T[] elements) {
List<List<T>> results = new ArrayList<>();
results.add(Collections.emptyList());
createAllCombinations(elements, results);
return results;
}

/**
* Helper class to create the combinations. See <a href="https://github.com/hmkcode/Java/tree/master/java-combinations">here</a>
* @param elements the elements
* @param results the result list.
* @param <T> the type of the elements
*/
private static <T> void createAllCombinations(T[] elements, List<List<T>> 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 <T> List<T> combinationAsList(int[] combination, T[] elements) {
return Arrays.stream(combination).mapToObj(i -> elements[i]).collect(Collectors.toList());
}

}