Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
babyfish-ct committed Oct 27, 2024
1 parent 9bf3a07 commit 936f952
Show file tree
Hide file tree
Showing 21 changed files with 451 additions and 6 deletions.
2 changes: 1 addition & 1 deletion project/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
group=org.babyfish.jimmer
version=0.9.3
version=0.9.4
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ fun <T: Any> SaveException.NotUnique.isMatched(
)

@Suppress("UNCHECKED_CAST")
operator fun <T: Any> SaveException.NotUnique.get(prop: KProperty1<*, T>): T =
operator fun <T> SaveException.NotUnique.get(prop: KProperty1<*, T>): T =
getValue(prop.toImmutableProp()) as T
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ interface KSaveCommandPartialDsl {

fun setAutoIdOnlyTargetChecking(prop: KProperty1<*, *>)

fun setKeyOnlyAsReferenceAll()

fun setKeyOnlyAsReference(prop: KProperty1<*, *>)

fun setDissociateAction(prop: KProperty1<*, *>, action: DissociateAction)

fun setLockMode(lockMode: LockMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ internal class KSaveCommandDslImpl(
javaCommand = javaCommand.setAutoIdOnlyTargetChecking(prop.toImmutableProp())
}

override fun setKeyOnlyAsReferenceAll() {
javaCommand = javaCommand.setKeyOnlyAsReferenceAll()
}

override fun setKeyOnlyAsReference(prop: KProperty1<*, *>) {
javaCommand = javaCommand.setKeyOnlyAsReference(prop.toImmutableProp())
}

override fun setDissociateAction(prop: KProperty1<*, *>, action: DissociateAction) {
javaCommand = javaCommand.setDissociateAction(prop.toImmutableProp(), action)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,38 @@ interface KNonNullTable<E: Any> : KTable<E>, KNonNullProps<E>, Selection<E> {

fun <S: View<E>> fetch(staticType: KClass<S>): Selection<S>

/**
* If you must convert table types using the "asTableEx()"
* function to find corresponding properties in IDE's
* intelligent suggestions, it likely indicates you are performing
* table join operations on collection-associated properties, for example:
*
* ```
* sql.executeQuery(Book::class) {
* // Table join based on collection association `Book.authors`
* where(table.asTableEx().authors.firstName eq "Alex")
* select(table)
* }
* ```
*
* This usage will lead to data duplication, and whether using
* SQL-level or application-level approaches, you'll need to handle
* data distinction yourself. More importantly, this duplication
* will invalidate the pagination mechanism.
*
* In fact, this usage is not recommended, and the purpose of
* "asTableEx()" is to remind you that you're using a feature
* that might cause problems. In most cases, sub-queries,
* especially implicit sub-queries, are more recommended. For example:
*
* ```
* sql.executeQuery(Book::class) {
* where += table.authors {
* firstName eq "Alex"
* }
* select(table)
* }
* ```
*/
override fun asTableEx(): KNonNullTableEx<E>
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,38 @@ interface KNullableTable<E: Any> : KTable<E>, KNullableProps<E>, Selection<E?> {

override fun <X : Any> getAssociatedId(prop: ImmutableProp): KNullablePropExpression<X>

/**
* If you must convert table types using the "asTableEx()"
* function to find corresponding properties in IDE's
* intelligent suggestions, it likely indicates you are performing
* table join operations on collection-associated properties, for example:
*
* ```
* sql.executeQuery(Book::class) {
* // Table join based on collection association `Book.authors`
* where(table.asTableEx().authors.firstName eq "Alex")
* select(table)
* }
* ```
*
* This usage will lead to data duplication, and whether using
* SQL-level or application-level approaches, you'll need to handle
* data distinction yourself. More importantly, this duplication
* will invalidate the pagination mechanism.
*
* In fact, this usage is not recommended, and the purpose of
* "asTableEx()" is to remind you that you're using a feature
* that might cause problems. In most cases, sub-queries,
* especially implicit sub-queries, are more recommended. For example:
*
* ```
* sql.executeQuery(Book::class) {
* where += table.authors {
* firstName eq "Alex"
* }
* select(table)
* }
* ```
*/
override fun asTableEx(): KNullableTableEx<E>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,44 @@ package org.babyfish.jimmer.sql.kt.ast.table

import org.babyfish.jimmer.sql.ast.impl.table.TableSelection
import org.babyfish.jimmer.sql.kt.ast.expression.KNonNullExpression
import org.babyfish.jimmer.sql.kt.ast.expression.KPropExpression
import org.babyfish.jimmer.sql.kt.ast.expression.isNotNull
import org.babyfish.jimmer.sql.kt.ast.expression.isNull

interface KTable<E: Any> : KProps<E> {

/**
* If you must convert table types using the "asTableEx()"
* function to find corresponding properties in IDE's
* intelligent suggestions, it likely indicates you are performing
* table join operations on collection-associated properties, for example:
*
* ```
* sql.executeQuery(Book::class) {
* // Table join based on collection association `Book.authors`
* where(table.asTableEx().authors.firstName eq "Alex")
* select(table)
* }
* ```
*
* This usage will lead to data duplication, and whether using
* SQL-level or application-level approaches, you'll need to handle
* data distinction yourself. More importantly, this duplication
* will invalidate the pagination mechanism.
*
* In fact, this usage is not recommended, and the purpose of
* "asTableEx()" is to remind you that you're using a feature
* that might cause problems. In most cases, sub-queries,
* especially implicit sub-queries, are more recommended. For example:
*
* ```
* sql.executeQuery(Book::class) {
* where += table.authors {
* firstName eq "Alex"
* }
* select(table)
* }
* ```
*/
fun asTableEx(): KTableEx<E>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,34 @@ public IdOnlyAutoCheckingCfg(Cfg prev, ImmutableProp prop, boolean checking) {
}
}

static class KeyOnlyAsReferenceCfg extends Cfg {

final MapNode<ImmutableProp, Boolean> mapNode;

final boolean defaultValue;

public KeyOnlyAsReferenceCfg(Cfg prev, boolean defaultValue) {
super(prev);
IdOnlyAutoCheckingCfg p = prev.as(IdOnlyAutoCheckingCfg.class);
this.mapNode = p != null ? p.mapNode : null;
this.defaultValue = defaultValue;
}

public KeyOnlyAsReferenceCfg(Cfg prev, ImmutableProp prop, boolean asReference) {
super(prev);
if (!prop.isAssociation(TargetLevel.PERSISTENT)) {
throw new IllegalArgumentException(
"The property \"" +
prop +
"\" is not association property"
);
}
IdOnlyAutoCheckingCfg p = prev.as(IdOnlyAutoCheckingCfg.class);
this.mapNode = new MapNode<>(p != null ? p.mapNode : null, prop, asReference);
this.defaultValue = p != null && p.defaultValue;
}
}

static class TargetTransferModeCfg extends Cfg {

final MapNode<ImmutableProp, TargetTransferMode> mapNode;
Expand Down Expand Up @@ -237,6 +265,10 @@ static final class OptionsImpl implements SaveOptions {

private final boolean autoCheckingAll;

private final Map<ImmutableProp, Boolean> keyOnlyAsReferenceMap;

private final boolean keyOnlyAsReferenceAll;

private final Map<ImmutableProp, DissociateAction> dissociateActionMap;

private final Map<ImmutableProp, TargetTransferMode> targetTransferModeMap;
Expand All @@ -257,6 +289,7 @@ static final class OptionsImpl implements SaveOptions {
DeleteModeCfg deleteModeCfg = cfg.as(DeleteModeCfg.class);
KeyPropsCfg keyPropsCfg = cfg.as(KeyPropsCfg.class);
IdOnlyAutoCheckingCfg idOnlyAutoCheckingCfg = cfg.as(IdOnlyAutoCheckingCfg.class);
KeyOnlyAsReferenceCfg keyOnlyAsReferenceCfg = cfg.as(KeyOnlyAsReferenceCfg.class);
DissociationActionCfg dissociationActionCfg = cfg.as(DissociationActionCfg.class);
TargetTransferModeCfg targetTransferModeCfg = cfg.as(TargetTransferModeCfg.class);
LockModeCfg lockModeCfg = cfg.as(LockModeCfg.class);
Expand All @@ -276,8 +309,10 @@ static final class OptionsImpl implements SaveOptions {
deleteModeCfg.mode :
DeleteMode.AUTO;
this.keyPropMultiMap = MapNode.toMap(keyPropsCfg, it -> it.mapNode);;
this.autoCheckingMap = MapNode.toMap(idOnlyAutoCheckingCfg, it -> it.mapNode);;
this.autoCheckingMap = MapNode.toMap(idOnlyAutoCheckingCfg, it -> it.mapNode);
this.autoCheckingAll = idOnlyAutoCheckingCfg != null && idOnlyAutoCheckingCfg.defaultValue;
this.keyOnlyAsReferenceMap = MapNode.toMap(keyOnlyAsReferenceCfg, it -> it.mapNode);
this.keyOnlyAsReferenceAll = keyOnlyAsReferenceCfg != null && keyOnlyAsReferenceCfg.defaultValue;
this.dissociateActionMap = MapNode.toMap(dissociationActionCfg, it -> it.mapNode);;
this.targetTransferModeMap = MapNode.toMap(targetTransferModeCfg, it -> it.mapNode);;
this.targetTransferModeAll = targetTransferModeCfg != null ?
Expand Down Expand Up @@ -363,6 +398,14 @@ public boolean isAutoCheckingProp(ImmutableProp prop) {
return autoCheckingAll || Boolean.TRUE.equals(autoCheckingMap.get(prop));
}

public boolean isKeyOnlyAsReference(ImmutableProp prop) {
Boolean value = keyOnlyAsReferenceMap.get(prop);
if (value != null) {
return value;
}
return keyOnlyAsReferenceAll;
}

@Override
public DissociateAction getDissociateAction(ImmutableProp prop) {
DissociateAction action = dissociateActionMap.get(prop);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ public BatchEntitySaveCommand<E> setAutoIdOnlyTargetChecking(ImmutableProp prop,
return new BatchEntitySaveCommandImpl<>(new IdOnlyAutoCheckingCfg(cfg, prop, checking));
}

@Override
public BatchEntitySaveCommand<E> setKeyOnlyAsReferenceAll() {
return new BatchEntitySaveCommandImpl<>(new KeyOnlyAsReferenceCfg(cfg, true));
}

@Override
public BatchEntitySaveCommand<E> setKeyOnlyAsReference(ImmutableProp prop, boolean asReference) {
return new BatchEntitySaveCommandImpl<>(new KeyOnlyAsReferenceCfg(cfg, prop,asReference));
}

@Override
public BatchEntitySaveCommand<E> setDissociateAction(ImmutableProp prop, DissociateAction dissociateAction) {
return new BatchEntitySaveCommandImpl<>(new DissociationActionCfg(cfg, prop, dissociateAction));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ public boolean isAutoCheckingProp(ImmutableProp prop) {
return false;
}

@Override
public boolean isKeyOnlyAsReference(ImmutableProp prop) {
return false;
}

@Override
public @Nullable ExceptionTranslator<Exception> getExceptionTranslator() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ final QueryReason queryReason(boolean hasId, Collection<DraftSpi> drafts) {
if (idGenerator == null) {
ctx.throwNoIdGenerator();
}
ImmutableProp prop = ctx.path.getProp();
if (prop != null && ctx.options.isKeyOnlyAsReference(prop) && isKeyOnly(drafts)) {
return QueryReason.KEY_ONLY_AS_REFERENCE;
}
if (!(idGenerator instanceof IdentityIdGenerator)) {
return QueryReason.IDENTITY_GENERATOR_REQUIRED;
}
Expand Down Expand Up @@ -542,6 +546,50 @@ private void validateAloneIds() {
}
}

private boolean isKeyOnly(Collection<DraftSpi> drafts) {
Set<ImmutableProp> keyProps = ctx.options.getKeyProps(ctx.path.getType());
if (keyProps == null || keyProps.isEmpty()) {
return false;
}
for (DraftSpi draft : drafts) {
if (!isKeyOnly(draft, keyProps)) {
return false;
}
}
return true;
}

private boolean isKeyOnly(DraftSpi draft, Set<ImmutableProp> keyProps) {
boolean hasKey = false;
for (ImmutableProp prop : ctx.path.getType().getProps().values()) {
if (!prop.isColumnDefinition()) {
continue;
}
boolean isLoaded = draft.__isLoaded(prop.getId());
if (prop.isReference(TargetLevel.PERSISTENT)) {
Object value = draft.__get(prop.getId());
if (value != null) {
ImmutableSpi target = (ImmutableSpi) value;
if (!target.__isLoaded(target.__type().getIdProp().getId())) {
isLoaded = false;
}
}
}
if (keyProps.contains(prop)) {
if (isLoaded) {
hasKey = true;
} else {
return false;
}
} else {
if (isLoaded) {
return false;
}
}
}
return hasKey;
}

private static Object columnValue(DraftSpi draft, ImmutableProp prop) {
PropId propId = prop.getId();
if (!draft.__isLoaded(propId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ public enum QueryReason {
*/
IDENTITY_GENERATOR_REQUIRED,

/**
* When the configuration `keyOnlyObjectAsReference`
* of the association is allowed, the key-only object
* will be ignored, the keys must be converted to ids
* by extra select statement.
*/
KEY_ONLY_AS_REFERENCE,

/**
* Direct use of a delete statement, but upon discovering that the
* object being deleted has child objects and the user requires Jimmer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public interface SaveOptions {

boolean isAutoCheckingProp(ImmutableProp prop);

boolean isKeyOnlyAsReference(ImmutableProp prop);

@Nullable
ExceptionTranslator<Exception> getExceptionTranslator();

Expand Down Expand Up @@ -124,6 +126,11 @@ public boolean isAutoCheckingProp(ImmutableProp prop) {
return raw.isAutoCheckingProp(prop);
}

@Override
public boolean isKeyOnlyAsReference(ImmutableProp prop) {
return raw.isKeyOnlyAsReference(prop);
}

@Override
@Nullable
public ExceptionTranslator<Exception> getExceptionTranslator() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.babyfish.jimmer.runtime.ImmutableSpi;
import org.babyfish.jimmer.sql.ast.impl.value.PropertyGetter;
import org.babyfish.jimmer.sql.runtime.JSqlClientImplementor;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.function.Predicate;
Expand Down Expand Up @@ -42,10 +43,12 @@ public static Shape fullOf(JSqlClientImplementor sqlClient, Class<?> type) {
return new Shape(immutableType, PropertyGetter.entityGetters(sqlClient, immutableType, null, null));
}

@NotNull
public ImmutableType getType() {
return type;
}

@NotNull
public List<PropertyGetter> getGetters() {
return getters;
}
Expand Down
Loading

0 comments on commit 936f952

Please sign in to comment.