All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.starnowski.posjsonhelper.hibernate6.Hibernate6JsonUpdateStatementBuilder Maven / Gradle / Ivy

The newest version!
/**
 * Posjsonhelper library is an open-source project that adds support of
 * Hibernate query for https://www.postgresql.org/docs/10/functions-json.html)
 * 

* Copyright (C) 2023 Szymon Tarnowski *

* This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. *

* This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. *

* You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ package com.github.starnowski.posjsonhelper.hibernate6; import com.github.starnowski.posjsonhelper.core.HibernateContext; import com.github.starnowski.posjsonhelper.hibernate6.functions.JsonbSetFunction; import com.github.starnowski.posjsonhelper.hibernate6.functions.RemoveJsonValuesFromJsonArrayFunction; import com.github.starnowski.posjsonhelper.hibernate6.operators.ConcatenateJsonbOperator; import com.github.starnowski.posjsonhelper.hibernate6.operators.DeleteJsonbBySpecifiedPathOperator; import com.github.starnowski.posjsonhelper.json.core.sql.*; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Path; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.json.JSONArray; import java.util.Collection; import static com.github.starnowski.posjsonhelper.json.core.sql.JsonUpdateStatementOperationType.*; /** * Builder for SQL statement part that allows to set particular json properties. * The idea is to execute some kind of patch operation instead of full update operation for json column value. * To set correct order for operation it uses {@link #jsonUpdateStatementConfigurationBuilder} component. * For example lets imagine that there is entity class Item that has jsonbContent that stores json. * It is possible to update json we below code: *

{@code
 *         // GIVEN
 *         Item item = tested.findById(23L);
 *         DocumentContext document = JsonPath.parse((Object) JsonPath.read(item.getJsonbContent(), "$"));
 *         assertThat(document.jsonString()).isEqualTo("{\"child\":{\"pets\":[\"dog\"]},\"inventory\":[\"mask\",\"fins\"],\"nicknames\":{\"school\":\"bambo\",\"childhood\":\"bob\"}}");
 *         CriteriaUpdate criteriaUpdate = entityManager.getCriteriaBuilder().createCriteriaUpdate(Item.class);
 *         Root root = criteriaUpdate.from(Item.class);
 *
 *         Hibernate6JsonUpdateStatementBuilder hibernate6JsonUpdateStatementBuilder = new Hibernate6JsonUpdateStatementBuilder(root.get("jsonbContent"), (NodeBuilder) entityManager.getCriteriaBuilder(), hibernateContext);
 *         hibernate6JsonUpdateStatementBuilder.appendJsonbSet(new JsonTextArrayBuilder().append("child").append("birthday").build(), quote("2021-11-23"));
 *         hibernate6JsonUpdateStatementBuilder.appendJsonbSet(new JsonTextArrayBuilder().append("child").append("pets").build(), "[\"cat\"]");
 *         hibernate6JsonUpdateStatementBuilder.appendDeleteBySpecificPath(new JsonTextArrayBuilder().append("inventory").append("0").build());
 *         hibernate6JsonUpdateStatementBuilder.appendJsonbSet(new JsonTextArrayBuilder().append("parents").append(0).build(), "{\"type\":\"mom\", \"name\":\"simone\"}");
 *         hibernate6JsonUpdateStatementBuilder.appendJsonbSet(new JsonTextArrayBuilder().append("parents").build(), "[]");
 *         hibernate6JsonUpdateStatementBuilder.appendDeleteBySpecificPath(new JsonTextArrayBuilder().append("nicknames").append("childhood").build());
 *
 *         // Set the property you want to update and the new value
 *         criteriaUpdate.set("jsonbContent", hibernate6JsonUpdateStatementBuilder.build());
 *
 *         // Add any conditions to restrict which entities will be updated
 *         criteriaUpdate.where(entityManager.getCriteriaBuilder().equal(root.get("id"), 23L));
 *
 *         // WHEN
 *         entityManager.createQuery(criteriaUpdate).executeUpdate();
 *
 *         // THEN
 *         entityManager.refresh(item);
 *         document = JsonPath.parse((Object) JsonPath.read(item.getJsonbContent(), "$"));
 *         assertThat(document.jsonString()).isEqualTo("{\"child\":{\"pets\":[\"cat\"],\"birthday\":\"2021-11-23\"},\"parents\":[{\"name\":\"simone\",\"type\":\"mom\"}],\"inventory\":[\"fins\"],\"nicknames\":{\"school\":\"bambo\"}}");
 * }
*

* The above code is going to execute below sql statement for update: * *

{@code
 * update
 *         item
 *     set
 *         jsonb_content=
 *          jsonb_set(
 *              jsonb_set(
 *                  jsonb_set(
 *                      jsonb_set(
 *                          (
 *                              (jsonb_content #- ?::text[]) -- the most nested #- operator
 *                          #- ?::text[])
 *                      , ?::text[], ?::jsonb) -- the most nested jsonb_set operation
 *                  , ?::text[], ?::jsonb)
 *              , ?::text[], ?::jsonb)
 *          , ?::text[], ?::jsonb)
 *     where
 *         id=?
 * }
*

*

* As it can be observed based on generated SQL, by default, the first operation is going to be an operation that deletes JSON content. * The most nested jsonb_set operation is going to set property "parents" with value "[]". * * @param * @param * @see #build() */ public class Hibernate6JsonUpdateStatementBuilder { /** * path object that refers to json property that suppose to be modified */ private final Path rootPath; /** * hibernate component used to create modification operations */ private final NodeBuilder nodeBuilder; /** * Hibernate context */ private final HibernateContext hibernateContext; private final JsonUpdateStatementConfigurationBuilder jsonUpdateStatementConfigurationBuilder; private JsonbSetFunctionFactory jsonbSetFunctionFactory = new DefaultJsonbSetFunctionFactory<>(); private DeleteJsonbBySpecifiedPathOperatorFactory deleteJsonbBySpecifiedPathOperatorFactory = new DefaultDeleteJsonbBySpecifiedPathOperatorFactory<>(); private RemoveArrayItemsFunctionFactory removeArrayItemsFunctionFactory = new DefaultRemoveArrayItemsFunctionFactory<>(); private AddArrayItemsFunctionFactory addArrayItemsFunctionFactory = new DefaultAddArrayItemsFunctionFactory<>(); private final CollectionToJsonArrayStringMapper collectionToJsonArrayStringMapper = new CollectionToJsonArrayStringMapper() { }; /** * Construction initialize property {@link #jsonUpdateStatementConfigurationBuilder} and an instance of * {@link DefaultJsonUpdateStatementOperationSort} as sort component ({@link JsonUpdateStatementConfigurationBuilder#sort}) and an instance * of {@link DefaultJsonUpdateStatementOperationFilter} as filter component ({@link JsonUpdateStatementConfigurationBuilder#postSortFilter}). * * @param rootPath value for {@link #rootPath} * @param nodeBuilder value for {@link #nodeBuilder} * @param hibernateContext value for {@link #hibernateContext} */ public Hibernate6JsonUpdateStatementBuilder(Path rootPath, NodeBuilder nodeBuilder, HibernateContext hibernateContext) { this.rootPath = rootPath; this.nodeBuilder = nodeBuilder; this.hibernateContext = hibernateContext; jsonUpdateStatementConfigurationBuilder = new JsonUpdateStatementConfigurationBuilder() .withSort(new DefaultJsonUpdateStatementOperationSort()) .withPostSortFilter(new DefaultJsonUpdateStatementOperationFilter()); } public Hibernate6JsonUpdateStatementBuilder withAddArrayItemsFunctionFactory(AddArrayItemsFunctionFactory addArrayItemsFunctionFactory) { this.addArrayItemsFunctionFactory = addArrayItemsFunctionFactory; return this; } public Hibernate6JsonUpdateStatementBuilder withRemoveArrayItemsFunctionFactory(RemoveArrayItemsFunctionFactory removeArrayItemsFunctionFactory) { this.removeArrayItemsFunctionFactory = removeArrayItemsFunctionFactory; return this; } public Hibernate6JsonUpdateStatementBuilder withJsonbSetFunctionFactory(JsonbSetFunctionFactory jsonbSetFunctionFactory) { this.jsonbSetFunctionFactory = jsonbSetFunctionFactory; return this; } public Hibernate6JsonUpdateStatementBuilder withDeleteJsonbBySpecifiedPathOperatorFactory(DeleteJsonbBySpecifiedPathOperatorFactory deleteJsonbBySpecifiedPathOperatorFactory) { this.deleteJsonbBySpecifiedPathOperatorFactory = deleteJsonbBySpecifiedPathOperatorFactory; return this; } public JsonUpdateStatementConfigurationBuilder getJsonUpdateStatementConfigurationBuilder() { return jsonUpdateStatementConfigurationBuilder; } /** * Adding {@link JsonUpdateStatementOperationType#JSONB_SET} type operation that set value for specific json path * * @param jsonTextArray json array that specified path for property * @param value json value that suppose to be set * @return a reference to the constructor component for which the methods were executed */ public Hibernate6JsonUpdateStatementBuilder appendJsonbSet(JsonTextArray jsonTextArray, String value) { return appendJsonbSet(jsonTextArray, value, null); } public Hibernate6JsonUpdateStatementBuilder appendJsonbSet(JsonTextArray jsonTextArray, String value, C customValue) { jsonUpdateStatementConfigurationBuilder.append(JSONB_SET, jsonTextArray, value, customValue); return this; } /** * Adding {@link JsonUpdateStatementOperationType#DELETE_BY_SPECIFIC_PATH} type operation that deletes property for specific json path * * @param jsonTextArray json array that specified path for property * @return a reference to the constructor component for which the methods were executed */ public Hibernate6JsonUpdateStatementBuilder appendDeleteBySpecificPath(JsonTextArray jsonTextArray) { jsonUpdateStatementConfigurationBuilder.append(DELETE_BY_SPECIFIC_PATH, jsonTextArray, null); return this; } /** * Setting the {@link JsonUpdateStatementConfigurationBuilder#sort} property for {@link #jsonUpdateStatementConfigurationBuilder} component * * @param sort sorting component * @return a reference to the constructor component for which the methods were executed */ public Hibernate6JsonUpdateStatementBuilder withSort(JsonUpdateStatementConfigurationBuilder.JsonUpdateStatementOperationSort sort) { jsonUpdateStatementConfigurationBuilder.withSort(sort); return this; } /** * Setting the {@link JsonUpdateStatementConfigurationBuilder#postSortFilter} property for {@link #jsonUpdateStatementConfigurationBuilder} component * * @param postSortFilter postSortFilter filtering component * @return a reference to the constructor component for which the methods were executed */ public Hibernate6JsonUpdateStatementBuilder withPostSortFilter(JsonUpdateStatementConfigurationBuilder.JsonUpdateStatementOperationFilter postSortFilter) { jsonUpdateStatementConfigurationBuilder.withPostSortFilter(postSortFilter); return this; } /** * Build part of statement that set json property specified by {@link #rootPath}. * Based on configuration produced by {@link #jsonUpdateStatementConfigurationBuilder} the method generates final expression. * For example: * Lest assume that method {@link JsonUpdateStatementConfigurationBuilder#build()} returns configuration with below list: * *

{@code
     * [
     * JsonUpdateStatementOperation{jsonTextArray={parents}, operation=JSONB_SET, value='[]'},
     * JsonUpdateStatementOperation{jsonTextArray={child,birthday}, operation=JSONB_SET, value='"2021-11-23"'},
     * JsonUpdateStatementOperation{jsonTextArray={child,pets}, operation=JSONB_SET, value='["cat"]'},
     * JsonUpdateStatementOperation{jsonTextArray={parents,0}, operation=JSONB_SET, value='{"type":"mom", "name":"simone"}'}
     * ]
     * }
*

* The expression generated on such would be translated to below sql part: * *

{@code
     *  jsonb_set(
     *      jsonb_set(
     *          jsonb_set(
     *              jsonb_set(jsonb_content, ?::text[], ?::jsonb) -- top operation
     *      , ?::text[], ?::jsonb)
     *  , ?::text[], ?::jsonb)
     * , ?::text[], ?::jsonb)
     * }
* * @return expression object generated based on {@link #jsonUpdateStatementConfigurationBuilder} configuration */ public Expression build() { JsonUpdateStatementConfiguration configuration = jsonUpdateStatementConfigurationBuilder.build(); SqmTypedNode current = null; for (JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation : configuration.getOperations()) { switch (operation.getOperation()) { case DELETE_BY_SPECIFIC_PATH: if (current == null) { current = deleteJsonbBySpecifiedPathOperatorFactory.build(nodeBuilder, rootPath, operation, hibernateContext); } else { current = deleteJsonbBySpecifiedPathOperatorFactory.build(nodeBuilder, current, operation, hibernateContext); } break; case JSONB_SET: if (current == null) { current = jsonbSetFunctionFactory.build(nodeBuilder, rootPath, operation, hibernateContext); } else { current = jsonbSetFunctionFactory.build(nodeBuilder, current, operation, hibernateContext); } break; case REMOVE_ARRAY_ITEMS: if (current == null) { current = removeArrayItemsFunctionFactory.build(nodeBuilder, rootPath, operation, hibernateContext); } else { current = removeArrayItemsFunctionFactory.build(nodeBuilder, current, operation, hibernateContext); } break; case ADD_ARRAY_ITEMS: if (current == null) { current = addArrayItemsFunctionFactory.build(nodeBuilder, rootPath, operation, hibernateContext); } else { current = addArrayItemsFunctionFactory.build(nodeBuilder, current, operation, hibernateContext); } break; } } return (Expression) current; } public Hibernate6JsonUpdateStatementBuilder appendRemoveArrayItems(JsonTextArray jsonTextArray, String jsonArrayString) { jsonUpdateStatementConfigurationBuilder.append(REMOVE_ARRAY_ITEMS, jsonTextArray, jsonArrayString); return this; } public Hibernate6JsonUpdateStatementBuilder appendRemoveArrayItems(JsonTextArray jsonTextArray, Collection collection) { return appendRemoveArrayItems(jsonTextArray, collectionToJsonArrayStringMapper.map(collection)); } public Hibernate6JsonUpdateStatementBuilder appendAddArrayItems(JsonTextArray jsonTextArray, String jsonArrayString) { jsonUpdateStatementConfigurationBuilder.append(ADD_ARRAY_ITEMS, jsonTextArray, jsonArrayString); return this; } public Hibernate6JsonUpdateStatementBuilder appendAddArrayItems(JsonTextArray jsonTextArray, Collection collection) { return appendAddArrayItems(jsonTextArray, collectionToJsonArrayStringMapper.map(collection)); } public interface AddArrayItemsFunctionFactory { default JsonbSetFunction build(NodeBuilder nodeBuilder, Path rootPath, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { ConcatenateJsonbOperator concatenateOperator = new ConcatenateJsonbOperator(nodeBuilder, new JsonBExtractPath(rootPath, nodeBuilder, operation.getJsonTextArray().getPathWithStringValues()), operation.getValue(), hibernateContext); return new JsonbSetFunction(nodeBuilder, (SqmTypedNode) rootPath, operation.getJsonTextArray().toString(), concatenateOperator, hibernateContext); } default JsonbSetFunction build(NodeBuilder nodeBuilder, SqmTypedNode sqmTypedNode, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { ConcatenateJsonbOperator concatenateOperator = new ConcatenateJsonbOperator(nodeBuilder, new JsonBExtractPath(sqmTypedNode, nodeBuilder, operation.getJsonTextArray().getPathWithStringValues()), operation.getValue(), hibernateContext); return new JsonbSetFunction(nodeBuilder, sqmTypedNode, operation.getJsonTextArray().toString(), concatenateOperator, hibernateContext); } } public interface JsonbSetFunctionFactory { default JsonbSetFunction build(NodeBuilder nodeBuilder, Path rootPath, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { return new JsonbSetFunction(nodeBuilder, rootPath, operation.getJsonTextArray().toString(), operation.getValue(), hibernateContext); } default JsonbSetFunction build(NodeBuilder nodeBuilder, SqmTypedNode sqmTypedNode, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { return new JsonbSetFunction(nodeBuilder, sqmTypedNode, operation.getJsonTextArray().toString(), operation.getValue(), hibernateContext); } } public interface RemoveArrayItemsFunctionFactory { default JsonbSetFunction build(NodeBuilder nodeBuilder, Path rootPath, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { RemoveJsonValuesFromJsonArrayFunction deleteOperator = new RemoveJsonValuesFromJsonArrayFunction(nodeBuilder, new JsonBExtractPath(rootPath, nodeBuilder, operation.getJsonTextArray().getPathWithStringValues()), operation.getValue(), hibernateContext); return new JsonbSetFunction(nodeBuilder, (SqmTypedNode) rootPath, operation.getJsonTextArray().toString(), deleteOperator, hibernateContext); } default JsonbSetFunction build(NodeBuilder nodeBuilder, SqmTypedNode sqmTypedNode, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { RemoveJsonValuesFromJsonArrayFunction deleteOperator = new RemoveJsonValuesFromJsonArrayFunction(nodeBuilder, new JsonBExtractPath(sqmTypedNode, nodeBuilder, operation.getJsonTextArray().getPathWithStringValues()), operation.getValue(), hibernateContext); return new JsonbSetFunction(nodeBuilder, sqmTypedNode, operation.getJsonTextArray().toString(), deleteOperator, hibernateContext); } } public interface DeleteJsonbBySpecifiedPathOperatorFactory { default DeleteJsonbBySpecifiedPathOperator build(NodeBuilder nodeBuilder, Path rootPath, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { return new DeleteJsonbBySpecifiedPathOperator(nodeBuilder, rootPath, operation.getJsonTextArray().toString(), hibernateContext); } default DeleteJsonbBySpecifiedPathOperator build(NodeBuilder nodeBuilder, SqmTypedNode sqmTypedNode, JsonUpdateStatementConfiguration.JsonUpdateStatementOperation operation, HibernateContext hibernateContext) { return new DeleteJsonbBySpecifiedPathOperator(nodeBuilder, sqmTypedNode, operation.getJsonTextArray().toString(), hibernateContext); } } public interface CollectionToJsonArrayStringMapper { default String map(Collection collection) { return new JSONArray(collection).toString(); } } public static class DefaultJsonbSetFunctionFactory implements JsonbSetFunctionFactory { } public static class DefaultRemoveArrayItemsFunctionFactory implements RemoveArrayItemsFunctionFactory { } public static class DefaultDeleteJsonbBySpecifiedPathOperatorFactory implements DeleteJsonbBySpecifiedPathOperatorFactory { } public static class DefaultAddArrayItemsFunctionFactory implements AddArrayItemsFunctionFactory { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy