From c044a7d89049830b69107679fc6d3b876fb02740 Mon Sep 17 00:00:00 2001 From: Martina Gallati Date: Mon, 23 Dec 2024 18:54:44 +0100 Subject: [PATCH] DOPE-271: added missing returning clause functionality --- .../dope/clauses/DeleteIntegrationTest.kt | 3 +- .../dope/clauses/UpdateIntegrationTest.kt | 2 + .../dope/resolvable/clause/IDeleteClause.kt | 21 +- .../dope/resolvable/clause/IUpdateClause.kt | 21 +- .../clause/model/ReturningClause.kt | 121 ++++++++-- .../expression/AsteriskExpression.kt | 3 +- .../dope/resolvable/fromable/Fromable.kt | 2 + .../ch/ergon/dope/buildTest/DeleteTest.kt | 2 +- .../resolvable/clause/ReturningClauseTest.kt | 214 ++++++++++++++++-- .../dope/extension/clause/DeleteClause.kt | 17 +- .../dope/extension/clause/UpdateClause.kt | 17 +- .../extensions/clause/DeleteClauseTest.kt | 59 ++++- .../extensions/clause/UpdateClauseTest.kt | 57 ++++- 13 files changed, 488 insertions(+), 51 deletions(-) diff --git a/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/DeleteIntegrationTest.kt b/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/DeleteIntegrationTest.kt index aa2983de..1cf29455 100644 --- a/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/DeleteIntegrationTest.kt +++ b/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/DeleteIntegrationTest.kt @@ -28,7 +28,8 @@ class DeleteIntegrationTest : BaseIntegrationTest() { ) .returning( idField, - ).build() + ).thenReturningAsterisk() + .build() tryUntil { val queryResult = queryWithoutParameters(dopeQuery) diff --git a/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/UpdateIntegrationTest.kt b/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/UpdateIntegrationTest.kt index 725bf8d1..48d6986e 100644 --- a/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/UpdateIntegrationTest.kt +++ b/core/src/integrationTest/kotlin/ch/ergon/dope/clauses/UpdateIntegrationTest.kt @@ -37,6 +37,8 @@ class UpdateIntegrationTest : BaseIntegrationTest() { ) .returning( newField, + ) + .thenReturning( nameField, ).build() diff --git a/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IDeleteClause.kt b/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IDeleteClause.kt index fa1d83e5..23d4e5e1 100644 --- a/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IDeleteClause.kt +++ b/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IDeleteClause.kt @@ -3,10 +3,16 @@ package ch.ergon.dope.resolvable.clause import ch.ergon.dope.resolvable.clause.model.DeleteLimitClause import ch.ergon.dope.resolvable.clause.model.DeleteOffsetClause import ch.ergon.dope.resolvable.clause.model.DeleteReturningClause +import ch.ergon.dope.resolvable.clause.model.DeleteReturningSingleClause import ch.ergon.dope.resolvable.clause.model.DeleteWhereClause +import ch.ergon.dope.resolvable.clause.model.ReturningExpression +import ch.ergon.dope.resolvable.clause.model.ReturningType.ELEMENT +import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW +import ch.ergon.dope.resolvable.clause.model.ReturningType.VALUE +import ch.ergon.dope.resolvable.expression.AsteriskExpression import ch.ergon.dope.resolvable.expression.TypeExpression -import ch.ergon.dope.resolvable.expression.unaliased.type.Field import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType +import ch.ergon.dope.resolvable.fromable.Returnable import ch.ergon.dope.validtype.BooleanType import ch.ergon.dope.validtype.NumberType import ch.ergon.dope.validtype.ValidType @@ -14,7 +20,18 @@ import ch.ergon.dope.validtype.ValidType interface IDeleteReturningClause : Clause interface IDeleteOffsetClause : IDeleteReturningClause { - fun returning(field: Field, vararg fields: Field) = DeleteReturningClause(field, *fields, parentClause = this) + fun returning(returningExpression: Returnable, vararg additionalReturningExpressions: Returnable) = + DeleteReturningClause(returningExpression, *additionalReturningExpressions, parentClause = this) + fun returning(typeExpression: TypeExpression) = + DeleteReturningClause(ReturningExpression(typeExpression), parentClause = this) + fun returningAsterisk() = DeleteReturningClause(AsteriskExpression(), parentClause = this) + + fun returningRaw(typeExpression: TypeExpression) = + DeleteReturningSingleClause(typeExpression, returningType = RAW, parentClause = this) + fun returningValue(typeExpression: TypeExpression) = + DeleteReturningSingleClause(typeExpression, returningType = VALUE, parentClause = this) + fun returningElement(typeExpression: TypeExpression) = + DeleteReturningSingleClause(typeExpression, returningType = ELEMENT, parentClause = this) } interface IDeleteLimitClause : IDeleteOffsetClause { diff --git a/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IUpdateClause.kt b/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IUpdateClause.kt index dadaffc3..f3e37a21 100644 --- a/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IUpdateClause.kt +++ b/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/IUpdateClause.kt @@ -1,14 +1,21 @@ package ch.ergon.dope.resolvable.clause +import ch.ergon.dope.resolvable.clause.model.ReturningExpression +import ch.ergon.dope.resolvable.clause.model.ReturningType.ELEMENT +import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW +import ch.ergon.dope.resolvable.clause.model.ReturningType.VALUE import ch.ergon.dope.resolvable.clause.model.SetClause import ch.ergon.dope.resolvable.clause.model.UnsetClause import ch.ergon.dope.resolvable.clause.model.UpdateLimitClause import ch.ergon.dope.resolvable.clause.model.UpdateReturningClause +import ch.ergon.dope.resolvable.clause.model.UpdateReturningSingleClause import ch.ergon.dope.resolvable.clause.model.UpdateWhereClause import ch.ergon.dope.resolvable.clause.model.to +import ch.ergon.dope.resolvable.expression.AsteriskExpression import ch.ergon.dope.resolvable.expression.TypeExpression import ch.ergon.dope.resolvable.expression.unaliased.type.Field import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType +import ch.ergon.dope.resolvable.fromable.Returnable import ch.ergon.dope.validtype.BooleanType import ch.ergon.dope.validtype.NumberType import ch.ergon.dope.validtype.StringType @@ -17,8 +24,18 @@ import ch.ergon.dope.validtype.ValidType interface IUpdateReturningClause : Clause interface IUpdateLimitClause : IUpdateReturningClause { - fun returning(field: Field, vararg fields: Field) = - UpdateReturningClause(field, *fields, parentClause = this) + fun returning(returningExpression: Returnable, vararg additionalReturningExpressions: Returnable) = + UpdateReturningClause(returningExpression, *additionalReturningExpressions, parentClause = this) + fun returning(typeExpression: TypeExpression) = + UpdateReturningClause(ReturningExpression(typeExpression), parentClause = this) + fun returningAsterisk() = UpdateReturningClause(AsteriskExpression(), parentClause = this) + + fun returningRaw(typeExpression: TypeExpression) = + UpdateReturningSingleClause(typeExpression, returningType = RAW, parentClause = this) + fun returningValue(typeExpression: TypeExpression) = + UpdateReturningSingleClause(typeExpression, returningType = VALUE, parentClause = this) + fun returningElement(typeExpression: TypeExpression) = + UpdateReturningSingleClause(typeExpression, returningType = ELEMENT, parentClause = this) } interface IUpdateWhereClause : IUpdateLimitClause { diff --git a/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/model/ReturningClause.kt b/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/model/ReturningClause.kt index 78e47af0..37673bd9 100644 --- a/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/model/ReturningClause.kt +++ b/core/src/main/kotlin/ch/ergon/dope/resolvable/clause/model/ReturningClause.kt @@ -2,47 +2,132 @@ package ch.ergon.dope.resolvable.clause.model import ch.ergon.dope.DopeQuery import ch.ergon.dope.DopeQueryManager +import ch.ergon.dope.resolvable.Resolvable import ch.ergon.dope.resolvable.clause.Clause import ch.ergon.dope.resolvable.clause.IDeleteOffsetClause import ch.ergon.dope.resolvable.clause.IDeleteReturningClause import ch.ergon.dope.resolvable.clause.IUpdateLimitClause import ch.ergon.dope.resolvable.clause.IUpdateReturningClause -import ch.ergon.dope.resolvable.expression.unaliased.type.Field +import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW +import ch.ergon.dope.resolvable.expression.AsteriskExpression +import ch.ergon.dope.resolvable.expression.TypeExpression import ch.ergon.dope.resolvable.formatToQueryStringWithSymbol +import ch.ergon.dope.resolvable.fromable.Returnable import ch.ergon.dope.validtype.ValidType +enum class ReturningType(val queryString: String) { + ELEMENT("ELEMENT"), + RAW("RAW"), + VALUE("VALUE"), +} + sealed class ReturningClause( - private val field: Field, - private vararg val fields: Field, + private val returnable: Returnable, + private vararg val additionalReturnables: Returnable, private val parentClause: Clause, -) { - fun toDopeQuery(manager: DopeQueryManager): DopeQuery { - val fieldsDopeQuery = fields.map { it.toDopeQuery(manager) } - val fieldDopeQuery = field.toDopeQuery(manager) +) : Resolvable { + override fun toDopeQuery(manager: DopeQueryManager): DopeQuery { val parentDopeQuery = parentClause.toDopeQuery(manager) + val returnableDopeQuery = returnable.toDopeQuery(manager) + val additionalReturnableDopeQueries = additionalReturnables.map { it.toDopeQuery(manager) } return DopeQuery( queryString = formatToQueryStringWithSymbol( parentDopeQuery.queryString, "RETURNING", - fieldDopeQuery.queryString, - *fieldsDopeQuery.map { it.queryString }.toTypedArray(), + returnableDopeQuery.queryString, + *additionalReturnableDopeQueries.map { it.queryString }.toTypedArray(), ), parameters = parentDopeQuery.parameters.merge( - fieldDopeQuery.parameters, - *fieldsDopeQuery.map { it.parameters }.toTypedArray(), + returnableDopeQuery.parameters, + *additionalReturnableDopeQueries.map { it.parameters }.toTypedArray(), ), ) } } +class ReturningExpression(private val typeExpression: TypeExpression) : Returnable { + override fun toDopeQuery(manager: DopeQueryManager): DopeQuery { + return typeExpression.toDopeQuery(manager) + } +} + class DeleteReturningClause( - field: Field, - vararg fields: Field, - parentClause: IDeleteOffsetClause, -) : IDeleteReturningClause, ReturningClause(field, *fields, parentClause = parentClause) + private val returnable: Returnable, + private vararg val additionalReturnables: Returnable, + private val parentClause: IDeleteOffsetClause, +) : IDeleteReturningClause, ReturningClause( + returnable, + *additionalReturnables, + parentClause = parentClause, +) { + fun thenReturning(typeExpression: TypeExpression) = DeleteReturningClause( + this.returnable, + *this.additionalReturnables, + ReturningExpression(typeExpression), + parentClause = this.parentClause, + ) + + fun thenReturningAsterisk() = DeleteReturningClause( + this.returnable, + *this.additionalReturnables, + AsteriskExpression(), + parentClause = this.parentClause, + ) +} class UpdateReturningClause( - field: Field, - vararg fields: Field, + private val returnable: Returnable, + private vararg val additionalReturnables: Returnable, + private val parentClause: IUpdateLimitClause, +) : IUpdateReturningClause, ReturningClause( + returnable, + *additionalReturnables, + parentClause = parentClause, +) { + fun thenReturning(typeExpression: TypeExpression) = UpdateReturningClause( + this.returnable, + *this.additionalReturnables, + ReturningExpression(typeExpression), + parentClause = this.parentClause, + ) + + fun thenReturningAsterisk() = UpdateReturningClause( + this.returnable, + *this.additionalReturnables, + AsteriskExpression(), + parentClause = this.parentClause, + ) +} + +sealed class ReturningSingleClause( + private val typeExpression: TypeExpression, + private val returningType: ReturningType = RAW, + private val parentClause: Clause, +) : Resolvable { + override fun toDopeQuery(manager: DopeQueryManager): DopeQuery { + val parentDopeQuery = parentClause.toDopeQuery(manager) + val typeExpressionDopeQuery = typeExpression.toDopeQuery(manager) + return DopeQuery( + queryString = formatToQueryStringWithSymbol( + parentDopeQuery.queryString, + "RETURNING " + returningType.queryString, + typeExpressionDopeQuery.queryString, + ), + parameters = parentDopeQuery.parameters.merge( + typeExpressionDopeQuery.parameters, + ), + ) + } +} + +class DeleteReturningSingleClause( + typeExpression: TypeExpression, + returningType: ReturningType = RAW, + parentClause: IDeleteOffsetClause, +) : IDeleteReturningClause, ReturningSingleClause(typeExpression, returningType, parentClause = parentClause) + +class UpdateReturningSingleClause( + typeExpression: TypeExpression, + returningType: ReturningType = RAW, parentClause: IUpdateLimitClause, -) : IUpdateReturningClause, ReturningClause(field, *fields, parentClause = parentClause) +) : IUpdateReturningClause, ReturningSingleClause(typeExpression, returningType, parentClause = parentClause) diff --git a/core/src/main/kotlin/ch/ergon/dope/resolvable/expression/AsteriskExpression.kt b/core/src/main/kotlin/ch/ergon/dope/resolvable/expression/AsteriskExpression.kt index d3cbdc2e..a1307291 100644 --- a/core/src/main/kotlin/ch/ergon/dope/resolvable/expression/AsteriskExpression.kt +++ b/core/src/main/kotlin/ch/ergon/dope/resolvable/expression/AsteriskExpression.kt @@ -3,10 +3,11 @@ package ch.ergon.dope.resolvable.expression import ch.ergon.dope.DopeQuery import ch.ergon.dope.DopeQueryManager import ch.ergon.dope.resolvable.fromable.Bucket +import ch.ergon.dope.resolvable.fromable.Returnable const val ASTERISK_STRING = "*" -class AsteriskExpression(private val bucket: Bucket? = null) : Expression { +class AsteriskExpression(private val bucket: Bucket? = null) : Expression, Returnable { override fun toDopeQuery(manager: DopeQueryManager): DopeQuery { val queryString = bucket?.toDopeQuery(manager)?.queryString?.let { "$it.$ASTERISK_STRING" } ?: ASTERISK_STRING return DopeQuery(queryString = queryString) diff --git a/core/src/main/kotlin/ch/ergon/dope/resolvable/fromable/Fromable.kt b/core/src/main/kotlin/ch/ergon/dope/resolvable/fromable/Fromable.kt index 0804a053..0457ec5e 100644 --- a/core/src/main/kotlin/ch/ergon/dope/resolvable/fromable/Fromable.kt +++ b/core/src/main/kotlin/ch/ergon/dope/resolvable/fromable/Fromable.kt @@ -9,3 +9,5 @@ interface Joinable : Resolvable interface Deletable : Resolvable interface Updatable : Resolvable + +interface Returnable : Resolvable diff --git a/core/src/test/kotlin/ch/ergon/dope/buildTest/DeleteTest.kt b/core/src/test/kotlin/ch/ergon/dope/buildTest/DeleteTest.kt index 26c8c860..8a1fcc35 100644 --- a/core/src/test/kotlin/ch/ergon/dope/buildTest/DeleteTest.kt +++ b/core/src/test/kotlin/ch/ergon/dope/buildTest/DeleteTest.kt @@ -85,7 +85,7 @@ class DeleteTest { val actual: String = create .deleteFrom(someBucket()) - .returning(someStringField(), someNumberField()) + .returning(someStringField()).thenReturning(someNumberField()) .build().queryString assertEquals(expected, actual) diff --git a/core/src/test/kotlin/ch/ergon/dope/resolvable/clause/ReturningClauseTest.kt b/core/src/test/kotlin/ch/ergon/dope/resolvable/clause/ReturningClauseTest.kt index 1f6072dd..6f347789 100644 --- a/core/src/test/kotlin/ch/ergon/dope/resolvable/clause/ReturningClauseTest.kt +++ b/core/src/test/kotlin/ch/ergon/dope/resolvable/clause/ReturningClauseTest.kt @@ -9,7 +9,15 @@ import ch.ergon.dope.helper.someNumberField import ch.ergon.dope.helper.someStringField import ch.ergon.dope.helper.someUpdateClause import ch.ergon.dope.resolvable.clause.model.DeleteReturningClause +import ch.ergon.dope.resolvable.clause.model.DeleteReturningSingleClause +import ch.ergon.dope.resolvable.clause.model.ReturningExpression +import ch.ergon.dope.resolvable.clause.model.ReturningType.ELEMENT +import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW +import ch.ergon.dope.resolvable.clause.model.ReturningType.VALUE import ch.ergon.dope.resolvable.clause.model.UpdateReturningClause +import ch.ergon.dope.resolvable.clause.model.UpdateReturningSingleClause +import ch.ergon.dope.resolvable.expression.AsteriskExpression +import ch.ergon.dope.resolvable.expression.unaliased.type.function.stringfunction.concat import kotlin.test.Test import kotlin.test.assertEquals @@ -21,7 +29,55 @@ class ReturningClauseTest : ManagerDependentTest { val expected = DopeQuery( queryString = "DELETE FROM `someBucket` RETURNING `stringField`", ) - val underTest = DeleteReturningClause(someStringField(), parentClause = someDeleteClause()) + val underTest = DeleteReturningClause(ReturningExpression(someStringField()), parentClause = someDeleteClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support delete returning with asterisk`() { + val expected = DopeQuery( + queryString = "DELETE FROM `someBucket` RETURNING *", + ) + val underTest = DeleteReturningClause(AsteriskExpression(), parentClause = someDeleteClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support delete returning raw`() { + val expected = DopeQuery( + queryString = "DELETE FROM `someBucket` RETURNING RAW `stringField`", + ) + val underTest = DeleteReturningSingleClause(someStringField(), RAW, parentClause = someDeleteClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support delete returning value`() { + val expected = DopeQuery( + queryString = "DELETE FROM `someBucket` RETURNING VALUE `stringField`", + ) + val underTest = DeleteReturningSingleClause(someStringField(), VALUE, parentClause = someDeleteClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support delete returning element`() { + val expected = DopeQuery( + queryString = "DELETE FROM `someBucket` RETURNING ELEMENT `stringField`", + ) + val underTest = DeleteReturningSingleClause(someStringField(), ELEMENT, parentClause = someDeleteClause()) val actual = underTest.toDopeQuery(manager) @@ -33,7 +89,28 @@ class ReturningClauseTest : ManagerDependentTest { val expected = DopeQuery( queryString = "DELETE FROM `someBucket` RETURNING `stringField`, `numberField`", ) - val underTest = DeleteReturningClause(someStringField(), someNumberField(), parentClause = someDeleteClause()) + val underTest = DeleteReturningClause( + ReturningExpression(someStringField()), + ReturningExpression(someNumberField()), + parentClause = someDeleteClause(), + ) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support delete returning with multiple fields, functions and asterisk`() { + val expected = DopeQuery( + queryString = "DELETE FROM `someBucket` RETURNING CONCAT(`stringField`, \"test\"), *, `numberField`", + ) + val underTest = DeleteReturningClause( + ReturningExpression(concat(someStringField(), "test")), + AsteriskExpression(), + ReturningExpression(someNumberField()), + parentClause = someDeleteClause(), + ) val actual = underTest.toDopeQuery(manager) @@ -44,7 +121,7 @@ class ReturningClauseTest : ManagerDependentTest { fun `should support delete returning function`() { val stringField = someStringField() val parentClause = someDeleteClause() - val expected = DeleteReturningClause(stringField, parentClause = parentClause) + val expected = DeleteReturningClause(ReturningExpression(stringField), parentClause = parentClause) val actual = parentClause.returning(stringField) @@ -56,9 +133,30 @@ class ReturningClauseTest : ManagerDependentTest { val stringField = someStringField() val numberArrayField = someNumberArrayField() val parentClause = someDeleteClause() - val expected = DeleteReturningClause(stringField, numberArrayField, parentClause = parentClause) + val expected = DeleteReturningClause( + ReturningExpression(stringField), + ReturningExpression(numberArrayField), + parentClause = parentClause, + ) + + val actual = parentClause.returning(stringField).thenReturning(numberArrayField) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support delete returning with multiple fields, functions and asterisk extension`() { + val stringConcatenation = concat(someStringField(), "test") + val numberArrayField = someNumberArrayField() + val parentClause = someDeleteClause() + val expected = DeleteReturningClause( + ReturningExpression(stringConcatenation), + AsteriskExpression(), + ReturningExpression(numberArrayField), + parentClause = parentClause, + ) - val actual = parentClause.returning(stringField, numberArrayField) + val actual = parentClause.returning(stringConcatenation).thenReturningAsterisk().thenReturning(numberArrayField) assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) } @@ -68,7 +166,7 @@ class ReturningClauseTest : ManagerDependentTest { val expected = DopeQuery( queryString = "UPDATE `someBucket` RETURNING `stringField`", ) - val underTest = UpdateReturningClause(someStringField(), parentClause = someUpdateClause()) + val underTest = UpdateReturningClause(ReturningExpression(someStringField()), parentClause = someUpdateClause()) val actual = underTest.toDopeQuery(manager) @@ -76,11 +174,80 @@ class ReturningClauseTest : ManagerDependentTest { } @Test - fun `should support returning with multiple fields`() { + fun `should support update returning with asterisk`() { + val expected = DopeQuery( + queryString = "UPDATE `someBucket` RETURNING *", + ) + val underTest = UpdateReturningClause(AsteriskExpression(), parentClause = someUpdateClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support update returning raw`() { + val expected = DopeQuery( + queryString = "UPDATE `someBucket` RETURNING RAW `stringField`", + ) + val underTest = UpdateReturningSingleClause(someStringField(), RAW, parentClause = someUpdateClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support update returning value`() { + val expected = DopeQuery( + queryString = "UPDATE `someBucket` RETURNING VALUE `stringField`", + ) + val underTest = UpdateReturningSingleClause(someStringField(), VALUE, parentClause = someUpdateClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support update returning element`() { + val expected = DopeQuery( + queryString = "UPDATE `someBucket` RETURNING ELEMENT `stringField`", + ) + val underTest = UpdateReturningSingleClause(someStringField(), ELEMENT, parentClause = someUpdateClause()) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support update returning with multiple fields`() { val expected = DopeQuery( queryString = "UPDATE `someBucket` RETURNING `stringField`, `numberField`", ) - val underTest = UpdateReturningClause(someStringField(), someNumberField(), parentClause = someUpdateClause()) + val underTest = UpdateReturningClause( + ReturningExpression(someStringField()), + ReturningExpression(someNumberField()), + parentClause = someUpdateClause(), + ) + + val actual = underTest.toDopeQuery(manager) + + assertEquals(expected, actual) + } + + @Test + fun `should support update returning with multiple fields, functions and asterisk`() { + val expected = DopeQuery( + queryString = "UPDATE `someBucket` RETURNING CONCAT(`stringField`, \"test\"), *, `numberField`", + ) + val underTest = UpdateReturningClause( + ReturningExpression(concat(someStringField(), "test")), + AsteriskExpression(), + ReturningExpression(someNumberField()), + parentClause = someUpdateClause(), + ) val actual = underTest.toDopeQuery(manager) @@ -88,10 +255,10 @@ class ReturningClauseTest : ManagerDependentTest { } @Test - fun `should support returning function`() { + fun `should support update returning function`() { val stringField = someStringField() val parentClause = someUpdateClause() - val expected = UpdateReturningClause(stringField, parentClause = parentClause) + val expected = UpdateReturningClause(ReturningExpression(stringField), parentClause = parentClause) val actual = parentClause.returning(stringField) @@ -99,13 +266,34 @@ class ReturningClauseTest : ManagerDependentTest { } @Test - fun `should support returning function with multiple fields`() { + fun `should support update returning function with multiple fields`() { val stringField = someStringField() val numberArrayField = someNumberArrayField() val parentClause = someUpdateClause() - val expected = UpdateReturningClause(stringField, numberArrayField, parentClause = parentClause) + val expected = UpdateReturningClause( + ReturningExpression(stringField), + ReturningExpression(numberArrayField), + parentClause = parentClause, + ) + + val actual = parentClause.returning(stringField).thenReturning(numberArrayField) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support update returning with multiple fields, functions and asterisk extension`() { + val stringConcatenation = concat(someStringField(), "test") + val numberArrayField = someNumberArrayField() + val parentClause = someUpdateClause() + val expected = UpdateReturningClause( + ReturningExpression(stringConcatenation), + AsteriskExpression(), + ReturningExpression(numberArrayField), + parentClause = parentClause, + ) - val actual = parentClause.returning(stringField, numberArrayField) + val actual = parentClause.returning(stringConcatenation).thenReturningAsterisk().thenReturning(numberArrayField) assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) } diff --git a/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/DeleteClause.kt b/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/DeleteClause.kt index 5d4556dc..6ba7fa2a 100644 --- a/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/DeleteClause.kt +++ b/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/DeleteClause.kt @@ -4,12 +4,25 @@ import ch.ergon.dope.resolvable.clause.IDeleteClause import ch.ergon.dope.resolvable.clause.IDeleteLimitClause import ch.ergon.dope.resolvable.clause.IDeleteOffsetClause import ch.ergon.dope.resolvable.clause.IDeleteWhereClause +import ch.ergon.dope.resolvable.clause.model.DeleteReturningClause import ch.ergon.dope.toDopeType import com.schwarz.crystalapi.schema.CMJsonField import com.schwarz.crystalapi.schema.CMType -fun IDeleteOffsetClause.returning(field: CMType, vararg fields: CMType) = - returning(field.toDopeType(), *fields.map { it.toDopeType() }.toTypedArray()) +fun IDeleteOffsetClause.returning(field: CMType) = + returning(field.toDopeType()) + +fun DeleteReturningClause.thenReturning(field: CMType) = + thenReturning(field.toDopeType()) + +fun IDeleteOffsetClause.returningRaw(field: CMType) = + returningRaw(field.toDopeType()) + +fun IDeleteOffsetClause.returningValue(field: CMType) = + returningValue(field.toDopeType()) + +fun IDeleteOffsetClause.returningElement(field: CMType) = + returningElement(field.toDopeType()) fun IDeleteLimitClause.offset(numberExpression: CMJsonField) = offset(numberExpression.toDopeType()) diff --git a/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/UpdateClause.kt b/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/UpdateClause.kt index 3853f42c..9603f52d 100644 --- a/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/UpdateClause.kt +++ b/crystal-map-connector/src/main/kotlin/ch/ergon/dope/extension/clause/UpdateClause.kt @@ -7,6 +7,7 @@ import ch.ergon.dope.resolvable.clause.IUpdateUnsetClause import ch.ergon.dope.resolvable.clause.IUpdateWhereClause import ch.ergon.dope.resolvable.clause.model.SetClause import ch.ergon.dope.resolvable.clause.model.UnsetClause +import ch.ergon.dope.resolvable.clause.model.UpdateReturningClause import ch.ergon.dope.resolvable.clause.model.to import ch.ergon.dope.resolvable.expression.TypeExpression import ch.ergon.dope.toDopeType @@ -19,8 +20,20 @@ import com.schwarz.crystalapi.schema.CMJsonField import com.schwarz.crystalapi.schema.CMJsonList import com.schwarz.crystalapi.schema.CMType -fun IUpdateLimitClause.returning(field: CMType, vararg fields: CMType) = - returning(field.toDopeType(), *fields.map { it.toDopeType() }.toTypedArray()) +fun IUpdateLimitClause.returning(field: CMType) = + returning(field.toDopeType()) + +fun UpdateReturningClause.thenReturning(field: CMType) = + thenReturning(field.toDopeType()) + +fun IUpdateLimitClause.returningRaw(field: CMType) = + returningRaw(field.toDopeType()) + +fun IUpdateLimitClause.returningValue(field: CMType) = + returningValue(field.toDopeType()) + +fun IUpdateLimitClause.returningElement(field: CMType) = + returningElement(field.toDopeType()) fun IUpdateWhereClause.limit(numberField: CMJsonField) = limit(numberField.toDopeType()) diff --git a/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/DeleteClauseTest.kt b/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/DeleteClauseTest.kt index 3a36ec2b..bb9f3eb3 100644 --- a/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/DeleteClauseTest.kt +++ b/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/DeleteClauseTest.kt @@ -4,6 +4,10 @@ import ch.ergon.dope.DopeQueryManager import ch.ergon.dope.extension.clause.limit import ch.ergon.dope.extension.clause.offset import ch.ergon.dope.extension.clause.returning +import ch.ergon.dope.extension.clause.returningElement +import ch.ergon.dope.extension.clause.returningRaw +import ch.ergon.dope.extension.clause.returningValue +import ch.ergon.dope.extension.clause.thenReturning import ch.ergon.dope.extension.clause.where import ch.ergon.dope.helper.ManagerDependentTest import ch.ergon.dope.helper.someCMBooleanField @@ -14,7 +18,13 @@ import ch.ergon.dope.helper.someDelete import ch.ergon.dope.resolvable.clause.model.DeleteLimitClause import ch.ergon.dope.resolvable.clause.model.DeleteOffsetClause import ch.ergon.dope.resolvable.clause.model.DeleteReturningClause +import ch.ergon.dope.resolvable.clause.model.DeleteReturningSingleClause import ch.ergon.dope.resolvable.clause.model.DeleteWhereClause +import ch.ergon.dope.resolvable.clause.model.ReturningExpression +import ch.ergon.dope.resolvable.clause.model.ReturningType.ELEMENT +import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW +import ch.ergon.dope.resolvable.clause.model.ReturningType.VALUE +import ch.ergon.dope.resolvable.expression.AsteriskExpression import ch.ergon.dope.toDopeType import kotlin.test.Test import kotlin.test.assertEquals @@ -59,7 +69,7 @@ class DeleteClauseTest : ManagerDependentTest { fun `should support delete returning with CM`() { val field = someCMBooleanField() val parentClause = someDelete() - val expected = DeleteReturningClause(field.toDopeType(), parentClause = parentClause) + val expected = DeleteReturningClause(ReturningExpression(field.toDopeType()), parentClause = parentClause) val actual = parentClause.returning(field) @@ -67,14 +77,53 @@ class DeleteClauseTest : ManagerDependentTest { } @Test - fun `should support delete returning with multiple CM`() { + fun `should support delete returning raw with CM`() { + val field = someCMBooleanField() + val parentClause = someDelete() + val expected = DeleteReturningSingleClause(field.toDopeType(), returningType = RAW, parentClause = parentClause) + + val actual = parentClause.returningRaw(field) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support delete returning value with CM`() { + val field = someCMBooleanField() + val parentClause = someDelete() + val expected = DeleteReturningSingleClause(field.toDopeType(), returningType = VALUE, parentClause = parentClause) + + val actual = parentClause.returningValue(field) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support delete returning element with CM`() { + val field = someCMBooleanField() + val parentClause = someDelete() + val expected = DeleteReturningSingleClause(field.toDopeType(), returningType = ELEMENT, parentClause = parentClause) + + val actual = parentClause.returningElement(field) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support delete returning with multiple CM and asterisk`() { val field1 = someCMBooleanField() val field2 = someCMNumberList() val field3 = someCMStringField() val parentClause = someDelete() - val expected = DeleteReturningClause(field1.toDopeType(), field2.toDopeType(), field3.toDopeType(), parentClause = parentClause) - - val actual = parentClause.returning(field1, field2, field3) + val expected = DeleteReturningClause( + ReturningExpression(field1.toDopeType()), + ReturningExpression(field2.toDopeType()), + AsteriskExpression(), + ReturningExpression(field3.toDopeType()), + parentClause = parentClause, + ) + + val actual = parentClause.returning(field1).thenReturning(field2).thenReturningAsterisk().thenReturning(field3) assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) } diff --git a/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/UpdateClauseTest.kt b/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/UpdateClauseTest.kt index a6caac49..146b141b 100644 --- a/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/UpdateClauseTest.kt +++ b/crystal-map-connector/src/test/kotlin/ch/ergon/dope/extensions/clause/UpdateClauseTest.kt @@ -3,7 +3,11 @@ package ch.ergon.dope.extensions.clause import ch.ergon.dope.DopeQueryManager import ch.ergon.dope.extension.clause.limit import ch.ergon.dope.extension.clause.returning +import ch.ergon.dope.extension.clause.returningElement +import ch.ergon.dope.extension.clause.returningRaw +import ch.ergon.dope.extension.clause.returningValue import ch.ergon.dope.extension.clause.set +import ch.ergon.dope.extension.clause.thenReturning import ch.ergon.dope.extension.clause.unset import ch.ergon.dope.extension.clause.where import ch.ergon.dope.helper.ManagerDependentTest @@ -21,12 +25,18 @@ import ch.ergon.dope.helper.someDate import ch.ergon.dope.helper.someNumber import ch.ergon.dope.helper.someString import ch.ergon.dope.helper.someUpdate +import ch.ergon.dope.resolvable.clause.model.ReturningExpression +import ch.ergon.dope.resolvable.clause.model.ReturningType.ELEMENT +import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW +import ch.ergon.dope.resolvable.clause.model.ReturningType.VALUE import ch.ergon.dope.resolvable.clause.model.SetClause import ch.ergon.dope.resolvable.clause.model.UnsetClause import ch.ergon.dope.resolvable.clause.model.UpdateLimitClause import ch.ergon.dope.resolvable.clause.model.UpdateReturningClause +import ch.ergon.dope.resolvable.clause.model.UpdateReturningSingleClause import ch.ergon.dope.resolvable.clause.model.UpdateWhereClause import ch.ergon.dope.resolvable.clause.model.to +import ch.ergon.dope.resolvable.expression.AsteriskExpression import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType import ch.ergon.dope.toDopeType import kotlin.test.Test @@ -630,7 +640,7 @@ class UpdateClauseTest : ManagerDependentTest { fun `should support update returning with CM`() { val field = someCMBooleanField() val parentClause = someUpdate() - val expected = UpdateReturningClause(field.toDopeType(), parentClause = parentClause) + val expected = UpdateReturningClause(ReturningExpression(field.toDopeType()), parentClause = parentClause) val actual = parentClause.returning(field) @@ -638,14 +648,53 @@ class UpdateClauseTest : ManagerDependentTest { } @Test - fun `should support update returning with multiple CM`() { + fun `should support update returning raw with CM`() { + val field = someCMBooleanField() + val parentClause = someUpdate() + val expected = UpdateReturningSingleClause(field.toDopeType(), returningType = RAW, parentClause = parentClause) + + val actual = parentClause.returningRaw(field) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support update returning value with CM`() { + val field = someCMBooleanField() + val parentClause = someUpdate() + val expected = UpdateReturningSingleClause(field.toDopeType(), returningType = VALUE, parentClause = parentClause) + + val actual = parentClause.returningValue(field) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support update returning element with CM`() { + val field = someCMBooleanField() + val parentClause = someUpdate() + val expected = UpdateReturningSingleClause(field.toDopeType(), returningType = ELEMENT, parentClause = parentClause) + + val actual = parentClause.returningElement(field) + + assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) + } + + @Test + fun `should support update returning with multiple CM and asterisk`() { val field1 = someCMBooleanField() val field2 = someCMNumberList() val field3 = someCMStringField() val parentClause = someUpdate() - val expected = UpdateReturningClause(field1.toDopeType(), field2.toDopeType(), field3.toDopeType(), parentClause = parentClause) + val expected = UpdateReturningClause( + ReturningExpression(field1.toDopeType()), + ReturningExpression(field2.toDopeType()), + AsteriskExpression(), + ReturningExpression(field3.toDopeType()), + parentClause = parentClause, + ) - val actual = parentClause.returning(field1, field2, field3) + val actual = parentClause.returning(field1).thenReturning(field2).thenReturningAsterisk().thenReturning(field3) assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager)) }